当前位置: 首页 > 科技观察

Linux设备驱动中的并发控制

时间:2023-03-20 14:08:10 科技观察

并发是指多个执行单元同时并行执行,而并发执行单元对共享资源的访问很容易导致竞争条件。linux内核中的主要raceconditions1.多对称处理器的多个CPU2.单CPU内的进程和抢占它的进程3.中断间共享内存资源的访问(硬中断,软中断,Tasklet,下半部分)而进程中的代码区被称为“临界区”。临界区需要通过某种互斥机制来保护。中断屏蔽、原子操作、自旋锁和信号量是可以在linux设备驱动程序中使用的互斥方法。这些相互排斥的介绍:1.中断屏蔽,这个主要是针对单CPU使用的,中断屏蔽会让中断和进程之间的并发不再发生。使用方法:local_irq_disable();//屏蔽中断...临界区...local_irq_enable();//启用中断由于linux的异步IO、进程调度等很多重要的操作都依赖于中断,所以中断对于内核的运行非常重要,在中断屏蔽期间不能处理所有的中断,所以长时间屏蔽中断是非常危险的,可能会导致数据丢失甚至系统崩溃。所以这不是讨论的重点。*******************************************************************************************************************************************************************************2。原子操作,原子操作是一系列不能被中断的操作。Linux内核提供了一系列函数来实现内核中的原子操作。这些函数分为两类,分别对位变量和整数变量进行原子操作。实现原子操作的步骤:1)。定义原子变量并设置变量值voidatomic_set(atomic_t*v,inti);//设置原子变量值iatomic_tv=ATOMIC_INIT(0);//定义原子变量v,初始化为02).获取原子变量atomic_read(atomic_t)的值*v);3).原子变量加减运算voidatomic_add(inti,atomic_t*v);//原子变量加ivoidatomic_sub(inti,atomic_t*v);//原子变量减i4).原子变量自增/自减voidatomic_inc(atomic_t*v);//自增1voidatomic_dec(atomic_t*v);//自减15)。运行测试:对原子变量(不加)进行自增自减后,测试是否为0,为0则返回true,否则返回false。intatomic_inc_and_test(atomic_t*v);intatomic_dec_and_test(atomic_t*v);intatomic_sub_and_test(inti,atomic_t*v);6).操作返回);intatomic_dec_return(atomic_t*v);***************************************************************************************************************************************************************************3.自旋锁自旋锁是一个忙锁,在一个小循环中不断重复测试和设置的操作。自旋锁保护的临界区的特点:临界区要小,临界区内不能有诱导睡眠的操作,否则系统可能崩溃。自旋锁会导致系统死锁,导致此问题的最常见原因是自旋锁的递归使用。自旋锁的操作步骤:1)。定义自旋锁spinlock_tlock;2).初始化自旋锁spin_lock_init(lock);//这是一个宏,用于动态初始化自旋锁lock;3).GetthespinLockspin_lock(lock);//这个宏是用来加锁的,如果能立即获得锁,就可以立即返回,否则,会一直在那里自旋,直到释放自旋锁的持有者。spin_trylock(lock);//如果能获取到则返回true,否则返回false,其实就是没有原地自旋。4).释放自旋锁spin_unlock(lock);搭配上面两个。例子:spinlock_tlock;spin_lock_init(&lock);spin_lock(&lock);//获取一个自旋锁来保护临界区。...Criticalsectionspin_unlock(&lock);//释放自旋锁自旋锁不关心加锁的临界区是如何执行的。不管是读操作还是写操作,其实在读共享资源的时候,应该可以让多个执行单元同时访问。在这种情况下,自旋锁就有缺点了。于是衍生出一个读写锁。它保留了自旋特性,但允许多个单元进程同时进行对操作。当然,读和写不能同时进行。现在还有一个问题。如果第一个进程写入共享资源,第二个进程读取共享资源,一旦写入,就无法读取。可能写的东西多了,但是第二个进程读的很少,所以总不能第一个进程写第二个进程读吧?当然,这就引出了顺序锁的概念。都是一样的操作。*******************************************************************************************************************************************************************************4。信号量是用于保护临界区的常用方法。它的使用类似于自旋锁,但它不会原地自旋。当无法获取信号量时,进程将进入睡眠等待状态。主要业务:1)。定义sem信号量structsemaphoresem;2).初始化信号量voidsema_init(structsemaphore*sem,intval);初始化信号量,并将sem的值设置为val。初始化的时候也可以这样使用,init_MUTEX(sem),这是一个宏#defineinit_MUTEX(sem)sema_init(sem,1)init_MUTEX_LOCKED(sem),这是一个宏#defineinit_MUTEX_LOCKED(sem)sema_init(sem,0)3).获取信号量voiddown(structsemaphore*sem);该函数用于获取信号量sem,会导致睡眠,所以不能在中断中使用。intdown_interruptible(structsemaphore*sem);和上面的函数类似,因为down进入休眠状态的过程不能被信号打断,但是可以被信号打断,信号也会导致函数返回。intdown_trylock(structsemaphore*sem);4).释放信号量voidup(structsemaphore*sem);该函数用于释放信号量,同时唤醒服务员。信号量一般这样使用:DECLARE_MUTEX(sem);down??(&sem);...criticalsectionup(&sem);linux自旋锁和semaphore采用“获取锁-访问临界区-释放锁”的方式。******************************************************************************************************************************************5.Mutex互斥量和信号量基本相同。不介绍了。总结:并发和竞争广泛存在。这些机制是解决问题的好方法。中断屏蔽很少单独使用。原子操作只能对整数执行。因此,自旋锁和信号量的应用最为广泛。自旋锁会造成死循环,加锁期间不允许阻塞,所以要求加锁的临界区要小。信号量允许临界区的阻塞,可以适用于临界区较大的情况。读写自旋锁和读写信号量是条件宽松的自旋锁和信号量。它们允许多个进程同时读取共享空间。fifo综合示例/*=======================================================================全局fifodriversanexampleofchardevicedrivers这个例子是为了介绍poll,blocking和non-blockingaccessTheoriginalcodeisBaohuaSong.AllRightsReserved.=======================================================================*/#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#defineGLOBALFIFO_SIZE0x1000/*全局fifo最大4K字节*/#defineFIFO_CLEAR0x1/*清除全局内存的长度*/#defineGLOBALFIFO_MAJOR250/*globalfifo预设主设备号*/staticintglobalfifo_major=GLOBALFIFO_MAJOR;/*globalfifo设备结构*/structglobalfifo_dev{structcdevcdev;/*cdev结构*/unsignedintcurrent_len;/*fifo有效数据长度*/unsignedBEFIALFO]GSI[GSI;/*全局内存*/structsemaphoresem;/*并发控制信号量*/wait_queue_head_tr_wait;/*阻塞读等待队列头*/wait_queue_head_tw_wait;/*阻塞写等待队列头*/structtasklet_structttlet;};structglobalfifo_dev*globalfifo_devp;/*Device结构指针*//*文件打开函数*/intglobalfifo_open(structinode*inode,structfile*filp){/*将设备结构指针赋值给文件私有数据指针*/filp->private_data=globalfifo_devp;return0;}/*文件释放函数*/intglobalfifo_release(structnode*inode,structfile*filp){return0;}/*ioctl设备控制函数*/staticintglobalfifo_ioctl(structinode*inodep,structfile*filp,unsignedintcmd,unsignedlongarg){structglobalfifo_dev*dev=filp->private_data;/*获取设备结构指针*/switch(cmd){caseFIFO_CLEAR:down(&dev->sem);//获取信号量dev->current_len=0;memset(dev->mem,0,GLOBALFIFO_SIZE);up(&dev->sem);//释放信号量printk(KERN_INFO"globalfifoissettozero\n");break;default:return-EINVAL;}return0;}staticunsignedintglobalfifo_poll(structfile*filp,poll_table*wait){unsignedintmask=0;structglobalfifo_dev*dev=filp->private_data;/*获取设备结构指针*/down(&dev->sem);poll_wait(filp,&dev->r_wait,wait);poll_wait(filp,&dev->w_wait,wait);/*fifo不为空*/if(dev->current_len!=0){mask|=POLLIN|POLLRDNORM;/*可以获取标记数据*/}/*fifo未满*/if(dev->current_len!=GLOBALFIFO_SIZE){mask|=POLLOUT|POLLWRNORM;/*可以获取标记数据written*//获取设备结构体指针DECLARE_WAITQUEUE(wait,current);//定义等待队列down(&dev->sem);//获取信号量add_wait_queue(&dev->r_wait,&wait);//入头ofthereadwaitingqueue/*WaitFIFOisnotempty*/if(dev->current_len==0){if(filp->f_flags&O_NONBLOCK){ret=-EAGAIN;gotoout;}__set_current_state(TASK_INTERRUPTIBLE);//改变进程状态睡眠(&dev->sem);schedule();//调度其他进程执行if(signal_pending(current))//如果被信号唤醒{ret=-ERESTARTSYS;gotoout2;}down(&dev->sem);}/*复制到用户空间*/if(count>dev->current_len)count=dev->current_len;if(copy_to_user(buf,dev->mem,count)){ret=-EFAULT;gotoout;}else{memcpy(dev->mem,dev->mem+count,dev->current_len-count);//fifo数据转发dev->current_len-=count;//有效数据长度缩减printk(KERN_INFO"read%dbytes(s),current_len:%d\n",count,dev->current_len);wake_up_interruptible(&dev->w_wait);//唤醒写等待队列ret=count;}out:up(&dev->sem);//释放thesemaphoreout2:remove_wait_queue(&dev->w_wait,&wait);//从附加的等待队列头中移除loff_t*ppos){structglobalfifo_dev*dev=filp->private_data;//获取设备结构指针intret;DECLARE_WAITQUEUE(wait,current);//定义等待队列down(&dev->sem);//获取信号量add_wait_queue(&dev->w_wait,&wait);//进入写等待队列的头部/*Waitfor先进先出full*/if(dev->current_len==GLOBALFIFO_SIZE){if(filp->f_flags&O_NONBLOCK)//如果是非阻塞访问{ret=-EAGAIN;gotoout;}__set_current_state(TASK_INTERRUPTIBLE);//改变进程状态tosleepup(&dev->sem);schedule();//调度其他进程执行if(signal_pending(current))//如果被信号唤醒{ret=-ERESTARTSYS;gotoout2;}down(&dev->sem);//获取Semaphore}/*从用户空间复制到内核空间*/if(count>GLOBALFIFO_SIZE-dev->current_len)count=GLOBALFIFO_SIZE-dev->current_len;if(copy_from_user(dev->mem+dev->current_len,buf,count)){ret=-EFAULT;gotoout;}else{dev->current_len+=count;printk(KERN_INFO"written%dbytes(s),current_len:%d\n",count,dev->current_len);wake_up_interruptible(&dev->r_wait);//唤醒读等待队列ret=count;}tasklet_schedule(&dev->tlet);printk("inwritejiffies=%ld\n",jiffies);out:up(&dev->sem);//释放信号量out2:remove_wait_queue(&dev->w_wait,&wait);//移除set_current_state(TASK_RUNNING);returnret;}/*文件操作结构体e*/staticconststructfile_operationsglobalfifo_fops来自附加的等待队列头={.owner=THIS_MODULE,.read=globalfifo_read,.write=globalfifo_write,.ioctl=globalfifo_ioctl,.poll=globalfifo_poll,.open=globalfifo_open,.release=globalfifo_release,};*dev,intindex){interr,devno=MKDEV(globalfifo_major,index);cdev_init(&dev->cdev,&globalfifo_fops);dev->cdev.owner=THIS_MODULE;dev->cdev.ops=&globalfifo_fops;err=cdev_add(&dev->cdev,devno,1);if(err)printk(KERN_NOTICE"Error%daddingLED%d",err,index);}voidjit_tasklet_fn(unsignedlongarg){printk("injit_tasklet_fnjiffies=%ld\n",jiffies);}/*设备驱动模块加载函数*/intglobalfifo_init(void){intret;dev_tdevno=MKDEV(globalfifo_major,0);/*申请设备号*/if(globalfifo_major)ret=register_chrdev_region(devno,1,"globalfifo");else/*动态申请设备号*/{ret=alloc_chrdev_region(&devno,0,1,"globalfifo");globalfifo_major=MAJOR(devno);}if(ret<0)returnret;/*动态申请设备结构内存*/globalfifo_devp=kmalloc(sizeof(structglobalfifo_dev),GFP_KERNEL);if(!globalfifo_devp)/*应用程序失败*/{ret=-ENOMEM;gotofail_malloc;}memset(globalfifo_devp,0,sizeof(structglobalfifo_dev));globalfifo_setup_cdev(globaldevo_setup_cdev(globaldevo_ut-ifglobUT-EX&ingllob_pdev,0)>sem);/*初始化信号量*/init_waitqueue_head(&globalfifo_devp->r_wait);/*初始化读等待队列头*/init_waitqueue_head(&globalfifo_devp->w_wait);/*初始化写等待队列头*//*registerthetasklet*/tasklet_init(&globalfifo_devp->tlet,jit_tasklet_fn,(unsignedlong)globalfifo_devp);return0;fail_malloc:unregister_chrdev_region(devno,1);returnret;}/*模块卸载函数*/voidglobalfifo_exit(void){cdev;f-delifc/>_deval)*注销cdev*/kfree(globalfifo_devp);/*释放设备结构内存*/unregister_chrdev_region(MKDEV(globalfifo_major,0),1);/*释放设备号*/}MODULE_AUTHOR("宋宝华");MODULE_LICENSE("DualBSD/GPL");module_param(globalfifo_major,int,S_IRUGO);module_init(globalfifo_init);module_ex它(globalfifo_exit);