一、proc文件系统
proc文件系统是一种无存储的文件系统,当读其中的文件时,其内容动态生成,当写文件时,文件所关联的写函数被调用。每个proc文件都关联的字节特定的读写函数,因而它提供了另外的一种和内核通信的机制:内核部件可以通过该文件系统向用户空间提供接口来提供查询信息、修改软件行为,因而它是一种比较重要的特殊文件系统。
1.1 proc文件系统的内容
由于proc文件系统以文件的形式向用户空间提供了访问接口,这些接口可以用于在运行时获取相关部件的信息或者修改部件的行为,因而它是非常方便的一个接口。内核中大量使用了该文件系统。proc文件系统就是一个文件系统,它可以挂载在目录树的任意位置,不过通常挂载在/proc下,它大致包含了如下信息:
- 内存管理
- 每个进程的相关信息
- 文件系统
- 设备驱动程序
- 系统总线
- 电源管理
- 终端
- 系统控制参数
- 网络
这些信息几乎涵盖内核的所有部分,因而改文件系统是了解系统信息的一个很重要的部件。由于proc文件系统的文件的内容取决于实现该文件的内核部件,因而各个文件的实现可能存在很大的不同。
由于proc文件系统是以文件的形式存在的,因而我们可以通过cat,echo等命令来直接访问文件(当然是在遵循文件访问权限的前提下)
比如我们可以通过“cat /proc/cmdline ”查看系统的启动命令
BOOT_IMAGE=/vmlinuz-3.5.0-40-generic root=UUID=80730516-33e7-420e-ae95-7d82bdfadb25 ro quiet splash vt.handoff=7
可以通过“cat /proc/2629/io ”查看特定进程的io信息
rchar: 6906149866
wchar: 14016909787
syscr: 5354112
syscw: 10764514
read_bytes: 7827566592
write_bytes: 14870413312
cancelled_write_bytes: 4096
可以通过“echo 7> /proc/sys/kernel/printk”来修改内核的log等级
需要说明的是,虽然使用/proc很方便,但是在linux内核开发中,已经不建议使用它了, 原因在于它的内容太庞杂了,很难管理和理解。
1.2 proc文件系统的初始化
使用proc文件系统之前必须将其初始化并且挂载到系统中。proc文件系统的的初始化主要完成:
- 调用proc_init_inodecache创建proc文件系统所使用的专用缓冲区
- 调用register_filesystem注册proc文件系统,这里会提供proc文件系统自己的file_system_type,其中包括了用于mount的函数指针。在执行mount的时候会用到这些信息,并最终找到mount函数进行挂载操作
- 调用proc_mkdir创建一些proc文件目录
- 在sys文件系统下注册proc文件系统的相关信息
在proc的mount函数中会调用proc_fill_super,它会给出proc文件系统超级块所需要的信息(比如文件系统的超级块操作函数指针,超级块大小等),并且会创建proc文件系统的根目录,在创建根目录时也会指定与之对应的inode_operations和file_operations,有了这些信息后,VFS就可以在该文件系统上进行各种操作了(创建、删除、查找文件)。
1.3 proc文件系统数据结构
proc文件及目录在内核中用proc_dir_entry来表示。它在proc文件系统内部包含了proc文件的所有信息。其数据结构如下所示:
[cpp] view plain copy print ?
- struct proc_dir_entry {
- unsigned int low_ino;
- umode_t mode;
- nlink_t nlink;
- kuid_t uid;
- kgid_t gid;
- loff_t size;
- const struct inode_operations *proc_iops;
- const struct file_operations *proc_fops;
- struct proc_dir_entry *next, *parent, *subdir;
- void *data;
- read_proc_t *read_proc;
- write_proc_t *write_proc;
- atomic_t count;
- int pde_users;
- struct completion *pde_unload_completion;
- struct list_head pde_openers;
- spinlock_t pde_unload_lock;
- u8 namelen;
- char name[];
- };
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;//inode操作
const struct file_operations *proc_fops;//文件操作
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;
write_proc_t *write_proc;
atomic_t count;
int pde_users;
struct completion *pde_unload_completion;
struct list_head pde_openers;
spinlock_t pde_unload_lock;
u8 namelen;
char name[];
};
内核还提供了一个数据结构proc_inode用于将特定于proc的数据与文件所对应的inode关联起来,其定义如下:
[cpp] view plain copy print ?
- struct proc_inode {
- struct pid *pid;
- int fd;
- union proc_op op;
- struct proc_dir_entry *pde;
- struct ctl_table_header *sysctl;
- struct ctl_table *sysctl_entry;
- void *ns;
- const struct proc_ns_operations *ns_ops;
- struct inode vfs_inode;
- };
struct proc_inode {
struct pid *pid;
int fd;
union proc_op op;
struct proc_dir_entry *pde;
struct ctl_table_header *sysctl;
struct ctl_table *sysctl_entry;
void *ns;
const struct proc_ns_operations *ns_ops;
struct inode vfs_inode;
};
借助该数据结构,内核可以方便的在inode和与该inode相关的proc数据之间进行转换。
1.4 proc文件系统API
内核为创建proc文件提供了一套API,相关API如下:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
该函数用于在proc文件系统中创建一个目录项,大多数时候,当我们期望实现自己的proc文件时,都要先创建一个自己的目录,然后在该目录里创建自己的文件,当然我们也可以直接在已经存在的proc文件系统目录里创建自己的文件。
该函数的各个参数含义如下:
- name:该目录的名字
- parent:该目录的父目录的名字
void proc_remove(struct proc_dir_entry *de);
该函数用于删除一个目录。
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops);
该函数用于在proc文件系统中创建一个proc文件,其参数含义如下:
- name:proc文件的名字,表现在proc文件系统中,就是文件的名字
- mode:proc文件的访问模式,表现在proc文件系统中,就是文件的访问模式
- parent:该proc文件所在的目录
- proc_fops:指向用于操作该文件的文件操作的指针
这些参数提供了一个文件所需要的关键信息,包括文件名,访问模式,目录项(用于指定文件在文件系统中的位置),以及文件操作指针。
当要删除一个文件时,需要使用API:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
参数的含义是显然的,该函数用于从proc文件系统的指定目录删除指定的proc文件。还有一些其它的API,都定义在include/linux/prof_fs.h中。
在proc文件系统中创建文件或者目录时,最终都会调到proc_register,该函数会为新创建的文件或目录指定正确的file_operations和inode_operations,它们将在访问文件时被使用。
1.5 文件操作过程
文件操作的过程基本都是类似的,首先VFS会找到文件对应的inode,然后调用inode上的相关函数完成对应的操作,比如当读写一个文件时,要执行的步骤:
- 首先要打开文件,对于proc文件系统来说,它要做的是:找到文件的,在找到文件后文件数据结构的的file_operations会被初始化为文件的inode中的file_operations(参考路径do_filp_open->path_openat->do_last->nameidata_to_filp->__dentry_open->fops_get)。之后就可以调用文件的打开操作了。
- 写文件,这一步也很简单, 文件数据结构中的写操作即可。
这个过程也很好理解, 因为对于任何文件系统,它们都存在inode,inode被用来表示一个文件,它包含了很多有用的信息,它用于维护文件自身而不涉及文件的内容。对于存储在存储器上的文件系统,其inode的有些信息是保存在存储器上的,但是仍有一部分是动态生成的;对于无存储器的文件系统,比如proc文件系统,其inode都是动态生成的。再使用一个文件时,其inode就会被加载到内存中以供使用,如果还不存在相关的inode,则就会创建一个新的。每个文件系统的超级块的super_operations包含了为本文件系统创建和删除inode节点的函数指针,在inode的操作函数集中包含了操作inode节点的函数指针,其中包括了查找inode节点的函数。打开一个文件时,如果inode缓冲中还没有该文件的inode,则经VFS处理后最终会调用lookup_real,它会调用inode操作函数集中的lookup函数用于查找所要打开的文件的inode;如果该文件的inode已经存在则会从缓存中得到该文件对应的inode,这部分工作由do_lookup完成。
简单的说,文件操作第一步时找到inode信息(如果没有就创建并初始化),然后用inode信息初始化文件的file结构。再用file结构对文件进行操作。
1.6 /proc/sys目录举例
对于/proc/sys目录,该目录在proc_sys_init中被创建:
[cpp] view plain copy print ?
- int __init proc_sys_init(void)
- {
- struct proc_dir_entry *proc_sys_root;
- proc_sys_root = proc_mkdir("sys", NULL);
- proc_sys_root->proc_iops = &proc_sys_dir_operations;
- proc_sys_root->proc_fops = &proc_sys_dir_file_operations;
- proc_sys_root->nlink = 0;
- return 0;
- }
int __init proc_sys_init(void)
{
struct proc_dir_entry *proc_sys_root;
proc_sys_root = proc_mkdir("sys", NULL);
proc_sys_root->proc_iops = &proc_sys_dir_operations;
proc_sys_root->proc_fops = &proc_sys_dir_file_operations;
proc_sys_root->nlink = 0;
return 0;
}
然后在使用该目录下的文件时,需要先找到该文件对应的inode,使用inode_operations中的lookup函数即proc_sys_lookup:
[cpp] view plain copy print ?
- static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
- struct nameidata *nd)
- {
- struct ctl_table_header *head = grab_header(dir);
static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
struct nameidata *nd)
{
struct ctl_table_header *head = grab_header(dir);
[cpp] view plain copy print ?
- static struct ctl_table_header *grab_header(struct inode *inode)
- {
- struct ctl_table_header *head = PROC_I(inode)->sysctl;
- if (!head)
- head = &sysctl_table_root.default_set.dir.header;
- return sysctl_head_grab(head);
- }
static struct ctl_table_header *grab_header(struct inode *inode)
{
struct ctl_table_header *head = PROC_I(inode)->sysctl;
if (!head)
head = &sysctl_table_root.default_set.dir.header;
return sysctl_head_grab(head);
}
而sysctl_table_root即为/proc/sys对应的ctl_table_root结构,它保存了/proc/sys下内容的入口信息,register_sysctl_paths用于向/proc/sys添加内容,/proc/sys/net等等就是通过该调用添加到/proc/sys下的,然后通过lookup函数即可找到起对应的内容(比如ctl_table_header)。