<Linux内核探秘>读书笔记

本文介绍了Linux操作系统中的文件系统概念,包括虚拟文件系统(VFS)、超级块(super_block)、目录项(dentry)和索引节点(inode)等核心组件。此外,还详细探讨了PCI总线的工作原理及其在Linux中的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(Linux内核探秘(高剑林著 )读书笔记)


Linux 文件系统探秘



一 文件系统基本概念
1.1 VFS
    Linux内核通过虚拟文件系统(VFS)管理文件系统。
    对每个具体文件系统的访问要通过VFS定义的接口来实现。
   VFS定义了几个重要的结构,dentry, inode, super_block,通过这些结构将一个真实的硬盘文件系统抽象到内存,通过管理这几个对象就可以完成对文件系统的一些操作。


1.2 超级块super_block
    超级块代表了整个文件系统本身。
    文件系统内所有的inode也都要链接到超级块的链表头。
    超级块是对应文件系统自身的控制块结构。
    超级块结构给出了文件系统的全局信息:文件系统的块大小,文件系统中最大文件的尺寸,指向file_system_type结构的指针,魔术数字,指向文件系统根dentry的指针,
    超级块对象还定义了一些链表头,用来链接文件系统内的重要成员:s_inodes指向文件系统内所有的inode, s_dirty指向所有dirty的inode对象,s_bdev指向文件系统存在的块设备指针;
    超级块结构包含一些函数指针,super_operations提供了最重要的超级块操作。


1.3 目录项dentry
    在VFS里,目录本身也是一个文件;每个文件都有一个dentry,这个dentry链接到上级目录的dentry。层层链接,形成一颗dentry树。
    为了加快对dentry的查找,内核使用了hash表来缓存dentry,称为dentry cache。
    dentry结构的成员:d_inode指向一个inode结构;d_subdirs是子项的链表头;d_child是dentry自身的链表头;d_parent指向父dentry结构;d_hash; d_name保存文件或目录的名字;d_mounted用来指示dentry是否是一个挂载点。


1.4 索引节点inode
    inode代表一个文件。
    dentry和inode代表一个文件?事实基本如此。


1.5 文件

    文件对象的作用是描述进程和文件交互的关系。硬盘上并在存在一个文件结构;进程打开一个文件,内核就动态创建一个文件对象。


设备的概念和总体架构


一 设备的配置表
    以PCI设备为准说明。
    PCI设备本身就包含一个配置表。
    配置表包含设备制造商填充的厂商信息、设备属性等通用配置信息。设备厂商还应该提供设备的控制寄存器信息。设备还可能配备了内存。
    设备本身有一些配置信息,如设备ID、制造商ID等。
    设备寄存器基址,可以将其看作一个地址,对这个地址写指令就可以控制设备。
    PCI总线规范定义的PCI设备配置空间总长度为256字节,配置信息按一定的顺序和大小依次存放。配置空间的前64字节称为配置头。配置头的主要功能是用来识别设备、定义主机访问PCI卡的方式。其余192字节称为本地配置空间,主要定义卡上局部总线的特性、本地空间基地址及范围等。


为设备服务的特殊文件系统sysfs



kobject结构
    sysfs创建目录时,传递的参数就是一个kobject结构。可以认为kobject代表sysfs文件系统的一个目录。
1 kobject和kset的关系
    kset结构里封装了一个kobject结构,同时包括一个链表头,属于这个kset的所有kobject都要链接到kset的链表头。
2 总线的注册
    总线的注册使用platform_bus_init函数。
    bus_register的作用是把总线对象注册到内核。
    bus对象内含两个kset,一个是devices,另一个是drivers。

serio总线


    驱动之间可以嵌套。一种类型的总线可以架构在另一种类型的总线之上。
一 总线适配器
    网卡、声卡、显卡、SCSI卡等设备很多都是以PCI卡的形式出现,并插入计算机的PCI插槽。声卡显卡加载驱动后,就可以直接读写操作。SCSI卡本身又可以连接


SCSI硬盘,因此加载SCSI卡的PCI驱动后,必须进行SCSI总线扫描,发现SCSI硬盘设备,才能正确地读写硬盘。这里,SCSI卡就担任了总线桥的任务,它提供了总线


之间的协议转换和互操作。像SCSI卡这样的设备,称为主机总线适配器(HBA),它一方面是PCI设备,另一方面它又管理SCSI总线的设备。


二 向serio总线注册设备
    serio总线建筑在platform总线之上,它们分工合作,共同提供了完整的驱动功能。
    从架构的角度看,serio总线这种总线嵌套使用模式类似于总线适配器的模式。


1 注册端口登记事件
    serio_register_port函数的作用是注册serio总线。
    serio_register_port函数的输入参数serio设置了端口类型。
    serio_queue_event函数首先遍历内核的serio_event_list链表。同一个端口只能注册一次。然后创建一个serio_event结构,设置这个serio_event结构的类型为端口注册,唤醒处理这个事件的任务。


2 遍历总线的驱动
    serio_add_port函数的关键部分是device_add函数。
    serio总线的match函数定义在serio.c文件。serio_bus_match函数在设备注册时多次调用,它的输入参数是serio总线上注册的每一个驱动,需要逐个检查端口设备serio和驱动的匹配情况。


3 注册input设备
    调用input_register_device注册input设备。


三 虚拟键盘驱动

PCI总线


一 深入理解PCI总线
    当PCI总线扫描到PCI设备后,已经为设备设置了DMA信息、中断信息和I/O端口、I/O内存信息。
1.1 PCI设备工作原理
    PCI设备具有自己的设备配置信息,也具备I/O端口和I/O内存,这些端口和内存构成一个独立的地址空间,就是PCI总线地址空间。
    CPU要通过主桥才能访问PCI地址空间,PCI设备也要通过主桥才能访问主存。
    主桥可以直接产生一条PCI总线,这条总线也是主桥管理的第一条总线,也是0号PCI总线。在内核代码中,会直接使用这条0号总线。
    从该总线还可以扩展出一系列的PCI总线,称为PCI桥,以主桥为根节点,这些桥和设备形成了一颗PCI树。
    一条PCI总线上,最多只能挂载256个PCI设备。
1.2 PCI总线域
    PCI设备具有一个8 bit的总线号,一个5bit的设备编号以及一个3bit的功能编号。
    一个主桥下最多拥有256个总线,对大型系统而言这是不够的,为此Linux引入PCI域的概念。
1.3 PCI资源管理
    为了管理PCI设备的I/O端口和I/O内存,内核定义了一个resource结构。

<think>好的,用户现在遇到了头文件报错的问题。我需要先分析可能的原因。用户之前已经解决了Makefile路径的问题,现在编译时头文件找不到,可能是内核头文件路径不正确或者相关依赖未安装。 首先,用户的代码中包含了多个Linux内核头文件,比如<linux/module.h>、<linux/netfilter.h>等。这些头文件通常位于内核源码树中的include目录下。之前的Makefile使用了KERNELDIR ?= /lib/modules/$(shell uname -r)/build,这个路径是否正确? 用户可能没有正确安装内核头文件。在Ubuntu系统中,通常需要安装linux-headers-$(uname -r)包。之前的回复中已经提到过这一点,但用户可能没有执行安装,或者安装的路径有问题。 另一个可能的原因是,用户使用了错误的编译环境。例如,如果用户是在虚拟机中运行的,可能需要检查宿主机和客户机之间的共享文件夹设置是否导致路径问题,或者内核版本不一致。 此外,Makefile中的编译参数是否正确?比如,是否需要指定额外的包含路径?不过标准的内核模块Makefile通常不需要额外指定,因为KERNELDIR已经指定了内核源码目录。 用户可能需要检查内核头文件是否真的存在于指定路径。例如,运行ls /usr/src/linux-headers-$(uname -r)/include/linux/,看看是否存在对应的头文件。如果不存在,说明内核头文件没有正确安装。 另外,用户是否在正确的目录下执行make命令?之前的错误信息显示用户是在~/文档/icmp_filter目录下执行make,而Makefile是否存在?之前的对话中用户已经修复了Makefile的位置问题,但可能还有其他问题。 还有可能用户的代码中包含了不兼容的头文件版本,或者某些头文件在特定内核版本中不存在。比如,用户使用的内核版本是4.15.0-142-generic,可能需要检查该版本是否支持某些功能。 总结可能的解决步骤: 1. 确认内核头文件已安装,执行sudo apt install linux-headers-$(uname -r)。 2. 检查KERNELDIR路径是否正确,指向内核源码目录。 3. 验证头文件是否存在,如检查module.h、netfilter.h等是否在/usr/src/linux-headers-$(uname -r)/include/linux/目录下。 4. 可能需要安装额外的依赖,如build-essential。 5. 检查Makefile是否正确,特别是obj-m的设置和源文件路径是否正确。 6. 如果用户使用了错误的编译器选项,可能导致头文件无法解析,但Makefile中没有看到相关配置,可能需要添加额外的CFLAGS。 现在需要将这些思考整理成用户容易理解的步骤,并提供具体的命令和示例代码,确保用户能够一步步排查问题。同时,要考虑到用户可能对Linux内核模块开发不熟悉,需要详细解释每个步骤的目的。</think>根据头文件报错信息,问题通常由以下原因导致,请按顺序排查: ### 1. 确认内核头文件安装 ```bash # 检查内核头文件是否存在 ls /usr/src/linux-headers-$(uname -r)/include/linux/module.h # 如果文件不存在,执行安装: sudo apt update sudo apt install linux-headers-$(uname -r) build-essential ``` ### 2. 验证KERNELDIR路径 ```bash # 检查Makefile中的KERNELDIR定义: echo $(KERNELDIR) # 应输出类似:/lib/modules/4.15.0-142-generic/build # 验证软链接有效性: ls -l /lib/modules/$(uname -r)/build # 应指向有效的内核源码目录(/usr/src/linux-headers-xxx) ``` ### 3. 检查头文件依赖关系 ```bash # 检查缺失的头文件位置: find /usr/src/linux-headers-$(uname -r)/include -name "*.h" | grep -i "module\|netfilter" # 应显示: # include/linux/module.h # include/linux/netfilter.h # include/linux/netfilter_ipv4.h ``` ### 4. 修正后的完整Makefile示例 ```makefile obj-m += icmp_filter.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) # 添加额外的包含路径(如果需要) EXTRA_CFLAGS += -I$(PWD)/include default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean install: default sudo insmod icmp_filter.ko n=512 dmesg | tail -20 uninstall: sudo rmmod icmp_filter dmesg | tail -10 .PHONY: default clean install uninstall ``` ### 5. 测试编译环境 ```bash # 创建测试文件test.c cat > test.c << 'EOL' #include <linux/module.h> #include <linux/kernel.h> int init_module(void) { return 0; } void cleanup_module(void) {} EOL # 手动编译测试: gcc -I/usr/src/linux-headers-$(uname -r)/include -c test.c -o test.o # 如果编译成功,说明头文件路径正确 ``` ### 6. 典型修复方案 ```bash # 如果发现头文件缺失,可尝试: cd /usr/src sudo ln -s linux-headers-$(uname -r) linux # 然后修改Makefile: KERNELDIR ?= /usr/src/linux ``` ### 7. 检查内核版本兼容性 ```bash # 检查当前内核版本: uname -r # 4.15.0-142-generic版本对应的Ubuntu 18.04 LTS # 该版本内核头文件需确认是否包含: grep "NF_INET_PRE_ROUTING" /usr/src/linux-headers-$(uname -r)/include/uapi/linux/netfilter.h # 应该看到: # #define NF_INET_PRE_ROUTING 0 ``` ### 8. 最终解决方案(新建测试模块) ```bash # 创建测试模块目录 mkdir -p ~/test_module/src cd ~/test_module # 创建测试源文件 cat > src/test.c << 'EOL' #include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> static struct nf_hook_ops *nfho; int init_module(void) { printk(KERN_INFO "Test module loaded\n"); return 0; } void cleanup_module(void) { printk(KERN_INFO "Test module unloaded\n"); } EOL # 创建Makefile cat > Makefile << 'EOL' obj-m += test.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean .PHONY: default clean EOL # 执行测试编译 cd ~/test_module make clean make ``` ### 常见问题解决方案: 1. **"No such file or directory"错误**: ```bash # 创建缺失的符号链接: sudo ln -s /usr/src/linux-headers-$(uname -r)/include/uapi/linux /usr/include/linux ``` 2. **"In file included from..."错误**: ```bash # 添加编译参数: EXTRA_CFLAGS += -I/usr/src/linux-headers-$(uname -r)/include/uapi ``` 3. **Netfilter头文件问题**: ```bash # 检查netfilter定义: grep "NF_INET_PRE_ROUTING" /usr/src/linux-headers-$(uname -r)/include/uapi/linux/netfilter.h # 如果没有定义,需要升级内核 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值