您的位置:首页 > 理论基础 > 数据结构算法

proc源码解析-proc文件系统的内容--proc数据结构--proc初始化

2010-12-26 22:41 573 查看
在linux系统中,伪文件系统是系统中重要的组成部分,它与普通文件的最大差别在于它的易失性。普通文件都有固定的存储介质,而伪文件系统则存储在内存中,这也使得伪文件系统在系统掉电后,它保存的内容就会挥发掉。
Linux系统中存在以下几类伪文件系统:
1.procfs:proc提供内核和用户交互的平台,由于其文件格式是ASCII,所以用户可以使用cat,vim,echo等命令查看或修改文件,当然必须在root用户下才有相应权限。
2.sysfs:与procfs类似,由于出现的比较晚,所以它克服了proc的一些缺点,比proc功能更强大。但是由于其文件格式是二进制形式,所以需要特殊工具。该文件系统主要为开发人员提供。
3.其它伪文件系统如:libfs,debugfs等。
在这些文件系统中,proc出现比较早,其实现也很典型。proc最初的设计目的是提供内核和用户交互的平台,使用户可以动态的管理系统,同时获取系统的运行时的信息。在proc中主要的信息便是系统进程信息。
本节主要说明proc的实现。
一.proc文件系统的内容
proc中主要的文件包含以下几类:
1.进程相关的目录
在proc下以数字命名的目录便是每个进程对应的目录,这些目录下存储着各个进程的信息。可以使用cat命令查看各个文件中保存的进程信息。例如/proc/0/下存储着系统初始化init进程的信息。
root@xuhengyang:/proc/1# ls -l
总用量 0
dr-xr-xr-x 2 root root 0 2009-11-19 20:09 attr
-r-------- 1 root root 0 2009-11-19 20:09 auxv
-r--r--r-- 1 root root 0 2009-11-19 20:09 cgroup
--w------- 1 root root 0 2009-11-19 20:09 clear_refs
-r--r--r-- 1 root root 0 2009-11-19 03:56 cmdline
-rw-r--r-- 1 root root 0 2009-11-19 20:09 coredump_filter
-r--r--r-- 1 root root 0 2009-11-19 20:09 cpuset
lrwxrwxrwx 1 root root 0 2009-11-19 20:09 cwd -> /
-r-------- 1 root root 0 2009-11-19 20:09 environ
。。。
root@xuhengyang:/proc/1# cat cmdline
/sbin/init
2.通用系统信息
通用系统信息主要包括内存管理信息,文件系统信息,设备驱动信息,系统总线信息,电源管理信息等。每种信息都有对应文件存在。例如:
root@xuhengyang:/proc# cat meminfo
MemTotal: 3059316 kB
MemFree: 702464 kB
Buffers: 444540 kB
Cached: 1183516 kB
SwapCached: 0 kB
Active: 1470404 kB
。。。
每种信息都有对应的文件名,此处就不一一说明了。
3.网络信息
/proc/net是一个符号链接,它指向当前进程的/self/net/目录,该目录下保存着当前进程的网络管理信息。
root@xuhengyang:/proc# ls -l net
lrwxrwxrwx 1 root root 8 2009-11-19 20:23 net -> self/net
4.系统控制信息
系统控制参数用来检测修改系统的运行参数。它存在与/proc/sys下。用户可以使用cat,echo来查看或修改系统的运行参数。例如:
root@xuhengyang:/proc/sys# ls -l
总用量 0
dr-xr-xr-x 0 root root 0 2009-11-19 10:33 crypto
dr-xr-xr-x 0 root root 0 2009-11-19 10:33 debug
dr-xr-xr-x 0 root root 0 2009-11-19 10:33 dev
dr-xr-xr-x 0 root root 0 2009-11-18 19:57 fs
dr-xr-xr-x 0 root root 0 2009-11-19 03:56 kernel
dr-xr-xr-x 0 root root 0 2009-11-18 19:57 net
dr-xr-xr-x 0 root root 0 2009-11-19 10:33 vm
swappiness的值的大小对如何使用swap分区是有着很大的联系的。swappiness=0的时候表示最大限度使用物理内存,然后才是 swap空间,swappiness=100的时候表示积极的使用swap分区,并且把内存上的数据及时的搬运到swap空间里面。此处可以使用echo 来修改该值,但是这种修改只是临时的,系统重启后还会恢复到60,要永久修改
root@xuhengyang:/proc/sys# cat vm/swappiness
60
root@xuhengyang:/proc/sys# echo "10" >vm/swappiness
root@xuhengyang:/proc/sys# cat vm/swappiness
10
但是这只是临时性的修改,在你重启系统后会恢复默认的60,所以,还要做一步:
$ sudo gedit /etc/sysctl.conf
在这个文档的最后加上这样一行:
vm.swappiness=10
然后保存,重启。ok,你的设置就生效了。

上节主要说明了proc下的内容,从本节开始解析proc的实现。与普通文件系统一样,proc当然需要虚拟文件系统的支持,所以它必须具备文件系统的几个主要的数据结构。

1.proc_dir_entry
在proc文件系统中,每个entry的实例是由proc_dir_entry来描述的,其结构如下:

<proc_fs.h>

struct proc_dir_entry {

unsigned int low_ino;	//inode号

unsigned short namelen;

const char *name;

mode_t mode;

nlink_t nlink;//子目录和软链接的数目

uid_t uid;

gid_t gid;

loff_t size;

const struct inode_operations *proc_iops;

const struct file_operations *proc_fops;

get_info_t *get_info;

struct module *owner;

struct proc_dir_entry *next, *parent, *subdir;

void *data;

read_proc_t *read_proc;

write_proc_t *write_proc;

atomic_t count; /* use count */

int deleted; /* delete flag */

kdev_t rdev;

};


大部分元素根据其名字就知道表达的意思。需要注意的get_info,read_proc,write_proc三个接口。
get_info:当用户向proc文件读取的数据小于一个页面大小时,可以使用这个函数向用户返回数据。
read_proc_t *read_proc 和write_proc_t *write_proc:这两个函数提供了对proc文件进行操作的简单接口。我们知道,对于proc文件,我们可以从中读取核心数据,还可以向其中写入数据,因此,对于一些功能比较简单的proc文件,我们只要实现这两个函数(或其中之一)即可,而不用设置inode_operations结构,这样, 整个操作比较简单。实际上,我们会在后面的分析中看到,在注册proc文件的时候,会自动为proc_fops设置一个缺省的 file_operations结构,如果我们只实现了上面提到的两个读写操作,而没有设置自己file_operations结构,那么,会由缺省的 inode_operations结构中的读写函数检查调用这两个函数。

2.proc inodes
内核中提供了一个名为proc_inode的结构来描述proc文件系统的inode结构。该结构如下:

<proc_fs.h>
union proc_op {
int (*proc_get_link)(struct inode *, struct dentry **,
struct vfsmount **);
int (*proc_read)(struct task_struct *task, char *page);
};
struct proc_inode {
struct pid *pid;
int fd;
union proc_op op;
struct proc_dir_entry *pde;
struct inode vfs_inode;
};

proc_inode的作用是建立proc的数据与VFS层之间的联系。
在该结构中,有几个比较诡异的地方,首先是最后一个域是struct inode vfs_inode,
一般情况下一个结构要建立与另外一个结构的链接,只需一个指向另外一个结构的实例的指针,而此处确实一个结构实例。也就是说proc_inode结构的一个实例形式如下:



在linux中可以使用container机制获取proc_inode结构,这是由一个函数完成的。

<proc_fs.h>
static inline struct proc_inode *PROC_I(const struct inode *inode)
{
return container_of(inode, struct proc_inode, vfs_inode);
}

proc_get_link 和 proc_read函数在一个union的原因是,在同一时间内只可能使用一个函数。proc_read用来获取进程相关的信息,proc_get_link用来创建指向VFS文件系统中与进程相关的数据的链接。
以上元素的用法和具体含义到后边与进程相关的信息一节还会详细解释。

在使用proc之前,我们必须首先初始化并挂载proc,并在内核内存中创建数据结构来描述文件系统。但是,不同的体系结构拥有不同的proc内容,所以,在初始化阶段并不完全创建子目录的内容,有些文件要等到系统运行时动态创建。Proc文件系统初始化的流程图如下:



proc_root_init的定义定义如下:

void __init proc_root_init(void)
{
int err = proc_init_inodecache();
err = register_filesystem(&proc_fs_type);
。。。
proc_mnt = kern_mount_data(&proc_fs_type, &init_pid_ns);
proc_misc_init();
proc_net_init();
。。。
proc_root_fs = proc_mkdir("fs", NULL);
proc_root_driver = proc_mkdir("driver", NULL);
proc_mkdir("fs/nfsd", NULL);
。。。
proc_bus = proc_mkdir("bus", NULL);
proc_sys_init();



int __init proc_init_inodecache(void):为proc_inode创建slab cache,这是proc文件系统的主要部分,通常需要快速创建或销毁。
int register_filesystem(struct file_system_type * fs):注册文件系统类型proc,这个过程是文件系统初始化的关键步骤,下边会专门解释这个步骤。

struct vfsmount *kern_mount_data(struct file_system_type *type, void *data)
{
return vfs_kern_mount(type, MS_KERNMOUNT, type->name, data);
}

vfs_kern_mount:创建与proc_fs_type相关的vfsmount结构,该函数在VFS中已有讲述。
proc_misc_init:创建/proc目录下的文件和子目录,每个文件的创建都由相应的函数来完成,例如:meminfo的创建由 create_proc_read_entry 创建,而meminfo_read_proc则用于初始化meminfo的read_proc函数。其实际的调用过程是:

void __init proc_misc_init(void)
{
static struct {
char *name;
int (*read_proc)(char*,char**,off_t,int,int*,void*);
} *p, simple_ones[] = {
{"loadavg", loadavg_read_proc},
{"uptime", uptime_read_proc},
{"meminfo", meminfo_read_proc},
{"version", version_read_proc},
。。。
{"filesystems", filesystems_read_proc},
{"cmdline", cmdline_read_proc},
{"execdomains", execdomains_read_proc},
{NULL,}
};
for (p = simple_ones; p->name; p++)
create_proc_read_entry(p->name, 0, NULL, p->read_proc, NULL);

proc_symlink("mounts", NULL, "self/mounts");
create_seq_entry("locks", 0, &proc_locks_operations);
create_seq_entry("devices", 0, &proc_devinfo_operations);
create_seq_entry("cpuinfo", 0, &proc_cpuinfo_operations);
。。。
}

另外一些文件是用create_seq_entry来调用创建函数。
create_seq_entry("locks", 0, &proc_locks_operations);
这两个函数 create_proc_read_entry与create_seq_entry的不同之处是前者创建的是只读文件,后者创建的是可读可写文件。
proc_net_init:创建大量与网络相关的文件,创建方法与前者相似,所以此处不在赘述。
以上步骤完成之后,初始化需要创建的文件都已完成,下边要调用proc_mkdir来创建子目录。
实际上,函数最后还调用proc_sys_init();来完成sys子目录的创建。

register_filesystem的实现
在proc_root_init函数中,调用该函数的格式是:

static struct file_system_type proc_fs_type = {

.name = "proc",
.get_sb = proc_get_sb,
.kill_sb = proc_kill_sb,
};

void __init proc_root_init(void)
{
。。。
err = register_filesystem(&proc_fs_type);
if (err)
return;
。。。
}

也是就调用register_filesystem函数把描述proc文件类型的结构 proc_fs_type注册到系统中,其中register_filesystem函数函数实现很简单,这里主要关注 proc_get_sb函数。该函数的主要功能是获取proc的super_block。其调用流程图如下:



proc_get_sb中主要的执行函数是proc_fill_super;该函数的主要执行过程如下,为了方便理解,我删去了"多余部分",只留下程序骨干。

int proc_fill_super(struct super_block *s)
{
struct inode * root_inode;
/* 初始化super block的元素*/
s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
s->s_magic = PROC_SUPER_MAGIC;
s->s_op = &proc_sops;
s->s_time_gran = 1; //初始化时间精度1ns

/* 获得root_inode*/
de_get(&proc_root);
root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);
。。。
/* 二者初始化为0说明proc只能是root用户的*/
root_inode->i_uid = 0;

root_inode->i_gid = 0;

s->s_root = d_alloc_root(root_inode);
。。。
}

第一个阶段: 初始化super block的元素,唯一值得注意的是s->s_op = &proc_sops;
这是初始化super block的struct super_operations *s_op;其中函数集是:

static const struct super_operations proc_sops = {
.alloc_inode = proc_alloc_inode,
.destroy_inode = proc_destroy_inode,
.read_inode = proc_read_inode,
.drop_inode = generic_delete_inode,
.delete_inode = proc_delete_inode,
.statfs = simple_statfs,
.remount_fs = proc_remount,
};

第二个阶段 : proc_get_inode函数用来获得proc的inode,其主要的实现过程如下,为了便于理解我删去了部分代码:

struct inode *proc_get_inode(struct super_block *sb, unsigned int ino,
struct proc_dir_entry *de)
{
struct inode * inode;
inode = iget(sb, ino);
if (de) {
/* 使用proc_root的mode初始化,此处的mode和虚拟文件系统的一样 */
if (de->mode) {
inode->i_mode = de->mode;
inode->i_uid = de->uid;
inode->i_gid = de->gid;
}
/* 使用proc_root的各个域初始化inode */
if (de->size)
inode->i_size = de->size;
if (de->nlink)
inode->i_nlink = de->nlink;
if (de->proc_iops)
inode->i_op = de->proc_iops;//proc_root的inode操作函数
if (de->proc_fops) {
if (S_ISREG(inode->i_mode)) {
inode->i_fop = &proc_reg_file_ops;
}
else
inode->i_fop = de->proc_fops; //proc_root的file操作函数
}
}
}

红色部分说明如果是普通文件使用 proc_reg_file_ops 初始化,其定义为:

static const struct file_operations proc_reg_file_ops = {
.llseek = proc_reg_llseek,
.read = proc_reg_read,
.write = proc_reg_write,
.poll = proc_reg_poll,
.unlocked_ioctl = proc_reg_unlocked_ioctl,
.mmap = proc_reg_mmap,
.open = proc_reg_open,
.release = proc_reg_release,
};

proc_fill_super在调用该函数时(proc_get_inode(s, PROC_ROOT_INO, &proc_root))传入的参数为proc_root,这是一个全局变量,是proc的根目录的dentry,其定义为:

static const struct file_operations proc_root_operations = {
.read = generic_read_dir,
.readdir = proc_root_readdir,
};
static const struct inode_operations proc_root_inode_operations = {
.lookup = proc_root_lookup,
.getattr = proc_root_getattr,
};
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,

};

在 proc_get_inode函数的初始化过程中,大部分的域都是用proc_root中相应的域来初始化,这也正好将proc的dentry和VFS层的dentry和inode对接起来。各个域的操作函数集合此处就不在深入,有需要的朋友可以查阅源码。

第三个阶段: 调用 d_alloc_root创建proc根结点的dentry,该函数比较短,此处引用它的定义:

/**
* d_alloc_root - allocate root dentry
* @root_inode: inode to allocate the root for
* Allocate a root ("/") dentry for the inode given. The inode is
* instantiated and returned. %NULL is returned if there is insufficient
* memory or the inode passed is %NULL.
*/
struct dentry * d_alloc_root(struct inode * root_inode)
{
struct dentry *res = NULL;

if (root_inode) {
static const struct qstr name = { .name = "/", .len = 1 };

res = d_alloc(NULL, &name);//allocate a dcache entry
if (res) {
res->d_sb = root_inode->i_sb;
res->d_parent = res;
d_instantiate(res, root_inode);//初始化dentry res
}
}
return res;
}

到此为止proc的初始化过程已经完成,此时,/proc目录下除了要动态创建的文件和子目录外,初始化过程中应该创建的文件和子目录都已创建完毕,接下来就需要挂载proc文件系统到目录树上,这是下一节要讲的内容。

挂载proc文件系统和挂载普通文件系统如ext2没有区别,其挂载过程会在VFS文件系统时详细描述,此处就不在多费唇舌了。

proc的挂载命令是:
root@xuhengyang # mount -t proc proc /proc
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: