一文搞懂 Linux 驱动并发与竞争(学习笔记)
1. 基本概念:
对于linux的系统编程,博主做过linux AI项目后,比较了解了,所以就不过多赘述, 如果时间允许后续会将系统的笔记发出来;
竞争产生的原因:
多线程访问
中断访问
抢占访问
多核并发访问
共享资源:
全局变量
内核缓冲区
设备结构体
保护共享资源
原子操作
自旋锁
互斥锁
信号量
2. 原子操作:
2.1 概念:
原子就是借用化学的概念,不可再分
原子操作就是Linux下,该操作在执行完之前不会被任何事物打断(这个操作不可再分)。
原子操作分类:用于整形变量和位的保护
整型原子操作
位原子操作
2.2 API:
头文件:#include <linux/atomic.h>
atomic_t 结构体:
typedefstruct{intcounter;}atomic_t;// 用于32位#ifdefCONFIG_64BITtypedefstruct{longcounter;}atomic64_t;// 用于64位#endif- 32位的, 64位只在函数上加个64:(用到来查)
- 例子:
2.3 code:
- 因为多个设备打开这个驱动程序,存在竞争的情况,所以使用原子操作实现打开时互斥
/* 1- 定义一个原子变量: 原子变量初始化,用于实现设备单开(同一时间仅允许一个进程打开) */staticatomic64_tv=ATOMIC64_INIT(1);/* 设备私有数据结构体 */structchrdev_test{dev_tdev_num;// 设备号intmajor,minor;// 主/次设备号structcdevcdev;// 字符设备结构体structclass*class;// 设备类};staticstructchrdev_testdev1;staticcharkbuf[10]={0};// 内核数据缓冲区/************************* 设备操作函数实现 *************************//* 2- 用原子变量实现互斥打开: 打开设备:原子变量判断,实现单开 */staticintopen_test(structinode*inode,structfile*file){if(atomic64_read(&v)!=1){return-EBUSY;// 设备已被占用,返回忙}atomic64_set(&v,0);// 标记设备已打开return0;}/* 读设备:内核 -> 用户 拷贝数据 */staticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata[]="topeet";printk(KERN_INFO"read_test: enter\n");ret=copy_to_user(ubuf,data,strlen(data));if(ret){printk(KERN_ERR"read_test: copy_to_user error\n");}else{printk(KERN_INFO"read_test: copy_to_user ok\n");}return0;}/* 写设备:用户 -> 内核 拷贝数据,并根据内容延时 */staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;ret=copy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERR"write_test: copy_from_user error\n");return-EFAULT;}printk(KERN_INFO"write_test: received data: %s\n",kbuf);/* 根据写入内容执行不同延时 */if(!strcmp(kbuf,"topeet")){ssleep(4);}elseif(!strcmp(kbuf,"itop")){ssleep(2);}returnlen;// 返回实际写入长度}/* 3- 关闭设备:释放原子变量 */staticintrelease_test(structinode*inode,structfile*file){atomic64_set(&v,1);// 标记设备空闲return0;}3. 自旋锁:
3.1 概念:
死等的现象: 当一个线程尝试获取自旋锁而发现该锁已被其他线程持有时, 它不会进入睡眠状态等待, 而是会持续循环地尝试获取锁, 直到成功为止。这称为自旋。
适用于自旋锁锁持有时间短且线程不希望在重新调度上花费过多时间的情况
因为死等,特别浪费CPU时间
3.2 API:
头文件: #include <linux/spinlock.h>
用到再记:
3.3 code
- 仅在 open 和 release 中对共享标志 flag 使用自旋锁
// 1- 自旋锁 + 设备状态标志(实现设备同一时间仅能被一个进程打开)staticspinlock_tspinlock_test;staticintflag=1;// 1=设备空闲 0=设备已被占用// 设备私有数据结构体structchrdev_test{dev_tdev_num;// 设备号intmajor,minor;// 主/次设备号structcdevcdev_test;// 字符设备structclass*class_test;// 设备类};staticstructchrdev_testdev1;staticcharkbuf[10]={0};// 内核缓冲区/************************* 设备操作函数 *************************/// 打开设备:自旋锁保护 flag,实现互斥单开staticintopen_test(structinode*inode,structfile*file){spin_lock(&spinlock_test);// 2-加锁if(flag!=1){spin_unlock(&spinlock_test);return-EBUSY;// 设备忙}flag=0;// 标记设备已占用spin_unlock(&spinlock_test);// 解锁return0;}// 读设备:内核 -> 用户 拷贝 "topeet"staticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata_buf[]="topeet";printk(KERN_INFO"this is read_test\n");ret=copy_to_user(ubuf,data_buf,strlen(data_buf));if(ret)printk(KERN_ERR"copy_to_user error\n");elseprintk(KERN_INFO"copy_to_user ok\n");return0;}// 写设备:用户 -> 内核 拷贝数据,并根据内容延时staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;ret=copy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERR"copy_from_user error\n");return-EFAULT;}// 根据写入内容执行延时if(!strcmp(kbuf,"topeet"))ssleep(4);elseif(!strcmp(kbuf,"itop"))ssleep(2);printk(KERN_INFO"copy_from_user buf: %s\n",kbuf);returnlen;}// 关闭设备:释放设备占用标志staticintrelease_test(structinode*inode,structfile*file){printk(KERN_INFO"this is release_test\n");spin_lock(&spinlock_test);flag=1;// 标记设备空闲spin_unlock(&spinlock_test);return0;}// 文件操作集合staticconststructfile_operationsfops_test={.owner=THIS_MODULE,.open=open_test,.read=read_test,.write=write_test,.release=release_test,};/************************* 模块加载/卸载 *************************/staticint__initspinlock_drv_init(void){intret;// 初始化自旋锁spin_lock_init(&spinlock_test);// 动态申请设备号ret=alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name");if(ret<0){printk(KERN_ERR"alloc_chrdev_region error\n");returnret;}printk(KERN_INFO"alloc_chrdev_region ok\n");// 获取主/次设备号dev1.major=MAJOR(dev1.dev_num);dev1.minor=MINOR(dev1.dev_num);printk(KERN_INFO"major=%d, minor=%d\n",dev1.major,dev1.minor);// 初始化并添加字符设备cdev_init(&dev1.cdev_test,&fops_test);dev1.cdev_test.owner=THIS_MODULE;cdev_add(&dev1.cdev_test,dev1.dev_num,1);// 创建设备类 + 设备节点(自动生成 /dev/device_test)dev1.class_test=class_create(THIS_MODULE,"class_test");device_create(dev1.class_test,NULL,dev1.dev_num,NULL,"device_test");printk(KERN_INFO"spinlock driver init success\n");return0;}4. 信号量
4.1 API:
- 头文件:#include <linux/semaphore.h>
4.2 code:
//1- 二值信号量:实现设备单开(同一时间仅允许一个进程打开)structsemaphoresemaphore_test;// 设备私有数据结构体structchrdev_test{dev_tdev_num;// 设备号intmajor,minor;// 主/次设备号structcdevcdev_test;// 字符设备structclass*class_test;// 设备类};staticstructchrdev_testdev1;staticcharkbuf[10]={0};// 内核数据缓冲区/************************* 设备操作函数实现 *************************/// 2- 打开设备:信号量 P 操作(申请资源)staticintopen_test(structinode*inode,structfile*file){printk(KERN_INFO"this is open_test\n");down(&semaphore_test);// 信号量-1,为0则阻塞等待return0;}// 读设备:内核 -> 用户 拷贝 "topeet"staticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata_buf[]="topeet";printk(KERN_INFO"this is read_test\n");ret=copy_to_user(ubuf,data_buf,strlen(data_buf));if(ret)printk(KERN_ERR"copy_to_user error\n");elseprintk(KERN_INFO"copy_to_user ok\n");return0;}// 写设备:用户 -> 内核 拷贝数据,并根据内容延时staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;ret=copy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERR"copy_from_user error\n");return-EFAULT;}// 根据写入内容执行不同延时if(!strcmp(kbuf,"topeet"))ssleep(4);elseif(!strcmp(kbuf,"itop"))ssleep(2);printk(KERN_INFO"copy_from_user buf: %s\n",kbuf);returnlen;}// 3-关闭设备:信号量 V 操作(释放资源)staticintrelease_test(structinode*inode,structfile*file){up(&semaphore_test);// 信号量+1,唤醒等待的进程printk(KERN_INFO"this is release_test\n");return0;}// 文件操作集合staticconststructfile_operationsfops_test={.owner=THIS_MODULE,.open=open_test,.read=read_test,.write=write_test,.release=release_test,};/************************* 模块加载/卸载 *************************/staticint__initsema_drv_init(void){intret;// 初始化信号量,值为 1(二值信号量 = 互斥锁)sema_init(&semaphore_test,1);// 动态申请设备号ret=alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name");if(ret<0){printk(KERN_ERR"alloc_chrdev_region error\n");returnret;}printk(KERN_INFO"alloc_chrdev_region ok\n");// 获取主/次设备号dev1.major=MAJOR(dev1.dev_num);dev1.minor=MINOR(dev1.dev_num);printk(KERN_INFO"major=%d, minor=%d\n",dev1.major,dev1.minor);// 初始化并添加字符设备cdev_init(&dev1.cdev_test,&fops_test);dev1.cdev_test.owner=THIS_MODULE;cdev_add(&dev1.cdev_test,dev1.dev_num,1);// 创建设备类 + 设备节点 /dev/device_testdev1.class_test=class_create(THIS_MODULE,"class_test");device_create(dev1.class_test,NULL,dev1.dev_num,NULL,"device_test");printk(KERN_INFO"semaphore driver init success\n");return0;}5. 互斥量:
5.1 API:
- #include <linux/mutex.h>
5.2 code
// 1-互斥锁:实现设备单开(同一时间仅允许一个进程打开)structmutexmutex_test;// 设备私有数据结构体structchrdev_test{dev_tdev_num;// 设备号intmajor,minor;// 主/次设备号structcdevcdev_test;// 字符设备structclass*class_test;// 设备类};staticstructchrdev_testdev1;staticcharkbuf[10]={0};// 内核数据缓冲区/************************* 设备操作函数实现 *************************/// 2-打开设备:互斥锁上锁staticintopen_test(structinode*inode,structfile*file){printk(KERN_INFO"this is open_test\n");mutex_lock(&mutex_test);// 上锁,已上锁则阻塞等待return0;}// 读设备:内核 -> 用户 拷贝 "topeet"staticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata_buf[]="topeet";printk(KERN_INFO"this is read_test\n");ret=copy_to_user(ubuf,data_buf,strlen(data_buf));if(ret)printk(KERN_ERR"copy_to_user error\n");elseprintk(KERN_INFO"copy_to_user ok\n");return0;}// 写设备:用户 -> 内核 拷贝数据,并根据内容延时staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;ret=copy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERR"copy_from_user error\n");return-EFAULT;}// 根据写入内容执行不同延时if(!strcmp(kbuf,"topeet"))ssleep(4);elseif(!strcmp(kbuf,"itop"))ssleep(2);printk(KERN_INFO"copy_from_user buf: %s\n",kbuf);returnlen;}// 3-关闭设备:互斥锁解锁staticintrelease_test(structinode*inode,structfile*file){mutex_unlock(&mutex_test);// 解锁,唤醒等待的进程printk(KERN_INFO"this is release_test\n");return0;}// 文件操作集合staticconststructfile_operationsfops_test={.owner=THIS_MODULE,.open=open_test,.read=read_test,.write=write_test,.release=release_test,};/************************* 模块加载/卸载 *************************/staticint__initmutex_drv_init(void){intret;// 初始化互斥锁mutex_init(&mutex_test);// 动态申请设备号ret=alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name");if(ret<0){printk(KERN_ERR"alloc_chrdev_region error\n");returnret;}printk(KERN_INFO"alloc_chrdev_region ok\n");// 获取主/次设备号dev1.major=MAJOR(dev1.dev_num);dev1.minor=MINOR(dev1.dev_num);printk(KERN_INFO"major=%d, minor=%d\n",dev1.major,dev1.minor);// 初始化并添加字符设备cdev_init(&dev1.cdev_test,&fops_test);dev1.cdev_test.owner=THIS_MODULE;cdev_add(&dev1.cdev_test,dev1.dev_num,1);// 创建设备类 + 设备节点 /dev/device_testdev1.class_test=class_create(THIS_MODULE,"class_test");device_create(dev1.class_test,NULL,dev1.dev_num,NULL,"device_test");printk(KERN_INFO"mutex driver init success\n");return0;}