news 2026/4/17 6:55:16

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/4/17 2:32:41

大模型Top-k采样实现:Miniconda-Python代码示例

大模型Top-k采样实现&#xff1a;Miniconda-Python代码示例 在大语言模型&#xff08;LLM&#xff09;日益普及的今天&#xff0c;我们不再只是惊叹于它们“能说会道”&#xff0c;而是更关注如何让生成内容既合理又有创造力。一个看似简单的技术选择——比如解码策略&#xff…

作者头像 李华
网站建设 2026/4/12 23:20:29

pikachu-RCE,越权,目录遍历

RCE 漏洞成因&#xff1a;RCE(remote command/code execute)概述 RCE漏洞&#xff0c;可以让攻击者直接向后台服务器远程注入操作系统命令或者代码&#xff0c;从而控制后台系统。 远程系统命令执行 一般出现这种漏洞&#xff0c;是因为应用系统从设计上需要给用户提供指定的…

作者头像 李华
网站建设 2026/4/9 0:28:23

Linux crontab定时任务:Miniconda-Python脚本自动化执行

Linux crontab定时任务&#xff1a;Miniconda-Python脚本自动化执行 在高校实验室的服务器机房里&#xff0c;一位研究生正为每周重复的手动模型训练感到疲惫——每次都要登录、激活环境、运行脚本、检查日志。而隔壁团队却早已实现“躺平式科研”&#xff1a;每天凌晨自动完成…

作者头像 李华
网站建设 2026/4/16 10:21:05

Token长度与成本关系分析:合理规划API调用

Token长度与成本关系分析&#xff1a;合理规划API调用 在AI应用日益普及的今天&#xff0c;大语言模型&#xff08;LLM&#xff09;已经深度嵌入到内容生成、智能客服、代码辅助等多个业务场景中。然而&#xff0c;随着调用量的增长&#xff0c;许多团队开始发现——账单的增长…

作者头像 李华
网站建设 2026/4/17 5:32:28

Conda info查看Miniconda环境详细信息

Conda info查看Miniconda环境详细信息 在如今的 AI 实验室、数据科学团队或云原生开发环境中&#xff0c;你是否遇到过这样的场景&#xff1a;同事说“代码在我机器上能跑”&#xff0c;但你拉下项目后却报错一堆依赖冲突&#xff1f;又或者&#xff0c;在服务器上部署模型训练…

作者头像 李华
网站建设 2026/4/15 4:59:17

开源贡献流程:向Miniconda-Python3.9镜像提PR

开源贡献流程&#xff1a;向Miniconda-Python3.9镜像提PR 在 AI 工程项目日益复杂的今天&#xff0c;一个常见的痛点浮出水面&#xff1a;不同团队成员使用不同的操作系统和 Python 环境&#xff0c;导致“在我机器上能跑”的尴尬局面频发。更别提当某个依赖包升级后&#xff0…

作者头像 李华