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

教Linux驱动7-KernelMutex

时间:2023-03-21 23:50:20 科技观察

Mutex概述信号量是一种在并行处理环境中保护多个处理器访问公共资源的机制。Mutex用于互斥操作。信号量的count初始化为1,down()/up()也可以实现类似mutex的功能。互斥量的语义比信号量的语义更简单、更轻巧。在锁竞争激烈的测试场景中,mutex比semaphore执行速度更快,扩展性更好。此外,互斥量数据结构的定义比信号量小。互斥锁的优点与信号量相比,互斥锁的效率要高得多:互斥锁首先实现了自旋等待机制,互斥锁在休眠前尝试获取锁,互斥锁实现了MCS,避免了多个CPU竞争锁导致的CPU缓存抖动。使用mutex的注意事项:一次只能有一个线程持有mutex。只有锁持有人才能解锁。您不能在一个进程中持有互斥量并在另一个进程中释放它。递归锁定和解锁是不允许的。当进程持有互斥量时,进程无法退出。必须使用官方API初始化互斥锁。Mutex可以休眠,所以不允许用在中断处理程序或者中断的下层,比如tasklets,timer等。目录:/linux/include/linux/mutex.h/**Simple,straightforwardmutexeswithstrictsemantics:**-onlyonetaskcanholdthemutexatatime*-onlytheownercanunlockthemutex*-multipleunlocksarenotpermitted*-recursivelockingisnotpermitted*-amutexobjectmustbeinitializedviatheAPI*-amutexobjectmustnotbeinitializedviamemsetorcopying*-taskmaynotexitwithmutexheld*-memoryareaswhereheldlocksresidemustnotbefreed*-heldmutexesmustnotbereinitialized*-mutexesmaynotbeusedinhardwareorsoftwareinterrupt*contextssuchastaskletsandtimers**ThesesemanticsarefullyenforcedwhenDEBUG_MUTEXESis*enabled.Furthermore,besidesenforcingtheaboverules,themutex*debuggingcodealsoimplementsanumberofadditionalfeatures*thatmakelockdebuggingeasierandfaster:**-usessymbolicnamesofmutexes,whenevertheyareprintedindebugoutput*-point-of-acquiretracking,symboliclookupoffunctionnames*-listofalllocksheldinthesystem,printoutofthem*-ownertracking*-detectsself-recursinglocksandprintsoutallrelevantinfo*-detectsmulti-任务circulardeadlocksandprintsoutallaffected*locksandtasks(andonlythosetasks)*/structmutex{/*1:unlocked,0:locked,negative:locked,possiblewaiters*/atomic_tcount;spinlock_twait_lock;structlist_headwait_list;#ifdefined(CONFIG_DEBUG_MUTEXES)||defined(CONFIG_SMP)structtask_struct*owner;#endif#ifdefCONFIG_MUTEX_SPIN_ON_OWNERvoid*spin_mlock;/*SpinnerMCSlock*/#endif#ifdefCONFIG_DEBUG_MUTEXESconstchar*name;void*magic;#endif#ifdefCONFIG_DEBUG_LOCK_ALLOCstructlockdep_mapdep_map;#endif};函数和访问规则:互斥锁主要用于访问内核中的函数互斥锁是在原子API之上实现的,但这对内核用户是不可见的。访问它必须遵循一些规则:一次只有一个任务可以持有互斥量,并且只有这个任务可以解锁互斥量。不能递归地锁定或解锁互斥量。互斥锁对象必须通过其API进行初始化,而不是使用memset或复制初始化。持有互斥体时任务无法结束。mutex占用的内存区域不能释放。无法重新初始化正在使用的互斥体。并且互斥量不能用于中断上下文。互斥锁比当前的内核信号量选项更快、更紧凑。mutex初始化的静态定义如下:DEFINE_MUTEX(name);mutex的动态初始化如下:mutex_init(&mutex);具体实现如下:#definemutex_init(mutex)\do{\staticstructlock_class_key__key;\\__mutex_init((mutex),#mutex,&__key);\}while(0)void__mutex_init(structmutex*lock,constchar*name,structlock_class_key*key){atomic_set(&lock->count,1);spin_lock_init(&lock->wait_lock);INIT_LIST_HEAD(&lock->wait_list);mutex_clear_owner(lock);#ifdefCONFIG_MUTEX_SPIN_ON_OWNERlock->spin_mlock=NULL;#endifdebug_mutex_init(lock,name,key);}申请互斥锁的操作列表如下:方法说明mutex_lock(structmutex*)锁定指定的互斥锁,Sleepmutex_unlock(structmutex*)ifnotavailablemutex_unlock(structmutex*)解锁指定的互斥锁mutex_trylock(structmutex*)view获取指定的mutex,成功则返回1;否则获得锁,返回值为0mutex_is_lock(structmutex*)ifReturns1如果锁已被征用;否则返回0。mutex的简单性和效率来自于比使用信号量更具限制性。它与信号量的不同之处在于,互斥量仅实现Dijkstra旨在执行的最基本行为。因此,mutex的使用场景相对比较严格。(1)代码:linux/kernel/mutex.cvoidinlinefastcall__schedmutex_lock(structmutex*lock);//获取互斥量。其实就是先减计数,然后用自己的自旋锁进入临界区操作。先获取count的值,然后设置count为-1??,判断如果原来的count设置为1,即可以获取互斥量,直接获取,跳出。否则,进入循环反复测试互斥锁的状态。循环中,先获取互斥量的原始状态,然后设置为-1,判断是否可以获取到(等于1),则退出循环,否则设置当前进程的状态为不可中断,并解锁自己的自旋锁,进入休眠状态,当被调度唤醒后,获取自己的自旋锁,进入新的查询自身状态(互斥锁的状态)的循环。(2)详见linux/kernel/mutex.cintfastcall__schedmutex_lock_interruptible(structmutex*lock);与mutex_lock()一样,它也获取一个互斥量。获取互斥量后返回0,或者休眠直到获取到互斥量。如果睡眠状态在等待获取锁时收到信号(睡眠被信号打断),则返回_EINIR。(3)详见linux/kernel/mutex.cintfastcall__schedmutex_trylock(structmutex*lock);尝试获取互斥锁,成功返回1,否则返回0,不要等待。有关释放互斥量的详细信息,请参阅linux/kernel/mutex.cvoidfastcallmutex_unlock(structmutex*lock);释放当前进程获取的互斥量。此函数不能在中断上下文中使用,并且不允许释放未锁定的互斥锁。互斥锁试用注意事项任何时候只有一个任务可以持有一个互斥锁,也就是说互斥锁的使用次数永远为1。锁住互斥锁的人必须负责再次解锁——你不能锁住一个互斥锁在一个上下文中,并在另一个上下文中解锁它。这种限制使得mutex不适用于内核和用户空间之间复杂的同步场景。最常用的方法是:在同一个上下文中加锁和解锁。递归锁定和解锁是不允许的。换句话说,你不能递归持有同一个锁,也不能解锁一个已经解锁的互斥体。持有互斥锁时,进程无法退出。互斥量不能用于中断或后半部分。即使使用mutex_trylock(),mutex也只能通过官方API进行管理:只能使用上一节介绍的方法进行初始化,不能复制,不能手动初始化,也不能重复初始化。信号量和互斥量与信号量非常相似,两者在内核中的共存可能会造成混淆。幸运的是,它们的标准用法有一个简单的规范:除非对互斥锁的约束阻止您使用它,否则优先使用互斥锁而不是信号量。当你编写新代码时,你只需要在特殊场合(通常是非常低级的代码)使用信号量。所以建议选择mutex。如果发现不能满足其约束条件,又没有其他选择,可以考虑选择信号量自旋锁和互斥量使用场合,知道什么时候使用自旋锁,什么时候使用互斥量(或信号量)对写的好代码很重要,但大多数时候,你不需要考虑太多,因为你只能在中断上下文中使用自旋锁,而在任务休眠时使用互斥锁。下面总结了各种锁的要求。推荐的锁定方法是低开销锁定。短期锁优先使用自旋锁。长期锁优先使用自旋锁。sleepisrequired使用互斥量加锁和解锁使用示例使用方法如下:1.structmutexmutex;2.mutex_init(&mutex);/*definition*/3.//lock4.mutex_lock(&mutex);5.6.//关键部分7.8.//解锁9.mutex_unlock(&mutex);可以看出,互斥量是信号量的简化版本,因为不需要管理任何使用计数。下面网卡DM9000的驱动使用了写eeprom的mutex机制:staticvoiddm9000_write_eeprom(board_info_t*db,intoffset,u8*data){unsignedlongflags;if(db->flags&DM9000_PLATF_NO_EEPROM)return;mutex_lock(&db->addr_qlock);spin_lock_ir(&db->lock,flags);iow(db,DM9000_EPAR,offset);iow(db,DM9000_EPDRH,data[1]);iow(db,DM9000_EPDRL,data[0]);iow(db,DM9000_EPCR,EPCR_WEP|EPCR_ERPRW);spin_unlock_irqrestore(&db->lock,flags);dm9000_wait_eeprom(db);mdelay(1);/*waitatleast150uStoclear*/spin_lock_irqsave(&db->lock,flags);iow(db,DM9000_EPCR,0);spin_unlock_irqrestore(&db->lock,flags);mutex_unlock(&db->addr_lock);}可以看到驱动每次向eeprom写入数据(访问关键资源),都需要先获取该资源对应的mutexdb->addr_lock,并且锁必须在使用后释放。本文转载自微信公众号“一口Linux”,可通过以下二维码关注。转载本文请联系易口Linux公众号。