Linux文件系统之proc文件系统

xiaoxiao2021-02-28  28

----------------------------------------------- #纯属个人理解,如有问题敬请谅解! #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…原理都是一样的了哦.
转载请注明原文地址: https://www.6miu.com/read-46590.html

最新回复(0)