news 2026/5/22 21:19:20

Linux驱动proc接口示例源码分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux驱动proc接口示例源码分析

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:模块参数,默认值为42
  • module_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:当前进程ID
    • current->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:文件当前位置

关键步骤:

  1. 限制写入长度,防止缓冲区溢出
  2. 使用copy_from_user将数据从用户空间复制到内核空间
  3. 将字符串转换为整数
  4. 更新模块参数
  5. 使用printk记录日志
  6. 返回实际写入的字节数

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.ko

Makefile关键部分解释

  1. 模块名称定义

    obj-m += proc_example.o

    这行告诉内核构建系统,我们要构建一个名为proc_example的可加载内核模块。

  2. 内核源码路径

    KDIR := /lib/modules/$(shell uname -r)/build

    $(shell uname -r)获取当前运行内核的版本号,然后构建内核源码树的路径。

  3. 当前目录

    PWD := $(shell pwd)

    获取当前工作目录的绝对路径。

  4. 默认编译目标

    default: $(MAKE) -C $(KDIR) M=$(PWD) modules

    这是默认的构建规则,它告诉make:

    • -C $(KDIR):切换到内核源码目录
    • M=$(PWD):指定模块源码所在目录
    • modules:执行内核模块构建目标
  5. 清理目标

    clean: $(MAKE) -C $(KDIR) M=$(PWD) clean

    清理所有编译生成的文件。

  6. 安装和卸载目标
    提供了方便的命令来安装和卸载模块。

4. 编译和使用说明

4.1 编译模块

make

4.2 安装模块

sudoinsmod proc_example.ko

或带参数安装:

sudoinsmod proc_example.koproc_value=100

4.3 查看proc文件内容

sudocat/proc/proc_example

输出示例:

=== Proc Interface Example === Module parameter value: 42 Current PID: 12345 Current task name: cat System uptime: 12345678 ticks

4.4 向proc文件写入数据

echo200|sudotee/proc/proc_example

4.5 查看内核日志

sudodmesg|tail

4.6 卸载模块

sudormmod proc_example

5. 技术要点总结

  1. proc文件系统:提供了用户空间与内核空间的通信接口
  2. 序列文件(seq_file):内核提供的简化文件内容生成的机制
  3. 模块参数:允许在加载模块时配置模块行为
  4. 用户空间与内核空间数据传输:使用copy_from_user等函数确保安全的数据传输
  5. 模块生命周期管理:完整的初始化和退出函数确保资源正确分配和释放
  6. 内核日志:使用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
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 11:33:49

Conda remove删除不再需要的包

Conda 包清理的艺术&#xff1a;如何精准移除不再需要的依赖 在现代 Python 开发中&#xff0c;尤其是人工智能、数据科学和机器学习项目里&#xff0c;环境管理早已不再是“装个包就行”的简单操作。随着 PyTorch、TensorFlow、scikit-learn 等大型库的频繁迭代&#xff0c;我…

作者头像 李华
网站建设 2026/5/12 18:53:23

HTML SEO优化:提升Miniconda技术文章搜索排名

HTML SEO优化&#xff1a;提升Miniconda技术文章搜索排名 在数据科学与人工智能的日常实践中&#xff0c;一个常见的痛点浮出水面&#xff1a;即便你写了一篇逻辑清晰、代码完整的技术教程&#xff0c;它依然可能“藏在深山无人知”。搜索引擎抓不到重点&#xff0c;读者搜不到…

作者头像 李华
网站建设 2026/5/7 14:59:13

IT运维不只有主业!22个副业方向让你实现“财富自由”!

运维人员搞副业的22种实战方式&#xff1a;技术变现指南&#xff0c;建议收藏&#xff01; 运维人员凭借日常工作中积累的系统架构、网络安全等专业知识&#xff0c;在副业市场具有天然优势。文章列举22个适合运维的副业场景&#xff0c;包括技术咨询、博客写作、应用开发、网…

作者头像 李华
网站建设 2026/5/13 13:08:57

常用文献检索网站有哪些 文献检索网站推荐与使用指南

刚开始做科研的时候&#xff0c;我一直以为&#xff1a; 文献检索就是在知网、Google Scholar 里反复换关键词。 直到后来才意识到&#xff0c;真正消耗精力的不是“搜不到”&#xff0c;而是—— 你根本不知道最近这个领域发生了什么。 生成式 AI 出现之后&#xff0c;学术检…

作者头像 李华
网站建设 2026/5/22 20:56:18

实习报告还在“写成值班表”?百考通AI平台3分钟生成有逻辑、有反思、有专业深度的高质量实践总结

实习结束&#xff0c;面对学校要求的3000–5000字实践报告&#xff0c;你是否还在苦恼于内容干瘪、结构松散、写来写去只有“早上打卡、下午开会、帮忙复印”这类值班表式记录&#xff1f;看似勤勉&#xff0c;实则缺乏主线、没有分析、更看不出你的专业成长与独立思考&#xf…

作者头像 李华
网站建设 2026/5/22 15:05:26

【Java毕设全套源码+文档】基于springboot+协同过滤算法的个性化音乐推荐系统的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华