第5章:并发与竞态条件-2:Semaphores and Mutexes

In continuation of the previous text 第5章:并发与竞态条件-1:Concurrency and Its Management, let's GO ahead.

Semaphores and Mutexes

So let us lookat how we can add locking to scull. Our goal is to make our operations on the scull data structure atomic, meaning that the entire operation happens at once as far as other threads of execution are concerned. For our memory leakexample, we need to ensure that if one thread finds that a particular chunkof memory must be allocated, it has the opportunity to perform that allocation before any other thread can make that test. To this end, we must set up critical sections: code that can be executed by only one thread at any given time.

接下来我们看看如何为 scull 驱动添加锁机制。我们的目标是让对 scull 数据结构的操作成为原子操作—— 即从其他执行线程的角度看,整个操作是一次性完成的。以内存泄漏的例子来说,我们需要确保:如果一个线程发现需要分配特定内存块,它能在其他线程进行同样检查前完成分配。为此,必须设置临界区:任何时刻只能有一个线程执行的代码段。

Not all critical sections are the same, so the kernel provides different primitives for different needs. In this case, every access to the scull data structure happens in process context as a result of a direct user request; no accesses will be made from interrupt handlers or other asynchronous contexts. There are no particular latency (response time) requirements; application programmers understand that I/O requests are not usually satisfied immediately. Furthermore, the scull is not holding any other critical system resource while it is accessing its own data structures. What all this means is that if the scull driver goes to sleep while waiting for its turn to access the data structure, nobody is going to mind.

并非所有临界区都完全相同,因此内核为不同需求提供了不同的同步原语。在 scull 驱动中,所有对数据结构的访问都发生在进程上下文(由用户直接请求触发),不会从中断处理程序或其他异步上下文中访问。它没有特殊的延迟(响应时间)要求 —— 应用程序员明白 I/O 请求通常不会立即完成。此外,scull 在访问自身数据结构时,不会持有其他关键系统资源。这意味着:如果 scull 驱动在等待访问数据结构时进入睡眠状态,不会有任何问题。

“Go to sleep” is a well-defined term in this context. When a Linux process reaches a point where it cannot make any further processes, it goes to sleep (or “blocks”), yielding the processor to somebody else until some future time when it can get work done again. Processes often sleep when waiting for I/O to complete. As we get deeper into the kernel, we will encounter a number of situations where we cannot sleep. The write method in scull is not one of those situations, however. So we can use a locking mechanism that might cause the process to sleep while waiting for access to the critical section.

“进入睡眠” 在这里是明确定义的术语。当 Linux 进程无法继续推进时,它会睡眠(或 “阻塞”),将处理器让给其他进程,直到未来某个可以继续工作的时刻再唤醒。进程在等待 I/O 完成时经常会睡眠。深入内核后,我们会遇到许多不能睡眠的场景,但 scull 的 write 方法不在此列。因此,我们可以使用一种 “等待进入临界区时可能导致进程睡眠” 的锁机制。

Just as importantly, we will be performing an operation (memory allocation with kmalloc) that could sleep—so sleeps are a possibility in any case. If our critical sections are to work properly, we must use a locking primitive that works when a thread that owns the locksleeps. Not all locking mechanisms can be used where sleeping is a possibility (we’ll see some that don’t later in this chapter). For our present needs, however, the mechanism that fits best is a semaphore.

同样重要的是,我们将执行可能睡眠的操作(如用 kmalloc 分配内存)—— 因此睡眠在任何情况下都可能发生。为了让临界区正常工作,必须使用一种 “持有锁的线程睡眠后仍能正常工作” 的同步原语。并非所有锁机制都适用于可能睡眠的场景(本章稍后会介绍不适用的类型)。就当前需求而言,最适合的机制是信号量

Semaphores are a well-understood concept in computer science. At its core, a semaphore is a single integer value combined with a pair of functions that are typically called P and V. A process wishing to enter a critical section will call P on the relevant semaphore; if the semaphore’s value is greater than zero, that value is decremented by one and the process continues. If, instead, the semaphore’s value is 0 (or less), the process must wait until somebody else releases the semaphore. Unlocking a semaphore is accomplished by calling V; this function increments the value of the semaphore and, if necessary, wakes up processes that are waiting.

信号量是计算机科学中一个广为人知的概念。其核心是一个整数值,配合一对通常称为 P 和 V 的函数。希望进入临界区的进程会对相关信号量调用 P:如果信号量的值大于 0,则值减 1 并继续执行;如果值为 0(或更小),进程必须等待,直到其他进程释放信号量。解锁信号量通过调用 V 实现:该函数增加信号量的值,并在必要时唤醒等待的进程。

When semaphores are used for mutual exclusion—keeping multiple processes from running within a critical section simultaneously—their value will be initially set to 1. Such a semaphore can be held only by a single process or thread at any given time. A semaphore used in this mode is sometimes called a mutex, which is, of course, an abbreviation for “mutual exclusion.” Almost all semaphores found in the Linux kernel are used for mutual exclusion.

当信号量用于互斥(防止多个进程同时进入临界区)时,其初始值通常设为 1。这种信号量在任何时刻只能被一个进程或线程持有,有时也称为互斥锁(mutex)(“mutual exclusion” 的缩写)。Linux 内核中的几乎所有信号量都用于互斥。

补充说明:

  1. 信号量与睡眠的兼容性

    信号量的关键特性是允许持有锁的进程睡眠(如等待内存分配或 I/O 完成),这使其适合进程上下文的同步。而自旋锁等机制不允许睡眠(会导致死锁),因此适用于中断上下文或需快速处理的场景。

  2. 互斥锁的本质

    ​​​​​​​初始值为 1 的信号量就是互斥锁,它严格保证 “同一时刻只有一个线程进入临界区”。内核中专门的 struct mutex 类型(比信号量更轻量)是现代驱动中互斥锁的首选,而传统信号量(struct semaphore)更灵活(初始值可大于 1,支持多线程并发)。

  3. 临界区设计原则

    ​​​​​​​临界区应尽可能小,仅包含必须原子执行的操作(如修改共享数据结构),避免长时间持有锁导致其他进程等待过久,影响系统并发性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值