Linux内核通信之---proc文件系统(详解)

使用 /proc 文件系统来访问 Linux 内核的内容,这个虚拟文件系统
在内核空间和用户空间之间打开了一个通信窗口:

/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux内核空间和用户间之间进行通信。在 /proc 文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。本文对 /proc 虚拟文件系统进行了介绍,并展示了它的用法。


最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。清单 1 是对 /proc 中部分元素进行一次交互查询的结果。它显示的是 /proc 文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在 GNU/Linux 中创建的第一个进程是 init 进程,因此它的 process-id 为 1。然后对这个目录执行一个 ls 命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。/proc 中另外一些有趣的文件有:cpuinfo,它标识了处理器的类型和速度;pci,显示在 PCI 总线上找到的设备;modules,标识了当前加载到内核中的模块。

另外,我们还可以使用 sysctl 来配置这些内核条目。/proc 文件系统并不是 GNU/Linux 系统中的惟一一个虚拟文件系统。在这种系统上,sysfs 是一个与 /proc 类似的文件系统,但是它的组织更好(从 /proc 中学习了很多教训)。不过 /proc 已经确立了自己的地位,因此即使 sysfs 与 /proc 相比有一些优点,/proc 也依然会存在。还有一个 debugfs 文件系统,不过(顾名思义)它提供的更多是调试接口。debugfs 的一个优点是它将一个值导出给用户空间非常简单(实际上这不过是一个调用而已)。

这些文件的解释和意义如下:

cmdline:系统启动时输入给内核命令行参数 
cpuinfo:CPU的硬件信息 (型号, 家族, 缓存大小等)  
devices:主设备号及设备组的列表,当前加载的各种设备(块设备/字符设备) 
dma:使用的DMA通道 
filesystems:当前内核支持的文件系统,当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统
interrupts :中断的使用及触发次数,调试中断时很有用 
ioports I/O:当前在用的已注册 I/O 端口范围 
kcore:该伪文件以 core 文件格式给出了系统的物理内存映象(比较有用),可以用 GDB 查探当前内核的任意数据结构。该文件的总长度是物理内存 (RAM) 的大小再加上 4KB
kmsg:可以用该文件取代系统调用 syslog(2) 来记录内核日志信息,对应dmesg命令
kallsym:内核符号表,该文件保存了内核输出的符号定义, modules(X)使用该文件动态地连接和捆绑可装载的模块
loadavg:负载均衡,平均负载数给出了在过去的 1、 5,、15 分钟里在运行队列里的任务数、总作业数以及正在运行的作业总数。
locks:内核锁 。
meminfo物理内存、交换空间等的信息,系统内存占用情况,对应df命令。
misc:杂项 。
modules:已经加载的模块列表,对应lsmod命令 。
mounts:已加载的文件系统的列表,对应mount命令,无参数。
partitions:系统识别的分区表 。
slabinfo:sla池信息。
stat:全面统计状态表,CPU内存的利用率等都是从这里提取数据。对应ps命令。
swaps:对换空间的利用情况。 
version:指明了当前正在运行的内核版本。


可加载内核模块(LKM)是用来展示 /proc 文件系统的一种简单方法,这是因为这是一种用来动态地向 Linux 内核添加或删除代码的新方法。LKM 也是 Linux 内核中为设备驱动程序和文件系统使用的一种流行机制。如果你曾经重新编译过 Linux 内核,就可能会发现在内核的配置过程中,有很多设备驱动程序和其他内核元素都被编译成了模块。如果一个驱动程序被直接编译到了内核中,那么即使这个驱动程序没有运行,它的代码和静态数据也会占据一部分空间。但是如果这个驱动程序被编译成一个模块,就只有在需要内存并将其加载到内核时才会真正占用内存空间。


集成到 /proc 文件系统中

内核程序员可以使用的标准 API,LKM 程序员也可以使用。

方法一:(create_proc_entry创建proc文件)

1.1 .创建目录:

[c]  view plain  copy
  1. struct proc_dir_entry *proc_mkdir(const char *name,  
  2.                 struct proc_dir_entry *parent);  

1.2 .创建proc文件:

[c]  view plain  copy
  1. struct proc_dir_entry *create_proc_entry( const char *name,  mode_t mode,  
  2.                 struct proc_dir_entry *parent );  

create_proc_entry函数用于创建一个一般的proc文件,其中name是文件名,比如“hello”,mode是文件模式,parent是要创建的proc文件的父目录(若parent = NULL则创建在/proc目录下)。create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。

[html]  view plain  copy
  1. struct proc_dir_entry {  
  2.     ......  
  3.     const struct file_operations *proc_fops;    <==文件操作结构体  
  4.     struct proc_dir_entry *next, *parent, *subdir;  
  5.     void *data;  
  6.     read_proc_t *read_proc;                    <==读回调  
  7.     write_proc_t *write_proc;                  <==写回调  
  8.     ......  
  9. }; 

1.3 .删除proc文件/目录:

  1. void remove_dir_entry(const char *name, struct proc_dir_entry *parent);  

要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在 /proc 文件系统中的位置(parent)


3、proc文件读回调函数

static int (*proc_read)(char *page, char **start,  off_t off, int count,  int *eof, void *data);


4、proc文件写回调函数

static int proc_write_foobar(struct file *file,  const char *buffer, unsigned long count,  void *data);

proc文件实际上是一个叫做proc_dir_entry的struct(定义在proc_fs.h),该struct中有int read_proc和int write_proc两个元素,要实现proc的文件的读写就要给这两个元素赋值。但这里不是简单地将一个整数赋值过去就行了,需要实现两个回调函数。在用户或应用程序访问该proc文件时,就会调用这个函数,实现这个函数时只需将想要让用户看到的内容放入page即可在用户或应用程序试图写入该proc文件时,就会调用这个函数,实现这个函数时需要接收用户写入的数据(buff参数)。

写回调函数

我们可以使用 write_proc 函数向 /proc 中写入一项。这个函数的原型如下:

int mod_write( struct file *filp, const char __user *buff,
               unsigned long len, void *data );

filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff 参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。len 参数定义了在 buff 中有多少数据要被写入。data 参数是一个指向私有数据的指针。在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。

Linux 提供了一组 API 来在用户空间和内核空间之间移动数据。对于 write_proc 的情况来说,我们使用了 copy_from_user 函数来维护用户空间的数据。

读回调函数

我们可以使用 read_proc 函数从一个 /proc 项中读取数据(从内核空间到用户空间)。这个函数的原型如下:

int mod_read( char *page, char **start, off_t off,
              int count, int *eof, void *data );

page 参数是这些数据写入到的位置,其中 count 定义了可以写入的最大字符数。在返回多页数据(通常一页是 4KB)时,我们需要使用 start和 off 参数。当所有数据全部写入之后,就需要设置 eof(文件结束参数)。与 write 类似,data 表示的也是私有数据。此处提供的 page 缓冲区在内核空间中。因此,我们可以直接写入,而不用调用 copy_to_user


实例代码:

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/init.h>  
#include <linux/proc_fs.h>  
#include <linux/jiffies.h>  
#include <asm/uaccess.h>  
  
  
#define MODULE_VERS "1.0"  
#define MODULE_NAME "procfs_example"  
  
#define FOOBAR_LEN 8  
  
struct fb_data_t {  
    char name[FOOBAR_LEN + 1];  
    char value[FOOBAR_LEN + 1];  
};  
  
  
static struct proc_dir_entry *example_dir, *foo_file;    
  
struct fb_data_t foo_data;  
    
static int proc_read_foobar(char *page, char **start,  
                off_t off, int count,   
                int *eof, void *data)  
{  
    int len;  
    struct fb_data_t *fb_data = (struct fb_data_t *)data;  
  
    /* DON'T DO THAT - buffer overruns are bad */  
    len = sprintf(page, "%s = '%s'\n",   
              fb_data->name, fb_data->value);  
  
    return len;  
}  
  
  
static int proc_write_foobar(struct file *file,  
                 const char *buffer,  
                 unsigned long count,   
                 void *data)  
{  
    int len;  
    struct fb_data_t *fb_data = (struct fb_data_t *)data;  
  
    if(count > FOOBAR_LEN)  
        len = FOOBAR_LEN;  
    else  
        len = count;  
  
    if(copy_from_user(fb_data->name, buffer, len))  
        return -EFAULT;  
  
    fb_data->value[len] = '\0';  
  
    return len;  
}  
  
  
static int __init init_procfs_example(void)  
{  
    int rv = 0;  
  
    /* create directory */  
    example_dir = proc_mkdir(MODULE_NAME, NULL);  
    if(example_dir == NULL) {  
        rv = -ENOMEM;  
        goto out;  
    }  
  
    /* create foo and bar files using same callback  
     * functions   
     */  
    foo_file = create_proc_entry("foo", 0644, example_dir);  
    if(foo_file == NULL) {  
        rv = -ENOMEM;  
        goto no_foo;  
    }  
  
    strcpy(foo_data.name, "foo");  
    strcpy(foo_data.value, "foo");  
    foo_file->data = &foo_data;  
    foo_file->read_proc = proc_read_foobar;  
    foo_file->write_proc = proc_write_foobar;  
         
    /* everything OK */  
    printk(KERN_INFO "%s %s initialised\n",  
           MODULE_NAME, MODULE_VERS);  
    return 0;  
  
 
no_foo:  
    remove_proc_entry("jiffies", example_dir);  
 
out:  
    return rv;  
}  
  
  
static void __exit cleanup_procfs_example(void)  
{  
  
    remove_proc_entry("foo", example_dir);    
    remove_proc_entry(MODULE_NAME, NULL);  
  
    printk(KERN_INFO "%s %s removed\n",  
           MODULE_NAME, MODULE_VERS);  
}  
  
  
module_init(init_procfs_example);  
module_exit(cleanup_procfs_example);  
  
MODULE_AUTHOR("Erik Mouw");  
MODULE_DESCRIPTION("procfs examples");  
MODULE_LICENSE("GPL");  


linux设备驱动学习笔记--内核调试方法之proc:http://blog.csdn.net/itsenlin/article/details/43374921

添加一个新的自定义的系统调用:http://blog.csdn.net/daydring/article/details/23913525

评论 3 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

逝去的浪花

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值