系统功能
系统 |
---|
用户空间接口,系统调用 |
驱动程序模型 |
模块 |
总线,PCI |
硬件接口,[重新]启动 |
本文介绍了用于支持和管理其他内核功能的基础设施。此功能以系统调用和 sysfs 命名。
用户空间通信是指用户空间应用程序和内核之间的数据和消息交换。用户空间应用程序是在操作系统用户空间中运行的程序,用户空间是内存中的一个受保护区域,它为应用程序提供安全且隔离的环境以在其内运行。
Linux 中有几种机制可用于用户空间与内核之间的通信。最常见的机制之一是通过系统调用,系统调用是允许用户空间应用程序请求内核服务的函数,例如打开文件、创建进程和访问系统资源。
用户空间通信的另一种机制是通过服务文件,服务文件是代表物理或虚拟设备的特殊文件,例如存储设备、网络接口和各种外围设备。用户空间应用程序可以通过读写它们相应的设备文件来与这些设备通信。
总之,Linux 内核提供了多种用户空间通信机制,包括系统调用、设备文件、procfs、sysfs 和 devtmpfs。这些机制使用户空间应用程序能够与内核通信并以安全且受控的方式访问系统资源。
⚲ API
- 用户空间的内核空间 API
- 内核空间的用户空间 API
📖 参考
系统调用是用户空间应用程序与 Linux 内核之间的基本接口。它们提供了一种方法让程序请求操作系统的服务,例如打开文件、分配内存或创建新进程。在 Linux 内核中,系统调用是作为函数实现的,用户空间程序可以使用软件中断机制来调用这些函数。
Linux 内核提供了数百个系统调用,每个系统调用都有其独特的功能。这些系统调用被组织成类别,例如进程管理、文件管理、网络通信和内存管理。用户空间应用程序可以使用这些系统调用来与内核交互并访问底层系统资源。
⚲ API
⚙️ 内部结构
📖 参考
- 系统调用
- 系统调用目录,man 手册第 2 节
- 系统调用的剖析,第 1 部分 和 第 2 部分
- syscalls ltp
💾 历史
经典的 UNIX 设备是作为字节流使用man 2 ioctl 的字符设备。
⚲ API
ls /dev cat /proc/devices cat /proc/misc
示例:misc_fops id usb_fops id memory_fops id
- 分配的设备 doc
- drivers/char src - 实际上是字节流设备
- 第 13 章。I/O 架构和设备驱动程序
⚠️ 警告:混淆。hiddev 不是真正的人机接口设备!它重新使用了 USBHID 基础设施。hiddev 例如用于监视器控制和不间断电源。此模块使用 /dev/usb/hiddevX 上的单独事件接口来单独支持这些设备(180:96 到 180:111)(⚙️ HIDDEV_MINOR_BASE id)
⚲ API
⚙️ 内部结构
📖 参考
📖 参考
🔧 待办事项
📖 参考
proc 文件系统(procfs)是一个特殊的 文件系统,它以层次文件结构的形式呈现有关进程和其他系统信息的信息,提供了一种比传统跟踪方法或直接访问内核内存更方便、更标准的方法来动态访问内核中保存的进程数据。通常,它在启动时映射到名为 /proc
的挂载点。proc 文件系统充当内核内部数据结构的接口。它可用于获取有关系统的信息,并在运行时更改某些内核参数。
/proc
包含一个目录,用于每个正在运行的进程(包括内核线程),这些目录命名为 /proc/PID
,其中 PID
是进程号。每个目录都包含有关一个进程的信息,包括最初启动该进程的命令(/proc/PID/cmdline
)、其环境变量的名称和值(/proc/PID/environ
)、指向其工作目录的符号链接(/proc/PID/cwd
)、指向原始可执行文件的另一个符号链接(如果仍然存在)(/proc/PID/exe
)、两个包含指向每个打开的文件描述符的符号链接的目录(/proc/PID/fd
)和每个文件描述符的状态(位置、标志等)(/proc/PID/fdinfo
)、有关映射文件和块的信息(例如堆栈和堆)(/proc/PID/maps
)、一个表示进程虚拟内存的二进制映像(/proc/PID/mem
)、指向进程看到的根路径的符号链接(/proc/PID/root
)、包含指向任何子进程或线程的硬链接的目录(/proc/PID/task
)、有关进程的基本信息,包括其运行状态和内存使用情况(/proc/PID/status
),以及更多信息。
📖 参考
sysfs
[edit | edit source]sysfs 是一个伪文件系统,它通过虚拟文件将有关内核子系统、硬件设备和相关设备驱动程序的信息从内核的设备模型导出到用户空间。除了提供有关各种设备和内核子系统的信息外,导出的虚拟文件还用于它们的配置。sysfs 旨在导出设备树中存在的信息,这样就不会再混乱 procfs。
sysfs 挂载在 /sys
挂载点下。
⚲ API
📖 参考
devtmpfs
[edit | edit source]devtmpfs 是一个内核/用户空间混合方法的设备文件系统,在 udev 首次运行之前提供节点。
📖 参考
容器化
[edit | edit source]容器化 是一项强大的技术,它彻底改变了软件应用程序的开发、部署和运行方式。容器化的核心是为运行应用程序提供一个隔离的环境,应用程序在其中拥有所有必要的依赖关系,并且可以轻松地从一个环境移动到另一个环境,而无需担心任何兼容性问题。
容器化技术起源于 chroot 命令,该命令于 1979 年在 Unix 操作系统中引入。chroot 提供了一种更改进程根目录的方法,有效地创建了一个具有自己的文件系统层次结构的新隔离环境。但是,这种早期容器化实现的功能有限,难以管理和控制容器内运行的各种进程。
在 2000 年代初期,Linux 内核引入了 命名空间 和 控制组,以提供更强大、更可扩展的容器化解决方案。命名空间 允许进程拥有自己的系统隔离视图,包括文件系统、网络和进程 ID 空间,而 控制组 则对分配给每个容器的资源(如 CPU、内存和 I/O)提供细粒度控制。
利用这些内核功能,容器化平台(如 Docker 和 Kubernetes)已成为大规模构建和部署容器化应用程序的流行解决方案。容器化已成为现代软件开发中必不可少的工具,使开发人员能够轻松地打包应用程序并在不同的环境中以一致且可预测的方式部署它们。
资源使用和限制
[edit | edit source]⚲ API
- man 2 chroot – 更改根目录
- man 2 sysinfo – 返回系统信息
- man 2 getrusage – 获取资源使用情况
- 获取/设置资源限制
📖 参考
命名空间
[edit | edit source]Linux 命名空间 提供了一种隔离和虚拟化操作系统不同方面的方法。命名空间允许应用程序的多个实例在彼此隔离的情况下运行,而不会干扰主机系统或其他实例。
🔧 待办事项
⚲ API
- /proc/self/ns
- man 8 lsns,man 2 ioctl_ns ↪ ns_ioctl id
- man 1 unshare,man 2 unshare
- man 1 nsenter,man 2 setns
- man 2 clone3 ↪ clone_args id
- linux/ns_common.hinc
- linux/proc_ns.hinc
- 命名空间定义
⚙️ 内部结构
- init_nsproxy id - 命名空间结构
- kernel/nsproxy.csrc
- fs/namespace.csrc
- fs/proc/namespaces.csrc
- net/core/net_namespace.csrc
- kernel/time/namespace.csrc
- kernel/user_namespace.csrc
- kernel/pid_namespace.csrc
- kernel/utsname.csrc
- kernel/cgroup/namespace.csrc
- ipc/namespace.csrc
📖 参考
- man 7 命名空间
- man 7 uts_namespaces
- man 7 ipc_namespaces
- man 7 mount_namespace
- man 7 pid_namespaces
- man 7 network_namespaces
- man 7 user_namespaces
- man 7 time_namespaces
- man 7 cgroup_namespaces
控制组
[edit | edit source]控制组 用于限制和控制进程组的资源使用。它们允许管理员设置对 CPU 使用率、内存使用率、磁盘 I/O、网络带宽和其他资源的限制,这对于管理系统性能和防止资源争用非常有用。
控制组有两个版本。与 v1 不同,控制组 v2 只有一个进程层次结构,它区分进程,而不是线程。
以下是控制组 v1 和 v2 之间的一些主要区别
控制组 v1 | 控制组 v2 | |
---|---|---|
层次结构 | 每个子系统都有自己的层次结构,这会导致复杂性和混乱 | 统一的层次结构,简化了管理,并实现了更好的资源分配 |
控制器 | 具有多个由独立控制器控制的子系统,每个子系统都有自己的配置文件和参数集 | 控制器被合并到单个 "cgroup2" 控制器中,该控制器提供了一个统一的接口来管理资源 |
资源分配 | 根据比例共享将资源分配给进程组,这会导致不可预测的结果 | 根据 "加权公平排队" 算法分配资源,这提供了更好的可预测性和公平性 |
控制组 v2 与控制组 v1 不兼容,这意味着从 v1 迁移到 v2 可能会很困难,并且需要仔细的规划。
🔧 待办事项
⚲ API
- linux/cgroup.hinc
- linux/cgroup-defs.hinc
- css_set id – 持有指向 cgroup_subsys_state id 对象的引用计数指针集
- cgroup_subsysid
- linux/cgroup_subsys.h inc – 控制组子系统的列表
⚙️ 内部结构
- cg_list id – task_struct 中 css_set id 的列表
- kernel/cgroupsrc
- cgroup_initid
- cgroup2_fs_typeid
📖 参考
- 控制组 v1 文档
- 手册 1 systemd-cgtop
- 手册 5 systemd.slice – 切片单元配置
- 手册 7 cgroups
- man 7 cgroup_namespaces
- CFS 带宽控制用于控制组 文档
- 实时组调度 文档
📚 进一步阅读
Linux 驱动模型(或设备模型,或简称 DM)是一个框架,它为设备驱动程序提供了一种一致且标准化的方式来与内核交互。它定义了一套规则、接口和数据结构,使设备驱动程序能够与内核通信并执行各种操作,例如管理资源、生命周期等等。
DM 核心结构由 DM 类、DM 总线、DM 驱动程序和 DM 设备组成。
在 Linux 内核中,kobject id 是一个基本数据结构,用于表示内核对象并提供与它们交互的标准化接口。kobject 是一个通用对象,可以表示任何类型的内核对象,包括设备、文件、模块等等。
kobject 数据结构包含几个描述对象的字段,例如它的名称、类型、父级和操作。每个 kobject 在其父级对象中都有一个唯一的名称,而父子关系形成一个 kobject 的层次结构。
kobject 由内核的 sysfs 文件系统管理,该文件系统提供一个虚拟文件系统,将内核对象暴露为用户空间中的文件和目录。每个 kobject 都与一个 sysfs 目录相关联,该目录包含可以读取或写入以与内核对象交互的文件和属性。
⚲ 基础设施 API
- linux/kobject.hinc
- kobjectid
- 内核对象操作 文档
- 🔧 待办事项
类是设备的更高级别的视图,它抽象出低级别实现细节。驱动程序可能看到 NVME 存储或 SATA 存储,但在类级别,它们都只是 block_class id 设备。类允许用户空间基于设备的功能来处理设备,而不是它们连接的方式或工作方式。通用 DM 类结构与 组合模式 匹配。
⚲ API
- ls /sys/class/
- class_register id 注册 class id
- linux/device/class.hinc
👁 例子:input_class id,block_class id net_class id
一个 外设总线 是处理器和一个或多个外设之间的一个通道。DM 总线是 代理 用于外设总线。通用 DM 总线结构与 组合模式 匹配。就设备模型而言,所有设备都通过总线连接,即使它是内部的、虚拟的,platform_bus_type id。总线可以互相连接。例如,USB 控制器通常是 PCI 设备。设备模型表示总线之间以及它们控制的设备之间的实际连接。总线由 bus_type id 结构表示。它包含名称、默认属性、总线的方法、PM 操作以及驱动程序内核的私有数据。
⚲ API
- ls /sys/bus/
- bus_register id 注册 bus_type id
- linux/device/bus.hinc
👁 例子:usb_bus_type id,hid_bus_type id,pci_bus_type id,scsi_bus_type id,platform_bus_type id
⚲ API
- ls /sys/bus/:/drivers/
- module_driver id - 简单的通用驱动程序初始化程序,👁 例如在 module_pci_driver id 中使用
- driver_register id 注册 device_driver id - 基本的设备驱动程序结构,每个设备实例一个。
- linux/device/driver.hinc
👁 例子:hid_generic id usb_register_device_driver id
平台驱动程序
- module_platform_driver id 注册 platform_driver id(device_driver id 的平台包装器)和 platform_bus_type id
- linux/platform_device.hinc
👁 例子:gpio_mouse_device_driver id
⚲ API
- ls /sys/devices/
- device_register id 注册 device id - 基本的设备结构,每个设备实例一个
- linux/device.hinc
- linux/dev_printk.hinc
- 设备资源管理 文档,devres,devm ...
👁 例子:platform_bus id mousedev_create
平台设备
- platform_device id - struct device - 基本的设备结构 文档 的平台包装器,包含与设备相关的资源
- 它可以由 platform_device_register_simple id 或 platform_device_alloc id 动态自动创建。或者使用 platform_device_register id 注册。
- platform_device_unregister id - 释放设备和相关资源
👁 例子:add_pcspkr id
⚲ API 🔧 待办事项
- platform_device_info platform_device_id platform_device_register_full platform_device_add
- platform_device_add_data platform_device_register_data platform_device_add_resources
- attribute_group dev_pm_ops
⚙️ 内部结构
📖 参考
⚲ API
- lsmod
- cat /proc/modules
⚙️ 内部结构
📖 参考
- LDD3:构建和运行模块
- http://www.xml.com/ldd/chapter/book/ch02.html
- http://www.tldp.org/LDP/tlk/modules/modules.html
- http://www.tldp.org/LDP/lkmpg/2.6/html/ Linux 内核模块编程指南
外设总线是用于将各种外围设备连接到计算机系统的通信通道。这些总线用于在外围设备和系统的处理器或内存之间传输数据。在 Linux 内核中,外设总线作为驱动程序实现,这些驱动程序使操作系统与硬件之间的通信成为可能。
Linux 内核中的外设总线包括 USB、PCI、SPI、I2C 等等。这些总线中的每一个都有其自身独特的特性,并且 Linux 内核为各种外设提供了支持。
PCI(外设组件互连)总线用于连接计算机系统中的内部硬件设备。它通常用于连接显卡、网卡和其他扩展卡。Linux 内核提供了 PCI 总线驱动程序,使操作系统与连接到总线的设备之间的通信成为可能。
USB(通用串行总线)是现代计算机系统中最常用的外设总线之一。它允许设备进行热插拔,并支持高速数据传输速率。
🔧 待办:设备枚举
⚲ API
- Shell 接口:ls /proc/bus/ /sys/bus/
另请参见 驱动模型的总线
参见 输入:键盘、鼠标等
PCI
⚲ Shell API
- lspci -vv
- column -t /proc/bus/pci/devices
主条目:PCI
USB
⚲ Shell API
- lsusb -v
- ls /sys/bus/usb/
- cat /proc/bus/usb/devices
⚙️ 内部结构
📖 参考
其他总线
🤖 嵌入式设备的总线
- linux/gpio/driver.h 包含 linux/gpio.h 包含 drivers/gpio 源码 tools/gpio 源码
- drivers/i2c 源码 https://i2c.wiki.kernel.org
SPI
⚲ API
⚙️ 内部结构
📖 参考
硬件接口是任何操作系统的基本组成部分,它使处理器与计算机系统的其他硬件组件(内存、外设和总线、各种控制器)之间的通信成为可能。
I/O 端口和寄存器是计算机系统中的电子组件,它们使 CPU 与其他电子控制器和设备之间的通信成为可能。
⚲ API
linux/regmap.h 包含 — 寄存器映射访问 API
asm-generic/io.h 包含 — 通用 I/O 端口模拟。
- ioread32 标识符 / iowrite32 标识符 ...
- readl 标识符/ writel 标识符 ...
- 宏 {in,out}[bwl] 用于模拟 x86 风格的 PCI/ISA IO 空间
linux/ioport.h 包含 — 定义用于检测、保留和分配系统资源的例程。
内存映射寄存器的函数
ioremapid ...
关键字:固件、热插拔、时钟、复用器、引脚
⚙️ 内部结构
- drivers/acpisrc
- drivers/basesrc
- drivers/sdio 源码 - 安全数字输入输出
- drivers/virtiosrc
- drivers/hwmonsrc
- drivers/thermalsrc
- drivers/pinctrlsrc
- drivers/clksrc
📖 参考
- 引脚控制子系统 文档
- Linux 硬件监控 文档
- 固件指南 文档
- 设备树 文档
- https://hwmon.wiki.kernel.org/
- LDD3:Linux 设备模型
- http://www.tldp.org/LDP/tlk/dd/drivers.html
- http://www.xml.com/ldd/chapter/book/
- http://examples.oreilly.com/linuxdrive2/
内核是在两个阶段加载的 - 在第一个阶段,内核(作为压缩的镜像文件)被加载到内存并解压缩,并且设置了一些基本功能,例如基本硬件和基本内存管理(内存分页)。然后,控制最终切换到调用start_kernel 标识符的主内核启动过程,然后在生成单独的空闲进程和调度器以及 init 进程(在用户空间执行)之前执行大部分系统设置(中断、内存管理的其余部分、设备和驱动程序初始化等)。
内核加载阶段
加载的内核通常是镜像文件,使用 zlib 压缩为 zImage 或 bzImage 格式。它头部的例程执行最少的硬件设置,将镜像完全解压缩到高内存,并注意是否配置了任何 RAM 磁盘。然后,它通过 startup_64(对于 x86_64 架构)执行内核启动。
- arch/x86/boot/compressed/vmlinux.lds.S 源码 - 链接器脚本定义了入口点 startup_64 标识符
- arch/x86/boot/compressed/head_64.S 源码 - 提取器的汇编
- extract_kernel 标识符 - C 语言中的提取器
- 打印
Decompressing Linux... done. Booting the kernel.
内核启动阶段
内核的启动函数(也称为交换器或进程 0)建立内存管理(分页表和内存分页),检测 CPU 类型和任何附加功能(例如浮点功能),然后通过调用 start_kernel 标识符切换到非架构特定的 Linux 内核功能。
↯ 启动调用层次结构
- arch/x86/kernel/vmlinux.lds.S src – 链接器脚本
- arch/x86/kernel/head_64.S src – 未压缩启动代码的汇编
- arch/x86/kernel/head64.c src – 平台相关的启动
- init/main.c src – 主要初始化代码
- start_kernel id 200 SLOC
- mm_initid
- sched_initid
- rcu_init id – 读-复制-更新
- rest_initid
- kernel_init id - 延迟内核线程 #1
- kernel_init_freeable id 此函数和以下函数都使用属性 __init id 定义
- run_init_process id 显然会运行第一个进程 man 1 init
- kthreadd id – 延迟内核线程 #2
- cpu_startup_entryid
- kernel_init id - 延迟内核线程 #1
- start_kernel id 200 SLOC
start_kernel id 执行了大量初始化函数。它设置了中断处理(IRQs),进一步配置内存,启动了 man 1 init 进程(第一个用户空间进程),然后通过 cpu_startup_entry id 启动了空闲任务。值得注意的是,内核启动过程还会挂载之前加载的 初始 RAM 磁盘(initrd),作为启动阶段的临时根文件系统。initrd 允许驱动程序模块直接从内存加载,而无需依赖其他设备(例如硬盘)及其访问所需驱动程序(例如 SATA 驱动程序)。这种将部分驱动程序静态编译到内核中而将其他驱动程序从 initrd 加载的方式可以缩减内核体积。稍后通过调用 man 8 pivot_root / man 2 pivot_root 来切换根文件系统,该调用会卸载临时根文件系统并将其替换为真正的根文件系统,前提是真正的根文件系统已可访问。然后,临时根文件系统所使用的内存会被回收。
⚙️ 内部结构
...
[edit | edit source]📖 参考
- 关于 内核启动 的文章
- 初始 RAM 磁盘 doc
- Linux 启动过程
- init
- Linux (U)EFI 启动过程
- 内核的命令行参数 doc
- 启动配置 doc
- 启动时内存管理 doc
- 内核启动过程
- 内核初始化过程
📚 进一步阅读
💾 历史
- http://tldp.org/HOWTO/Linux-i386-Boot-Code-HOWTO/
- http://www.tldp.org/LDP/lki/lki-1.html
- http://www.tldp.org/HOWTO/KernelAnalysis-HOWTO-4.html
挂起或重启
[edit | edit source]🔧 待办事项
⚲ API
⚙️ 内部结构
电源管理
[edit | edit source]关键字:挂起、警报、休眠。
⚲ API
- /sys/power/
- /sys/kernel/debug/wakeup_sources
- ⌨️动手操作
- sudo awk '{gsub("^ ","?")} NR>1 {if ($6) {print $1}}' /sys/kernel/debug/wakeup_sources
- linux/pm.hinc
- linux/pm_qos.hinc
- linux/pm_clock.hinc
- linux/pm_domain.hinc
- linux/pm_wakeirq.hinc
- linux/pm_wakeup.hinc
- linux/suspend.hinc
- pm_suspend id 挂起系统
- 挂起和唤醒依赖于
- man 2 timer_create 和 man 2 timerfd_create,如果使用时钟 ID CLOCK_REALTIME_ALARM id 或 CLOCK_BOOTTIME_ALARM id,系统在挂起时会被唤醒。
- man 2 epoll_ctl 使用标志 EPOLLWAKEUP id 会阻止挂起
- 另请参阅 man 7 capabilities CAP_WAKE_ALARM id、CAP_BLOCK_SUSPEND id
⚙️ 内部结构
- CONFIG_PMid
- CONFIG_SUSPENDid
- kernel/powersrc
- alarm_initid
- kernel/time/alarmtimer.csrc
- drivers/base/power src: wakeup_sources id
📖 参考
- PM 管理 doc
- CPU 和设备 PM doc
- 电源管理 doc
- sysfs 电源测试 ABI doc
- https://lwn.net/Kernel/Index/#Power_management
- PowerTOP
- cpupower
- tlp – 应用笔记本电脑电源管理设置
- ACPI – 高级配置和电源接口
运行时 PM
[edit | edit source]关键字:运行时电源管理、设备电源管理、机会性挂起、自动挂起、自动休眠。
⚲ API
- /sys/devices/.../power/
- async autosuspend_delay_ms control runtime_active_kids runtime_active_time runtime_enabled runtime_status runtime_suspended_time runtime_usage
- linux/pm_runtime.hinc
- SET_RUNTIME_PM_OPSid
👁 例子:ac97_pm id
⚙️ 内部结构
📖 参考