- Linux 内核的实质
内核(kernel)将硬件资源映射为抽象的资源,并提供一些抽象的接口,供进程使用。
Linux 内核所提供的抽象资源表示方法: - 内存空间(memory):以地址为索引 - 线程(thread):以线程描述符为索引 - 文件(file):以文件描述符为索引
抢占性: - 用户资源始终是抢占模式 - 从 2.6 起,支持内核抢占 - 从 4.0 起,支持实时模式
版本号:
- 2.6 前内核版本号,x.y.z.p
,如果 y
是偶数,则表示稳定版,否则表示开发版。
- 2.6 后内核版本号,x.y.z-foo
。
内核对于硬件资源的设计原则: - MS/DOS:允许所有用户程序直接访问硬件资源。 - Windows NT:提供中间层(硬件抽象层,HAL),屏蔽硬件差异。 - Linux:倾向于向用户程序隐藏一切硬件资源,附带严格的基于用户和文件的权限控制。 - 用户:资源的权限 - 用户组:共享资源的权限
在这种抽象下,内核令进程认为自己是这台机器上唯一的程序,只不过这台机器: - 提供时钟 - 具有不定数量的资源,资源申请可能失败
为了实现对硬件资源的这种抽象,在 Linux 中,CPU 执行的被分为了两种模态: - 用户态 - 内核态
基于 modules 的模块化: - 可以在运行时链接或取消链接 - 由内核直接调用,与内核共享内存空间
文件系统(在用户的视角下):
- 数据结构:文件是一系列可随机访问的字节,无论其上可以建立怎样的数据结构,至少能够进行顺序读取或写入,有些(比如块设备)支持随机访问。文件不包含任何控制信息,例如 EOF 并不属于文件的一部分,而是文件读取操作的结束标志。
- Linux 只向进程提供文件描述符(套接字作为文件类型),而不像 Windows 那样向进程提供多种类型的句柄(作为特殊接口)。
- 文件列表是按树状的形式组织起来的,可以用路径来唯一对应一个文件。
- 为了方便,如果要承认根目录有一个文件名 /
,那么所有目录名都应视作以 /
结尾,只是在没有歧义时应当略去。
- ./
指示当前目录,../
指示父目录,在 /
中这两者皆指示根目录。
- 硬链接和软链接:
- 硬链接:指向同一文件的多个文件名。
- 软链接(=符号链接):指向另一个文件的文件名。
文件系统(在内核的视角下):
- 文件信息组织在 inode
结构中。
内核控制路径(kernel control path):当遭遇系统调用、中断、异常时,内核控制路径会根据当前的上下文切换到内核态,执行相应的一系列指令。
内核的可重入性(Reentrancy):多种内核控制路径交织能够在一起。 - 多个执行实体可以同时进入相同的内核代码路径,而不会互相干扰。例如一个异常处理流程可以被中断,转入中断处理流程。 - 不会因共享数据修改导致数据不一致或竞争条件。 - 不会因中断、进程切换等因素导致错误。
Linux 内核实现可重入性实现方式:si yz ks j - 自旋锁 - 信号量 - 原子操作 - 临时禁用中断 - 临时禁用抢占 - 避免死锁:统一资源的申请顺序。
进程地址空间:
- 进程有两个地址空间:用户空间和内核空间。它们都是私有空间。
- 使用自定义方式管理内存,如 malloc()
C 标准函数。
- 使用系统调用管理内存,如 mmap()
系统调用映射文件到内存。
内核同进程通信:
- 信号(Signals):通知系统事件,可以同步或异步,例如 SIGINT
和 SIGSEGV
,由 POSIX 标准规定,进程可以选择处理某些信号量,否则内核将执行默认操作。
进程间通信:
- 信号量(Semaphores):semget()
创建文件。
- 消息队列(Message Queues):msgget()
创建文件,用 msgsnd()
及 msgrcv()
写入或读取。
- 共享内存(System V Shared memory):shmget()
创建文件。
- 共享内存(POSIX Shared memory):用 mmap()
或 shm_open()
创建文件。
- 套接字(Sockets):socket()
创建文件。
避免共享资源被第三方进程访问:
- 如果是同一个程序创建的进程,使用 IPC_PRIVATE
做为键。
- 如果是不同程序创建的进程,使用 ftok()
动态生成键。
- 使用 shmctl()
设置更严格的权限要求。
- 使用 POSIX 共享内存。
进程管理:
- fork()
_exit
wait4()
- init 进程jb ig会接管孤儿进程
- 用户组(例如 bash -c "ls | sort | more"
)使用同一个 pid 来管理
内存管理: - 虚拟内存:除了内核的几 m 空间之外,都存放在虚拟内存中。
驱动: - I/O 架构 - The Device Driver Model 设备驱动模型 - 设备文件 - 设备驱动
概念: - 总线:一组共享的通信线路。 - 系统总线:连接内部的外围设备(例如 PCIE=Peripheral Component Interconnect) - ISA、EISA、MCA、SCSI、USB - 前端总线:连接 CPU 和 RAM 控制器、北桥。Frontside 指的是 CPU 正面连接的总线,它直接暴露在 CPU 外部,连接 CPU 和整个系统(如主存、I/O)。FSB 架构由于速度问题逐渐被淘汰,导致了后来的内存控制器集成到 CPU 内部。 - 后端总线:连接 CPU 和 L2、L3 缓存。名为 Backside 因为不暴露给外部。BSB 架构被片上互连(On-Die Interconnect)技术所淘汰,现在 L2、L3 缓存也集成到 CPU 内部了。 - 桥:连接多个总线的枢纽,基本上是一个伪概念。 - 北桥:位于 CPU 的北方,负责高速数据传输(CPU、内存、PCIE、南桥)。现在被集成到 CPU 内部。 - 南桥:位于 CPU 的南方,负责低速数据传输(北桥、USB、SATA、LAN)。随着芯片组演变,其独立性被取消,成为控制器集线器(Controller Hub)。
每一个设备由且仅由一条总线管理。
一个 I/O 架构通常包含三部分:
- I/O Ports:相当于 I/O 设备的地址。叫端口,是为了与数据访问的接口区分开来。
- 除了数据端口外,通常在设备内部规定一组特殊的寄存器来管理设备,它们可以通过特定端口来访问:控制寄存器、状态寄存器、输入寄存器、输出寄存器……
- I/O Ports 的访问:
- 有时规定了一些汇编指令用于访问这些端口。例如 x86 架构中保留的 Port-Mapped I/O 访问 IO 设备,形如 mov dx, 0x60; in al, dx;
:
| 指令 | 作用 |
|---|---|
| in | 从 I/O 端口读数据 |
| out | 向 I/O 端口写数据 |
| insb, insw, insd | 从 I/O 端口批量读取数据(字节/字/双字)|
| outsb, outsw, outsd | 向 I/O 端口批量写入数据(字节/字/双字)|
- IO 设备也可以被映射到地址空间中去操作,DMA。例如 ARM 就不支持 Port-Mapped I/O,仅支持 Memory-mapped I/O,没有专门的 IO 指令。现代 PCIe 设备(如显卡、网卡、USB)几乎都使用 MMIO。尤其是在 DMA 技术的支持下,设备被允许直接访问内存数据。
- 如何得到 I/O 端口呢?
- Linux:可以通过 /proc/ioports
或 /proc/iomem
来查看 I/O 端口或 MMIO 地址(需要 root 权限)。
- 总的来说,内存由内核分配。内核把设备视作是相对驱动而言的资源,每组资源由且仅由一个驱动来管理。资源按树的方式来索引,用于满足各种设备连接方式(菊花链、并行链、集线器)。
- I/O Interfaces:
- 在端口和控制器之间。
- 分为自定义接口(例如键盘、显卡、硬盘)和通用接口(并口,串口,SCSI,USB)。
- USB Mass Storage(USB 大容量存储协议) 采用 SCSI 命令集 进行数据传输。
- 设备控制器:
- 负责将高级代码翻译为机器指令,以及根据运行情况设定状态寄存器。
- 典型例子是硬盘控制器,将读取命令翻译为磁头操作。
设备驱动模型:
- 早期设备之间差异很大,Linux 仅提供基本的统一能力:内存分配、预订地址区间、中断管理。
- 随着主线类型的标准化(PCI 标准、USB 标准),在 Linux 2.6,Linux 提供了所有总线、所有设备、所有驱动的统一模型。
- /sys/
是特殊文件系统(sysfs),供设备、驱动、总线、模块等信息。其层级关系关联于 kobject
对象(Kernel Object)的组织层级关系。
/sys/
bus/ --- subsystem
pci/ --- subsystem
drivers/ --- kset
serial/ --- kobject
new-id --- attribute
- 因而要设备驱动要做的第一件事是创建一个 kobject。其次,为其分配相关的 kset 和 subsystem 等信息,从而为它在层级制中定位。接下来,并通过 kobject_register()
函数将其注册到系统中。最后,通过 sysfs_create_file()
和 sysfs_create_link()
函数创建属性。
- 设备驱动模型分为三层:device
、bus_type
、device_driver
:
- device
包含 kobject
、bus_type
、device_driver
等信息。
- bus_type
通过实现 match()
函数指针来匹配合适的驱动。
- device_driver
通过实现 probe()
和 remove()
函数指针来完成设备的初始化和卸载。
- class_device
定义逻辑上的设备类型,这些类型会被映射到 /dev
下。
设备文件:/dev
下的文件。
- 它们是提供给用户程序使用的,内核本身不依赖设备文件的名字。
- 通过 VFS 访问设备文件,可以实现对设备的读写操作,需要实现 file_operations
接口。
- 使用 ioctl()
系统调用来传递自定义参数。
设备驱动: - 注册 - 初始化 - 监控 I/O 操作 - Polling mode - Interrupt mode - 访问共享内存 - DMA