上面代码的作用是确保同一时间只有1个进程能够访问 hello_dev 驱动设备,但是由于 CPU 的指令调度,可能存在如下的执行顺序,
初始情况,open_count 为 0,进程 A 执行到 if 语句,条件为假,不执行分支内部的语句,此时,由于 CPU 调度(可能是因为进程 A 时间片到期或者被更高优先级的进程 B 抢占),进程 B 也执行到 if 判断,条件也为假,不执行分支内部的语句,然后进程 A 和 B 都获得了设备的使用权
将判断、增加使用量的代码称为临界区,在进入临界区之前,先调用 down 函数,将信号量 sema 的值减 1;离开临界区时,调用 up 函数,对信号量 sema 的值加 1
1.2.4 原理浅析
1.2.4.1 基本原理
当对某个信号量调用 down 函数,会对信号量的值减 1,如果得到的结果不小于 0,则可以继续执行临界区代码;如果得到的结果小于 0,则会导致进程被阻塞休眠
当对某个信号量调用 up 函数,会对信号量的值加 1,如果得到的结果不大于0,就会从进程阻塞队列中根据某种策略选取 1 个进程唤出,执行临界区代码
1.2.4.2 案例分析
在初始情况下,信号量 sema 被初始化为 1,此时进程 A 调用 down 函数,将信号量的值减 1,得到的结果为 0,可以继续执行临界区代码
在进程 A 调用 up 函数前,进程 B 也想使用同一设备,于是调用 down 函数,对信号量 sema 的值减 1,得到结果为 -1,从而被阻塞
当进程 A 执行完临界区代码后,调用 up 函数,将信号量 sema 的值加 1,得到结果为 0,于是将进程 B 从进程阻塞队列唤出,于是 B 也能执行临界区代码
进程 B 执行完临界区代码后,也调用 up 函数,将信号量 sema 的值加 1,又回到初始状态 1
2 Semaphore 源码浅析
2.1 数据结构
1 2 3 4 5 6
/* Please don't access any members of this structure directly */ structsemaphore { raw_spinlock_t lock; unsignedint count; structlist_headwait_list; };
/** * down - acquire the semaphore * @sem: the semaphore to be acquired * * Acquires the semaphore. If no more tasks are allowed to acquire the * semaphore, calling this function will put the task to sleep until the * semaphore is released. * * Use of this function is deprecated, please use down_interruptible() or * down_killable() instead. */ voiddown(struct semaphore *sem) { unsignedlong flags;
如果其他进程对这个信号量调用了 up 函数,那么 waiter.up 就置为 1,__down_common 函数返回 0
2.4 释放信号量的过程
2.4.1 up 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/** * up - release the semaphore * @sem: the semaphore to release * * Release the semaphore. Unlike mutexes, up() may be called from any * context and even by tasks which have never called down(). */ voidup(struct semaphore *sem) { unsignedlong flags;
raw_spin_lock_irqsave(&sem->lock, flags); if (likely(list_empty(&sem->wait_list))) sem->count++; else __up(sem); raw_spin_unlock_irqrestore(&sem->lock, flags); }
因为对信号量 sem 内部成员的访问可能是并发的,所以使用自旋锁 sem->lock 确保共享变量的互斥访问
如果此时等待队列是空的,则将 count 加 1,然后释放自旋锁 sem-> lock,退出 up 函数