尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

Linux 内核模块深度实操:从 Hello World 到 /proc 接口 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/modules比lsmod提供了更多信息包括模块状态Live和加载地址0xffffffffc0885000。二、模块信息查看modinfo2.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 Tso and others alias: fs-ext4 alias: ext3 alias: fs-ext3 alias: ext2 alias: fs-ext2ext4 在此系统中是内建模块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 模块源代码#includelinux/init.h#includelinux/module.h#includelinux/kernel.hMODULE_LICENSE(GPL);MODULE_AUTHOR(Linux Practice);MODULE_DESCRIPTION(A simple hello world kernel module);MODULE_VERSION(1.0);staticint__inithello_init(void){printk(KERN_INFOHello World: Kernel module loaded successfully!\n);printk(KERN_INFOHello World: This message is from inside the kernel.\n);return0;}staticvoid__exithello_exit(void){printk(KERN_INFOHello 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 Makefileobj-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) cleanMakefile 说明obj-m: 声明要构建的模块列表.o文件构建系统会自动生成.koKDIR: 内核构建目录M$(PWD): 告诉构建系统模块源码在外部目录4.3 编译模块cd/tmp/kmodmake实际输出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.ko4.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 04.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 modversions4.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 模块源代码#includelinux/init.h#includelinux/module.h#includelinux/kernel.h#includelinux/moduleparam.hMODULE_LICENSE(GPL);MODULE_AUTHOR(Linux Practice);MODULE_DESCRIPTION(Kernel module with parameters);MODULE_VERSION(1.0);staticintmy_int42;staticchar*my_stringdefault;staticintmy_array[5]{1,2,3,4,5};staticintarray_size5;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_INFOParam module loaded!\n);printk(KERN_INFOmy_int %d\n,my_int);printk(KERN_INFOmy_string %s\n,my_string);printk(KERN_INFOmy_array );for(i0;iarray_size;i){printk(KERN_CONT%d ,my_array[i]);}printk(KERN_CONT\n);return0;}staticvoid__exitparam_exit(void){printk(KERN_INFOParam 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_int100my_stringcustom_value实际输出Param module loaded查看 dmesgdmesg|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 55.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 模块源代码#includelinux/init.h#includelinux/module.h#includelinux/kernel.h#includelinux/proc_fs.h#includelinux/uaccess.h#includelinux/jiffies.hMODULE_LICENSE(GPL);MODULE_AUTHOR(Linux Practice);MODULE_DESCRIPTION(Kernel module with /proc interface);MODULE_VERSION(1.0);staticstructproc_dir_entry*proc_entry;staticunsignedlonglongcounter0;staticssize_tproc_read(structfile*file,char__user*ubuf,size_tcount,loff_t*ppos){charbuf[128];intlen0;if(*ppos0)return0;counter;lensnprintf(buf,sizeof(buf),Proc module read count: %llu\nJiffies: %lu\n,counter,jiffies);if(copy_to_user(ubuf,buf,len))return-EFAULT;*pposlen;returnlen;}staticconststructproc_opsproc_fops{.proc_readproc_read,};staticint__initproc_init(void){proc_entryproc_create(myproc,0666,NULL,proc_fops);if(!proc_entry){printk(KERN_ERRFailed to create /proc/myproc\n);return-ENOMEM;}printk(KERN_INFOProc module loaded! /proc/myproc created\n);return0;}staticvoid__exitproc_exit(void){proc_remove(proc_entry);printk(KERN_INFOProc module unloaded! /proc/myproc removed\n);}module_init(proc_init);module_exit(proc_exit);关键 APIproc_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 created6.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_SYMBOL和EXPORT_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 内核模块的开发流程模块查看通过lsmod和/proc/modules查看已加载模块使用modinfo查看模块详细信息环境准备安装内核头文件和构建工具搭建模块开发环境Hello World 模块编写、编译、加载、验证、卸载完整的模块生命周期模块参数实现带参数的模块通过insmod传参通过/sys接口查看和修改参数/proc 接口在内核模块中创建/proc条目实现内核与用户空间的数据通信内核符号表通过/proc/kallsyms理解内核符号导出机制许可证机制理解 GPL 与非 GPL 模块的符号访问差异内核模块开发是深入理解 Linux 内核工作原理的最佳途径。通过本文的实操你不仅掌握了模块开发的基本技能还理解了内核与用户空间的交互机制为后续开发设备驱动、文件系统等复杂模块打下了坚实基础。
返回列表