LV10-04-并发与竞争-02-并发控制机制
本文主要是并发控制机制的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
Linux开发板 | 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2 |
u-boot | 2013.01 |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
文件 | 下载链接 |
--- | --- |
前边大概了解了并发控制机制大概有原子变量、自旋锁、互斥锁和信号量几种,下边就来详细学习一下吧。
一、原子变量
1. 原子变量简介
首先看一下原子变量,原子变量就是指存取不可被打断的特殊整型变量。假如现在要对无符号整形变量 a 赋值,值为 3,对于 C 语言来讲很简单,直接就是:
1 | a = 3; |
但是编译成汇编之后,就变成了下边的样子:
1 | ldr r0, =0X30000000 /* 变量 a 地址 */ |
这样的话,一个简单的赋值被拆分为三步,一个线程正在将 a 赋值为 3,只剩下最后一步了,此时另一个线程需要将其赋值为 5 ,而打断了上一个线程赋值为 3 的操作,那么之前赋值为 3 的线程得到的 a 的值将会是 5 了,这是不被允许的。要解决这个问题就要保证三行汇编指令作为一个整体运行,也就是作为一个原子存在,这个时候键可以将 a 设置为原子变量。
【注意事项】
(1)在linux内核中,有区分32位原子变量和64位原子变量,他们的原理是一样的,只是操作函数名可能会有不同,需要注意一下。后边写的 API 函数笔记都是 32 位原子变量使用的。
【适用场合】共享资源为单个整型变量的互斥场合
2. 相关结构体
Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量,这个结构体定义在linux内核源码的这个文件中:
1 | include/linux/types.h # 32位 |
我们打开这个文件,会看到该结构体定义如下:
1 | // 32位 |
3. 原子整型操作API
以下是针对于32位原子变量进行操作的相关函数。
3.1 ATOMIC_INIT()
我们使用以下命令查询一下函数所在头文件:
1 | grep ATOMIC_INIT -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数是一个宏,用于定义原子变量的时候对其初始化。
【函数参数】
- i :int 类型,表示定义原子变量时要赋的初值。
【返回值】none
【使用格式】
1 | atomic_t a = ATOMIC_INIT(0); //定义原子变量 a 并赋初值为 0 |
【注意事项】 none
3.2 atomic_set()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_set -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数是一个宏,用于设置原子变量的值,就是向 v 写入 i 值。
【函数参数】
- v :atomic_t * 类型,表示要赋值的原子变量。
- i :int 类型,表示要设置的值。
【返回值】none
【使用格式】none
【注意事项】 none
3.3 atomic_read()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_read -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数是一个宏,用于读取原子变量 v 的值。
【函数参数】
- v :atomic_t * 类型,表示要操作的原子变量。
【返回值】none
【使用格式】none
【注意事项】 none
3.4 atomic_add()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_add -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于给原子变量 v 加上 i 值。
【函数参数】
i :int 类型,表示要加的值。
v :atomic_t * 类型,表示要操作的原子变量。
【返回值】none
【使用格式】none
【注意事项】 none
3.5 atomic_sub()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_sub -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于给原子变量 v 减去 i 值。
【函数参数】
i :int 类型,表示要减的值。
v :atomic_t * 类型,表示要操作的原子变量。
【返回值】none
【使用格式】none
【注意事项】 none
3.6 atomic_inc()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_inc -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于给原子变量 v 加 1,也就是自增。
【函数参数】
- v :atomic_t * 类型,表示要操作的原子变量。
【返回值】none
【使用格式】none
【注意事项】 none
3.7 atomic_dec()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_dec -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于将原子变量 v 减 1,就是自减。
【函数参数】
- v :atomic_t * 类型,表示要操作的原子变量。
【返回值】none
【使用格式】none
【注意事项】 none
3.8 atomic_inc_and_test()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_inc_and_test -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于给 v 加 1,如果结果为 0 就返回真,否则返回假。
【函数参数】
- v :atomic_t * 类型,表示要操作的原子变量。
【返回值】int类型,返回真表示原子变量值为0,返回假表示原子变量值非0。
【使用格式】none
【注意事项】 none
3.9 atomic_dec_and_test()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_dec_and_test -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于给 v 减 1,如果结果为 0 就返回真,否则返回假。
【函数参数】
- v :atomic_t * 类型,表示要操作的原子变量。
【返回值】int类型,返回真表示原子变量值为0,返回假表示原子变量值非0。
【使用格式】none
【注意事项】 none
3.10 atomic_sub_and_test()
我们使用以下命令查询一下函数所在头文件:
1 | grep atomic_sub_and_test -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件,定义在 include/asm-generic/atomic.h 中 */ |
【函数说明】该函数用于从 v 减 i,如果结果为 0 就返回真,否则返回假。
【函数参数】
i :int 类型,表示要减的值。
v :atomic_t * 类型,表示要操作的原子变量。
【返回值】int类型,返回真表示原子变量值为0,返回假表示原子变量值非0。
【使用格式】none
【注意事项】 none
4. 原子位操作API
位操作也是很常用的操作, Linux 内核也提供了一系列的原子位操作 API 函数,只不过原子位操作不像原子整形变量那样有个 atomic_t 的数据结构,原子位操作是直接对内存进行操作 ,这里就不详细写了。
1 | void set_bit(nr, void *addr); // 设置addr的第nr位为1 |
5. 使用步骤
以保证一个设备文件只能被打开一次为例:
- (1)定义一个原子变量;
- (2)设置原子变量的初始值为1;
- (3)在驱动的open()函数中将原子变量减1并判断结果否为0,若为0,则说明可以进行操作,若为负数,说明已经有其他应用程序打开了这个设备文件,此时恢复减1之前的原子变量值,就是再加1,然后提示错误并返回。
- (4)在关闭设备的时候,就是在驱动的close()函数中要对原子变量进行加1的操作,这样才能保证其他的进程或者线程可以正常打开这个设备文件。
二、自旋锁
1. 自旋锁简介
原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的线程来访问此结构体变量,这些工作原子操作都不能胜任。
当一个线程要访问某个共享资源的时候首先要先获取相应的锁, 锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。
对于自旋锁而言,如果自旋锁正在被线程 A 持有,线程 B 想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态,线程 B 不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等待锁可用。
比如现在有个公用电话亭,一次肯定只能进去一个人打电话,现在电话亭里面有人正在打电话,相当于获得了自旋锁。此时我们到了电话亭门口,因为里面有人,所以我们不能进去打电话,相当于没有获取自旋锁,这个时候我们肯定是站在原地等待,我们可能因为无聊的等待而转圈圈消遣时光,反正就是哪里也不能去,要一直等到里面的人打完电话出来。终于,里面的人打完电话出来了,相当于释放自旋锁,这个时候我们就可以使用电话亭打电话了,相当于获取到了自旋锁。
自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源。可以看到自旋锁的一个缺点:那就等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。所以自旋锁适用于短时期的轻量级加锁,如果遇到需要长时间持有锁的场景那就需要换其他的方法了。
【注意事项】
(1)因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式。
(2)自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能导致死锁。
(3)不能递归申请自旋锁,因为一旦通过递归的方式申请一个正在持有的锁,那么就必须“自旋”,等待锁被释放,然而正处于“自旋”状态,根本没法释放锁。结果就是自己把自己锁死了。
(4)在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管用的是单核的还是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。
【适用场合】自旋锁适用于以下场合:
(1)异常上下文之间或异常上下文与任务上下文之间共享资源时。
(2)任务上下文之间且临界区执行时间很短时。
(3)互斥问题。
2. 相关结构体
Linux 内核使用结构体 spinlock_t 表示自旋锁 ,该结构体定义在linux内核源码的这个文件中:
1 | include/linux/spinlock_types.h |
我们打开这个文件,可以看到自旋锁结构体定义如下:
1 | typedef struct spinlock { |
3.自旋锁操作API
下边的这些自旋锁API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。
自旋锁会自动禁止抢占,也就说当线程 A 得到锁以后会暂时禁止内核抢占。如果线程 A 在持有锁期间进入了休眠状态,那么线程 A 会自动放弃 CPU 使用权。线程 B 开始运行,线程 B 也想要获取锁,但是此时锁被 A 线程持有,而且内核抢占还被禁止了,线程 B 无法被调度出去,那么线程 A 就无法运行,锁也就无法释放,此时,死锁发生了!
3.1 spin_lock_init()
我们使用以下命令查询一下函数所在头文件:
1 | grep spin_lock_init -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件*/ |
【函数说明】该函数初始化自旋锁。
【函数参数】
- lock :spinlock_t * 类型,表示要初始化的自旋锁。
【返回值】int类型,一般不怎么关心这个返回值。
【使用格式】none
【注意事项】 none
3.2 spin_lock()
我们使用以下命令查询一下函数所在头文件:
1 | grep spin_lock -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件*/ |
【函数说明】该函数用于获取自旋锁,也叫作加锁。
【函数参数】
- lock :spinlock_t * 类型,表示要获取的自旋锁。
【返回值】none
【使用格式】none
【注意事项】 none
3.3 spin_trylock()
我们使用以下命令查询一下函数所在头文件:
1 | grep spin_trylock -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于尝试获取指定的自旋锁,如果没有获取到就返回 0。
【函数参数】
- lock :spinlock_t * 类型,表示要获取的自旋锁。
【返回值】int 类型,没有获取到锁,返回0;获取到锁,返回非0值。
【使用格式】none
【注意事项】 none
3.4 spin_unlock()
我们使用以下命令查询一下函数所在头文件:
1 | grep spin_unlock -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于。释放自旋锁,也叫解锁
【函数参数】
- lock :spinlock_t * 类型,表示要释放的自旋锁。
【返回值】none
【使用格式】none
【注意事项】 none
4. 用于中断的API
上边的那些API 函数用于线程之间的并发访问,如果此时发生了中断,中断也想访问共享资源,那该怎么办呢?
首先,中断里面可以使用自旋锁,但是在中断里面使用自旋锁的时候,在获取锁之前一定要先禁止本地中断(也就是本 CPU 中断,对于多核 SOC来说会有多个 CPU 核),否则可能导致锁死现象的发生 。
如上图,线程 A 先运行,并且获取到了 lock 这个锁,当线程 A 运行 funcA 函数的时候中断发生了,中断抢走了 CPU 使用权。右边的中断服务函数也要获取 lock 这个锁,但是这个锁被线程 A 占有着,中断就会一直自旋,等待锁有效。但是在中断服务函数执行完前,线程 A 是不可能执行的,所以,死锁又发生了。所以就需要在获取锁之前关闭本地中断。
linux内核为我们也提供了一些API函数:
1 | void spin_lock_irq(spinlock_t *lock); /* 禁止本地中断,并获取自旋锁 */ |
使用 spin_lock_irq / spin_unlock_irq 的时候需要用户能够确定加锁之前的中断状态,但实际上内核很庞大,运行也是“千变万化”,我们是很难确定某个时刻的中断状态,因此不推荐使用 spin_lock_irq/spin_unlock_irq。建议使用 spin_lock_irqsave / spin_unlock_irqrestore,因为这一组函数会保存中断状态,在释放锁的时候会恢复中断状态。
一般在线程中使用 spin_lock_irqsave / spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock ,实例如下:
1 | DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */ |
5. 用于下半部的API
下半部(BH)也会竞争共享资源,如果要在下半部里面使用自旋锁 ,可以用这些函数:
1 | void spin_lock_bh(spinlock_t *lock); /* 关闭下半部,并获取自旋锁 */ |
6. 使用步骤
- (1)定义自旋锁;
- (2)初始化自旋锁;
- (3)需要加锁的地方获取自旋锁,加锁的代码执行完一定要解锁,加锁和解锁一定要成对出现。
三、互斥锁
1. 互斥锁简介
自旋锁在上锁的时候会一直自旋,占用CPU资源,这是很浪费资源和时间的,linux中还有一种互斥锁。互斥锁加锁的资源每次只能有一个线程访问,若是没有获取到互斥锁,线程或者进程便会休眠,让出CPU。
【注意事项】
(1)互斥锁可以导致休眠,因此不能在中断中使用互斥锁,中断中只能使用自旋锁。
(2)互斥锁保护的临界区可以调用引起阻塞的 API 函数。
(3)为一次只有一个线程可以持有互斥锁,因此,必须由互斥锁的持有者释放互斥锁。并且互斥锁不能递归上锁和解锁。
【适用场合】任务上下文之间且临界区执行时间较长时的互斥问题。
2. 相关结构体
linux 内核中使用 mutex 表示互斥锁,该结构体变量定义在linux内核源码的这个文件中
1 | include/linux/mutex.h |
我们打开这个文件,就可以看到互斥锁的结构体定义如下:
1 | struct mutex { |
3.互斥锁操作API
3.1 mutex_init()
我们使用以下命令查询一下函数所在头文件:
1 | grep mutex_init -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件*/ |
【函数说明】该函数初始化互斥锁。
【函数参数】
- lock :mutex * 类型,表示要初始化的互斥锁。
【返回值】none
【使用格式】none
【注意事项】 none
3.2 mutex_lock()
我们使用以下命令查询一下函数所在头文件:
1 | grep mutex_lock -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件*/ |
【函数说明】该函数用于获取互斥锁,也叫作加锁,如果获取不到就进行休眠。
【函数参数】
- lock :struct mutex * 类型,表示要获取的互斥锁。
【返回值】none
【使用格式】none
【注意事项】 none
3.3 mutex_trylock()
我们使用以下命令查询一下函数所在头文件:
1 | grep spin_trylock -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于尝试获取指定的互斥锁,如果没有获取到就返回 0。
【函数参数】
- lock :struct mutex * 类型,表示要获取的互斥锁。
【返回值】int 类型,成功获取到锁,返回1;没有获取到锁,返回0。
【使用格式】none
【注意事项】 none
3.4 mutex_unlock()
我们使用以下命令查询一下函数所在头文件:
1 | grep mutex_unlock -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于释放指定的互斥锁。
【函数参数】
- lock :struct mutex * 类型,表示要释放的互斥锁。
【返回值】none
【使用格式】none
【注意事项】 none
3.5 mutex_is_locked()
我们使用以下命令查询一下函数所在头文件:
1 | grep mutex_is_locked -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于判断 mutex 是否被获取。
【函数参数】
- lock :struct mutex * 类型,表示要判断的互斥锁。
【返回值】int类型,已经被获取返回1,否则返回0。
【使用格式】none
【注意事项】 none
4. 使用步骤
- (1)定义一个互斥锁;
- (2)初始化互斥锁;
- (3)需要加锁的地方获取互斥锁,加锁的代码执行完毕一定要释放互斥锁,互斥锁的获取和释放必须成对出现。
四、信号量
1. 信号量简介
Linux 内核提供了信号量机制,信号量常常用于控制对共享资源的访问。
有这么一个例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这100 个停车位就是共享资源。假设现在这个停车场正常运行,我们要把车停到这个这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车场满了。停车场满的时我们可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时我们就可以把车停进去了,我们把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。
相比于自旋锁,信号量可以使线程进入休眠状态,使用信号量会提高处理器的使用效率,毕竟不用一直在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使线程进入休眠状态以后会切换线程,切换线程就会有开销。
信号量的特点:
(1)因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
(2)因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
(3)如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
信号量有一个信号量值,相当于一个房子有 10 把钥匙,这 10 把钥匙就相当于信号量值为10。因此,可以通过信号量来控制访问共享资源的访问数量,如果要想进房间,那就要先获取一把钥匙,信号量值减 1,直到 10 把钥匙都被拿走,信号量值为 0,这个时候就不允许任何人进入房间了,因为没钥匙了。如果有人从房间出来,那他要归还他所持有的那把钥匙,信号量值加 1,此时有 1 把钥匙了,那么可以允许进去一个人。相当于通过信号量控制访问资源的线程数,在初始化的时候将信号量值设置的大于 1,那么这个信号量就是计数型信号量,计数型信号量不能用于互斥访问,因为它允许多个线程同时访问共享资源。如果要互斥的访问共享资源那么信号量的值就不能大于 1,此时的信号量就是一个二值信号量。
【适用场合】任务上下文之间且临界区执行时间较长时的互斥或同步问题。
2. 相关结构体
Linux 内核使用 semaphore 结构体表示信号量 ,该结构体定义在linux内核源码的这个文件中:
1 | include/linux/semaphore.h |
我们打开这个文件,会看到这个结构体定义如下:
1 | /* Please don't access any members of this structure directly */ |
3. 信号量操作API
3.1 sema_init()
我们使用以下命令查询一下函数所在头文件:
1 | grep sema_init -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件*/ |
【函数说明】该函数初始化信号量。
【函数参数】
- sem :struct semaphore * 类型,表示要初始化的信号量。
- val :int 类型,表示信号量的初始值。
【返回值】none
【使用格式】none
【注意事项】 none
3.2 down()
我们使用以下命令查询一下函数所在头文件:
1 | grep down -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件*/ |
【函数说明】该函数用于获取信号量,因为会导致休眠(浅度休眠),因此不能在中断中使用。使用 down 进入休眠状态的线程不能被信号打断。
【函数参数】
- sem :struct semaphore * 类型,表示要获取的信号量。
【返回值】none
【使用格式】none
【注意事项】 none
3.3 down_interruptible()
我们使用以下命令查询一下函数所在头文件:
1 | grep down_interruptible -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于获取信号量,也会导致休眠(浅度休眠),使用此函数进入休眠以后是可以被信号打断的。
【函数参数】
- sem :struct semaphore * 类型,表示要获取的信号量。
【返回值】int 类型,没怎么用这个函数,用到了再补充。
【使用格式】none
【注意事项】 none
3.4 up()
我们使用以下命令查询一下函数所在头文件:
1 | grep up -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于释放信号量。
【函数参数】
- sem :struct semaphore * 类型,表示要释放的信号量。
【返回值】none
【使用格式】none
【注意事项】 none
4. 使用步骤
- (1)定义一个信号量;
- (2)初始化信号量;
- (3)需要保护的代码段申请信号量,当执行完毕后要释放信号量,需要注意申请信号量和释放信号量要成对出现。