一直以为PROC文件系统很是晦涩难懂,平时仅仅是使用它,不愿意去触碰内核中的具体实现。今天突发奇想,想看看里面究竟是怎么实现的,结果……真是大跌眼镜,没想到里面并不复杂
关于PROC文件系统的功能以及在Linux中的地位就不多说了,在用户空间和内核空间交互的界面也扮演者举足轻重的地位。我们今天就从proc_create函数开始,看看其中的实现。该函数会创建一个PROC entry,用户可以通过对文件系统中的该文件,和内核进行数据的交互。
static inline struct proc_dir_entry *proc_create( const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops) { return proc_create_data(name, mode, parent, proc_fops, NULL); }
简要介绍下参数:
name:名字
mod:模式
parent:父entry,为NULL的话,默认父entry是/proc
struct proc_dir_entry proc_root = {
.low_ino = PROC_ROOT_INO,
.namelen = 5,
.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,
.name = "/proc",
};
proc_fops:操作函数表
函数返回一个proc_dir_entry。可以看到proc_create中直接调用了proc_create_data,而该函数主要完成2个功能1、调用__proc_create完成具体proc_dir_entry的创建。2、调用proc_register把entry注册进系统。
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data) { struct proc_dir_entry *pde; if ((mode & S_IFMT) == 0) mode |= S_IFREG; if (!S_ISREG(mode)) { WARN_ON(1); /* use proc_mkdir() */ return NULL; } if ((mode & S_IALLUGO) == 0) mode |= S_IRUGO; pde = __proc_create(&parent, name, mode, 1); if (!pde) goto out; pde->proc_fops = proc_fops; pde->data = data; if (proc_register(parent, pde) < 0) goto out_free; return pde; out_free: kfree(pde); out: return NULL; }
先看proc_dir_entry的创建,这里通过__proc_create函数,其实该函数内部也很简单,就是为entry分配了空间,并对相关字段进行设置,主要包含name,namelen,mod,nlink等。创建好后,就设置操作函数proc_fops和data。然后就调用proc_register进行注册,
static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp) { struct proc_dir_entry *tmp; int ret; ret = proc_alloc_inum(&dp->low_ino); if (ret) return ret; /*如果是 目录*/ if (S_ISDIR(dp->mode)) { dp->proc_fops = &proc_dir_operations; dp->proc_iops = &proc_dir_inode_operations; dir->nlink++; /*如果是链接*/ } else if (S_ISLNK(dp->mode)) { dp->proc_iops = &proc_link_inode_operations; /*如果是文件*/ } else if (S_ISREG(dp->mode)) { BUG_ON(dp->proc_fops == NULL); dp->proc_iops = &proc_file_inode_operations; } else { WARN_ON(1); return -EINVAL; } spin_lock(&proc_subdir_lock); for (tmp = dir->subdir; tmp; tmp = tmp->next) if (strcmp(tmp->name, dp->name) == 0) { WARN(1, "proc_dir_entry '%s/%s' already registered ", dir->name, dp->name); break; } /*子dir链接成链表,且子dir中含有父dir的指针*/ dp->next = dir->subdir; dp->parent = dir; dir->subdir = dp; spin_unlock(&proc_subdir_lock); return 0; }
函数首先分配一个inode number,然后根据entry的类型对其进行操作函数赋值,主要分为目录、链接、文件。这里我们只关注文件,文件的操作函数一般由用户自己定义,即上面我们设置的ops,这里仅仅是设置inode操作函数表,设置成了全局的proc_file_inode_operations,然后插入到父目录的子文件链表中,注意是头插法。基本结构如下,其中每个子节点都有指向父节点的指针。
其实创建entry的过程就这么简单,由于PROC也是一种文件系统,所以可以和ext2/ext3等文件系统一样,作为一个实体文件系统,通过VFS给用户提供统一的接口。相对于实体的文件系统而言,PROC文件系统要简单的多。因为其不需要管理具体磁盘上的文件,不需要和硬件打交道。正常情况下用户发起文件操作流程为:用户层序->系统调用->VFS层->具体文件系统->磁盘驱动程序。而针对PROC文件系统而言,其不需要和磁盘驱动打交道,最低层的部分就是操作系统各个子模块提供的操作函数表。这个就需要根据不同的模块进行不同的操作了,所以都是某个模块自己通过PROC的接口,向PROC注册内容,针对我们普通用户添加的entry,最低层的操作自然是我们注册的ops函数表了。
以马内利
参考资料:
LInux内核3.10.1源码