Chinaunix首页 | 论坛 | 博客
  • 博客访问: 250388
  • 博文数量: 101
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-12 12:35
文章分类

全部博文(101)

文章存档

2016年(5)

2015年(16)

2014年(37)

2013年(32)

2012年(8)

2011年(3)

我的朋友

分类: LINUX

2013-12-03 17:37:27

------------------------------------------------
#纯属个人理解,如有问题敬请谅解!
#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关联在一起(inodeproc_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的根已经在内存中建立起来了. 其实在这个时候内存中只有根目录的inodedentry存在,而其他文件的inodedentry都是动态建立起来的那么下面就要分析如何在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…原理都是一样的了哦.
 
阅读(1919) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~