我正在阅读“Linux 设备驱动程序,第三版”,并遇到了一些我不太理解的内核项目。
Linux 内核在内部使用虚拟地址还是物理地址进行操作?尤其让我困惑的是,地址有多种类型(逻辑、虚拟、总线和物理),并且它们都是有效的并且可由内核操作。
CPU 指令不能直接寻址存储在外围设备中的数据,因此使用可寻址存储器(即缓冲区)来实现这些目的,这是正确的吗?
当请求信号量(其值为 0)时,进程是否可以休眠并且必须等待它?
原子操作——这些是由特定的CPU指令保证的吗?
答案1
最好不要一次提出多个问题,因为并不是每个人都能或愿意回答所有问题。尽管如此,我还是会对每个问题给出一个简短的答案。
Linux 内核在内部使用虚拟地址还是物理地址进行操作?尤其让我困惑的是,地址有多种类型(逻辑、虚拟、总线和物理),并且它们都是有效的并且可由内核操作。
是的。内核的不同部分使用不同的地址空间。
当内核代码正在处理系统调用时,其内存映射包括整个内核内存空间和进程的整个内存空间(除非您的内核配置了高内存,但这太复杂了,这里不予赘述)。这些都是逻辑(也称为虚拟)地址:地址的高位指示要在哪个页面中查找内存管理单元,低位是页内的线性地址。每当任务切换发生时,MMU 内部的内存映射都会发生变化(更改 MMU 中的页表是任务切换的一个重要部分)。
某些设备驱动程序需要操作对其所驱动的设备有效的内存地址。这些通常是物理地址,尽管某些架构具有IOMMU这样设备也能看到自己的逻辑地址。当然,内核中的内存管理子系统需要操作许多不同类型的地址。
CPU 指令不能直接寻址存储在外围设备中的数据,因此使用可寻址存储器(即缓冲区)来实现这些目的,这是正确的吗?
这是依赖于架构的。大多数架构确实都有某种DMA(直接内存访问),这至少允许通过 RAM 完成与设备的某些通信。此外,在某些体系结构(例如 ARM)上,所有设备访问都是通过适当地址处的加载和存储指令完成的,而其他体系结构(例如 i386)则具有用于此目的的特定处理器指令。看内存映射 I/O更多细节。
当请求信号量(其值为 0)时,进程是否可以休眠并且必须等待它?
是的,获取信号量(down
和朋友)是一个阻塞操作。这在书中有很好的解释。
原子操作——这些是由特定的CPU指令保证的吗?
是的。细节是非常特定于架构的。所有用于多任务处理的平台都至少提供一个用于同步的原子原语,例如比较和交换,测试并设置,加载链接+条件存储等等。除了使用正确的原语之外,代码可能还需要注意使用正确的记忆障碍在多处理器系统上。 Linux 内核为其支持的每种体系结构提供了同步原语的实现,因此您只需使用内核的可移植原语即可。
答案2
我可以尝试回答1,3和4。
Linux 内核使用不同的步骤将程序代码转换为电信号。
逻辑地址:这些包含在机器语言指令中以寻址操作或指令。分为段和偏移。
线性地址:分段单元将逻辑地址转换为线性地址。这是一个十六进制数(在 32 位架构上:)0x00000000-0xffffffff
,用于寻址内存中的空间。
实际地址:此外,分页单元将线性地址转换为物理地址。这些是通过微处理器引脚对存储单元进行寻址的电信号。
巴士地址:被除CPU之外的所有硬件设备用来对内存单元进行寻址(DMA不需要CPU,但仍然寻址)。这些地址大部分与物理地址相同,但在某些其他架构上除外,例如包含单独的 I/O 内存管理单元的 SPARC 和 Alpha。
内核使用所有地址进行操作,每个地址都是用户请求与硬件级别上该请求的实际处理之间的一个步骤。
如果一个进程接近一个值为 0 或更低的信号量,他就会被挂起,直到该值达到 1 或更大。这只发生在可以睡眠的进程上。中断处理程序无法休眠,因此禁止使用信号量。
原子操作可以通过使用汇编语言指令来实现,这些指令的定义如下:
- 零次或一次内存访问
- 前缀为
LOCK_PREFIX
在C级别,内核提供了atomic_t
前缀为的类型和宏atomic_
(添加LOCK_PREFIX
到组装说明中)。