使用 /proc 文件系统来访问 Linux 内核的内容

来源:互联网 发布:加内特生涯数据排行 编辑:程序博客网 时间:2024/06/18 10:05

使用 /proc 文件系统来访问 Linux 内核的内容

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

最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息,或启用动态运行时配置。
/proc 文件系统包含了一些目录(用作组织信息的方式)和虚拟文件。虚拟文件可以向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。实际上我们并不会同时需要实现这两点,但是本文将向您展示如何配置这个文件系统进行输入和输出。

尽管像本文这样短小的一篇文章无法详细介绍 /proc 的所有用法,但是它依然对这两种用法进行了展示,从而可以让我们体会一下 /proc 是多么强大。

[html] view plaincopy
  1. root@ubuntu:~# cd /proc/  
  2. root@ubuntu:/proc# ls  
  3. 1     1629  1813   1977   26712  4    843  acpi         key-users      softirqs  
  4. 10    1633  1816   2      26994  41   849  asound       kmsg           stat  
  5. 1008  1667  1819   20     26995  42   857  buddyinfo    kpagecount     swaps  
  6. 11    1668  1820   2030   27     43   858  bus          kpageflags     sys  
  7. 1152  1697  1822   2039   27043  44   860  cgroups      latency_stats  sysrq-trigger  
  8. 1182  17    1830   20791  27054  45   861  cmdline      loadavg        sysvipc  
  9. 12    1706  1835   2090   27055  46   867  cpuinfo      locks          timer_list  
  10. 13    1711  1836   2091   27056  5    868  crypto       mdstat         timer_stats  
  11. 132   1712  1844   21     27059  571  876  devices      meminfo        tty  
  12. 14    1726  1852   2160   27064  6    878  diskstats    misc           uptime  
  13. 1407  1744  1854   2194   27083  667  9    dma          modules        version  
  14. 1427  1779  18642  2195   28     7    938  driver       mounts         version_signature  
  15. 1475  1782  1885   22     29     721  939  execdomains  mpt            vmallocinfo  
  16. 1483  1783  1887   2213   3      746  940  fb           mtrr           vmmemctl  
  17. 1487  1786  1893   23     30     760  941  filesystems  net            vmstat  
  18. 15    1792  1896   238    31     776  942  fs           pagetypeinfo   zoneinfo  
  19. 1552  1795  19     24     32     778  943  interrupts   partitions  
  20. 1557  1799  1900   240    326    782  944  iomem        sched_debug  
  21. 16    18    1906   248    328    784  945  ioports      schedstat  
  22. 1602  1806  1908   25476  35     785  946  irq          scsi  
  23. 1621  1808  1920   265    37     797  947  kallsyms     self  
  24. 1625  1811  1970   266    38     8    951  kcore        slabinfo  
  25. root@ubuntu:/proc# cd 1  
  26. root@ubuntu:/proc/1# ls  
  27. attr        coredump_filter  fd       loginuid   mountstats  personality  smaps   syscall  
  28. auxv        cpuset           fdinfo   maps       net         root         stack   task  
  29. cgroup      cwd              io       mem        oom_adj     sched        stat    wchan  
  30. clear_refs  environ          latency  mountinfo  oom_score   schedstat    statm  
  31. cmdline     exe              limits   mounts     pagemap     sessionid    status  
  32. root@ubuntu:/proc/1# cat cmdline   
  33. /sbin/initroot@ubuntu:/proc/1#   
  34. root@ubuntu:/proc/1# cat sta  
  35. stack   stat    statm   status    
  36. root@ubuntu:/proc/1# cat stat  
  37. stat    statm   status    
  38. root@ubuntu:/proc/1# cat status   
  39. Name:   init  
  40. State:  S (sleeping)  
  41. Tgid:   1  
  42. Pid:    1  
  43. PPid:   0  
  44. TracerPid:  0  
  45. Uid:    0   0   0   0  
  46. Gid:    0   0   0   0  
  47. FDSize: 32  
  48. Groups:   
  49. VmPeak:     2804 kB  
  50. VmSize:     2792 kB  
  51. VmLck:         0 kB  
  52. VmHWM:      1672 kB  
  53. VmRSS:      1672 kB  
  54. VmData:      436 kB  
  55. VmStk:        84 kB  
  56. VmExe:       100 kB  
  57. VmLib:      2068 kB  
  58. VmPTE:        28 kB  
  59. Threads:    1  
  60. SigQ:   1/7949  
  61. SigPnd: 0000000000000000  
  62. ShdPnd: 0000000000000000  
  63. SigBlk: 0000000000000000  
  64. SigIgn: 0000000000001000  
  65. SigCgt: 00000001a0012623  
  66. CapInh: 0000000000000000  
  67. CapPrm: ffffffffffffffff  
  68. CapEff: fffffffffffffeff  
  69. CapBnd: ffffffffffffffff  
  70. Cpus_allowed:   ff  
  71. Cpus_allowed_list:  0-7  
  72. Mems_allowed:   1  
  73. Mems_allowed_list:  0  
  74. voluntary_ctxt_switches:    2044  
  75. nonvoluntary_ctxt_switches: 259  
  76. Stack usage:    16 kB  
  77. root@ubuntu:/proc/1#   
上面是对 /proc 中部分元素进行一次交互查询的结果。它显示的是 /proc 文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在 GNU/Linux 中创建的第一个进程是 init 进程,因此它的 process-id 为 1。然后对这个目录执行一个 ls 命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。例如,要查看 init 的 command-line 项的内容,只需对 cmdline 文件执行 cat 命令。

/proc 中另外一些有趣的文件有:

cpuinfo,它标识了处理器的类型和速度;

pci,显示在 PCI 总线上找到的设备;

modules,标识了当前加载到内核中的模块。

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

创建并删除 /proc 项

要在 /proc 文件系统中创建一个虚拟文件,请使用 create_proc_entry 函数。这个函数可以接收一个文件名、一组权限和这个文件在 /proc 文件系统中出现的位置。create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。create_proc_entry 的原型和 proc_dir_entry 结构中的一部分如下面 所示。

[html] view plaincopy
  1. struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,  
  2.                                              struct proc_dir_entry *parent );  
  3. struct proc_dir_entry {  
  4.     const char *name;           // virtual file name  
  5.     mode_t mode;                // mode permissions  
  6.     uid_t uid;              // File's user id  
  7.     gid_t gid;              // File's group id  
  8.     struct inode_operations *proc_iops; // Inode operations functions  
  9.     struct file_operations *proc_fops;  // File operations functions  
  10.     struct proc_dir_entry *parent;      // Parent directory  
  11.     ...  
  12.     read_proc_t *read_proc;         // /proc read function  
  13.     write_proc_t *write_proc;       // /proc write function  
  14.     void *data;             // Pointer to private data  
  15.     atomic_t count;             // use count  
  16.     ...  
  17. };  
  18. void remove_proc_entry( const char *name, struct proc_dir_entry *parent );  

假如上面create_proc_entry 的返回值是一个 proc_dir_entry 指针,那么使用以下方法配置该文件执行读写操作时应该调用的函数

[html] view plaincopy
  1. proc_dir_entry ->read_proc = read_proc ;   
  2. proc_dir_entry ->write_proc = write_proc ;   
稍后我们就可以看到如何使用 read_proc 和 write_proc 命令来插入对这个虚拟文件进行读写的函数。

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

parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。下面列表列出了可以使用的其他一些父 proc_dir_entry,以及它们在这个文件系统中的位置。

proc_dir_entry在文件系统中的位置proc_root_fs/procproc_net/proc/netproc_bus/proc/busproc_root_driver/proc/driver
写回调函数

我们可以使用 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。

其他有用的函数

我们还可以使用 proc_mkdir、symlinks 以及 proc_symlink 在 /proc 文件系统中创建目录。对于只需要一个 read 函数的简单 /proc 项来说,可以使用 create_proc_read_entry,这会创建一个 /proc 项,并在一个调用中对 read_proc 函数进行初始化。这些函数的原型如下所示。

[html] view plaincopy
  1. /* Create a directory in the proc filesystem */  
  2. struct proc_dir_entry *proc_mkdir( const char *name,  
  3.                                      struct proc_dir_entry *parent );  
  4. /* Create a symlink in the proc filesystem */  
  5. struct proc_dir_entry *proc_symlink( const char *name,  
  6.                                        struct proc_dir_entry *parent,  
  7.                                        const char *dest );  
  8. /* Create a proc_dir_entry with a read_proc_t in one call */  
  9. struct proc_dir_entry *create_proc_read_entry( const char *name,  
  10.                                                   mode_t mode,  
  11.                                                   struct proc_dir_entry *base,  
  12.                                                   read_proc_t *read_proc,  
  13.                                                   void *data );  
  14. /* Copy buffer to user-space from kernel-space */  
  15. unsigned long copy_to_user( void __user *to,  
  16.                               const void *from,  
  17.                               unsigned long n );  
  18. /* Copy buffer to kernel-space from user-space */  
  19. unsigned long copy_from_user( void *to,  
  20.                                 const void __user *from,  
  21.                                 unsigned long n );  
  22. /* Allocate a 'virtually' contiguous block of memory */  
  23. void *vmalloc( unsigned long size );  
  24. /* Free a vmalloc'd block of memory */  
  25. void vfree( void *addr );  
  26. /* Export a symbol to the kernel (make it visible to the kernel) */  
  27. EXPORT_SYMBOL( symbol );  
  28. /* Export all symbols in a file to the kernel (declare before module.h) */  
  29. EXPORT_SYMTAB  
create_proc_read_entry 

说明:

name : 要创建的文件名;

mode : 文件掩码,为 0 则按照系统默认的掩码创建文件。

base : 指定该文件所在的目录,如果为 NULL,则文件被创建在 /proc 根目录下。

read_proc : 实现该文件的 read_proc 函数。也就是说,当我们读取 "name" 这个文件时(如 cat /proc/myproc_name) ,读取请求会通过这个函数发送到驱动模块,然后在函数里处理的数据会写到 myproc_name 文件中。

data : 内核忽略此参数,但会把它当作参数传递给 read_proc 这个自定义函数。

用法:

[html] view plaincopy
  1. struct proc_dir_entry *parent;  
  2. parent = proc_mkdir ("myproc", NULL);  
  3. create_proc_read_entry ("scullmem", 0744, parent, scull_read_procmem, NULL);  
这样,就在 /proc 下创建了 myproc 目录,并在 myproc 目录下创建了一个名为 scullmem 的文件,且这个文件的权限为 0744 :
[html] view plaincopy
  1. # ll /proc/myproc/scullmem   
  2. -rwxr--r-- 1 root root 0 2010-09-27 20:48 /proc/myproc/scullmem*  

上面的 scullmem 后有 1 星号表示此文件可执行,实际上 /proc 下的文件一般都是只读的,这里只是演示权限位。

通过 /proc 文件系统实现财富分发,一个比较经典的实例

这个简单的程序提供了一个财富甜点分发。在加载这个模块之后,用户就可以使用 echo 命令向其中导入文本财富,然后再使用 cat 命令逐一读出。

init 函数(init_fortune_module)负责使用 vmalloc 来为这个点心罐分配空间,然后使用 memset 将其全部清零。使用所分配并已经清空的 cookie_pot 内存,我们在 /proc 中创建了一个 proc_dir_entry 项,并将其称为 fortune。当 proc_entry 成功创建之后,对自己的本地变量和 proc_entry 结构进行了初始化。我们加载了 /proc read 和 write 函数,并确定这个模块的所有者。cleanup 函数简单地从 /proc 文件系统中删除这一项,然后释放 cookie_pot 所占据的内存。

cookie_pot 是一个固定大小(4KB)的页,它使用两个索引进行管理。第一个是 cookie_index,标识了要将下一个 cookie 写到哪里去。变量 next_fortune 标识了下一个 cookie 应该从哪里读取以便进行输出。在所有的 fortune 项都读取之后,我们简单地回到了 next_fortune。

[html] view plaincopy
  1. #include <linux/module.h>  
  2. #include <linux/kernel.h>  
  3. #include <linux/proc_fs.h>  
  4. #include <linux/string.h>  
  5. #include <linux/vmalloc.h>  
  6. #include <asm/uaccess.h>  
  7. MODULE_LICENSE("GPL");  
  8. MODULE_DESCRIPTION("Fortune Cookie Kernel Module");  
  9. MODULE_AUTHOR("M. Tim Jones");  
  10. #define MAX_COOKIE_LENGTH       PAGE_SIZE  
  11. static struct proc_dir_entry *proc_entry;  
  12. static char *cookie_pot;  // Space for fortune strings  
  13. static int cookie_index;  // Index to write next fortune  
  14. static int next_fortune;  // Index to read next fortune  
  15. int init_fortune_module( void )  
  16. {  
  17.   int ret = 0;  
  18.   cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );  
  19.   if (!cookie_pot) {  
  20.     ret = -ENOMEM;  
  21.   } else {  
  22.     memset( cookie_pot, 0, MAX_COOKIE_LENGTH );  
  23.     proc_entry = create_proc_entry( "fortune", 0644, NULL );  
  24.     if (proc_entry == NULL) {  
  25.       ret = -ENOMEM;  
  26.       vfree(cookie_pot);  
  27.       printk(KERN_INFO "fortune: Couldn't create proc entry\n");  
  28.     } else {  
  29.       cookie_index = 0;  
  30.       next_fortune = 0;  
  31.       proc_entry->read_proc = fortune_read;  
  32.       proc_entry->write_proc = fortune_write;  
  33.       proc_entry->owner = THIS_MODULE;  
  34.       printk(KERN_INFO "fortune: Module loaded.\n");  
  35.     }  
  36.   }  
  37.   return ret;  
  38. }  
  39. void cleanup_fortune_module( void )  
  40. {  
  41.   remove_proc_entry("fortune", &proc_root);  
  42.   vfree(cookie_pot);  
  43.   printk(KERN_INFO "fortune: Module unloaded.\n");  
  44. }  
  45. module_init( init_fortune_module );  
  46. module_exit( cleanup_fortune_module );  
向这个罐中新写入一个 cookie 非常简单。使用这个写入 cookie 的长度,我们可以检查是否有这么多空间可用。如果没有,就返回 -ENOSPC,它会返回给用户空间。否则,就说明空间存在,我们使用 copy_from_user 将用户缓冲区中的数据直接拷贝到 cookie_pot 中。然后增大 cookie_index(基于用户缓冲区的长度)并使用 NULL 来结束这个字符串。最后,返回实际写入 cookie_pot 的字符的个数,它会返回到用户进程。

[html] view plaincopy
  1. ssize_t fortune_write( struct file *filp, const char __user *buff,  
  2.                         unsigned long len, void *data )  
  3. {  
  4.   int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;  
  5.   if (len > space_available) {  
  6.     printk(KERN_INFO "fortune: cookie pot is full!\n");  
  7.     return -ENOSPC;  
  8.   }  
  9.   if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {  
  10.     return -EFAULT;  
  11.   }  
  12.   cookie_index += len;  
  13.   cookie_pot[cookie_index-1] = 0;  
  14.   return len;  
  15. }  
对 fortune 进行读取也非常简单。由于我们刚才写入数据的缓冲区(page)已经在内核空间中了,因此可以直接对其进行操作,并使用 sprintf 来写入下一个 fortune。如果 next_fortune 索引大于 cookie_index(要写入的下一个位置),那么我们就将 next_fortune 返回为 0,这是第一个 fortune 的索引。在将这个 fortune 写入用户缓冲区之后,在 next_fortune 索引上增加刚才写入的 fortune 的长度。这样就变成了下一个可用 fortune 的索引。这个 fortune 的长度会被返回并传递给用户。

[html] view plaincopy
  1. int fortune_read( char *page, char **start, off_t off,  
  2.                    int count, int *eof, void *data )  
  3. {  
  4.   int len;  
  5.   if (off > 0) {  
  6.     *eof = 1;  
  7.     return 0;  
  8.   }  
  9.   /* Wrap-around */  
  10.   if (next_fortune >= cookie_index) next_fortune = 0;  
  11.   len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);  
  12.   next_fortune += len;  
  13.   return len;  
  14. }  
从这个简单的例子中,我们可以看出通过 /proc 文件系统与内核进行通信实际上是件非常简单的事情。现在让我们来看一下这个 fortune 模块的用法
[html] view plaincopy
  1. [root@plato]# insmod fortune.ko  
  2. [root@plato]# echo "Success is an individual proposition.    
  3.           Thomas Watson" > /proc/fortune  
  4. [root@plato]# echo "If a man does his best, what else is there?    
  5.                 Gen. Patton" > /proc/fortune  
  6. [root@plato]# echo "Cats: All your base are belong to us.    
  7.                       Zero Wing" > /proc/fortune  
  8. [root@plato]# cat /proc/fortune  
  9. Success is an individual proposition.  Thomas Watson  
  10. [root@plato]# cat /proc/fortune  
  11. If a man does his best, what else is there?  General Patton  
  12. [root@plato]#  
/proc 虚拟文件系统可以广泛地用来报告内核的信息,也可以用来进行动态配置。我们会发现它对于驱动程序和模块编程来说都是非常完整的。
0 0
原创粉丝点击