0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

i.MX6ULL|并发与竞争实验

玩转单片机 来源:玩转单片机 2023-08-02 15:40 次阅读

常用的处理并发和竞争的机制有四种,原子操作、自旋锁、信号量和互斥体。下边就通过编写驱动来实现,展示一下相关效果。当前台的应用一直运行,控制台是不能输入指令,测试并发与竞争最好是在后台运行,而解决并非与竞争最直接的手段就是只允许一个应用去调用相关资源,这里为了好展示效果就通过任务运行来体现。下面的实验使用了新字符驱动GPIO源码,只需要复制一份即可使用。

|原子操作

驱动源码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

/*添加头文件*/
#include
#include
#include
#include

#defineCHRDEVBASE_CNT1/*设备号个数*/
#defineCHRDEVBASE_NAME"chrdevbase"/*名字*/

#defineLEDOFF0/*关灯*/
#defineLEDON1/*开灯*/

/*chrdevbase设备结构体*/
structnewchr_dev{
dev_tdevid;/*设备号*/
structcdevcdev;/*cdev*/
structclass*class;/*类*/
structdevice*device;/*设备*/
intmajor;/*主设备号*/
intminor;/*次设备号*/
structdevice_node*nd;/*设备节点*/
intled_gpio;/*led所使用的GPIO编号*/

atomic_tlock;/*原子变量*/
};

structnewchr_devchrdevbase;/*自定义字符设备*/

/*
*@description:LED硬件初始化
*@param:无
*@return:无
*/
staticintled_hal_init(void)
{
intret=0;

/*设置LED所使用的GPIO*/
/* 1、获取设备节点:gpioled */
chrdevbase.nd=of_find_node_by_path("/gpioled");
if(chrdevbase.nd==NULL){
printk("chrdevbasenodecantnotfound!
");
return-EINVAL;
}else{
printk("chrdevbasenodehasbeenfound!
");
}

/*2、获取设备树中的gpio属性,得到LED所使用的LED编号*/
chrdevbase.led_gpio=of_get_named_gpio(chrdevbase.nd,"led-gpio",0);
if(chrdevbase.led_gpio< 0){
printk("can'tgetled-gpio");
return-EINVAL;
}
printk("led-gpionum=%d
",chrdevbase.led_gpio);

/*3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯*/
ret=gpio_direction_output(chrdevbase.led_gpio,1);
if(ret< 0){
printk("can'tsetgpio!
");
}

return0;
}

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
/*通过判断原子变量的值来检查LED有没有被别的应用使用*/
if(!atomic_dec_and_test(&chrdevbase.lock))
{
atomic_inc(&chrdevbase.lock);/*小于0的话就加1,使其原子变量等于0*/
return-EBUSY;/*LED被使用,返回忙*/
}
printk("[BSP]chrdevbaseopen!
");
filp->private_data=&chrdevbase;/*设置私有数据*/
return0;
}

/*
*@description:从设备读取数据
*@param-filp:要打开的设备文件(文件描述符)
*@param-buf:返回给用户空间的数据缓冲区
*@param-cnt:要读取的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:读取的字节数,如果为负值,表示读取失败
*/
staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt)
{
printk("chrdevbaseread!
");
return0;
}

/*
*@description:向设备写数据
*@param-filp:设备文件,表示打开的文件描述符
*@param-buf:要写给设备写入的数据
*@param-cnt:要写入的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:写入的字节数,如果为负值,表示写入失败
*/
staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt)
{
intretvalue=0;
charwritebuf[1];
structnewchr_dev*dev=filp->private_data;

/*接收用户空间传递给内核的数据并且打印出来*/
retvalue=copy_from_user(writebuf,buf,cnt);
printk("[BSP]kernelrecevdatadata:%d!
",writebuf[0]);

if(writebuf[0]==LEDON){
gpio_set_value(dev->led_gpio,0);/*打开LED灯*/
}elseif(writebuf[0]==LEDOFF){
gpio_set_value(dev->led_gpio,1);/*关闭LED灯*/
}

//printk("chrdevbasewrite!
");
return0;
}

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
structnewchr_dev*dev=filp->private_data;
/*关闭驱动文件的时候释放原子变量*/
atomic_inc(&dev->lock);
printk("[BSP]release!
");
return0;
}

/*
*设备操作函数结构体
*/
staticstructfile_operationschrdevbase_fops={
.owner=THIS_MODULE,
.open=chrdevbase_open,
.read=chrdevbase_read,
.write=chrdevbase_write,
.release=chrdevbase_release,
};

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化原子变量*/
atomic_set(&chrdevbase.lock,1);/*原子变量初始值为1*/

/*初始化硬件*/
led_hal_init();

/*注册字符设备驱动*/
/*1、创建设备号*/
if(chrdevbase.major){/*定义了设备号*/
chrdevbase.devid=MKDEV(chrdevbase.major,0);
register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME);
}else{/*没有定义设备号*/
alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申请设备号*/
chrdevbase.major=MAJOR(chrdevbase.devid);/*获取主设备号*/
chrdevbase.minor=MINOR(chrdevbase.devid);/*获取次设备号*/
}
printk("newcheledmajor=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

/*2、初始化cdev*/
chrdevbase.cdev.owner=THIS_MODULE;
cdev_init(&chrdevbase.cdev,&chrdevbase_fops);

/*3、添加一个cdev*/
cdev_add(&chrdevbase.cdev,chrdevbase.devid,CHRDEVBASE_CNT);

/*4、创建类*/
chrdevbase.class=class_create(THIS_MODULE,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.class)){
returnPTR_ERR(chrdevbase.class);
}

/*5、创建设备*/
chrdevbase.device=device_create(chrdevbase.class,NULL,chrdevbase.devid,NULL,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.device)){
returnPTR_ERR(chrdevbase.device);
}

return0;
}

/*
*@description:驱动出口函数
*@param:无
*@return:无
*/
staticvoid__exitchrdevbase_exit(void)
{
/*注销字符设备*/
cdev_del(&chrdevbase.cdev);/*删除cdev*/
unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注销设备号*/

device_destroy(chrdevbase.class,chrdevbase.devid);/*销毁设备*/
class_destroy(chrdevbase.class);/*销毁类*/

printk("[BSP]chrdevbaseexit!
");
}

/*
*将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/*
*LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用源码

#include"stdio.h"
#include"unistd.h"
#include"sys/types.h"
#include"sys/stat.h"
#include"fcntl.h"
#include"stdlib.h"
#include"string.h"

/*
*@description:main主程序
*@param-argc:argv数组元素个数
*@param-argv:具体参数
*@return:0成功;其他失败
*/
intmain(intargc,char*argv[])
{
intfd,retvalue;
char*filename;
charwritebuf[100];
unsignedchardatabuf[1];
unsignedcharcnt=0;

if(argc!=3){
printf("[APP]ErrorUsage!
");
return-1;
}

filename=argv[1];

/*打开驱动文件*/
fd=open(filename,O_RDWR);
if(fd< 0){
printf("[APP]Can'topenfile%s
",filename);
return-1;
}

/*把第三个参数赋值给databuf*/
databuf[0]=atoi(argv[2]);

/*向设备驱动写数据*/
memcpy(writebuf,databuf,sizeof(databuf));
retvalue=write(fd,writebuf,sizeof(databuf));
if(retvalue< 0){
printf("[APP]writefile%sfailed!
",filename);
}

/*模拟占用25SLED*/
while(1)
{
sleep(5);
cnt++;
printf("Apprunningtimes:%d
",cnt);
if(cnt>=5)break;
}
printf("Apprunningfinished!");

/*关闭设备*/
retvalue=close(fd);
if(retvalue< 0){
printf("[APP]Can'tclosefile%s
",filename);
return-1;
}

return0;
}

实验现象

加了原子操作后,应用程序运行时,再次触发是不能运行的,这就解决了在复杂环境下的并发和竞争的问题。注意不加“&”表示直接运行,控制台不能输入指令,加了“&”表示后台运行,可以继续输入指令。

baf1cecc-3083-11ee-9e74-dac502259ad0.png

套路分析

1、先在结构体定义一个变量

/*chrdevbase设备结构体*/
structnewchr_dev{
......
atomic_tlock;/*原子变量*/
};

2、在驱动入口初始化原子变量

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化原子变量*/
atomic_set(&chrdevbase.lock,1);/*原子变量初始值为1*/
......
return0;
}

3、在打开设备时判断原子变量

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
/*通过判断原子变量的值来检查LED有没有被别的应用使用*/
if(!atomic_dec_and_test(&chrdevbase.lock))
{
atomic_inc(&chrdevbase.lock);/*小于0的话就加1,使其原子变量等于0*/
return-EBUSY;/*LED被使用,返回忙*/
}
......
return0;
}

4、在释放设备时释放原子变量

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
structnewchr_dev*dev=filp->private_data;
/*关闭驱动文件的时候释放原子变量*/
atomic_inc(&dev->lock);
......
return0;
}

| 自旋锁

驱动源码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

/*添加头文件*/
#include
#include
#include
#include

#defineCHRDEVBASE_CNT1/*设备号个数*/
#defineCHRDEVBASE_NAME"chrdevbase"/*名字*/

#defineLEDOFF0/*关灯*/
#defineLEDON1/*开灯*/

/*chrdevbase设备结构体*/
structnewchr_dev{
dev_tdevid;/*设备号*/
structcdevcdev;/*cdev*/
structclass*class;/*类*/
structdevice*device;/*设备*/
intmajor;/*主设备号*/
intminor;/*次设备号*/
structdevice_node*nd;/*设备节点*/
intled_gpio;/*led所使用的GPIO编号*/

intdev_stats;/*设备状态,0,设备未使用;>0,设备已经被使用*/
spinlock_tlock;/*自旋锁*/
};

structnewchr_devchrdevbase;/*自定义字符设备*/

/*
*@description:LED硬件初始化
*@param:无
*@return:无
*/
staticintled_hal_init(void)
{
intret=0;

/*设置LED所使用的GPIO*/
/* 1、获取设备节点:gpioled */
chrdevbase.nd=of_find_node_by_path("/gpioled");
if(chrdevbase.nd==NULL){
printk("chrdevbasenodecantnotfound!
");
return-EINVAL;
}else{
printk("chrdevbasenodehasbeenfound!
");
}

/*2、获取设备树中的gpio属性,得到LED所使用的LED编号*/
chrdevbase.led_gpio=of_get_named_gpio(chrdevbase.nd,"led-gpio",0);
if(chrdevbase.led_gpio< 0){
printk("can'tgetled-gpio");
return-EINVAL;
}
printk("led-gpionum=%d
",chrdevbase.led_gpio);

/*3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯*/
ret=gpio_direction_output(chrdevbase.led_gpio,1);
if(ret< 0){
printk("can'tsetgpio!
");
}

return0;
}

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
unsignedlongflags;
filp->private_data=&chrdevbase;/*设置私有数据*/

spin_lock_irqsave(&chrdevbase.lock,flags);/*上锁*/
if(chrdevbase.dev_stats){/*如果设备被使用了*/
spin_unlock_irqrestore(&chrdevbase.lock,flags);/*解锁*/
return-EBUSY;
}
chrdevbase.dev_stats++;/*如果设备没有打开,那么就标记已经打开了*/
spin_unlock_irqrestore(&chrdevbase.lock,flags);/*解锁*/

printk("[BSP]chrdevbaseopen!
");
return0;
}

/*
*@description:从设备读取数据
*@param-filp:要打开的设备文件(文件描述符)
*@param-buf:返回给用户空间的数据缓冲区
*@param-cnt:要读取的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:读取的字节数,如果为负值,表示读取失败
*/
staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt)
{
printk("chrdevbaseread!
");
return0;
}

/*
*@description:向设备写数据
*@param-filp:设备文件,表示打开的文件描述符
*@param-buf:要写给设备写入的数据
*@param-cnt:要写入的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:写入的字节数,如果为负值,表示写入失败
*/
staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt)
{
intretvalue=0;
charwritebuf[1];
structnewchr_dev*dev=filp->private_data;

/*接收用户空间传递给内核的数据并且打印出来*/
retvalue=copy_from_user(writebuf,buf,cnt);
printk("[BSP]kernelrecevdatadata:%d!
",writebuf[0]);

if(writebuf[0]==LEDON){
gpio_set_value(dev->led_gpio,0);/*打开LED灯*/
}elseif(writebuf[0]==LEDOFF){
gpio_set_value(dev->led_gpio,1);/*关闭LED灯*/
}

//printk("chrdevbasewrite!
");
return0;
}

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
unsignedlongflags;
structnewchr_dev*dev=filp->private_data;
/*关闭驱动文件的时候将dev_stats减1*/
spin_lock_irqsave(&dev->lock,flags);/*上锁*/
if(dev->dev_stats){
dev->dev_stats--;
}
spin_unlock_irqrestore(&dev->lock,flags);/*解锁*/

printk("[BSP]release!
");
return0;
}

/*
*设备操作函数结构体
*/
staticstructfile_operationschrdevbase_fops={
.owner=THIS_MODULE,
.open=chrdevbase_open,
.read=chrdevbase_read,
.write=chrdevbase_write,
.release=chrdevbase_release,
};

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化自旋锁*/
spin_lock_init(&chrdevbase.lock);

/*初始化硬件*/
led_hal_init();

/*注册字符设备驱动*/
/*1、创建设备号*/
if(chrdevbase.major){/*定义了设备号*/
chrdevbase.devid=MKDEV(chrdevbase.major,0);
register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME);
}else{/*没有定义设备号*/
alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申请设备号*/
chrdevbase.major=MAJOR(chrdevbase.devid);/*获取主设备号*/
chrdevbase.minor=MINOR(chrdevbase.devid);/*获取次设备号*/
}
printk("newcheledmajor=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

/*2、初始化cdev*/
chrdevbase.cdev.owner=THIS_MODULE;
cdev_init(&chrdevbase.cdev,&chrdevbase_fops);

/*3、添加一个cdev*/
cdev_add(&chrdevbase.cdev,chrdevbase.devid,CHRDEVBASE_CNT);

/*4、创建类*/
chrdevbase.class=class_create(THIS_MODULE,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.class)){
returnPTR_ERR(chrdevbase.class);
}

/*5、创建设备*/
chrdevbase.device=device_create(chrdevbase.class,NULL,chrdevbase.devid,NULL,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.device)){
returnPTR_ERR(chrdevbase.device);
}

return0;
}

/*
*@description:驱动出口函数
*@param:无
*@return:无
*/
staticvoid__exitchrdevbase_exit(void)
{
/*注销字符设备*/
cdev_del(&chrdevbase.cdev);/*删除cdev*/
unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注销设备号*/

device_destroy(chrdevbase.class,chrdevbase.devid);/*销毁设备*/
class_destroy(chrdevbase.class);/*销毁类*/

printk("[BSP]chrdevbaseexit!
");
}

/*
*将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/*
*LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用源码

和原子操作应用源码一样,不需要修改!

实验现象

bb468e58-3083-11ee-9e74-dac502259ad0.png

套路分析

自旋锁和RTOS中的临界保护有点类似,套路分析如下:

1、在结构体加入自旋锁变量和一个状态变量

/*chrdevbase设备结构体*/
structnewchr_dev{
......
intdev_stats;/*设备状态,0,设备未使用;>0,设备已经被使用*/
spinlock_tlock;/*自旋锁*/
};

2、在驱动入口初始化自旋锁

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化自旋锁*/
spin_lock_init(&chrdevbase.lock);
......
return0;
}

3、在打开设备时判断设备是否被使用

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
unsignedlongflags;
filp->private_data=&chrdevbase;/*设置私有数据*/

spin_lock_irqsave(&chrdevbase.lock,flags);/*上锁*/
if(chrdevbase.dev_stats){/*如果设备被使用了*/
spin_unlock_irqrestore(&chrdevbase.lock,flags);/*解锁*/
return-EBUSY;
}
chrdevbase.dev_stats++;/*如果设备没有打开,那么就标记已经打开了*/
spin_unlock_irqrestore(&chrdevbase.lock,flags);/*解锁*/

......
return0;
}

4、在释放设备时对状态变量自减

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
unsignedlongflags;
structnewchr_dev*dev=filp->private_data;
/*关闭驱动文件的时候将dev_stats减1*/
spin_lock_irqsave(&dev->lock,flags);/*上锁*/
if(dev->dev_stats){
dev->dev_stats--;
}
spin_unlock_irqrestore(&dev->lock,flags);/*解锁*/

printk("[BSP]release!
");
return0;
}

| 信号量

驱动源码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

/*添加头文件*/
#include
#include
#include
#include

#defineCHRDEVBASE_CNT1/*设备号个数*/
#defineCHRDEVBASE_NAME"chrdevbase"/*名字*/

#defineLEDOFF0/*关灯*/
#defineLEDON1/*开灯*/

/*chrdevbase设备结构体*/
structnewchr_dev{
dev_tdevid;/*设备号*/
structcdevcdev;/*cdev*/
structclass*class;/*类*/
structdevice*device;/*设备*/
intmajor;/*主设备号*/
intminor;/*次设备号*/
structdevice_node*nd;/*设备节点*/
intled_gpio;/*led所使用的GPIO编号*/

structsemaphoresem;/*信号量*/
};

structnewchr_devchrdevbase;/*自定义字符设备*/

/*
*@description:LED硬件初始化
*@param:无
*@return:无
*/
staticintled_hal_init(void)
{
intret=0;

/*设置LED所使用的GPIO*/
/* 1、获取设备节点:gpioled */
chrdevbase.nd=of_find_node_by_path("/gpioled");
if(chrdevbase.nd==NULL){
printk("chrdevbasenodecantnotfound!
");
return-EINVAL;
}else{
printk("chrdevbasenodehasbeenfound!
");
}

/*2、获取设备树中的gpio属性,得到LED所使用的LED编号*/
chrdevbase.led_gpio=of_get_named_gpio(chrdevbase.nd,"led-gpio",0);
if(chrdevbase.led_gpio< 0){
printk("can'tgetled-gpio");
return-EINVAL;
}
printk("led-gpionum=%d
",chrdevbase.led_gpio);

/*3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯*/
ret=gpio_direction_output(chrdevbase.led_gpio,1);
if(ret< 0){
printk("can'tsetgpio!
");
}

return0;
}

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
printk("[BSP]chrdevbaseopen!
");
filp->private_data=&chrdevbase;/*设置私有数据*/

/*获取信号量,进入休眠状态的进程可以被信号打断*/
if(down_interruptible(&chrdevbase.sem)){
return-ERESTARTSYS;
}
#if0
down(&chrdevbase.sem);/*不能被信号打断*/
#endif
return0;
}

/*
*@description:从设备读取数据
*@param-filp:要打开的设备文件(文件描述符)
*@param-buf:返回给用户空间的数据缓冲区
*@param-cnt:要读取的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:读取的字节数,如果为负值,表示读取失败
*/
staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt)
{
printk("chrdevbaseread!
");
return0;
}

/*
*@description:向设备写数据
*@param-filp:设备文件,表示打开的文件描述符
*@param-buf:要写给设备写入的数据
*@param-cnt:要写入的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:写入的字节数,如果为负值,表示写入失败
*/
staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt)
{
intretvalue=0;
charwritebuf[1];
structnewchr_dev*dev=filp->private_data;

/*接收用户空间传递给内核的数据并且打印出来*/
retvalue=copy_from_user(writebuf,buf,cnt);
printk("[BSP]kernelrecevdatadata:%d!
",writebuf[0]);

if(writebuf[0]==LEDON){
gpio_set_value(dev->led_gpio,0);/*打开LED灯*/
}elseif(writebuf[0]==LEDOFF){
gpio_set_value(dev->led_gpio,1);/*关闭LED灯*/
}

//printk("chrdevbasewrite!
");
return0;
}

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
structnewchr_dev*dev=filp->private_data;
up(&dev->sem);/*释放信号量,信号量值加1*/
printk("[BSP]release!
");
return0;
}

/*
*设备操作函数结构体
*/
staticstructfile_operationschrdevbase_fops={
.owner=THIS_MODULE,
.open=chrdevbase_open,
.read=chrdevbase_read,
.write=chrdevbase_write,
.release=chrdevbase_release,
};

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化信号量*/
sema_init(&chrdevbase.sem,1);

/*初始化硬件*/
led_hal_init();

/*注册字符设备驱动*/
/*1、创建设备号*/
if(chrdevbase.major){/*定义了设备号*/
chrdevbase.devid=MKDEV(chrdevbase.major,0);
register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME);
}else{/*没有定义设备号*/
alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申请设备号*/
chrdevbase.major=MAJOR(chrdevbase.devid);/*获取主设备号*/
chrdevbase.minor=MINOR(chrdevbase.devid);/*获取次设备号*/
}
printk("newcheledmajor=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

/*2、初始化cdev*/
chrdevbase.cdev.owner=THIS_MODULE;
cdev_init(&chrdevbase.cdev,&chrdevbase_fops);

/*3、添加一个cdev*/
cdev_add(&chrdevbase.cdev,chrdevbase.devid,CHRDEVBASE_CNT);

/*4、创建类*/
chrdevbase.class=class_create(THIS_MODULE,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.class)){
returnPTR_ERR(chrdevbase.class);
}

/*5、创建设备*/
chrdevbase.device=device_create(chrdevbase.class,NULL,chrdevbase.devid,NULL,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.device)){
returnPTR_ERR(chrdevbase.device);
}

return0;
}

/*
*@description:驱动出口函数
*@param:无
*@return:无
*/
staticvoid__exitchrdevbase_exit(void)
{
/*注销字符设备*/
cdev_del(&chrdevbase.cdev);/*删除cdev*/
unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注销设备号*/

device_destroy(chrdevbase.class,chrdevbase.devid);/*销毁设备*/
class_destroy(chrdevbase.class);/*销毁类*/

printk("[BSP]chrdevbaseexit!
");
}

/*
*将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/*
*LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用源码

和原子操作应用源码一样,不需要修改!

实验现象

使用信号量不会出现设备打不开的问题,它会在任务结束后再次执行!

bb90e480-3083-11ee-9e74-dac502259ad0.png

套路分析

1、在结构体加入信号量

/*chrdevbase设备结构体*/
structnewchr_dev{
......
structsemaphoresem;/*信号量*/
};

2、在驱动入口初始信号量

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化信号量*/
sema_init(&chrdevbase.sem,1);
......
return0;

3、在打开设备时获取信号量

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
printk("[BSP]chrdevbaseopen!
");
filp->private_data=&chrdevbase;/*设置私有数据*/

/*获取信号量,进入休眠状态的进程可以被信号打断*/
if(down_interruptible(&chrdevbase.sem)){
return-ERESTARTSYS;
}
#if0
down(&chrdevbase.sem);/*不能被信号打断*/
#endif
return0;
}

4、在释放设备时释放信号量

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
structnewchr_dev*dev=filp->private_data;
up(&dev->sem);/*释放信号量,信号量值加1*/
printk("[BSP]release!
");
return0;
}

| 互斥体

驱动源码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

/*添加头文件*/
#include
#include
#include
#include

#defineCHRDEVBASE_CNT1/*设备号个数*/
#defineCHRDEVBASE_NAME"chrdevbase"/*名字*/

#defineLEDOFF0/*关灯*/
#defineLEDON1/*开灯*/

/*chrdevbase设备结构体*/
structnewchr_dev{
dev_tdevid;/*设备号*/
structcdevcdev;/*cdev*/
structclass*class;/*类*/
structdevice*device;/*设备*/
intmajor;/*主设备号*/
intminor;/*次设备号*/
structdevice_node*nd;/*设备节点*/
intled_gpio;/*led所使用的GPIO编号*/

structmutexlock;/*互斥体*/
};

structnewchr_devchrdevbase;/*自定义字符设备*/

/*
*@description:LED硬件初始化
*@param:无
*@return:无
*/
staticintled_hal_init(void)
{
intret=0;

/*设置LED所使用的GPIO*/
/*1、获取设备节点:gpioled*/
chrdevbase.nd=of_find_node_by_path("/gpioled");
if(chrdevbase.nd==NULL){
printk("chrdevbasenodecantnotfound!
");
return-EINVAL;
}else{
printk("chrdevbasenodehasbeenfound!
");
}

/*2、获取设备树中的gpio属性,得到LED所使用的LED编号*/
chrdevbase.led_gpio=of_get_named_gpio(chrdevbase.nd,"led-gpio",0);
if(chrdevbase.led_gpio< 0){
printk("can'tgetled-gpio");
return-EINVAL;
}
printk("led-gpionum=%d
",chrdevbase.led_gpio);

/*3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯*/
ret=gpio_direction_output(chrdevbase.led_gpio,1);
if(ret< 0){
printk("can'tsetgpio!
");
}

return0;
}

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
printk("[BSP]chrdevbaseopen!
");
filp->private_data=&chrdevbase;/*设置私有数据*/

/*获取互斥体,可以被信号打断*/
if(mutex_lock_interruptible(&chrdevbase.lock)){
return-ERESTARTSYS;
}
#if0
mutex_lock(&chrdevbase.lock);/*不能被信号打断*/
#endif

return0;
}

/*
*@description:从设备读取数据
*@param-filp:要打开的设备文件(文件描述符)
*@param-buf:返回给用户空间的数据缓冲区
*@param-cnt:要读取的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:读取的字节数,如果为负值,表示读取失败
*/
staticssize_tchrdevbase_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt)
{
printk("chrdevbaseread!
");
return0;
}

/*
*@description:向设备写数据
*@param-filp:设备文件,表示打开的文件描述符
*@param-buf:要写给设备写入的数据
*@param-cnt:要写入的数据长度
*@param-offt:相对于文件首地址的偏移
*@return:写入的字节数,如果为负值,表示写入失败
*/
staticssize_tchrdevbase_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt)
{
intretvalue=0;
charwritebuf[1];
structnewchr_dev*dev=filp->private_data;

/*接收用户空间传递给内核的数据并且打印出来*/
retvalue=copy_from_user(writebuf,buf,cnt);
printk("[BSP]kernelrecevdatadata:%d!
",writebuf[0]);

if(writebuf[0]==LEDON){
gpio_set_value(dev->led_gpio,0);/*打开LED灯*/
}elseif(writebuf[0]==LEDOFF){
gpio_set_value(dev->led_gpio,1);/*关闭LED灯*/
}

//printk("chrdevbasewrite!
");
return0;
}

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
structnewchr_dev*dev=filp->private_data;
/*释放互斥锁*/
mutex_unlock(&dev->lock);
printk("[BSP]release!
");
return0;
}

/*
*设备操作函数结构体
*/
staticstructfile_operationschrdevbase_fops={
.owner=THIS_MODULE,
.open=chrdevbase_open,
.read=chrdevbase_read,
.write=chrdevbase_write,
.release=chrdevbase_release,
};

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化互斥体*/
mutex_init(&chrdevbase.lock);

/*初始化硬件*/
led_hal_init();

/*注册字符设备驱动*/
/*1、创建设备号*/
if(chrdevbase.major){/*定义了设备号*/
chrdevbase.devid=MKDEV(chrdevbase.major,0);
register_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT,CHRDEVBASE_NAME);
}else{/*没有定义设备号*/
alloc_chrdev_region(&chrdevbase.devid,0,CHRDEVBASE_CNT,CHRDEVBASE_NAME);/*申请设备号*/
chrdevbase.major=MAJOR(chrdevbase.devid);/*获取主设备号*/
chrdevbase.minor=MINOR(chrdevbase.devid);/*获取次设备号*/
}
printk("newcheledmajor=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

/*2、初始化cdev*/
chrdevbase.cdev.owner=THIS_MODULE;
cdev_init(&chrdevbase.cdev,&chrdevbase_fops);

/*3、添加一个cdev*/
cdev_add(&chrdevbase.cdev,chrdevbase.devid,CHRDEVBASE_CNT);

/*4、创建类*/
chrdevbase.class=class_create(THIS_MODULE,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.class)){
returnPTR_ERR(chrdevbase.class);
}

/*5、创建设备*/
chrdevbase.device=device_create(chrdevbase.class,NULL,chrdevbase.devid,NULL,CHRDEVBASE_NAME);
if(IS_ERR(chrdevbase.device)){
returnPTR_ERR(chrdevbase.device);
}

return0;
}

/*
*@description:驱动出口函数
*@param:无
*@return:无
*/
staticvoid__exitchrdevbase_exit(void)
{
/*注销字符设备*/
cdev_del(&chrdevbase.cdev);/*删除cdev*/
unregister_chrdev_region(chrdevbase.devid,CHRDEVBASE_CNT);/*注销设备号*/

device_destroy(chrdevbase.class,chrdevbase.devid);/*销毁设备*/
class_destroy(chrdevbase.class);/*销毁类*/

printk("[BSP]chrdevbaseexit!
");
}

/*
*将上面两个函数指定为驱动的入口和出口函数
*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/*
*LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用源码

和原子操作应用源码一样,不需要修改!

实验现象

互斥体和信号量的效果类似,也会在任务完成后再运行。

bba62d0e-3083-11ee-9e74-dac502259ad0.png

套路分析

1、在结构体加入互斥体

/*chrdevbase设备结构体*/
structnewchr_dev{
....
structmutexlock;/*互斥体*/
};

2、在驱动入口初始化互斥体

/*
*@description:驱动入口函数
*@param:无
*@return:0成功;其他失败
*/
staticint__initchrdevbase_init(void)
{
/*初始化互斥体*/
mutex_init(&chrdevbase.lock);
......
return0;
}

3、在打开设备时获取互斥体

/*
*@description:打开设备
*@param-inode:传递给驱动的inode
*@param-filp:设备文件,file结构体有个叫做private_data的成员变量
*一般在open的时候将private_data指向设备结构体。
*@return:0成功;其他失败
*/
staticintchrdevbase_open(structinode*inode,structfile*filp)
{
printk("[BSP]chrdevbaseopen!
");
filp->private_data=&chrdevbase;/*设置私有数据*/

/*获取互斥体,可以被信号打断*/
if(mutex_lock_interruptible(&chrdevbase.lock)){
return-ERESTARTSYS;
}
#if0
mutex_lock(&chrdevbase.lock);/*不能被信号打断*/
#endif

return0;
}

4、在释放设备时释放互斥体

/*
*@description:关闭/释放设备
*@param-filp:要关闭的设备文件(文件描述符)
*@return:0成功;其他失败
*/
staticintchrdevbase_release(structinode*inode,structfile*filp)
{
structnewchr_dev*dev=filp->private_data;
/*释放互斥锁*/
mutex_unlock(&dev->lock);
printk("[BSP]release!
");
return0;
}


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 驱动
    +关注

    关注

    12

    文章

    1743

    浏览量

    84607
  • 控制台
    +关注

    关注

    0

    文章

    82

    浏览量

    10276
  • 源码
    +关注

    关注

    8

    文章

    607

    浏览量

    28758

原文标题:i.MX6ULL|并发与竞争实验

文章出处:【微信号:玩转单片机,微信公众号:玩转单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    i.MX6ULL 驱动开发7—按键输入捕获与GPIO输入配置与高低电平读取

    本篇主要介绍了i.MX6ULL的按键检测的使用,主要的知识点是设备树的修改,以及GPIO的输入配置与高低电平的读取。
    的头像 发表于 05-24 09:11 5700次阅读
    <b class='flag-5'>i.MX6ULL</b> 驱动开发7—按键输入捕获与GPIO输入配置与高低电平读取

    使用i.MX6ULL开发板进行Linux根文件系统的完善

    上一篇推文讲了怎么移植根文件系统,并在i.MX6ULL开发板中运行起来,但是会出现一些提示,现在来进行根文件的完善。
    发表于 10-17 11:13 630次阅读

    移植NXP官方linux 5.4内核到i.MX6ULL开发板

    本文描述移植NXP官方 linux 5.4 内核到i.MX6ULL开发板。
    发表于 12-19 11:10 1684次阅读

    i.MX6UL/i.MX6ULL开发常见问题】单独编译内核,uboot生成很多文件,具体用哪一个?

    i.MX6UL/i.MX6ULL开发常见问题》基于米尔电子 i.MX6UL/i.MX6ULL产品(V.10)2.3单独编译内核,uboot生成很多文件,具体用哪一个?答:内核编译出来
    发表于 07-01 17:50

    I.MX6ULL终结者开发板裸机仿真jlink调试

    I.MX6ULL‘终结者’开发板预留了JTAG仿真接口,并给出了开发文档,可以实现在JLINK仿真器条件下的单步跟踪、断点调试等功能,使得开发研究i.MX6ULL处理器的相关技术更加直观便利。
    发表于 07-07 10:56

    i.MX6ULL开发板硬件资源

    迅为i.MX6ULL 终结者开发板硬件资源非常丰富,几乎将 i.MX6ULL 芯片的所有资源都扩展引出到底板上了,底板提供了丰富的外设接口,开发板的尺寸是 190mm*125mm,充分考虑了人性化设计,整体显得十分大。
    发表于 12-29 06:18

    初识 i.MX6ULL 寄存器

    裸机开发_L1_汇编LED实验0. 本节目标1. 硬件层电路2. 初识 i.MX6ULL 寄存器2.1 i.MX6ULL 时钟控制寄存器2.2 i.MX6ULL IO复用寄存器2.3
    发表于 12-20 07:13

    I.MX6ULL无法枚举USB2514是为什么?

    你好目前,I.MX6ULL开发存在一些问题。其中之一是OTG USB2无法正常挂载USB2514,无法正确枚举下游设备,只显示设备id。usb设计要注意什么。
    发表于 04-03 06:55

    I.MX6ULL UART传输问题求解

    I.MX6ULL UART传输问题
    发表于 04-21 08:09

    珠海明远智睿科技联合NXP强势推出i.MX6ull核心板

    NXP I.MX6ULL是一个高性能、低功耗、高性价比处理器系列,基于ARM Cortex-A7内核,主频可达900MHz。i.MX 6ULL应用处理器包括一个集成的电源管理模块,可以省掉外部
    发表于 04-24 14:10 466次阅读

    飞凌i.MX6ULL开发板的评测,再次进阶拥有更高的性价比

    处理器MCIMX6Y2开发设计,采用先进的ARMCortex-A7内核,运行速度高达800MHz。i.MX6ULL应用处理器包括一个集成的电源管理模块,降低了外接电源的复杂性,并简化了上电时序。 i.MX6ULL
    发表于 10-27 11:55 1291次阅读
    飞凌<b class='flag-5'>i.MX6ULL</b>开发板的评测,再次进阶拥有更高的性价比

    基于NXP i.MX6ULL处理器的FETMX6ULL-C核心板

    “性价比高,功能接口丰富,资料齐全,稳定性强”这是许多用户对飞凌FETMX6ULL-S核心板的评价。作为NXP公司一颗经典的MPU,i.MX6ULL的市场认可度无需多言。而作为NXP公司的金牌
    发表于 04-11 15:05 902次阅读
    基于NXP <b class='flag-5'>i.MX6ULL</b>处理器的FETMX<b class='flag-5'>6ULL</b>-C核心板

    i.MX6ULL】驱动开发4——点亮LED(寄存器版)

    本篇主要介绍了如何通过操作寄存器来点亮i.MX6ULL开发板上的led,通过编写LED对应的驱动程序和应用程序,实现程序设计的分层。
    的头像 发表于 05-21 21:26 2727次阅读
    【<b class='flag-5'>i.MX6ULL</b>】驱动开发4——点亮LED(寄存器版)

    【北京迅为】i.MX6ULL开发板移植 Debian 文件系统

    【北京迅为】i.MX6ULL开发板移植 Debian 文件系统
    的头像 发表于 02-10 15:34 887次阅读
    【北京迅为】<b class='flag-5'>i.MX6ULL</b>开发板移植 Debian 文件系统

    基于i.MX6ULL的掉电检测设计与软件测试

    基于i.MX6ULL的掉电检测设计与软件测试基于i.MX6ULL平台设计实现掉电检测功能,首先选择一路IO,利用IO电平变化触发中断,在编写驱动时捕获该路GPIO的中断,然后在中断响应函数中发
    的头像 发表于 11-09 10:40 580次阅读
    基于<b class='flag-5'>i.MX6ULL</b>的掉电检测设计与软件测试