Linux 内核模块深度实操:从 Hello World 到 /proc 接口
2026/9/10 5:22:24 网站建设 项目流程

Linux 内核模块深度实操:从 Hello World 到 /proc 接口

服务器环境: Ubuntu 24.04.4 LTS, 内核 6.8.0-106-generic, x86_64
实操时间: 2026-09-08
核心目标: 在真实服务器上编写、编译、加载和卸载内核模块,掌握模块参数、printk 日志、/proc 接口等核心技能。

前言

内核模块(Kernel Module)是 Linux 内核的可扩展机制,允许在运行时动态加载和卸载代码,而无需重新编译内核或重启系统。这是 Linux 架构灵活性的核心体现——从设备驱动到文件系统,从网络协议到安全模块,几乎所有内核扩展都通过模块机制实现。本文将在一台真实的 Ubuntu 24.04 服务器上,从零开始编写、编译、加载内核模块,并逐步深入到模块参数、/proc 接口等高级主题。


一、查看已加载的内核模块

1.1 lsmod 命令

lsmod命令列出当前已加载的内核模块,其数据来源于/proc/modules

lsmod|head-30

实际输出:

Module Size Used by tls 155648 0 qrtr 53248 2 intel_rapl_msr 20480 0 intel_rapl_common 40960 1 intel_rapl_msr joydev 32768 0 i2c_piix4 32768 0 input_leds 12288 0 mac_hid 12288 0 serio_raw 20480 0 binfmt_misc 24576 1 sch_fq_codel 24576 5 dm_multipath 45056 0 msr 12288 0 efi_pstore 12288 0 nfnetlink 20480 1 dmi_sysfs 24576 0 qemu_fw_cfg 24576 0 ip_tables 32768 0 x_tables 65536 1 ip_tables autofs4 57344 2 btrfs 2052096 0 blake2b_generic 24576 0 raid10 77824 0 raid456 192512 0 async_raid6_recov 20480 1 raid456 async_memcpy 16384 2 raid456,async_raid6_recov async_pq 20480 2 raid456,async_raid6_recov async_xor 16384 3 async_pq,raid456,async_raid6_recov async_tx 16384 5 async_pq,async_memcpy,async_xor,raid456,async_raid6_recov

字段说明:

  • Module: 模块名称
  • Size: 模块大小(字节)
  • Used by: 被多少个模块/进程使用,以及依赖此模块的模块名

1.2 /proc/modules 文件

cat/proc/modules|head-20

实际输出:

tls 155648 0 - Live 0xffffffffc0885000 qrtr 53248 2 - Live 0xffffffffc086b000 intel_rapl_msr 20480 0 - Live 0xffffffffc0873000 intel_rapl_common 40960 1 intel_rapl_msr, Live 0xffffffffc083e000 joydev 32768 0 - Live 0xffffffffc085d000 i2c_piix4 32768 0 - Live 0xffffffffc084c000 input_leds 12288 0 - Live 0xffffffffc0842000 mac_hid 12288 0 - Live 0xffffffffc0838000 serio_raw 20480 0 - Live 0xffffffffc082f000 binfmt_misc 24576 1 - Live 0xffffffffc0825000 sch_fq_codel 24576 5 - Live 0xffffffffc081c000 dm_multipath 45056 0 - Live 0xffffffffc080d000 msr 12288 0 - Live 0xffffffffc0807000 efi_pstore 12288 0 - Live 0xffffffffc0801000 nfnetlink 20480 1 - Live 0xffffffffc07f9000 dmi_sysfs 24576 0 - Live 0xffffffffc07ef000 qemu_fw_cfg 24576 0 - Live 0xffffffffc07e5000 ip_tables 32768 0 - Live 0xffffffffc07dd000 x_tables 65536 1 ip_tables, Live 0xffffffffc07ce000 autofs4 57344 2 - Live 0xffffffffc05bc000

/proc/moduleslsmod提供了更多信息,包括模块状态(Live)和加载地址(0xffffffffc0885000)。


二、模块信息查看:modinfo

2.1 查看模块详细信息

modinfo ext4

实际输出:

name: ext4 filename: (builtin) softdep: pre: crc32c license: GPL file: fs/ext4/ext4 description: Fourth Extended Filesystem author: Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others alias: fs-ext4 alias: ext3 alias: fs-ext3 alias: ext2 alias: fs-ext2

ext4 在此系统中是内建模块(filename: (builtin)),不是可加载的.ko文件。

2.2 模块依赖关系

modprobe --show-depends ext4

实际输出:

builtin ext4

由于 ext4 是内建的,没有可加载的依赖。对于可加载模块,此命令会显示需要先加载的依赖模块路径。


三、安装内核头文件和构建工具

编写内核模块需要内核头文件和编译工具链:

apt-getinstall-ylinux-headers-$(uname-r)build-essential

实际输出:

linux-headers-6.8.0-106-generic is already the newest version (6.8.0-106.106). linux-headers-6.8.0-106-generic set to manually installed. build-essential is already the newest version (12.10ubuntu1). build-essential set to manually installed. 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

验证安装:

ls/lib/modules/$(uname-r)/build/|head-10

实际输出:

arch block certs crypto Documentation drivers fs include init io_uring Headers installed

四、编写 Hello World 内核模块

4.1 模块源代码

#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("Linux Practice");MODULE_DESCRIPTION("A simple hello world kernel module");MODULE_VERSION("1.0");staticint__inithello_init(void){printk(KERN_INFO"Hello World: Kernel module loaded successfully!\n");printk(KERN_INFO"Hello World: This message is from inside the kernel.\n");return0;}staticvoid__exithello_exit(void){printk(KERN_INFO"Hello World: Kernel module unloaded successfully!\n");}module_init(hello_init);module_exit(hello_exit);

代码解析:

  • MODULE_LICENSE("GPL"): 声明许可证,GPL 许可的模块可以使用内核导出的所有符号
  • MODULE_AUTHOR/DESCRIPTION/VERSION: 模块元信息
  • __init: 标记初始化函数,加载完成后其代码会被释放以节省内存
  • __exit: 标记退出函数,仅在模块可卸载时保留
  • printk(KERN_INFO ...): 内核打印函数,KERN_INFO是日志级别
  • module_init/module_exit: 注册模块的加载和卸载入口

4.2 Makefile

obj-m += hello_module.o obj-m += param_module.o obj-m += proc_module.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean

Makefile 说明:

  • obj-m: 声明要构建的模块列表(.o文件,构建系统会自动生成.ko
  • KDIR: 内核构建目录
  • M=$(PWD): 告诉构建系统模块源码在外部目录

4.3 编译模块

cd/tmp/kmod&&make

实际输出:

warning: the compiler differs from the one used to build the kernel The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 You are using: gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 CC [M] /tmp/kmod/hello_module.o CC [M] /tmp/kmod/param_module.o CC [M] /tmp/kmod/proc_module.o MODPOST /tmp/kmod/Module.symvers CC [M] /tmp/kmod/hello_module.mod.o LD [M] /tmp/kmod/hello_module.ko CC [M] /tmp/kmod/param_module.mod.o LD [M] /tmp/kmod/param_module.ko CC [M] /tmp/kmod/proc_module.mod.o LD [M] /tmp/kmod/proc_module.ko make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-106-generic'

查看生成的模块文件:

ls-la/tmp/kmod/*.ko

实际输出:

-rw-r--r-- 1 root root 161144 Sep 8 22:37 /tmp/kmod/hello_module.ko -rw-r--r-- 1 root root 299496 Sep 8 22:37 /tmp/kmod/param_module.ko -rw-r--r-- 1 root root 302008 Sep 8 22:37 /tmp/kmod/proc_module.ko

4.4 加载模块

insmod /tmp/kmod/hello_module.ko

实际输出:

Module loaded

查看 dmesg 确认模块加载消息:

dmesg|tail-5

实际输出:

[ 3739.567598] hello_module: loading out-of-tree module taints kernel. [ 3739.567605] hello_module: module verification failed: signature and/or required key missing - tainting kernel [ 3739.567980] Hello World: Kernel module loaded successfully! [ 3739.567980] Hello World: This message is from inside the kernel.

注意:“taints kernel” 警告是因为模块不在内核树内(out-of-tree),且没有数字签名。这不影响功能,但在生产环境中应使用签名模块。

4.5 验证模块已加载

lsmod|grephello

实际输出:

hello_module 12288 0

4.6 查看模块信息

modinfo /tmp/kmod/hello_module.ko

实际输出:

filename: /tmp/kmod/hello_module.ko version: 1.0 description: A simple hello world kernel module author: Linux Practice license: GPL srcversion: F8E89A07700771F43532613 depends: retpoline: Y name: hello_module vermagic: 6.8.0-106-generic SMP preempt mod_unload modversions

4.7 卸载模块

rmmod hello_moduledmesg|tail-3

实际输出:

[ 3739.567980] Hello World: Kernel module loaded successfully! [ 3739.567980] Hello World: This message is from inside the kernel. [ 3740.240622] Hello World: Kernel module unloaded successfully!

五、带参数的内核模块

5.1 模块源代码

#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>#include<linux/moduleparam.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("Linux Practice");MODULE_DESCRIPTION("Kernel module with parameters");MODULE_VERSION("1.0");staticintmy_int=42;staticchar*my_string="default";staticintmy_array[5]={1,2,3,4,5};staticintarray_size=5;module_param(my_int,int,0644);MODULE_PARM_DESC(my_int,"An integer parameter");module_param(my_string,charp,0644);MODULE_PARM_DESC(my_string,"A string parameter");module_param_array(my_array,int,&array_size,0644);MODULE_PARM_DESC(my_array,"An integer array");staticint__initparam_init(void){inti;printk(KERN_INFO"Param module loaded!\n");printk(KERN_INFO"my_int = %d\n",my_int);printk(KERN_INFO"my_string = %s\n",my_string);printk(KERN_INFO"my_array = ");for(i=0;i<array_size;i++){printk(KERN_CONT"%d ",my_array[i]);}printk(KERN_CONT"\n");return0;}staticvoid__exitparam_exit(void){printk(KERN_INFO"Param module unloaded!\n");}module_init(param_init);module_exit(param_exit);

参数声明说明:

  • module_param(name, type, perm): 声明参数,perm/sys/module/.../parameters/中文件的权限
  • module_param_array(name, type, &count, perm): 声明数组参数
  • MODULE_PARM_DESC: 参数描述(显示在 modinfo 中)
  • KERN_CONT: 继续前一条 printk 的输出(不换行)

5.2 加载模块并传递参数

insmod /tmp/kmod/param_module.komy_int=100my_string="custom_value"

实际输出:

Param module loaded

查看 dmesg:

dmesg|tail-8

实际输出:

[ 3740.240622] Hello World: Kernel module unloaded successfully! [ 3740.573574] Param module loaded! [ 3740.573579] my_int = 100 [ 3740.573581] my_string = custom_value [ 3740.573582] my_array = 1 2 3 4 5

5.3 通过 /sys 查看和修改参数

ls/sys/module/param_module/parameters/cat/sys/module/param_module/parameters/my_intcat/sys/module/param_module/parameters/my_string

实际输出:

my_array my_int my_string 100 custom_value

由于参数权限设为0644,可以在运行时通过/sys接口修改参数值,实现动态配置。

5.4 卸载模块

rmmod param_module

实际输出:

Param module unloaded

六、/proc 接口:内核与用户空间通信

6.1 模块源代码

#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>#include<linux/proc_fs.h>#include<linux/uaccess.h>#include<linux/jiffies.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("Linux Practice");MODULE_DESCRIPTION("Kernel module with /proc interface");MODULE_VERSION("1.0");staticstructproc_dir_entry*proc_entry;staticunsignedlonglongcounter=0;staticssize_tproc_read(structfile*file,char__user*ubuf,size_tcount,loff_t*ppos){charbuf[128];intlen=0;if(*ppos>0)return0;counter++;len=snprintf(buf,sizeof(buf),"Proc module read count: %llu\nJiffies: %lu\n",counter,jiffies);if(copy_to_user(ubuf,buf,len))return-EFAULT;*ppos=len;returnlen;}staticconststructproc_opsproc_fops={.proc_read=proc_read,};staticint__initproc_init(void){proc_entry=proc_create("myproc",0666,NULL,&proc_fops);if(!proc_entry){printk(KERN_ERR"Failed to create /proc/myproc\n");return-ENOMEM;}printk(KERN_INFO"Proc module loaded! /proc/myproc created\n");return0;}staticvoid__exitproc_exit(void){proc_remove(proc_entry);printk(KERN_INFO"Proc module unloaded! /proc/myproc removed\n");}module_init(proc_init);module_exit(proc_exit);

关键 API:

  • proc_create(name, mode, parent, fops): 创建/proc条目
  • proc_remove(entry): 移除/proc条目
  • copy_to_user(): 从内核空间拷贝数据到用户空间
  • jiffies: 内核时钟计数器

6.2 加载并测试

insmod /tmp/kmod/proc_module.ko

实际输出:

Proc module loaded

读取/proc/myproc(第一次):

cat/proc/myproc

实际输出:

Proc module read count: 1 Jiffies: 4298410723

读取/proc/myproc(第二次):

cat/proc/myproc

实际输出:

Proc module read count: 2 Jiffies: 4298410883

每次读取都会递增计数器,并显示当前的 jiffies 值。这展示了内核模块通过/proc文件系统与用户空间程序通信的机制。

6.3 dmesg 确认

dmesg|tail-5

实际输出:

[ 3740.573582] my_array = 1 2 3 4 5 [ 3741.077703] Param module unloaded! [ 3741.246978] Proc module loaded! /proc/myproc created

6.4 卸载并验证

rmmod proc_modulels/proc/myproc

实际输出:

ls: cannot access '/proc/myproc': No such file or directory

模块卸载后,/proc/myproc条目自动消失。


七、内核符号表:/proc/kallsyms

内核符号表记录了内核中所有导出的函数和变量地址。

cat/proc/kallsyms|head-20

实际输出:

0000000000000000 A fixed_percpu_data 0000000000000000 A __per_cpu_start 0000000000001000 A cpu_debug_store 0000000000002000 A irq_stack_backing_store 0000000000006000 A cpu_tss_rw 000000000000b000 A gdt_page 000000000000c000 A exception_stacks 0000000000018000 A entry_stack_storage 0000000000019000 A espfix_waddr 0000000000019008 A espfix_stack 0000000000019010 A cpu_l2c_shared_map 0000000000019018 A cpu_llc_shared_map 0000000000019040 A amd_dr_addr_mask 0000000000019440 A mce_banks_array 0000000000019460 A mce_num_banks 0000000000019460 A smca_bank_counts 00000000000194a0 A smca_banks 00000000000198a0 A logical_maps 00000000000198b0 A cpu_die_map 00000000000198b8 A cpu_core_map

符号类型说明:

类型含义
A绝对地址符号
T/t代码段符号(大写=全局,小写=局部)
D/d数据段符号
B/bBSS 段符号
R/r只读数据段符号
W/w弱符号

在非 root 用户下,所有地址显示为 0(出于安全考虑)。root 用户可以看到实际地址。


八、模块许可证与符号导出

8.1 许可证的重要性

内核模块必须通过MODULE_LICENSE()声明许可证。不同许可证决定了模块可以使用的内核符号范围:

  • GPL: 可以使用所有EXPORT_SYMBOLEXPORT_SYMBOL_GPL导出的符号
  • 非 GPL: 只能使用EXPORT_SYMBOL导出的符号(非 GPL 限制的)

8.2 符号导出机制

在内核模块中,可以通过以下宏导出符号供其他模块使用:

EXPORT_SYMBOL(my_function);// 所有模块可用EXPORT_SYMBOL_GPL(my_function);// 仅 GPL 模块可用

8.3 vermagic 机制

modinfo输出中的vermagic字段确保模块与当前内核版本匹配:

vermagic: 6.8.0-106-generic SMP preempt mod_unload modversions

这包含内核版本、SMP 支持、抢占模式等信息。如果 vermagic 不匹配,模块将无法加载。


总结

本文在真实的 Ubuntu 24.04 服务器上,从零开始完整实践了 Linux 内核模块的开发流程:

  1. 模块查看:通过lsmod/proc/modules查看已加载模块,使用modinfo查看模块详细信息
  2. 环境准备:安装内核头文件和构建工具,搭建模块开发环境
  3. Hello World 模块:编写、编译、加载、验证、卸载完整的模块生命周期
  4. 模块参数:实现带参数的模块,通过insmod传参,通过/sys接口查看和修改参数
  5. /proc 接口:在内核模块中创建/proc条目,实现内核与用户空间的数据通信
  6. 内核符号表:通过/proc/kallsyms理解内核符号导出机制
  7. 许可证机制:理解 GPL 与非 GPL 模块的符号访问差异

内核模块开发是深入理解 Linux 内核工作原理的最佳途径。通过本文的实操,你不仅掌握了模块开发的基本技能,还理解了内核与用户空间的交互机制,为后续开发设备驱动、文件系统等复杂模块打下了坚实基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询