前面讲了proc文件系统的编程,其实在使用API的时候注意一下proc文件系统的实现也是很有益处的。在学习使用proc的时候我顺便看了一下proc的底层实现,发现原理非常简单,毕竟是一个基于内存的文件系统。
首先众所周知,所有的文件系统都位于VFS的层,proc也不例外。在内核启动的时,start_kernel在完成了VFS的初始化不久就调用了:
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
用于初始化proc文件系统:
fs/proc/root.c
void __init proc_root_init(void)
{
struct vfsmount *mnt;
int err;
proc_init_inodecache();
err = register_filesystem(&proc_fs_type); //向VFS注册procfs
if (err)
return;
mnt = kern_mount_data(&proc_fs_type, &init_pid_ns);
if (IS_ERR(mnt)) {
unregister_filesystem(&proc_fs_type);
return;
}
init_pid_ns.proc_mnt = mnt;
proc_symlink("mounts", NULL, "self/mounts"); //创建“mounts”符号连接文件
proc_net_init(); //创建“net”符号连接及内部目录树结构
#ifdef CONFIG_SYSVIPC
proc_mkdir("sysvipc", NULL); //创建“sysvipc”目录
#endif
proc_mkdir("fs", NULL); //创建“fs”目录
proc_mkdir("driver", NULL); //创建“driver”目录
proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */
#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)
/* just give it a mountpoint */
proc_mkdir("openprom", NULL);
#endif
proc_tty_init(); //创建“tty”目录及内部结构
#ifdef CONFIG_PROC_DEVICETREE
proc_device_tree_init();
#endif
proc_mkdir("bus", NULL); //创建“bus”目录
proc_sys_init(); //创建“sys”目录并初始化
}
在这之后,许多内核组件和模块就可以向proc文件系统及其子目录添加目录和文件了。其中最重要的数据结构体就是:struct proc_dir_entry,她在《内核proc文件系统与seq接口(2)---内核proc文件系统编程接口》介绍过。她相当于proc文件系统中的数据节点,procfs中许多文件、符号连接和目录文件都是由她表示的。但是请注意,并不是左右的proc文件都对应这样的数据结构,对于/proc/sys、/proc/${PID}/等特定目录下的文件时需要时动态生成的,有其自己的内部实现。而这个proc_dir_entry只对普通的proc文件实现,及我们自己创建的proc文件。
其根目录的表示:
fs/proc/root.c
/*
* This is the root "inode" in the /proc tree..
*/
struct proc_dir_entry proc_root = {
.low_ino = PROC_ROOT_INO,
.namelen = 5,
.name = "/proc",
.mode = S_IFDIR | S_IRUGO | S_IXUGO,
.nlink = 2,
.count = ATOMIC_INIT(1),
.proc_iops = &proc_root_inode_operations,
.proc_fops = &proc_root_operations,
.parent = &proc_root,
};
除了这个节点的结构体在代码中静态定义的之外,其他所有的节点都是系统和模块初始化产生的。也除了这个节点的mane字符串是在内核映像的只读数据段中,其他的节点的name字符串都是在创建的时候紧跟在其struct proc_dir_entry之后的,如下图:
而看了源码(fs/proc/generic.c)你就会很快的了解这个文件系统的数据连接方式:用*next指针通过单向链表连接同目录下文件;用*parent指针指向父目录的proc_dir_entry结构体;用*subdir指针指向本目录中的文件(如果这个文件目录的话),总体底层结构示意图如下所示:
上图只是一个结构示意图,一些指针的指向不确定的,比如proc_root的*subdir肯定不是指向sys,因为初始化之后还有很多目录和文件要产生。但是/root目录下单向链表的最后一个应该是mounts,因为他是最早注册的(起码在3.0上是这样)。上图同时示意了符号连接注册后的一般情况。
对于一个目录注册后的一般如下,主要是注意*proc_iops inode操作函数和*proc_fops 文件操作函数的注册:
在《内核proc文件系统与seq接口(2)---内核proc文件系统编程接口》中曾经介绍过,对于创建 /proc 入口,有两种选择:
(1)使用proc的默认操作函数集proc_file_operations,但必须实现回调函数*read_proc和*write_proc,示意图如下:
(2)使用自己实现的file_operations,以下用/proc/config.gz文件的实现做示意:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
以上的解析只是对procfs的浅析,有兴趣的朋友可以自己看看fs/proc/generic.c,非常简单的文件系统实现,是一个很好的学习样本。RTFSC – Read The Fucking Source Code。