/proc文件系统是Linux内核的一个虚拟文件系统,说它虚拟,是因为和磁盘分区上的文件系统不同,它只有在内核启动以后,工作起来的时候才会被动态创建。

/proc下面的内容会随着内核的配置和工作的状态在变化,比如/proc/meminfo这个文件,里面的内容是内存信息,不同的机器自然不同;还有就是/proc目录下面用数字命名的子目录,每个子目录的名字也就是数字,对应当前系统正在运行的进程,而子目录里面的内容就是对应进程的信息。

/proc里面还有其他许多信息,这里就不一一描述了,有兴趣可以谷歌一下。

Linux内核经过多年发展,/proc目录下的林林总总,已经是非常多了,虽然已经开发出了新的/sys文件系统替代/proc,但是,许多传统的程序仍然使用/proc文件系统作为内核与用户程序的接口。

如何让内核更高效,那是内核开发人员的事情。作为一个使用内核的开发人员,掌握/proc文件系统的使用方法也是很有用的,和/sys文件系统组织严密的结构相比,用/proc有时候还是很方便的。

不多说废话了,先入个门,介绍/proc的最基本操作。

这一集我们就介绍操作/proc文件系统的三个最基本函数:

代码片段1 定义在内核代码的<linux/proc_fs.h>

 
  
  1. struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,  
  2.       struct proc_dir_entry *parent);  
  3.  
  4. struct proc_dir_entry *proc_mkdir(const char *name,
  5. struct proc_dir_entry *parent);  
  6.  
  7. void remove_proc_entry(const char *name, struct proc_dir_entry *parent);  
  8.  

先插一句,本例所有代码使用内核版本2.6.38.2,系统是ubuntu 11.04 amd64,要注意哦。

这三个函数,从函数名称就可以理解出来意思,第一个create_proc_entry()函数用做创建一个节点,也就是创建一个文件;

第二个proc_mkdir()函数建立一个目录;

第三个remove_proc_entry()函数是删除一个节点,实际上,不仅能删除节点,也可以删除proc_mkdir()函数创建的目录。

需要注意的是,proc相关的函数,操作的目标都是在/proc目录下。好,继续看函数参数:

create_proc_entry()函数:
name 是节点的名称,一个字符串;
mode 访问权限,可以使用八进制表示,chmod命令使用的权限一样;
parent 父目录,注意是一个struct proc_dir_entry结构,如果写NULL表示/proc目录。

proc_mkdir()函数:
name 子目录名称,一个字符串;
parent 父目录,和上面解释一样。

remove_proc_entry()函数:
参数同上面一样。

函数介绍完毕。有这三个函数就可以开工了。废话少说,贴代码:

 代码片段2 自己写的小模块,演示如何建立删除/proc节点

 
  
  1. #include <linux/module.h>  
  2. #include <linux/init.h>  
  3. #include <linux/version.h>  
  4. #include <linux/proc_fs.h>  
  5.  
  6. MODULE_LICENSE("Dual BSD/GPL");  
  7. MODULE_AUTHOR("<freshpassport@gmail.com>");  
  8.  
  9. #define USER_ROOT_DIR "pt"  
  10. #define USER_ENTRY1   "pt_entry1"  
  11.  
  12. // user defined directory  
  13. static struct proc_dir_entry *pt_root;  
  14. static struct proc_dir_entry *pt_entry1;  
  15.  
  16. static int proc_test_init(void)  
  17. {  
  18.   // Create user root dir under /proc  
  19.   pt_root = proc_mkdir(USER_ROOT_DIR, NULL);  
  20.   if (NULL==pt_root)  
  21.   {  
  22.     printk(KERN_ALERT "Create dir /proc/%s error!\n",  
  23.         USER_ROOT_DIR);  
  24.     return -1;  
  25.   }  
  26.   printk(KERN_INFO "Create dir /proc/%s\n", USER_ROOT_DIR);  
  27.  
  28.   // Create a test entry under USER_ROOT_DIR  
  29.   pt_entry1 = create_proc_entry(USER_ENTRY1,  
  30.       0666, pt_root);  
  31.   if (NULL == pt_entry1)  
  32.   {  
  33.     printk(KERN_ALERT "Create entry %s under /proc/%s error!\n",  
  34.         USER_ENTRY1, USER_ROOT_DIR);  
  35.     goto err_out;  
  36.   }  
  37.   printk(KERN_INFO "Create /proc/%s/%s\n",  
  38.       USER_ROOT_DIR, USER_ENTRY1);  
  39.  
  40.   pt_entry1->read_proc = NULL;  
  41.   pt_entry1->write_proc = NULL;  
  42.  
  43.   return 0;  
  44.  
  45. err_out:  
  46.   remove_proc_entry(USER_ROOT_DIR, pt_root);  
  47.   return -1;  
  48. }  
  49.  
  50. static void proc_test_exit(void)  
  51. {  
  52.   // Remove all entries  
  53.   remove_proc_entry(USER_ENTRY1, pt_root);  
  54.   remove_proc_entry(USER_ROOT_DIR, NULL);  
  55.   printk(KERN_INFO "All Proc Entry Removed!\n");  
  56. }  
  57.  
  58. module_init(proc_test_init);  
  59. module_exit(proc_test_exit);  

一个标准的2.6内核模块写法。proc_test_init()函数是入口,proc_test_exit()函数是出口。

最开始引入了4个头文件,其中3个是内核模块标配的头文件,第4个是主角,proc_fs.h,前面说了,操作/proc的函数就定义在里面。

第9行,USER_ROOT_DIR定义了我们要在/proc目录下建立的子目录名称,也是用户的根目录;第10行,USER_ENTRY1定义了欲在USER_ROOT_DIR下面建立的节点名称。

第13、14行分别定义了USER_ROOT_DIR和USER_ENTRY1对应的目录结构pt_root和pt_entry1。

操作的数据介绍好了,说下流程吧。

proc_test_init()函数:

此函数是加载模块的时候执行的入口函数,会被内核自动调用。第19行,函数一开始就调用proc_mkdir()函数,在/proc目录下建立一个子目录,注意proc_mkdir()函数的第二个参数,NULL表示以/proc作为根目录。

子目录USER_ROOT_DIR建立成功后,紧接着调用create_proc_entry()函数建立一个文件节点,注意函数的第三个参数parent,不是NULL,而是pt_root,表示以USER_ROOT_DIR作为根目录。

文件建立成功后,需要设置pt_entry1的读写回调函数为NULL,这样做的目的是该文件节点读写操作都不处理。

proc_test_exit()函数:

用户调用rmmod卸载内核模块的时候被内核调用。此函数删除已经建立的节点,要注意的是remove_proc_entry()函数的parent实参,这里容易出错,导致内核报错。

最后贴一下Makefile:

 
  
  1. obj-m += proc_test1.o  
  2.  
  3. all:  
  4.     make -C /usr/src/linux-headers-2.6.38-8-generic M=`pwd` modules  
  5.  
  6. clean:  
  7.     make -C /usr/src/linux-headers-2.6.38-8-generic M=`pwd` clean 

看看执行的效果:

下面是加载模块后列出文件节点

 
  
  1. root@iscsia:~/proc_test# ll /proc/pt/  
  2. total 0  
  3. -rw-rw-rw- 1 root root 0 2011-07-01 15:04 pt_entry1 

下面是读文件节点/proc/pt/pt_entry1

 
  
  1. root@iscsia:~/proc_test# cat /proc/pt/pt_entry1   
  2. root@iscsia:~/proc_test#  

什么结果都没有,正常,因为回调函数都给设置为NULL了。

下面是写文件节点/proc/pt/pt_entry1

 
  
  1. root@iscsia:~/proc_test# echo "hello,entry" > /proc/pt/pt_entry1  
  2. -bash: echo: write error: Input/output error 

看来写操作不是很理想,报错了,pt_entry1不是很合作啊,也是,前面都给人家设置成NULL了,也不能全怪人家了。哈哈。下次继续介绍如何进行自己的读写操作。