一文搞懂 Linux 驱动并发与竞争(学习笔记)
1. 基本概念对于linux的系统编程博主做过linux AI项目后比较了解了所以就不过多赘述, 如果时间允许后续会将系统的笔记发出来竞争产生的原因多线程访问中断访问抢占访问多核并发访问共享资源全局变量内核缓冲区设备结构体保护共享资源原子操作自旋锁互斥锁信号量2. 原子操作2.1 概念原子就是借用化学的概念不可再分原子操作就是Linux下该操作在执行完之前不会被任何事物打断这个操作不可再分。原子操作分类用于整形变量和位的保护整型原子操作位原子操作2.2 API:头文件#include linux/atomic.hatomic_t 结构体typedefstruct{intcounter;}atomic_t;// 用于32位#ifdefCONFIG_64BITtypedefstruct{longcounter;}atomic64_t;// 用于64位#endif32位的 64位只在函数上加个64用到来查例子2.3 code:因为多个设备打开这个驱动程序存在竞争的情况所以使用原子操作实现打开时互斥/* 1- 定义一个原子变量 原子变量初始化用于实现设备单开同一时间仅允许一个进程打开 */staticatomic64_tvATOMIC64_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_INFOread_test: enter\n);retcopy_to_user(ubuf,data,strlen(data));if(ret){printk(KERN_ERRread_test: copy_to_user error\n);}else{printk(KERN_INFOread_test: copy_to_user ok\n);}return0;}/* 写设备用户 - 内核 拷贝数据并根据内容延时 */staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;retcopy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERRwrite_test: copy_from_user error\n);return-EFAULT;}printk(KERN_INFOwrite_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;staticintflag1;// 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;// 设备忙}flag0;// 标记设备已占用spin_unlock(spinlock_test);// 解锁return0;}// 读设备内核 - 用户 拷贝 topeetstaticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata_buf[]topeet;printk(KERN_INFOthis is read_test\n);retcopy_to_user(ubuf,data_buf,strlen(data_buf));if(ret)printk(KERN_ERRcopy_to_user error\n);elseprintk(KERN_INFOcopy_to_user ok\n);return0;}// 写设备用户 - 内核 拷贝数据并根据内容延时staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;retcopy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERRcopy_from_user error\n);return-EFAULT;}// 根据写入内容执行延时if(!strcmp(kbuf,topeet))ssleep(4);elseif(!strcmp(kbuf,itop))ssleep(2);printk(KERN_INFOcopy_from_user buf: %s\n,kbuf);returnlen;}// 关闭设备释放设备占用标志staticintrelease_test(structinode*inode,structfile*file){printk(KERN_INFOthis is release_test\n);spin_lock(spinlock_test);flag1;// 标记设备空闲spin_unlock(spinlock_test);return0;}// 文件操作集合staticconststructfile_operationsfops_test{.ownerTHIS_MODULE,.openopen_test,.readread_test,.writewrite_test,.releaserelease_test,};/************************* 模块加载/卸载 *************************/staticint__initspinlock_drv_init(void){intret;// 初始化自旋锁spin_lock_init(spinlock_test);// 动态申请设备号retalloc_chrdev_region(dev1.dev_num,0,1,chrdev_name);if(ret0){printk(KERN_ERRalloc_chrdev_region error\n);returnret;}printk(KERN_INFOalloc_chrdev_region ok\n);// 获取主/次设备号dev1.majorMAJOR(dev1.dev_num);dev1.minorMINOR(dev1.dev_num);printk(KERN_INFOmajor%d, minor%d\n,dev1.major,dev1.minor);// 初始化并添加字符设备cdev_init(dev1.cdev_test,fops_test);dev1.cdev_test.ownerTHIS_MODULE;cdev_add(dev1.cdev_test,dev1.dev_num,1);// 创建设备类 设备节点自动生成 /dev/device_testdev1.class_testclass_create(THIS_MODULE,class_test);device_create(dev1.class_test,NULL,dev1.dev_num,NULL,device_test);printk(KERN_INFOspinlock driver init success\n);return0;}4. 信号量4.1 API:头文件#include linux/semaphore.h4.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_INFOthis is open_test\n);down(semaphore_test);// 信号量-1为0则阻塞等待return0;}// 读设备内核 - 用户 拷贝 topeetstaticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata_buf[]topeet;printk(KERN_INFOthis is read_test\n);retcopy_to_user(ubuf,data_buf,strlen(data_buf));if(ret)printk(KERN_ERRcopy_to_user error\n);elseprintk(KERN_INFOcopy_to_user ok\n);return0;}// 写设备用户 - 内核 拷贝数据并根据内容延时staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;retcopy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERRcopy_from_user error\n);return-EFAULT;}// 根据写入内容执行不同延时if(!strcmp(kbuf,topeet))ssleep(4);elseif(!strcmp(kbuf,itop))ssleep(2);printk(KERN_INFOcopy_from_user buf: %s\n,kbuf);returnlen;}// 3-关闭设备信号量 V 操作释放资源staticintrelease_test(structinode*inode,structfile*file){up(semaphore_test);// 信号量1唤醒等待的进程printk(KERN_INFOthis is release_test\n);return0;}// 文件操作集合staticconststructfile_operationsfops_test{.ownerTHIS_MODULE,.openopen_test,.readread_test,.writewrite_test,.releaserelease_test,};/************************* 模块加载/卸载 *************************/staticint__initsema_drv_init(void){intret;// 初始化信号量值为 1二值信号量 互斥锁sema_init(semaphore_test,1);// 动态申请设备号retalloc_chrdev_region(dev1.dev_num,0,1,chrdev_name);if(ret0){printk(KERN_ERRalloc_chrdev_region error\n);returnret;}printk(KERN_INFOalloc_chrdev_region ok\n);// 获取主/次设备号dev1.majorMAJOR(dev1.dev_num);dev1.minorMINOR(dev1.dev_num);printk(KERN_INFOmajor%d, minor%d\n,dev1.major,dev1.minor);// 初始化并添加字符设备cdev_init(dev1.cdev_test,fops_test);dev1.cdev_test.ownerTHIS_MODULE;cdev_add(dev1.cdev_test,dev1.dev_num,1);// 创建设备类 设备节点 /dev/device_testdev1.class_testclass_create(THIS_MODULE,class_test);device_create(dev1.class_test,NULL,dev1.dev_num,NULL,device_test);printk(KERN_INFOsemaphore driver init success\n);return0;}5. 互斥量5.1 API:#include linux/mutex.h5.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_INFOthis is open_test\n);mutex_lock(mutex_test);// 上锁已上锁则阻塞等待return0;}// 读设备内核 - 用户 拷贝 topeetstaticssize_tread_test(structfile*file,char__user*ubuf,size_tlen,loff_t*off){intret;chardata_buf[]topeet;printk(KERN_INFOthis is read_test\n);retcopy_to_user(ubuf,data_buf,strlen(data_buf));if(ret)printk(KERN_ERRcopy_to_user error\n);elseprintk(KERN_INFOcopy_to_user ok\n);return0;}// 写设备用户 - 内核 拷贝数据并根据内容延时staticssize_twrite_test(structfile*file,constchar__user*ubuf,size_tlen,loff_t*off){intret;retcopy_from_user(kbuf,ubuf,len);if(ret){printk(KERN_ERRcopy_from_user error\n);return-EFAULT;}// 根据写入内容执行不同延时if(!strcmp(kbuf,topeet))ssleep(4);elseif(!strcmp(kbuf,itop))ssleep(2);printk(KERN_INFOcopy_from_user buf: %s\n,kbuf);returnlen;}// 3-关闭设备互斥锁解锁staticintrelease_test(structinode*inode,structfile*file){mutex_unlock(mutex_test);// 解锁唤醒等待的进程printk(KERN_INFOthis is release_test\n);return0;}// 文件操作集合staticconststructfile_operationsfops_test{.ownerTHIS_MODULE,.openopen_test,.readread_test,.writewrite_test,.releaserelease_test,};/************************* 模块加载/卸载 *************************/staticint__initmutex_drv_init(void){intret;// 初始化互斥锁mutex_init(mutex_test);// 动态申请设备号retalloc_chrdev_region(dev1.dev_num,0,1,chrdev_name);if(ret0){printk(KERN_ERRalloc_chrdev_region error\n);returnret;}printk(KERN_INFOalloc_chrdev_region ok\n);// 获取主/次设备号dev1.majorMAJOR(dev1.dev_num);dev1.minorMINOR(dev1.dev_num);printk(KERN_INFOmajor%d, minor%d\n,dev1.major,dev1.minor);// 初始化并添加字符设备cdev_init(dev1.cdev_test,fops_test);dev1.cdev_test.ownerTHIS_MODULE;cdev_add(dev1.cdev_test,dev1.dev_num,1);// 创建设备类 设备节点 /dev/device_testdev1.class_testclass_create(THIS_MODULE,class_test);device_create(dev1.class_test,NULL,dev1.dev_num,NULL,device_test);printk(KERN_INFOmutex driver init success\n);return0;}