原子、信号量、互斥锁、自旋锁_第1页
原子、信号量、互斥锁、自旋锁_第2页
原子、信号量、互斥锁、自旋锁_第3页
原子、信号量、互斥锁、自旋锁_第4页
原子、信号量、互斥锁、自旋锁_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、一、原子操作 所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。 原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。 原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下: typedef struct volatile int counter; atomic_t; volatile修饰

2、字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。 原子操作API包括: atomic_read(atomic_t * v); 该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。 atomic_set(atomic_t * v, int i); 该函数设置原子类型的变量v的值为i。 void atomic_add(int i, atomic_t *v); 该函数给原子类型的变量v增加值i。 atomic_sub(int i, atomic_t *v); 该函数从原子类型的变量v中减去i。 int atomic_sub_and_test

3、(int i, atomic_t *v); 该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。 void atomic_inc(atomic_t *v); 该函数对原子类型变量v原子地增加1。 void atomic_dec(atomic_t *v); 该函数对原子类型的变量v原子地减1。 int atomic_dec_and_test(atomic_t *v); 该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。 int atomic_inc_and_test(atomic_t *v); 该函数对原子类型的变量v原子地增加

4、1,并判断结果是否为0,如果为0,返回真,否则返回假。 int atomic_add_negative(int i, atomic_t *v); 该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。 int atomic_add_return(int i, atomic_t *v); 该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。 int atomic_sub_return(int i, atomic_t *v); 该函数从原子类型的变量v中减去i,并且返回指向v的指针。 int atomic_inc_return(atomic_t * v);

5、 该函数对原子类型的变量v原子地增加1并且返回指向v的指针。 int atomic_dec_return(atomic_t * v); 该函数对原子类型的变量v原子地减1并且返回指向v的指针。 原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。 当不需要引用该IP碎片时,就使用函

6、数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。二、信号量(semaphore) Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外使用,因此它与System V的IPC机制信号量毫不相干。 信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(

7、Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。 一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。 当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。Semaphore 操作API: 声明初始化信号量 DECLARE_MUTEX(name)

8、 该宏声明一个信号量name并初始化它的值为1,即声明一个互斥信号量。 DECLARE_MUTEX_LOCKED(name) 该宏声明一个互斥信号量name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。因此对于这种锁,一般是先释放后获得。 void sema_init (struct semaphore *sem, int val); 该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。 void init_MUTEX (struct semaphore *sem);#define init_MUTEX(sem)sema_init(sem, 1) 该函数用于初始化一个互斥

9、信号量,即它把信号量sem的值设置为1。 void init_MUTEX_LOCKED (struct semaphore *sem);#define init_MUTEX_LOCKED(sem)sema_init(sem, 0)该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态 获取一个信号量void down(struct semaphore * sem); 该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,

10、直到别的任务释放该信号量才能继续运行。 int down_interruptible(struct semaphore * sem); 该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。 int down_trylock(struct semaphore * sem); 该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,它

11、不会导致调用者睡眠,可以在中断上下文使用。 释放一个信号量void up(struct semaphore * sem); 该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。Basic usage of the semaphore interface is as follows:#include /* Architecture dependent header */* Statically declare a semaphore. To dynamically create a semaphore, use init_MUTEX()

12、 */static DECLARE_MUTEX(mysem);down(&mysem); /* Acquire the semaphore */* . Critical Section code . */up(&mysem); /* Release the semaphore */三、互斥锁互斥锁主要用于实现内核中的互斥访问功能。内核互斥锁是在原子API之上实现的,但这对于内核用户是不可见的。对它的访问必须遵循一些规则:同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行 解锁。互斥锁不能进行递归锁定或解锁。一个互斥锁对象必须通过其API初始化,而不能使用memset或复制初始化

13、。一个任务在持有互斥锁的时候是不能结束的。互斥锁所使用的内存区域是不能被释放的。使用中的互斥锁是不能被重新初始化的。并且互斥锁不能用于中断上下文。但是互斥锁比当前的内核信号量选项更快,并且更加紧凑,因此如果它们满足您的需求,那么它们将是您明智的选择。Mutex 操作API: 定义并初始化:struct mutex mutex;mutex_init(&mutex); 获取互斥锁:void mutex_lock(structmutex*lock);int mutex_trylock(struct mutex *lock); 释放互斥锁:void mutex_unlock(struct mutex

14、*lock); 清除互斥锁void mutex_destroy(struct mutex *lock)-清除互斥锁,使互斥锁不可用用mutex_destroy()函数解除由lock指向的互斥锁的任何状态。在调用执行这个函数的时候,lock指向的互斥锁不能在被锁状态。储存互斥锁的内存不被释放。返回值-mutex_destroy()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。EINVAL 非法参数EFAULT mp指向一个非法地址。 测试互斥锁的状态static inline int mutex_is_locked(struct mutex *lock)-测试互斥

15、锁的状态这个调用实际上编译成一个内联函数。如果互斥锁被持有(锁定),那么就会返回 1;否则,返回 0。Basic mutex usage is as follows:#include /* Statically declare a mutex. To dynamically create a mutex, use mutex_init() */static DEFINE_MUTEX(mymutex);/* Acquire the mutex. This is inexpensive if there * is no one inside the critical section. In the

16、 face of * contention, mutex_lock() puts the calling thread to sleep. */mutex_lock(&mymutex);/* . Critical Section code . */mutex_unlock(&mymutex); /* Release the mutex */四、自旋锁在概念上,自旋锁非常简单。一个自旋锁是一个互斥设备,它只能有两个值:“锁定”和“解锁”。它通常实现为某个整数值中的单个位。期望获得某特定锁的代码测试相关的位。如果锁可用,则“锁定”位被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入

17、忙循环并重复检查这个锁,直到该锁可用为止。这个循环就是自旋锁的“自旋”部分。适用于自旋锁的核心规则是:任何拥有自旋锁的代码都必须是原子的。它不能休眠,事实上,它不能因为任何原因放弃处理器,除了服务中断外(某些情况下此时也不能放弃处理器)。(休眠可发生在许多无法预期的地方;当我们编写需要在自旋锁下执行代码时,必须注意每一个所调用的函数。); 自旋锁必须在可能的最短时间内拥有。自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁。由于自旋锁使用者一般保持锁时间非常短,自旋锁的效率远高于互斥锁。(不需要切换)

18、自旋锁可以在任何上下文使用,而信号量(和互斥锁)适合保持时间较长的情况,它们会导致调用者睡眠,因此信号量(和互斥锁)只能在进程上下文使用(_trylock的变种能够在中断上下文使用)。(中断代码中不允许睡眠)自旋锁保持期间是抢占失效的,而信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。缺点:一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间)。所以,自旋锁不应该被长时间持有。当然,可以采用另外的方式处理对锁的争用:让请求线程睡眠,直到锁重新可用时在唤醒它。但是,这里有两次明显的上

19、下文切换,被阻塞的线程要换入或换出。因此,持有自旋锁的时间最好小于完成两次上下文切换的耗时。Spin lock 操作API: 初始化spinlock_t my_lock = SPIN_LOCK_UNLOCKED;或者在运行时,调用下面的函数:void spin_lock_init(spinlock_t *lock); 获得自旋锁void spin_lock(spinlock_t *lock);void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);void spin_lock_irq(spinlock_t *lock);voi

20、d spin_lock_bh(spinlock_t *lock)spin_loc_irqsave 禁止中断(只在本地处理器)在获得自旋锁之前; 之前的中断状态保存在 flags 里. 如果你绝对确定在你的处理器上没有禁止中断的(或者, 换句话说, 你确信你应当在你释放你的自旋锁时打开中断), 你可以使用 spin_lock_irq 代替, 并且不必保持跟踪 flags. 最后, spin_lock_bh 在获取锁之前禁止软件中断, 但是硬件中断留作打开的.如果你有一个可能被在(硬件或软件)中断上下文运行的代码获得的自旋锁, 你必须使用一种 spin_lock 形式来禁止中断. 其他做法可能死锁系统, 迟早. 如果你不在硬件中断处理里存取你的锁, 但是你通过软件中断(例如, 在一个 tasklet 运行的代码, 后面会讲到), 你可以使用 spin_lock_bh 来安全地避免死锁, 而仍然允许硬件中断被服务. 释放自旋锁void spin_unlock(spinlock_t *lock);void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);void spin_unlock_irq(spinlock_t *lock);void

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论