
1. 这不是教科书目录而是内核工程师的“作战地图”你打开 Linux 内核源码树第一眼看到的是drivers/、fs/、net/、mm/这些目录——它们不是文件夹名而是二十个彼此咬合、协同运转的“作战单元”。每个单元背后都站着一个核心子系统每个子系统内部都靠几个关键结构体作为“神经中枢”来调度资源、传递状态、协调动作。我带团队做过 7 个嵌入式 Linux 项目从工业 PLC 控制器到车载信息娱乐系统每次调试卡死、内存泄漏、设备无法识别最终追根溯源90% 都落在这些结构体的初始化顺序、成员赋值错误或链表指针断裂上。比如struct file_operations没填全.ioctl就注册字符设备用户空间一调用就 panic又比如struct net_device的netdev_ops成员为空却启动网卡ifconfig up直接触发空指针解引用。这不是理论题是每天在dmesg里滚动的真实日志。本文不罗列教科书定义只讲这 20 个子系统怎么分工、哪些结构体必须亲手写、哪些字段改错会导致整机重启、以及为什么input_dev和iio_dev看似相似却绝不能混用。适合正在读linux-6.6源码的驱动开发者、准备 Linux 内核面试的应届生以及想真正看懂cat /proc/kallsyms输出含义的运维工程师。2. 子系统划分逻辑与结构体设计哲学2.1 为什么是“20个”而不是“15个”或“25个”内核子系统数量并非固定不变它随内核版本演进动态调整。所谓“20个”是基于linux-6.6主线内核中功能边界清晰、有独立初始化入口、且被Kconfig显式归类的模块集合。它不等于init/main.c中start_kernel()调用的函数个数那是 40 个也不等于drivers/下的子目录数那是 80 个。判断标准有三条第一是否拥有独立的subsys_initcall()或fs_initcall()注册点第二是否在Documentation/下有对应子系统文档如input/input.rst第三是否在include/linux/中定义了以该子系统命名的头文件如iio/iio.h、input/input.h。例如crypto/目录下有aes,sha,rsa等算法实现但它们共用struct crypto_alg和struct crypto_tfm统一由crypto_initcall()初始化因此整个密码学框架算作 1 个子系统而非十几个。再如lib/目录下的crc32.c、string.c是通用库函数无独立初始化流程不计入子系统。这种划分方式直接决定了你写驱动时要#include哪个头文件、调用哪个注册函数、以及调试时该grep哪个结构体名。2.2 结构体不是“数据容器”而是“状态契约”内核中每个核心结构体本质是一份运行时状态契约。它规定了谁有权修改哪些字段const修饰符的使用、哪些字段必须在注册前初始化WARN_ON(!ptr)的触发点、哪些字段由内核自动填充如struct device的kobj成员、哪些字段仅在特定上下文有效如struct task_struct的stack指针在进程退出后失效。以struct platform_driver为例它的probe、remove、suspend函数指针必须由驱动作者提供而driver.name字段则由内核在platform_driver_register()中根据driver.name自动设置driver.bus和driver.owner。如果误将driver.owner THIS_MODULE写成driver.owner NULLmodprobe加载时会因module_put()空指针崩溃。再看struct i2c_client其addr字段必须在i2c_new_client_device()调用前由设备树或板级代码指定若留为 0i2c_transfer()发送地址帧时会生成非法的 0x00 地址导致总线锁死。这些约束不是编译期检查而是运行时断言只有亲手填过、调过、崩过才能真正理解结构体每个字段背后的“责任归属”。2.3 20个子系统的功能边界与协作关系编号子系统名称核心职责简述关键结构体示例非全部典型协作对象1进程管理进程创建、调度、内存隔离、信号处理struct task_struct,struct mm_struct内存管理、IPC、文件系统2内存管理物理页分配、虚拟内存映射、SLAB 分配器、OOM 处理struct page,struct vm_area_struct进程管理、设备驱动、文件系统3文件系统VFS 抽象层、ext4/xfs/btrfs 实现、inode/dentry 缓存struct super_block,struct inode内存管理、块设备、网络文件系统4设备驱动模型统一设备/驱动注册、电源管理、热插拔、sysfs 接口struct device,struct device_driver所有硬件相关子系统5块设备磁盘/SSD/NVMe 抽象、请求队列、I/O 调度器struct gendisk,struct request_queue文件系统、内存管理swap6网络协议栈TCP/IP/UDP 实现、socket 接口、路由转发、防火墙struct sock,struct net_device设备驱动、文件系统/proc/net7输入子系统统一处理键盘、鼠标、触摸屏、传感器等输入事件struct input_dev,struct input_handler设备驱动、事件分发evdev8IIO 子系统工业级传感器ADC/DAC/IMU数据采集、校准、触发配置struct iio_dev,struct iio_trigger设备驱动、输入子系统复用9PCI 子系统PCI/PCIe 总线枚举、配置空间访问、DMA 映射struct pci_dev,struct pci_bus设备驱动、中断管理、DMA10中断管理IRQ 分配、中断处理程序注册、软中断/任务队列调度struct irq_desc,struct irqaction所有外设驱动11DMA 子系统无 CPU 干预的数据搬运、scatter-gather 列表管理、DMA 引擎控制struct dma_chan,struct dma_slave_config设备驱动、块设备、网络12时钟子系统系统时钟源、定时器、高精度延时、clocksource/clockevent 抽象struct clocksource,struct hrtimer进程调度、电源管理、RTC13电源管理系统休眠/唤醒、设备 runtime PM、CPU idle、thermal 管理struct dev_pm_ops,struct pm_domain所有设备驱动、时钟、中断14同步机制自旋锁、互斥体、信号量、RCU、完成量、原子操作spinlock_t,struct mutex,struct rcu_head所有并发场景15IPC 机制信号量、消息队列、共享内存、信号、POSIX 信号量struct sem_array,struct msg_queue进程管理、文件系统/dev/shm16字符设备串口、TTY、LED、GPIO 等字节流设备抽象struct cdev,struct tty_port设备驱动模型、输入子系统17网络设备网卡驱动接口、MAC 地址管理、MTU 设置、统计信息收集struct net_device,struct netdev_stats网络协议栈、DMA、中断18虚拟化支持KVM 接口、virtio 设备抽象、vCPU 管理、内存虚拟化struct kvm,struct virtio_device内存管理、中断、PCI19安全模块SELinux/AppArmor/Smack 等 LSM 框架、安全上下文、访问控制决策struct security_hook_list,struct cred文件系统、进程管理、网络20调试与追踪ftrace、kprobes、perf、sysctl、debugfs 接口struct trace_event_call,struct dentry所有子系统提供可观测性这张表不是静态快照而是动态协作图。例如struct net_device在网络设备子系统中定义但它的dev-dma_mask字段由 DMA 子系统在dma_set_coherent_mask()中设置struct input_dev的input_dev-dev.parent字段指向struct device从而接入设备驱动模型的电源管理链struct iio_dev的indio_dev-dev.parent同样指向struct device但它通过iio_device_register()注册而非input_register_device()这就是 IIO 与输入子系统“同源不同流”的根本原因——它们共享设备模型但事件处理路径和配置接口完全分离。3. 20个子系统核心结构体详解与实操要点3.1 进程管理struct task_struct—— 进程的“数字躯体”task_struct是内核中最大、最复杂的结构体之一linux-6.6中约 12KB它不是简单的进程描述符而是进程在内核态的完整“数字躯体”。其字段按功能域分组理解分组逻辑比死记字段更重要调度域struct sched_entity seCFS 调度实体、int prio动态优先级、struct list_head run_list就绪队列节点。prio不是固定值nice值经NICE_TO_PRIO()转换后叠加RT_PRIO_OFFSET得到实时进程prio 100普通进程prio 100。内存域struct mm_struct *mm用户空间地址空间、struct mm_struct *active_mm内核线程借用的 mm、unsigned long stack内核栈底地址。注意内核线程无mm故mm NULL此时active_mm指向其创建者或当前执行上下文的mm。文件域struct files_struct *files打开文件表、struct fs_struct *fs根目录/工作目录。files-fdt-fd数组是struct file*指针数组fdt-max_fds决定ulimit -n上限。信号域struct signal_struct *signal进程组信号状态、struct sigpending pending待决信号队列。sigpending包含list链表和signal位图前者存sigqueue结构含siginfo_t后者存简单信号。实操陷阱在自定义系统调用中修改current-comm进程名需加get_task_struct()锁定否则ps命令可能读到截断字符串。更安全的做法是使用pr_info(pid %d comm %s\n, current-pid, current-comm)因为comm是TASK_COMM_LEN16 字节的数组内核保证其以\0结尾。3.2 内存管理struct page—— 物理页的“身份证”struct page是内存管理的基石每个物理页帧4KB对应一个page实例。它不存储数据只记录元数据。linux-6.6中采用struct page的“联合体压缩”设计不同用途的页复用同一内存块struct page { union { struct { /* 用于伙伴系统 */ struct list_head lru; unsigned long _refcount; }; struct { /* 用于 slab */ struct kmem_cache *slab_cache; void *freelist; }; struct { /* 用于匿名页 */ struct address_space *mapping; pgoff_t index; }; struct { /* 用于文件页 */ struct address_space *mapping; pgoff_t index; }; }; };关键字段解读_refcount页引用计数get_page()增 1put_page()减 1。当为 0 时页可被伙伴系统回收。mapping指向struct address_space对匿名页是NULL对文件页指向inode-i_mapping。index页在文件或交换区中的偏移单位页。实操要点调试内存泄漏时cat /proc/buddyinfo查看各阶空闲页数cat /proc/slabinfo查看 slab 缓存使用。若kmalloc-128缓存持续增长说明有kmem_cache_alloc()分配未kmem_cache_free()。用slabtop可实时监控。3.3 文件系统struct inode—— 文件的“元数据核心”inode是 VFS 层的核心代表一个文件的元数据权限、大小、时间戳、块指针与文件名无关硬链接共享同一inode。其关键字段struct i_opens *i_opinode 操作函数集create,lookup,link。struct file_operations *i_fop文件操作函数集read,write,ioctl仅对字符/块设备文件有效。union { struct list_head i_dentry; struct hlist_node i_hash; }dentry 缓存链表节点或哈希桶节点。unsigned long i_inoinode 号在文件系统内唯一。实操陷阱在ext4驱动中ext4_iget()从磁盘读取inode数据后必须调用insert_inode_hash()将其加入全局哈希表否则open()时iget5_locked()无法查找到已缓存的inode导致重复加载。i_ino是磁盘上的编号i_generation用于检测 inode 是否被重用防止 NFS stale handle。3.4 设备驱动模型struct device—— 设备的“户籍档案”device是设备在内核中的统一身份标识所有设备平台设备、PCI 设备、USB 设备都继承此结构。其设计体现“面向对象”思想struct device *parent父设备如 USB 设备的 parent 是 USB hub。struct device_driver *driver绑定的驱动。struct bus_type *bus所属总线platform_bus_type,pci_bus_type。struct device_node *of_node设备树节点指针ARM/DT 系统必备。实操要点注册平台设备时platform_device_register()会自动调用device_add()后者执行将dev-kobj添加到sysfs生成/sys/devices/platform/xxx/触发uevent通知用户空间udev创建/dev/xxx调用bus_probe_device()尝试匹配驱动。若of_node为NULLdevice_add()会跳过of_populate()导致设备树属性无法解析。务必在platform_device初始化时设置pdev-dev.of_node of_node。3.5 块设备struct gendisk—— 磁盘的“控制面板”gendisk是块设备的顶层抽象代表一个完整的磁盘如/dev/sda而非分区。其关键字段struct request_queue *queueI/O 请求队列所有读写请求经此排队。struct block_device_operations *fops块设备操作函数集open,release,ioctl。char disk_name[DISK_NAME_LEN]磁盘名sda。int minors次设备号数量主设备号固定次设备号区分分区如sda1,sda2。实操陷阱alloc_disk()分配gendisk后必须调用set_capacity()设置容量单位扇区512 字节否则cat /proc/partitions显示容量为 0。queue的make_request_fn或request_fn必须在blk_mq_init_sq()后设置否则submit_bio()会触发BUG_ON(!q-mq_ops)。3.6 网络协议栈struct sock—— socket 的“内核化身”sock是 socket 在内核中的表示TCP/UDP/RAW socket 共享此结构协议特有数据存于sk_prot指向的私有结构如struct tcp_sock。关键字段struct proto *sk_prot协议操作集tcp_prot,udp_prot。struct sock *sk_socket指向用户空间 socket 的struct socket。struct sk_buff_head sk_write_queue待发送数据包队列。struct sk_buff_head sk_receive_queue已接收数据包队列。实操要点tcp_v4_rcv()处理收到的 TCP 包时先查__inet_lookup()获取sk再调用sk-sk_prot-rcv()即tcp_v4_do_rcv()。若sk的sk_state为TCP_LISTEN则进入三次握手流程若为TCP_ESTABLISHED则交由tcp_rcv_established()处理数据。sk-sk_backlog用于暂存软中断无法立即处理的包避免丢包。3.7 输入子系统struct input_dev—— 输入设备的“事件发射器”input_dev是输入设备的抽象负责将硬件事件转换为标准输入事件。关键字段struct input_id id设备 IDbustype,vendor,product,version。unsigned long evbit[EV_MAX / BITS_PER_LONG 1]支持的事件类型位图EV_KEY,EV_REL,EV_ABS。unsigned long keybit[KEY_MAX / BITS_PER_LONG 1]支持的按键位图KEY_A,KEY_B。struct input_handler *handler绑定的事件处理器evdev,keyboard,mouse。实操陷阱注册前必须调用set_bit(EV_KEY, input_dev-evbit)和set_bit(KEY_A, input_dev-keybit)否则evtest无法检测到按键。input_report_key()发送事件后必须调用input_sync()提交事件批次否则用户空间libinput可能延迟响应。3.8 IIO 子系统struct iio_dev—— 传感器的“数据工厂”IIOIndustrial I/O专为高精度传感器设计与输入子系统并行存在。iio_dev关键字段struct iio_chan_spec const *channels通道规格数组定义 ADC 通道、量程、分辨率。struct iio_info *info操作函数集read_raw,write_raw,read_event_value。struct iio_trigger *trig触发器硬件触发或软件定时触发。实操要点iio_device_register()注册后/sys/bus/iio/devices/下生成iio:deviceXcat in_voltage0_raw即调用info-read_raw()。channels数组中scan_index字段决定iio_buffer中数据排列顺序必须从 0 开始连续编号否则iio_buffer解析失败。3.9 PCI 子系统struct pci_dev—— PCIe 设备的“总线身份证”pci_dev描述一个 PCI 设备包含配置空间信息。关键字段u8 bus/u8 devfn总线号和设备功能号。u16 vendor/u16 device厂商 ID 和设备 ID。struct resource resource[DEVICE_COUNT_RESOURCE]I/O 内存/端口资源。struct pci_driver *driver绑定的驱动。实操陷阱pci_enable_device()必须在request_region()或request_mem_region()之前调用否则ioremap()会失败。resource[0].start是 BAR0 地址resource[0].flags IORESOURCE_MEM判断是内存还是 I/O 端口。3.10 中断管理struct irq_desc—— IRQ 的“调度中心”每个 IRQ 号对应一个irq_desc管理中断处理。关键字段struct irq_chip *irq_data.chip底层芯片操作mask,unmask,eoi。struct irqaction *action中断处理链表头。struct irq_flow_handler *handle_irq流控处理函数handle_level_irq,handle_edge_irq。实操要点request_irq()将irqaction加入action链表并调用irq_startup()启用中断。handle_irq根据irq_data.typeIRQ_TYPE_LEVEL_HIGH选择流控函数错误设置会导致中断丢失或风暴。3.11 DMA 子系统struct dma_chan—— DMA 通道的“搬运工”dma_chan抽象 DMA 通道。关键字段struct dma_device *device所属 DMA 控制器。struct dma_slave_config slave_cfg从设备配置direction,src_addr,dst_addr。struct dma_async_tx_descriptor *tx当前传输描述符。实操陷阱dmaengine_prep_slave_sg()返回tx后必须调用tx-tx_submit()提交否则传输不启动。dma_map_sg()返回的nents是实际映射的 scatterlist 项数可能小于传入的nents需用返回值遍历。3.12 时钟子系统struct clocksource—— 时间的“标尺”clocksource提供单调递增的纳秒级时间源。关键字段cycle_t (*read)(struct clocksource *)读取当前 cycle 计数。u32 mult,u32 shift缩放因子用于clocksource_cyc2ns()转换 cycle 为 ns。struct list_head list加入clocksource_list链表。实操要点clocksource_register_hz()注册后timekeeping模块选择最高 rating 的clocksource作为curr_clocksource。mult/shift计算公式mult (NSEC_PER_SEC shift) / freqfreq是计数器频率。3.13 电源管理struct dev_pm_ops—— 设备的“休眠契约”dev_pm_ops定义设备电源状态转换函数。关键函数.prepare()进入 suspend 前准备禁止中断、保存寄存器。.suspend()挂起设备关闭时钟、断电。.resume()恢复设备上电、恢复寄存器、使能中断。.complete()suspend 完成后清理。实操陷阱.suspend()中若调用msleep()会阻塞应使用pm_schedule_suspend()或wait_event_timeout()。pm_runtime_get_sync()在.suspend()前调用确保设备处于 active 状态。3.14 同步机制struct mutex—— 互斥体的“排队系统”mutex是睡眠锁用于保护临界区。关键字段atomic_t count锁计数1空闲0已锁负数等待者数。struct list_head wait_list等待队列。struct task_struct *owner当前持有者。实操要点mutex_lock()若锁被占用则调用schedule()睡眠因此绝不能在中断上下文或 atomic 上下文中使用。替代方案是spin_lock()忙等适用于短临界区或down()信号量。3.15 IPC 机制struct sem_array—— 信号量的“资源池”sem_array管理一组信号量。关键字段struct kern_ipc_perm sem_permIPC 权限结构uid/gid/mode。struct sem sem_base[0]信号量数组。unsigned short sem_nsems信号量个数。实操陷阱semop()系统调用中sembuf数组的sem_num必须小于sem_nsems否则EINVAL。semctl()的SETVAL命令设置单个信号量值SETALL设置整个数组。3.16 字符设备struct cdev—— 字符设备的“服务台”cdev将字符设备连接到 VFS。关键字段const struct file_operations *ops文件操作函数集。struct kobject kobjsysfs 对象。struct list_head list加入cdev_list链表。实操要点cdev_init()初始化后必须调用cdev_add()将其添加到cdev_map否则open()无法找到设备。cdev_add()的dev参数是设备号MKDEV(major, minor)count是次设备号数量。3.17 网络设备struct net_device—— 网卡的“操作系统”net_device是网络设备的顶层抽象。关键字段const struct net_device_ops *netdev_ops设备操作函数集open,stop,start_xmit。struct wireless_dev *ieee80211_ptr无线扩展指针WiFi 设备。struct ethtool_ops *ethtool_opsethtool 接口。实操陷阱register_netdev()前必须设置netdev-netdev_ops-ndo_start_xmit否则ping时dev_queue_xmit()调用空指针。netdev-mtu默认 1500若硬件不支持需在ndo_change_mtu()中验证。3.18 虚拟化支持struct kvm—— KVM 的“虚拟机管理器”kvm结构体管理一个虚拟机实例。关键字段struct kvm_memslots *memslots内存槽位GPA 到 HPA 映射。struct list_head vm_list加入vm_list链表。struct kvm_arch arch架构相关数据x86/arm。实操要点kvm_vm_ioctl()处理KVM_SET_USER_MEMORY_REGION时调用__kvm_set_memory_region()更新memslots并刷新 EPT/Stage-2 TLB。3.19 安全模块struct security_hook_list—— LSM 的“钩子链表”LSM 框架通过钩子链表插入安全检查。关键字段struct security_hook_heads hooks所有钩子的头节点。struct list_head list加入全局钩子链表。实操陷阱自定义 LSM 模块需在security_initcall()中注册钩子security_module_enable()在security_init()中调用决定是否启用该模块。3.20 调试与追踪struct trace_event_call—— ftrace 的“事件模板”trace_event_call定义一个 tracepoint。关键字段struct trace_event_class *class事件类trace_event_reg。struct trace_event_fields *fields字段定义__string,__field。void *data事件数据缓冲区。实操要点trace_event_reg()注册后echo 1 /sys/kernel/debug/tracing/events/xxx/enable即可启用。trace_printk()会触发trace_event_call但开销大生产环境禁用。4. 实操过程从零构建一个输入设备驱动以 GPIO 按键为例4.1 环境准备与代码骨架目标在linux-6.6.119上编写一个 GPIO 按键驱动注册为input_dev支持KEY_ENTER。开发环境Ubuntu 22.04 arm-linux-gnueabihf-gcc交叉编译。创建驱动目录drivers/input/keyboard/gpio-keys-simple.c编写 Makefileobj-$(CONFIG_KEYBOARD_GPIO_SIMPLE) gpio-keys-simple.o在drivers/input/keyboard/Kconfig中添加config KEYBOARD_GPIO_SIMPLE tristate Simple GPIO keys depends on GPIOLIB INPUT help Say Y here if you want to support simple GPIO keys.4.2 核心结构体初始化与注册#include linux/module.h #include linux/platform_device.h #include linux/input.h #include linux/gpio/consumer.h #include linux/interrupt.h struct gpio_keys_simple_data { struct input_dev *input; struct gpio_desc *gpiod; int irq; }; static irqreturn_t gpio_keys_isr(int irq, void *dev_id) { struct gpio_keys_simple_data *data dev_id; int state gpiod_get_value(data-gpiod); // GPIO 低电平有效按键按下时 state0 input_report_key(data-input, KEY_ENTER, !state); input_sync(data-input); // 提交事件 return IRQ_HANDLED; } static int gpio_keys_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct gpio_keys_simple_data *data; int error; data devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; // 1. 分配 input_dev >