Linux文件系统之proc文件系统

----------------------------------------------- 
#纯属个人理解,如有问题敬请谅解!

#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…原理都是一样的了哦.



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值