Linux文件系统之proc文件系统

来源:互联网 发布:linux cp 所有文件 编辑:程序博客网 时间:2024/05/16 01:41
----------------------------------------------- #纯属个人理解,如有问题敬请谅解!#kernel version: 2.6.26#Author: andy wang-------------------------------------------------一: 概述      Proc文件系统是一个虚拟文件系统, 它是一个非常有用的文件系统, 用户通过它可以查看内核信息,修改内核设置的机制, 是一种用户空间和内核空间通讯的一种方法. Proc下的文件都是虚拟文件;是系统动态创建的. 本文将分析proc文件系统是如何实现 ,其实只要有了VFS知识以后再分析procfs还是比较简单的.二: procfs初始化既然procfs是一个文件系统, 那么我们首先要将它注册到内核中, 如果要使用它,还的需要安装此文件系统. 下面我们就从profs 的初始化入手吧.如果CONFIG_PROC_FS编译选项打开以后,kernel在启动时就会调用proc_root_init()初始化procfs了.先看看初始化的代码:void __init proc_root_init(void){         int err = proc_init_inodecache();         if (err)                   return;         err = register_filesystem(&proc_fs_type); //注册proc文件系统         if (err)                   return;         proc_mnt = kern_mount_data(&proc_fs_type, &init_pid_ns); //挂载proc文件系统         err = PTR_ERR(proc_mnt);         if (IS_ERR(proc_mnt)) {                   unregister_filesystem(&proc_fs_type);                   return;         } …………….. }首先是注册proc文件系统register_filesystem(),先看看procfs定义:static struct file_system_type proc_fs_type = {         .name                   = "proc",         .get_sb                 = proc_get_sb,         .kill_sb       = proc_kill_sb,};接下来调用kern_mount_data()挂载procfs ,这个函数的流程已经在挂载rootfs文章中介绍了.我们需要关注的就是在挂载过程中 proc_get_sb()这个回调函数的实现过程.下面是proc_get_sb()的代码片段:static int proc_get_sb(struct file_system_type *fs_type,         int flags, const char *dev_name, void *data, struct vfsmount *mnt){         int err;         struct super_block *sb;         struct pid_namespace *ns;         struct proc_inode *ei;          if (proc_mnt) {                   ei = PROC_I(proc_mnt->mnt_sb->s_root->d_inode);                   if (!ei->pid)                            ei->pid = find_get_pid(1);         }          if (flags & MS_KERNMOUNT)                   ns = (struct pid_namespace *)data;         else                   ns = current->nsproxy->pid_ns;          sb = sget(fs_type, proc_test_super, proc_set_super, ns); //获取procfs超级块         if (IS_ERR(sb))                   return PTR_ERR(sb);          if (!sb->s_root) {    //判断是否建立procfs根目录                   sb->s_flags = flags;                   err = proc_fill_super(sb);                   if (err) {                            up_write(&sb->s_umount);                            deactivate_super(sb);                            return err;                   }                    ei = PROC_I(sb->s_root->d_inode);                   if (!ei->pid) {                            rcu_read_lock();                            ei->pid = get_pid(find_pid_ns(1, ns));                            rcu_read_unlock();                   }                    sb->s_flags |= MS_ACTIVE;                   ns->proc_mnt = mnt;         }          return simple_set_mnt(mnt, sb);}在这个函数中首先是为procfs分配一个超级块 ,然后填充这个超级块super , 这是一个比较重要的函数,因为在这个函数中会建立proc文件系统的根目录.下面跟踪一下代码:int proc_fill_super(struct super_block *s){         struct inode * root_inode;          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;                  de_get(&proc_root);         root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root); //分配procfs根目录索引节点         if (!root_inode)                   goto out_no_root;         root_inode->i_uid = 0;         root_inode->i_gid = 0;         s->s_root = d_alloc_root(root_inode);  //分配procfs根目录的目录项对象         if (!s->s_root)                   goto out_no_root;         return 0; out_no_root:         printk("proc_read_super: get root inode failed\n");         iput(root_inode);         de_put(&proc_root);         return -ENOMEM;}继续跟踪procfs根目录索引节点实现函数:首先需要看看proc_root的定义struct proc_dir_entry proc_root = {         .low_ino     = PROC_ROOT_INO,  //inode编号         .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,    //父对象};这个结构记录了procfs根目录的信息, 在后面建立procfs根索引节点时会利用它初始化根inode . 下面看看根目录索引节点是如何建立并初始化的:struct inode *proc_get_inode(struct super_block *sb, unsigned int ino,                                     struct proc_dir_entry *de){         struct inode * inode;          if (!try_module_get(de->owner))                   goto out_mod;          inode = iget_locked(sb, ino);  //在索引节点高速缓存中分配一个inode         if (!inode)                   goto out_ino;         if (inode->i_state & I_NEW) {                   PROC_I(inode)->fd = 0;                   PROC_I(inode)->pde = de;                   if (de->proc_iops)                            inode->i_op = de->proc_iops;  //初始化索引节点操作方法                   if (de->proc_fops) {                            if (S_ISREG(inode->i_mode)) {……….                                                inode->i_fop = &proc_reg_file_ops;  //procfs普通文件的操作方法                            } else {                                     inode->i_fop = de->proc_fops;   //非普通文件的操作方法                            }                   }                   unlock_new_inode(inode);         } else                module_put(de->owner);         return inode; out_ino:         module_put(de->owner);out_mod:         return NULL;}       函数iget_locked()就是分配一个inode, procfs分配inode是比较特殊的. 先看看在alloc_inode()函数中的这段代码:if (sb->s_op->alloc_inode)         inode = sb->s_op->alloc_inode(sb);else         inode = (struct inode *) kmem_cache_alloc(inode_cachep, GFP_KERNEL);                   procfs定义的超级块操作方法为:static const struct super_operations proc_sops = {                 .alloc_inode = proc_alloc_inode,                 .destroy_inode     = proc_destroy_inode,                 .drop_inode = generic_delete_inode,                 .delete_inode        = proc_delete_inode,                 .statfs          = simple_statfs,                 .remount_fs = proc_remount,};在这个结构体中可以看到定义了proc分配inode的方法 ,此函数为proc_alloc_inode()跟踪一下代码:static struct inode *proc_alloc_inode(struct super_block *sb){                 struct proc_inode *ei;                 struct inode *inode;                  ei = (struct proc_inode *)kmem_cache_alloc(proc_inode_cachep, GFP_KERNEL);                 if (!ei)                   return NULL;                 ei->pid = NULL;                 ei->fd = 0;                 ei->op.proc_get_link = NULL;                 ei->pde = NULL;                 inode = &ei->vfs_inode;                 inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;                 return inode;}在这个函数中先是在cache中分配一个proc_inode结构体. 然后初始化它,最后返回proc_inode中嵌套的inode.下面再回到proc_get_inode()函数中,在分配到procfs根目录的inode后就需要初始化这个inode了, 回到函数proc_get_inode(),注意函数PROC_I(inode)->pde = de;就是将proc_dir_entry对象与inode关联在一起(inode和proc_dir_entry是一一对应的) ,在建立proc文件后proc_dir_entry对象会在内存中建立起一颗目录树, 因为我们现在建立的是procfs根目录的inode所以这里的proc_dir_entry对象就是这个树的根proc_root .后面的代码就是利用proc_dir_entry对象初始化我们根目录的inode了,其中就包括最重要的inode操作方法和proc文件操作方法.的初始化. 既然根目录索引节点inode已经建好, 下面就是调用函数d_alloc_root() 建立一个名字为”/”的根目录项对象dentry,,这个就是procfs的根了.好了,这个时候procfs的根已经在内存中建立起来了. 其实在这个时候内存中只有根目录的inode和dentry存在,而其他文件的inode和dentry都是动态建立起来的.  那么下面就要分析如何在procfs中建立一个文件了. 三: 在procfs中创建文件   在procfs中创建一个文件用函数proc_create() 或者用create_proc_read_entry()创建一个只读文件.这里我们用proc_create()函数来分析在procfs下是如何创建文件的: static inline struct proc_dir_entry *proc_create(const char *name, mode_t mode,                 struct proc_dir_entry *parent, const struct file_operations *proc_fops){                 return proc_create_data(name, mode, parent, proc_fops, NULL);}接着跟踪proc_create_data():struct proc_dir_entry *proc_create_data(const char *name, mode_t mode,                                               struct proc_dir_entry *parent,                                               const struct file_operations *proc_fops,                                               void *data){                 struct proc_dir_entry *pde;                 nlink_t nlink;                  if (S_ISDIR(mode)) {                   if ((mode & S_IALLUGO) == 0)                            mode |= S_IRUGO | S_IXUGO;  //可读写                   nlink = 2;                 } else {                   if ((mode & S_IFMT) == 0)                            mode |= S_IFREG;    //普通文件                   if ((mode & S_IALLUGO) == 0)                            mode |= S_IRUGO; //可读                   nlink = 1;                 }                  pde = __proc_create(&parent, name, mode, nlink); //创建proc_dir_entry对象                 if (!pde)                   goto out;                 pde->proc_fops = proc_fops;  //proc文件操作方法                 pde->data = data;                 if (proc_register(parent, pde) < 0)  //注册proc_dir_entry对象                   goto out_free;                 return pde;out_free:                 kfree(pde);out:                 return NULL;}继续跟踪proc_dir_entry对象的创建过程.static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,         const char *name, mode_t mode,  nlink_t nlink){                 struct proc_dir_entry *ent = NULL;                 const char *fn = name;                 int len;                 if (!name || !strlen(name)) goto out;                  if (xlate_proc_name(name, parent, &fn) != 0)  //查找要建立的proc_dir_entry对象是否存在                   goto out;                  if (strchr(fn, '/'))                   goto out;                  len = strlen(fn);                  ent = kmalloc(sizeof(struct proc_dir_entry) + len + 1, GFP_KERNEL); //分配proc_dir_entry对象                 if (!ent) goto out;                  memset(ent, 0, sizeof(struct proc_dir_entry));                 memcpy(((char *) ent) + sizeof(struct proc_dir_entry), fn, len + 1);                 ent->name = ((char *) ent) + sizeof(*ent);                 ent->namelen = len;               //初始化名字长度                 ent->mode = mode;               //初始化模式                  …………… out:                 return ent;}xlate_proc_name()函数首先判断parent对象是否为空, 如果为空将proc_root指定为默认的parent对象 ,在xlate_proc_name()函数中还需要判断要建立的roc_dir_entry对象是否已经存在.如果存在就会返回错误 . 接下来就是在内存中建立并根据传入的参数初始化proc_dir_entry对象 .在建立好proc_dir_entry对象后当然需要把这个它注册到内核中, 以便在后面动态建立该文件时根据名字找到这个proc_dir_entry对象.其实注册proc_dir_entry对象就是把它加到以proc_root为根的目录树中.下面来看一下注册proc_dir_entry对象的代码:static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp){         unsigned int i;         struct proc_dir_entry *tmp;                  i = get_inode_number();  //动态分配inode num         if (i == 0)                   return -EAGAIN;         dp->low_ino = i;          if (S_ISDIR(dp->mode)) {      //目录文件                   if (dp->proc_iops == NULL) {                            dp->proc_fops = &proc_dir_operations;   //默认的目录文件操作方法                            dp->proc_iops = &proc_dir_inode_operations;  //默认目录文件的索引节点操作方法                   }                   dir->nlink++;         } else if (S_ISLNK(dp->mode)) {                   if (dp->proc_iops == NULL)                                dp->proc_iops = &proc_link_inode_operations;   //默认符号链接文件操作方法         } else if (S_ISREG(dp->mode)) {                   if (dp->proc_fops == NULL)                            dp->proc_fops = &proc_file_operations;  //默认普通文件的操作方法                   if (dp->proc_iops == NULL)                            dp->proc_iops = &proc_file_inode_operations; //默认普通文件索引节点操作方法         }          spin_lock(&proc_subdir_lock);          for (tmp = dir->subdir; tmp; tmp = tmp->next)                     if (strcmp(tmp->name, dp->name) == 0) {     //判断是否已经注册                            printk(KERN_WARNING "proc_dir_entry '%s' already "                                               "registered\n", dp->name);                            dump_stack();                            break;                   }          dp->next = dir->subdir;         dp->parent = dir;         dir->subdir = dp;         spin_unlock(&proc_subdir_lock);          return 0;}到这里proc_dir_entry对象就已经创建和注册到内核中了. 在内核中会建立一个以proc_root为根的树结构, 一个proc_dir_entry对象就对应一个文件. 在动态建立文件时,会根据文件名查找这棵树,找到对应的proc_dir_entry对象, 将其中的信息初始化给文件索引节点inode. 那么procfs是如何动态建立文件的呢 ,在以前的文章中介绍过vfs路径查找的流程 do_path_lookup()->…..-> real_lookup()->dir->i_op->lookup() ; lookup这个回调函数就是在内存中建立文件inode .在procfs中默认定义的目录文件索引节点操作方法是:static const struct inode_operations proc_dir_inode_operations = {         .lookup                = proc_lookup,         .getattr       = proc_getattr,         .setattr       = proc_notify_change,};看看proc_lookup()中是如何建立inode的:proc_lookup()->proc_lookup_de()->proc_get_inode();在找到父目录proc_dir_entry对象后, 调用函数proc_lookup_de() 根据目标文件的dentry->d_name查找到对应的proc_dir_entry对象 ,然后调用proc_get_inode()分配inode,并由proc_dir_entry对象初始化inode .此时文件操作方法也是在proc_dir_entry对象中取得.  四 : procfs文件续写   还是找个具体例子来看看吧. 在/proc下有个devices文件,就拿它来分析: 我们read这个虚拟的device文件就会打印中注册到内核中的字符和块设备.首先在procfs初始化的时候,调用proc_create("devices", 0, NULL, &proc_devinfo_operations);在proc顶层目录创建一个device文件. 在以前已经分析过了VFS读写文件的过程 ,现在就来看看读device的流程, 如果cat /proc/device 函数执行流程为devinfo_open()->seq_read()->devinfo_show()static int devinfo_show(struct seq_file *f, void *v){         int i = *(loff_t *) v;          if (i < CHRDEV_MAJOR_HASH_SIZE) {                   if (i == 0)                            seq_printf(f, "Character devices:\n");                   chrdev_show(f, i);         }#ifdef CONFIG_BLOCK         else {                   i -= CHRDEV_MAJOR_HASH_SIZE;                   if (i == 0)                            seq_printf(f, "\nBlock devices:\n");                   blkdev_show(f, i);         }#endif         return 0;}chrdev_show()和blkdev_show();就是分别遍历注册在内核中的字符和块设备,返回设备信息. 五:小结   可见procfs是一个伪文件系统,它只存在于内存中,而porcfs下面的文件全部都是虚拟的文件,他们的实际大小都是0 . 其实在分析完procfs文件系统以后再去分析Linux其他伪文件系统就比较简单了,如: tmpfs,ramfs,sysfs…原理都是一样的了哦.

原创粉丝点击