Linux驱动proc接口示例源码分析
1. 概述
本文档详细分析了一个简单的Linux内核模块示例,该示例展示了如何创建和使用proc文件系统接口。proc文件系统是Linux内核提供的一种特殊文件系统,用于在运行时访问内核内部数据结构、改变内核设置。
本文包含Makefile(Ubuntu系统环境下测试通过),用于编译、安装和管理驱动模块。
2. 源码详细分析
2.1 头文件包含
#include<linux/module.h>// 模块相关函数和宏定义#include<linux/kernel.h>// 内核核心函数和宏定义#include<linux/proc_fs.h>// proc文件系统相关函数#include<linux/uaccess.h>// 用户空间与内核空间数据传输函数#include<linux/seq_file.h>// 序列文件操作相关函数#include<linux/sched.h>// 进程调度相关结构体和函数#include<linux/jiffies.h>// 系统时钟滴答相关函数#include<linux/utsname.h>// 系统名称和版本相关函数这些头文件提供了编写内核模块和proc接口所需的所有函数、结构体和宏定义。
2.2 宏定义和全局变量
#definePROC_NAME"proc_example"// proc文件名// 模块参数,可通过insmod时指定staticintproc_value=42;module_param(proc_value,int,0644);MODULE_PARM_DESC(proc_value,"A simple integer parameter");PROC_NAME:定义了将在/proc目录下创建的文件名proc_value:模块参数,默认值为42module_param:宏用于将变量声明为模块参数,格式为module_param(name, type, perm)name:参数名type:参数类型perm:参数在/sys/module下的权限
MODULE_PARM_DESC:为模块参数添加描述信息
2.3 proc文件读取函数
staticintproc_show(structseq_file*m,void*v){// 输出一些系统信息到proc文件seq_printf(m,"=== Proc Interface Example ===\n");seq_printf(m,"Module parameter value: %d\n",proc_value);seq_printf(m,"Current PID: %d\n",current->pid);seq_printf(m,"Current task name: %s\n",current->comm);seq_printf(m,"System uptime: %lu ticks\n",jiffies);return0;}这是proc文件的读取函数,当用户执行cat /proc/proc_example时会调用此函数:
seq_file *m:序列文件指针,用于向用户空间输出数据seq_printf:类似于标准C的printf函数,但用于序列文件current:指向当前进程结构体的指针current->pid:当前进程IDcurrent->comm:当前进程名
jiffies:系统启动以来的时钟滴答数
2.4 proc文件打开函数
staticintproc_open(structinode*inode,structfile*file){returnsingle_open(file,proc_show,NULL);}当用户打开/proc/proc_example文件时调用此函数:
single_open:内核提供的辅助函数,用于创建简单的序列文件file:文件指针proc_show:读取数据的回调函数NULL:传递给回调函数的私有数据
2.5 proc文件写入函数
staticssize_tproc_write(structfile*file,constchar__user*buffer,size_tcount,loff_t*pos){charbuf[64];intret;// 限制写入的长度if(count>sizeof(buf)-1)count=sizeof(buf)-1;// 从用户空间复制数据到内核空间if(copy_from_user(buf,buffer,count))return-EFAULT;buf[count]='\0';// 将字符串转换为整数ret=kstrtoint(buf,10,&proc_value);if(ret)returnret;printk(KERN_INFO"[proc_example] proc_value updated to %d\n",proc_value);returncount;}当用户向/proc/proc_example文件写入数据时调用此函数:
const char __user *buffer:用户空间的缓冲区指针size_t count:要写入的字节数loff_t *pos:文件当前位置
关键步骤:
- 限制写入长度,防止缓冲区溢出
- 使用
copy_from_user将数据从用户空间复制到内核空间 - 将字符串转换为整数
- 更新模块参数
- 使用
printk记录日志 - 返回实际写入的字节数
2.6 文件操作结构体
staticconststructfile_operationsproc_fops={.owner=THIS_MODULE,.open=proc_open,.read=seq_read,.write=proc_write,.llseek=seq_lseek,.release=single_release,};file_operations结构体定义了内核如何处理对文件的各种操作:
.owner:指向模块的指针,用于模块引用计数.open:打开文件的回调函数.read:读取文件的回调函数,这里使用内核提供的seq_read.write:写入文件的回调函数.llseek:定位文件指针的回调函数,使用内核提供的seq_lseek.release:关闭文件的回调函数,使用内核提供的single_release
2.7 模块初始化函数
staticint__initproc_example_init(void){// 创建proc文件if(!proc_create(PROC_NAME,0666,NULL,&proc_fops)){printk(KERN_ERR"[proc_example] Failed to create proc file\n");return-ENOMEM;}printk(KERN_INFO"[proc_example] Module loaded successfully\n");printk(KERN_INFO"[proc_example] Proc file created at /proc/%s\n",PROC_NAME);return0;}当模块被加载时(insmod命令)调用此函数:
proc_create:创建proc文件PROC_NAME:文件名0666:文件权限NULL:父目录,NULL表示/proc根目录&proc_fops:文件操作结构体
printk:在内核日志中记录信息
2.8 模块退出函数
staticvoid__exitproc_example_exit(void){// 删除proc文件remove_proc_entry(PROC_NAME,NULL);printk(KERN_INFO"[proc_example] Module unloaded successfully\n");}当模块被卸载时(rmmod命令)调用此函数:
remove_proc_entry:删除proc文件PROC_NAME:文件名NULL:父目录
2.9 模块信息
// 注册模块的初始化和退出函数module_init(proc_example_init);module_exit(proc_example_exit);// 模块信息MODULE_LICENSE("GPL");MODULE_AUTHOR("Embedded Linux Strategist");MODULE_DESCRIPTION("A simple proc interface example module");MODULE_VERSION("1.0");module_init:注册模块初始化函数module_exit:注册模块退出函数MODULE_LICENSE:声明模块的许可证(必须是GPL兼容的许可证)MODULE_AUTHOR:模块作者信息MODULE_DESCRIPTION:模块功能描述MODULE_VERSION:模块版本信息
3. Makefile分析
# Makefile for proc_example module # 模块名称 obj-m += proc_example.o # 内核源码路径 KDIR := /lib/modules/$(shell uname -r)/build # 当前目录 PWD := $(shell pwd) # 默认目标:编译模块 default: $(MAKE) -C $(KDIR) M=$(PWD) modules # 清理目标:删除编译生成的文件 clean: $(MAKE) -C $(KDIR) M=$(PWD) clean # 安装模块 install: sudo insmod proc_example.ko # 卸载模块 uninstall: sudo rmmod proc_example # 显示模块信息 info: modinfo proc_example.koMakefile关键部分解释
模块名称定义:
obj-m += proc_example.o这行告诉内核构建系统,我们要构建一个名为
proc_example的可加载内核模块。内核源码路径:
KDIR := /lib/modules/$(shell uname -r)/build$(shell uname -r)获取当前运行内核的版本号,然后构建内核源码树的路径。当前目录:
PWD := $(shell pwd)获取当前工作目录的绝对路径。
默认编译目标:
default: $(MAKE) -C $(KDIR) M=$(PWD) modules这是默认的构建规则,它告诉make:
-C $(KDIR):切换到内核源码目录M=$(PWD):指定模块源码所在目录modules:执行内核模块构建目标
清理目标:
clean: $(MAKE) -C $(KDIR) M=$(PWD) clean清理所有编译生成的文件。
安装和卸载目标:
提供了方便的命令来安装和卸载模块。
4. 编译和使用说明
4.1 编译模块
make4.2 安装模块
sudoinsmod proc_example.ko或带参数安装:
sudoinsmod proc_example.koproc_value=1004.3 查看proc文件内容
sudocat/proc/proc_example输出示例:
=== Proc Interface Example === Module parameter value: 42 Current PID: 12345 Current task name: cat System uptime: 12345678 ticks4.4 向proc文件写入数据
echo200|sudotee/proc/proc_example4.5 查看内核日志
sudodmesg|tail4.6 卸载模块
sudormmod proc_example5. 技术要点总结
- proc文件系统:提供了用户空间与内核空间的通信接口
- 序列文件(seq_file):内核提供的简化文件内容生成的机制
- 模块参数:允许在加载模块时配置模块行为
- 用户空间与内核空间数据传输:使用
copy_from_user等函数确保安全的数据传输 - 模块生命周期管理:完整的初始化和退出函数确保资源正确分配和释放
- 内核日志:使用
printk记录模块运行信息
6. 总结
这个简单的示例展示了Linux内核模块中proc接口的基本实现方法。通过创建proc文件,内核模块可以提供一种简单而有效的方式让用户空间程序访问内核数据或控制内核行为。proc文件系统是Linux内核中非常强大的特性,广泛用于系统监控、调试和配置。
7. 完整源码
7.1 proc_example.c
#include<linux/module.h>#include<linux/kernel.h>#include<linux/proc_fs.h>#include<linux/uaccess.h>#include<linux/seq_file.h>#include<linux/sched.h>#include<linux/jiffies.h>#include<linux/utsname.h>#definePROC_NAME"proc_example"// 模块参数,可通过insmod时指定staticintproc_value=42;module_param(proc_value,int,0644);MODULE_PARM_DESC(proc_value,"A simple integer parameter");// proc文件的读取函数staticintproc_show(structseq_file*m,void*v){// 输出一些系统信息到proc文件seq_printf(m,"=== Proc Interface Example ===\n");seq_printf(m,"Module parameter value: %d\n",proc_value);seq_printf(m,"Current PID: %d\n",current->pid);seq_printf(m,"Current task name: %s\n",current->comm);seq_printf(m,"System uptime: %lu ticks\n",jiffies);return0;}// proc文件的打开函数staticintproc_open(structinode*inode,structfile*file){returnsingle_open(file,proc_show,NULL);}// proc文件的写入函数staticssize_tproc_write(structfile*file,constchar__user*buffer,size_tcount,loff_t*pos){charbuf[64];intret;// 限制写入的长度if(count>sizeof(buf)-1)count=sizeof(buf)-1;// 从用户空间复制数据到内核空间if(copy_from_user(buf,buffer,count))return-EFAULT;buf[count]='\0';// 将字符串转换为整数ret=kstrtoint(buf,10,&proc_value);if(ret)returnret;printk(KERN_INFO"[proc_example] proc_value updated to %d\n",proc_value);returncount;}// 定义proc文件的操作函数集合staticconststructfile_operationsproc_fops={.owner=THIS_MODULE,.open=proc_open,.read=seq_read,.write=proc_write,.llseek=seq_lseek,.release=single_release,};// 模块初始化函数staticint__initproc_example_init(void){// 创建proc文件if(!proc_create(PROC_NAME,0666,NULL,&proc_fops)){printk(KERN_ERR"[proc_example] Failed to create proc file\n");return-ENOMEM;}printk(KERN_INFO"[proc_example] Module loaded successfully\n");printk(KERN_INFO"[proc_example] Proc file created at /proc/%s\n",PROC_NAME);return0;}// 模块退出函数staticvoid__exitproc_example_exit(void){// 删除proc文件remove_proc_entry(PROC_NAME,NULL);printk(KERN_INFO"[proc_example] Module unloaded successfully\n");}// 注册模块的初始化和退出函数module_init(proc_example_init);module_exit(proc_example_exit);// 模块信息MODULE_LICENSE("GPL");MODULE_AUTHOR("Embedded Linux Strategist");MODULE_DESCRIPTION("A simple proc interface example module");MODULE_VERSION("1.0");7.2 Makefile
# Makefile for proc_example module # 模块名称 obj-m += proc_example.o # 内核源码路径 KDIR := /lib/modules/$(shell uname -r)/build # 当前目录 PWD := $(shell pwd) # 默认目标:编译模块 default: $(MAKE) -C $(KDIR) M=$(PWD) modules # 清理目标:删除编译生成的文件 clean: $(MAKE) -C $(KDIR) M=$(PWD) clean # 安装模块 install: sudo insmod proc_example.ko # 卸载模块 uninstall: sudo rmmod proc_example # 显示模块信息 info: modinfo proc_example.ko