嵌入式(驱动-基础):25---调试技术之(/proc文件系统信息接口:read_proc、get_info、create_proc_read_entry、remove_proc_entry)
程序员文章站
2022-05-09 21:09:32
...
-
首先声明:
- ①我们并不鼓励在/proc文件系统下添加我们自己的文件来记录内核信息,因为/proc文件凶已经提供了很多的信息,此文是用来介绍知识的
- ②我们建议新的代码通过sysfs来向外界导出信息,我们将在后面讲到
一、/proc文件系统介绍
- /proc文件系统是一种特殊的、由软件创建的文件系统,内核使用它向外界导出信息
- /proc下面的每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态地生成文件的“内容”。例如,/proc/modules文件记录了系统中所有装载的内核,这个文件也相应的绑定了一个函数,我们通过lsmod查看文件的内容时,这些内容就是与/proc/modules文件绑定的函数返回的
- 在Linux系统中对/proc的使用很频繁,例如ps、top、uptime等工具都是通过读取/proc目录下的文件来获取信息的。当然,有些设备驱动程序也通过/proc导出信息,而我们自己的驱动程序当然也这么做。因此/proc文件系统是动态地,所以驱动程序模块可以在任何时候添加或删除其中的入口项
- 头文件:所有使用/proc的模块都必须包含<linux/proc_fs.h>
二、在/proc中实现文件(read_proc函数)
-
原理:
- ①为创建一个只读的/proc文件,驱动程序必须实现一个函数,用来在读取文件时生成数据。当某个进程读取这个文件时(使用read系统调用),读取请求会通过这个函数发送到驱动模块
- ②在某个进程读取我们的/proc文件时,内核会分配一个内存页(PAGE_SIZE字节的内存块),驱动程序可以将数据通过这个内存页返回到用户空间
#include <linux/proc_fs.h>
int (*read_proc)(char *page,char **start,off_t offset,int count,
int *eof,void *data);
参数:
- page:用来记录数据的缓冲区,需要调用者传入
- start:用来指示要返回给用户的数据写到内存页的哪个位置(用法比较复杂,可以帮助实现大(大于一个内存页)的/proc文件)
- offset:用来指定返回给用户的数据从内存页的多少偏移处开始返回(受start参数影响)
- count:返回数据的长度
- eof:当没有数据可返回时,驱动程序必须设置这个参数,这个参数指向一个整型数
- data:提供给驱动程序的专用数据指针,可用于内部记录
返回值:
- 成功返回存放到内存页缓冲区的字节数,这一点与read对其他类型文件的处理相同
start参数介绍
- start参数有些特别,其用来指示要返回给用户的数据保存在内存页的什么位置
- 在我们的read_proc方法被调用时,*star的初始值为NULL
- *start取值:
- 如果*start为空,内核将假定数据保存在内存页偏移量0的地方,也就是说,内存将对read_proc作如下简单假定:该函数将虚拟文件的整个内容放到了内存页,并同时忽略offset参数
- 如果将*star设置为非空,内核将认为由*start指定的数据是offset指定的偏移量处的数据,可直接返回给用户
- 通常,返回少量数据的简单read_proc方法可忽略start参数,复杂的read_proc方法会将*start设置为页面,并将所请求偏移量处的数据方法内存页中
start参数解决的另一个问题
- 关于/proc文件还有一个主要的问题,这也是start意图要解决的
- 有时,在连续的read调用之间,内核数据结构的ASCII表述会发生变化,以至于读取进程发现前后两次调用所获得的数据不一致
- 如果把*start设为一个小的整数值,那么调用程序可以利用它来增加filp->f_pos的值,而不依赖于返回的数据量,因此也就使f_ops成为read_proc过程的一个内部记录值
- 例如,如果read_proc函数从一个大的结构数组返回数据,并且这些结构的前5个已经在第一次调用中返回,那么可将*start设置为5。下次调用中这个值将作为偏移量,驱动程序也就知道应该从数组的第6个结构开始返回数据。这种方法被它的作者称为“hack”(可以在Linux目录树的*fs*proc/generic.c中看到)
演示案例
- 下面是scull设备的read_proc函数的简单实现,此函数假定不会有生成多于一页的数据的请求,因此忽略了start和offset值,但是要小心不要超出缓冲区:
其他在/proc实现文件的方法
- seq_file接口:在后面介绍
- 老的接口:get_info,已经不建议使用了(参数与read_proc一样,只是没有eof和data参数)
#include <linux/proc_fs.h> int (*get_info)(char *page,char **start,off_t offset,int count);
三、创建自己的/proc文件(create_proc_entry)
#include <linux/proc_fs.h>
struct proc_dir_entry *create_proc_read_entry(const char *name,
mode_t mode,struct proc_dir_enrty *base
read_proc_t *read_proc,void *data);
- 功能:上面定义好了read_proc函数,就需要把它与一个/proc入口项(创建的文件)连接起来,这个过程通过create_proc_read_entry实现
参数:
- name:要在/proc下创建的文件名称
- mode:该文件的保护掩码(可传入0表示系统默认值)
- base:创建的文件所在的目录(填NULL,该文件将创建在/proc的根目录)
- read_proc:实现该文件的read_proc函数(就是上面我们自己定义的read_proc函数)
- data:内核会忽略该参数,但是会将该参数传递给read_proc
base参数进一步介绍
- base参数可用来在/proc下创建完整地目录层次结构。不过请注意,将入口项置于/proc的子目录中有更为简单的方法,即把目录名称作为入口项名称的一部分——只要目录本身已经存在
- 例如:有个经常被忽略的约定,要求把设备驱动程序对应的proc入口项转移到/proc目录的子目录driver/中。scull可以简单地指定它的入口项名称为driver/scullmem,从而把它的proc文件方法放到子目录中
演示案例:
- 下面是scull调用该函数创建/proc文件的代码(下面代码在/proc目录下创建一个成为scullmem的文件,并默认具有全局可读权限设置):
四、删除自己的/proc文件(remove_proc_entry)
- 在卸载模块时,/proc的入口项(文件)也应该被删除,remove_proc_entry就是用来撤销create_proc_read_entry所做的工作的
- 如果删除入口项文件失败,将导致未预期的调用,如果模块已被卸载,内核会崩溃
-
删除入口项文件的常见错误现象:
- 删除调用可能在文件正在被使用时发生,因为/proc入口项不存在关联的所有者,因此对这些文件的使用并不会作用到模块的引用计数上。在移除模块时,执行sleep 100 < /proc/filename命令就可以触发这个问题
- 可能会出现使用同一名字注册两个入口项的现象。内核新人驱动程序,因此不会检查某个名称是否已被注册,因此如果不小心,将可能导致两个或多个入口项具有相同的名称。由于入口项无法区分,因此不论是访问这些入口项的时候还是调用remove_proc_entry的时候,都会出现问题
- 例如:下面是scull模块的remove_proc_entry函数