Linux内核中的锁机制详解

张开发
2026/4/10 0:53:36 15 分钟阅读

分享文章

Linux内核中的锁机制详解
Linux内核中的锁机制详解引言锁机制是Linux内核中用于保护共享资源的核心组件它确保多个执行线程能够安全地访问共享资源避免数据竞争和不一致。Linux内核的锁机制支持多种锁类型包括自旋锁、互斥锁、读写锁、信号量等同时提供了丰富的锁管理功能如死锁检测、锁的优先级继承等。本文将深入探讨Linux内核中的锁机制包括其设计原理、架构、核心机制和应用场景。锁的基本概念1. 什么是锁锁是一种同步机制用于保护共享资源确保同一时间只有一个执行线程能够访问共享资源。2. 为什么需要锁数据竞争多个线程同时访问共享资源会导致数据竞争数据不一致数据竞争会导致数据不一致竞态条件竞态条件会导致程序行为不确定3. 锁的分类按等待方式分类自旋锁spinlock忙等待睡眠锁sleeping lock阻塞等待按访问权限分类互斥锁mutex独占访问读写锁rwlock允许多个读者或一个写者按实现方式分类原子操作自旋锁互斥锁信号量完成量原子操作1. 原子操作的概念原子操作是指不可中断的操作要么全部完成要么完全不执行。2. 原子整数操作#includelinux/atomic.h// 定义原子变量atomic_tvATOMIC_INIT(0);// 原子加法atomic_add(inti,atomic_t*v);// 原子减法atomic_sub(inti,atomic_t*v);// 原子自增atomic_inc(atomic_t*v);// 原子自减atomic_dec(atomic_t*v);// 读取原子变量intatomic_read(atomic_t*v);// 设置原子变量voidatomic_set(atomic_t*v,inti);// 原子测试并设置intatomic_test_and_set_bit(intnr,volatileunsignedlong*addr);3. 原子位操作#includelinux/bitops.h// 设置位voidset_bit(intnr,volatileunsignedlong*addr);// 清除位voidclear_bit(intnr,volatileunsignedlong*addr);// 改变位voidchange_bit(intnr,volatileunsignedlong*addr);// 测试位inttest_bit(intnr,constvolatileunsignedlong*addr);// 测试并设置位inttest_and_set_bit(intnr,volatileunsignedlong*addr);// 测试并清除位inttest_and_clear_bit(intnr,volatileunsignedlong*addr);自旋锁1. 自旋锁的概念自旋锁是一种忙等待的锁当锁被占用时线程会循环等待直到锁可用。2. 自旋锁的使用#includelinux/spinlock.h// 定义自旋锁spinlock_tlock;// 初始化自旋锁spin_lock_init(lock);// 获取自旋锁spin_lock(lock);// 释放自旋锁spin_unlock(lock);3. 自旋锁的变体#includelinux/spinlock.h// 禁止本地中断spin_lock_irq(lock);spin_unlock_irq(lock);// 保存并禁止本地中断unsignedlongflags;spin_lock_irqsave(lock,flags);spin_unlock_irqrestore(lock,flags);// 禁止下半部spin_lock_bh(lock);spin_unlock_bh(lock);读写自旋锁1. 读写自旋锁的概念读写自旋锁允许多个读者同时持有锁但只允许一个写者持有锁。2. 读写自旋锁的使用#includelinux/rwlock.h// 定义读写自旋锁rwlock_tlock;// 初始化读写自旋锁rwlock_init(lock);// 获取读锁read_lock(lock);// 释放读锁read_unlock(lock);// 获取写锁write_lock(lock);// 释放写锁write_unlock(lock);顺序锁1. 顺序锁的概念顺序锁是一种特殊的读写锁它允许读者在写者持有锁的情况下继续执行。2. 顺序锁的使用#includelinux/seqlock.h// 定义顺序锁seqlock_tlock;// 初始化顺序锁seqlock_init(lock);// 写者write_seqlock(lock);// 修改数据write_sequnlock(lock);// 读者unsignedintseq;do{seqread_seqbegin(lock);// 读取数据}while(read_seqretry(lock,seq));互斥锁1. 互斥锁的概念互斥锁是一种睡眠锁当锁被占用时线程会阻塞等待直到锁可用。2. 互斥锁的使用#includelinux/mutex.h// 定义互斥锁structmutexmy_mutex;// 初始化互斥锁mutex_init(my_mutex);// 获取互斥锁mutex_lock(my_mutex);// 尝试获取互斥锁if(mutex_trylock(my_mutex)){// 成功获取锁mutex_unlock(my_mutex);}// 释放互斥锁mutex_unlock(my_mutex);读写互斥锁1. 读写互斥锁的概念读写互斥锁是一种睡眠锁允许多个读者或一个写者持有锁。2. 读写互斥锁的使用#includelinux/rwsem.h// 定义读写互斥锁structrw_semaphoremy_rwsem;// 初始化读写互斥锁init_rwsem(my_rwsem);// 获取读锁down_read(my_rwsem);// 释放读锁up_read(my_rwsem);// 获取写锁down_write(my_rwsem);// 释放写锁up_write(my_rwsem);信号量1. 信号量的概念信号量是一种计数器用于控制对共享资源的访问。2. 信号量的使用#includelinux/semaphore.h// 定义信号量structsemaphoremy_sem;// 初始化信号量sema_init(my_sem,1);// 获取信号量down(my_sem);// 可中断获取信号量if(down_interruptible(my_sem)){// 被信号中断return-ERESTARTSYS;}// 释放信号量up(my_sem);完成量1. 完成量的概念完成量是一种用于一个线程通知另一个线程某个事件已经发生的机制。2. 完成量的使用#includelinux/completion.h// 定义完成量structcompletionmy_comp;// 初始化完成量init_completion(my_comp);// 等待完成wait_for_completion(my_comp);// 可中断等待完成if(wait_for_completion_interruptible(my_comp)){// 被信号中断return-ERESTARTSYS;}// 完成complete(my_comp);// 完成所有等待者complete_all(my_comp);RCU读-复制-更新1. RCU的概念RCU是一种同步机制允许多个读者同时访问数据写者通过复制数据来更新。2. RCU的使用#includelinux/rcupdate.h// 读者rcu_read_lock();// 读取数据rcu_read_unlock();// 写者structmy_struct*prcu_dereference(global_ptr);structmy_struct*newkmalloc(sizeof(*new),GFP_KERNEL);// 复制和修改数据rcu_assign_pointer(global_ptr,new);synchronize_rcu();// 等待所有读者完成kfree(p);锁的选择1. 锁的选择原则短时间临界区使用自旋锁长时间临界区使用互斥锁多读少写使用读写锁资源计数使用信号量事件通知使用完成量2. 性能考虑减少锁的粒度使用细粒度锁减少锁的持有时间尽量缩短临界区避免锁竞争合理设计数据结构使用无锁数据结构在合适的场景使用RCU等死锁1. 死锁的概念死锁是指两个或多个线程互相等待对方持有的锁导致所有线程都无法继续执行。2. 死锁的条件互斥资源只能被一个线程持有持有并等待线程持有资源并等待其他资源非抢占资源不能被抢占循环等待线程形成循环等待链3. 死锁的避免按顺序获取锁按照统一的顺序获取锁避免持有并等待一次获取所有需要的锁使用超时设置锁的超时时间使用锁depot使用内核的锁depot机制实际案例分析案例使用自旋锁的驱动#includelinux/module.h#includelinux/kernel.h#includelinux/init.h#includelinux/spinlock.hstaticspinlock_tmy_lock;staticintshared_data0;staticint__initmylock_init(void){// 初始化自旋锁spin_lock_init(my_lock);// 获取锁spin_lock(my_lock);// 修改共享数据shared_data;printk(KERN_INFOmylock: shared_data %d\n,shared_data);// 释放锁spin_unlock(my_lock);printk(KERN_INFOmylock: init\n);return0;}staticvoid__exitmylock_exit(void){printk(KERN_INFOmylock: exit\n);}module_init(mylock_init);module_exit(mylock_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(Your Name);MODULE_DESCRIPTION(Spinlock example);案例使用互斥锁的驱动#includelinux/module.h#includelinux/kernel.h#includelinux/init.h#includelinux/mutex.hstaticstructmutexmy_mutex;staticintshared_data0;staticint__initmymutex_init(void){// 初始化互斥锁mutex_init(my_mutex);// 获取锁mutex_lock(my_mutex);// 修改共享数据shared_data;printk(KERN_INFOmymutex: shared_data %d\n,shared_data);// 释放锁mutex_unlock(my_mutex);printk(KERN_INFOmymutex: init\n);return0;}staticvoid__exitmymutex_exit(void){printk(KERN_INFOmymutex: exit\n);}module_init(mymutex_init);module_exit(mymutex_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(Your Name);MODULE_DESCRIPTION(Mutex example);案例使用完成量的驱动#includelinux/module.h#includelinux/kernel.h#includelinux/init.h#includelinux/completion.h#includelinux/kthread.hstaticstructcompletionmy_comp;staticstructtask_struct*my_thread;staticintmy_thread_func(void*data){printk(KERN_INFOmycomp: thread running\n);// 模拟工作msleep(1000);// 完成complete(my_comp);printk(KERN_INFOmycomp: thread done\n);return0;}staticint__initmycomp_init(void){// 初始化完成量init_completion(my_comp);// 创建内核线程my_threadkthread_run(my_thread_func,NULL,my_thread);if(IS_ERR(my_thread)){returnPTR_ERR(my_thread);}printk(KERN_INFOmycomp: waiting for completion\n);// 等待完成wait_for_completion(my_comp);printk(KERN_INFOmycomp: completed\n);return0;}staticvoid__exitmycomp_exit(void){printk(KERN_INFOmycomp: exit\n);}module_init(mycomp_init);module_exit(mycomp_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(Your Name);MODULE_DESCRIPTION(Completion example);结论Linux内核的锁机制是一个功能强大、设计完善的同步机制它提供了多种锁类型满足不同场景下的同步需求。通过深入了解Linux锁机制的原理、类型和使用方法我们可以更好地保护共享资源避免数据竞争和死锁提高系统的性能和可靠性。在实际应用中我们需要根据场景选择合适的锁类型合理设计锁的粒度避免锁的滥用和误用。作为系统开发者和内核工程师掌握锁机制的知识是非常重要的它将帮助我们更好地设计和实现并发程序解决同步相关的问题提高系统的性能和稳定性。

更多文章