In continuation of the previous text 第5章:并发与竞态条件-2:Semaphores and Mutexes
, let's GO ahead.
The Linux Semaphore Implementation
The Linux kernel provides an implementation of semaphores that conforms to the above semantics, although the terminology is a little different. To use semaphores, kernel code must include . The relevant type is struct semaphore; actual semaphores can be declared and initialized in a few ways. One is to create a semaphore directly, then set it up with sema_init:
Linux 内核提供的信号量实现符合上述语义,只是术语略有不同。要使用信号量,内核代码必须包含 <linux/semaphore.h> 头文件。相关类型为 struct semaphore,实际的信号量可以通过几种方式声明和初始化。一种方式是直接创建信号量,然后用 sema_init 初始化:
void sema_init(struct semaphore *sem, int val);
where val is the initial value to assign to a semaphore.
Usually, however, semaphores are used in a mutex mode. To make this common case a little easier, the kernel has provided a set of helper functions and macros. Thus, a mutex can be declared and initialized with one of the following:
其中 val 是分配给信号量的初始值。
不过,信号量通常用于互斥模式。为了简化这种常见场景,内核提供了一组辅助函数和宏。因此,互斥锁可以通过以下方式之一声明和初始化:
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);
Here, the result is a semaphore variable (called name) that is initialized to 1 (with DECLARE_MUTEX) or 0 (with DECLARE_MUTEX_LOCKED). In the latter case, the mutex starts out in a locked state; it will have to be explicitly unlocked before any thread will be allowed access.
If the mutex must be initialized at runtime (which is the case if it is allocated dynamically, for example), use one of the following:
这里会创建一个名为 name 的信号量变量,DECLARE_MUTEX 将其初始化为 1,DECLARE_MUTEX_LOCKED 将其初始化为 0(后者表示互斥锁初始处于锁定状态,必须显式解锁后,线程才能访问)。
如果互斥锁需要在运行时初始化(例如动态分配的情况),可以使用以下函数:
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
In the Linux world, the P function is called down—or some variation of that name. Here, “down” refers to the fact that the function decrements the value of the semaphore and, perhaps after putting the caller to sleep for a while to wait for the semaphore to become available, grants access to the protected resources. There are three versions of down:
在 Linux 中,对应 P 操作的函数是 down 及其变体。“down” 意为该函数会递减信号量的值,并且可能会让调用者睡眠一段时间(等待信号量可用),之后授予对受保护资源的访问权。down 有三个版本:
void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(struct semaphore *sem);
down decrements the value of the semaphore and waits as long as need be. down_ interruptible does the same, but the operation is interruptible. The interruptible version is almost always the one you will want; it allows a user-space process that is waiting on a semaphore to be interrupted by the user. You do not, as a general rule, want to use noninterruptible operations unless there truly is no alternative. Noninterruptible operations are a good way to create unkillable processes (the dreaded “D state” seen in ps), and annoy your users. Using down_interruptible requires some extra care, however, if the operation is interrupted, the function returns a nonzero value, and the caller does not hold the semaphore. Proper use of down_interruptible requires always checking the return value and responding accordingly.
down 会递减信号量的值,并无限期等待(直到信号量可用)。down_interruptible 功能类似,但操作是可中断的 —— 这几乎是最常用的版本,它允许等待信号量的用户空间进程被用户中断(如 Ctrl+C)。一般来说,除非别无选择,否则不应使用不可中断的操作,否则可能导致进程无法被杀死(即 ps 中显示的可怕 “D 状态”),惹恼用户。不过,使用 down_interruptible 需要格外注意:如果操作被中断,函数会返回非零值,且调用者不持有信号量。因此必须始终检查返回值并做出相应处理。
The final version (down_trylock) never sleeps; if the semaphore is not available at the time of the call, down_trylock returns immediately with a nonzero return value.
Once a thread has successfully called one of the versions of down, it is said to be “holding” the semaphore (or to have “taken out” or “acquired” the semaphore). That thread is now entitled to access the critical section protected by the semaphore. When the operations requiring mutual exclusion are complete, the semaphore must be returned. The Linux equivalent to V is up:
最后一个版本 down_trylock 从不睡眠:如果调用时信号量不可用,它会立即返回非零值。
一旦线程成功调用了某个 down 变体,就称其 “持有”(或 “获取”)了信号量。此时线程有权访问该信号量保护的临界区。当需要互斥的操作完成后,必须释放信号量。Linux 中对应 V 操作的函数是 up:
void up(struct semaphore *sem);
Once up has been called, the caller no longer holds the semaphore.
As you would expect, any thread that takes out a semaphore is required to release it with one (and only one) call to up. Special care is often required in error paths; if an error is encountered while a semaphore is held, that semaphore must be released before returning the error status to the caller. Failure to free a semaphore is an easy error to make; the result (processes hanging in seemingly unrelated places) can be hard to reproduce and track down.
调用 up 后,调用者不再持有信号量。
如你所料,任何获取信号量的线程都必须通过一次(且仅一次)up 调用释放它。错误处理路径通常需要特别注意:如果持有信号量时遇到错误,必须在向调用者返回错误状态前释放信号量。忘记释放信号量是一个容易犯的错误,其后果(进程在看似无关的地方挂起)可能难以复现和追踪。
补充说明:
-
信号量初始化的历史变化
现代 Linux 内核(2.6.37+)中,
DECLARE_MUTEX、init_MUTEX等宏已被更规范的struct mutex取代(通过DEFINE_MUTEX、mutex_init初始化),但信号量的核心语义保持一致。传统信号量仍用于需要 “允许多个线程同时进入临界区” 的场景(初始值 >1)。 -
该函数返回非零时表示被信号中断(通常为down_interruptible的返回值处理-ERESTARTSYS),此时必须放弃临界区操作并返回错误,示例:if (down_interruptible(&sem)) { // 被信号中断,未持有锁,直接返回 return -ERESTARTSYS; } // 临界区操作... up(&sem); -
死锁风险
若线程持有信号量时调用可能睡眠的函数(如
kmalloc(GFP_KERNEL)),需确保其他线程不会在相同资源上形成等待循环(例如 A 持有锁 1 等待锁 2,B 持有锁 2 等待锁 1),否则会导致死锁。 -
信号量与互斥锁的性能差异
信号量的实现比
struct mutex更复杂(支持更多特性),因此在纯互斥场景下,struct mutex更轻量、性能更好,是现代驱动的首选。
905

被折叠的 条评论
为什么被折叠?



