proc文件系统分析

来源:互联网 发布:怎么用vps建站 编辑:程序博客网 时间:2024/06/06 03:04

-------------

内核:2.6.30

-------------

//入口

void __init proc_root_init(void)
{
    int err;

    proc_init_inodecache();
    err = register_filesystem(&proc_fs_type); //注册proc文件系统
    if (err)
        return;
    proc_mnt = kern_mount_data(&proc_fs_type, &init_pid_ns); //必须在注册函数之后调用vfs_kern_mount(),使得在内核范围内的vfsmnt被放置在->kern_mnt处。 
    err = PTR_ERR(proc_mnt);
    if (IS_ERR(proc_mnt)) {
        unregister_filesystem(&proc_fs_type);  //未挂载上注销文件系统
        return;
    }

    proc_symlink("mounts", NULL, "self/mounts");  //符号链接

    proc_net_init();//注册初始net子目录


/* 创建相关目录文件*/
#ifdef CONFIG_SYSVIPC
    proc_mkdir("sysvipc", NULL);
#endif
    proc_mkdir("fs", NULL);
    proc_mkdir("driver", NULL);
    proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */
#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)
    /* just give it a mountpoint */
    proc_mkdir("openprom", NULL);
#endif
    proc_tty_init();//tty子目录
#ifdef CONFIG_PROC_DEVICETREE
    proc_device_tree_init();
#endif
    proc_mkdir("bus", NULL);
    proc_sys_init();  //sys子目录
}

     proc文件系统的注册非常简单,主要有如下几个步骤: 
1.调用register_filesystem(&proc_fs_type),将proc文件类型加入到文件类型的单向链表中,如果发生错误,则返回。 
2.调用 kern_mount_data函数,来mount 该文件系统,其又调用vfs_kern_mount使用add_vfsmnt()函数建立proc文件系统的vfsmount结构,并将其加入到已装载文件系统的链表中。 
最后,返回该vfsmount结构,并利用返回值,使用指针proc_mnt指向该vfsmount结构。 
3.判断返回值是否错误,如果错误,那么就卸载文件系统。

注册成功接下调用type->get_sb()的函数执行:

 

static struct file_system_type proc_fs_type = {
    .name        = "proc",
    .get_sb     = proc_get_sb,  //调用
    .kill_sb    = proc_kill_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;

//mount 成功
    if (proc_mnt) {
        /* Seed the root directory with a pid so it doesn't need
         * to be special in base.c. I would do this earlier but
         * the only task alive when /proc is mounted the first time
         * is the init_task and it doesn'
t have any pids.
         */
        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); //分配一个的超级块
    if (IS_ERR(sb))
        return PTR_ERR(sb);

    if (!sb->s_root) {
        sb->s_flags = flags;
        err = proc_fill_super(sb);//超级块例程
        if (err) {
            deactivate_locked_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;
    }

    simple_set_mnt(mnt, sb);
    return 0;
}

在vfs_kern_mount函数中,建立了新的超级块结构,然后就会调用文件系统自己提供的读取超级块的例程,用来填充自己的超级块结构,下面我们看一下proc文件系统的超级块读取例程proc_fill_super()是如何工作的:


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;  //block大小
    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);  //增加proc_dir_entry结构proc_root 的引用计数
    root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root); ////获取proc 文件系统inode结点
    if (!root_inode)
        goto out_no_root;

/*root 操作权限*/

    root_inode->i_uid = 0;

    root_inode->i_gid = 0;
    s->s_root = d_alloc_root(root_inode);  //创建root_inode 根目录
    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;
}

该函数,把作为参数传入的超级块写入文件系统的基本信息,超级块的函数集设置为proc_sops,、设置proc文件系统中的文件最大字节数为MAX_NON_LFS,其中root_inode 的类型是struct inode *, 而s_root的类型是struct dentry *,目录高速缓存以树状结构存在,因此在建立文件系统的根结点后,需要使用d_alloc_root()函数建立一个根目录(root dentry),最终成功返回超级块,这时,超级块已经填上了必要的数据信息。

超级块读取例程主要完成了两部分的工作,1、向超级块写入必要的数据,其次建立了该文件系统的根结点。2、在目录高速缓存中建立了相应的dentry结构

s->s_op = &proc_sops超级块的函数集:

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,
}

这是GNU的C扩展,这样在初始化结构时,不必按照结构的顺序,只要指明域名,就可初始化其值,而对于没有提到的域,将自动设置为0。


其他的操作函数为什么不定义了呢?proc文件系统仅仅存在于内存中,并不需要物理设备,因此write_inode函数就不需要定义了。而函数notify_change,在索引节点的属性被改变的时候会被调用,而对于proc文件系统的inode来说,并未提供setattr 函数,换句话说,文件的属性不会被改变,所以,notif_change也就不会被调用,基于类似的原因,其他的函数,诸如put_super,write_super,以及clear_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); // 新inode,并填充基本信息
    if (!ei)
        return NULL;
    ei->pid = NULL;
    ei->fd = 0;
    ei->op.proc_get_link = NULL;
    ei->pde = NULL;
    ei->sysctl = NULL;
    ei->sysctl_entry = NULL;
    inode = &ei->vfs_inode;
    inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
    return inode;
}

 此函数是建立一个新的索引节点,填充一些基本的信息.


proc_destroy_inode函数如下:

static void proc_destroy_inode(struct inode *inode)
{
    kmem_cache_free(proc_inode_cachep, PROC_I(inode));
}

释放缓存


generic_delete_inode函数如下:

void generic_delete_inode(struct inode *inode)
{
    const struct super_operations *op = inode->i_sb->s_op;

    list_del_init(&inode->i_list);
    list_del_init(&inode->i_sb_list);
    WARN_ON(inode->i_state & I_NEW);
    inode->i_state |= I_FREEING;
    inodes_stat.nr_inodes--;
    spin_unlock(&inode_lock);

    security_inode_delete(inode);

    if (op->delete_inode) {
        void (*delete)(struct inode *) = op->delete_inode;
        if (!is_bad_inode(inode))
            vfs_dq_init(inode);
        /* Filesystems implementing their own
         * s_op->delete_inode are required to call
         * truncate_inode_pages and clear_inode()
         * internally */
        delete(inode);
    } else {
        truncate_inode_pages(&inode->i_data, 0);
        clear_inode(inode);
    }
    spin_lock(&inode_lock);
    hlist_del_init(&inode->i_hash);
    spin_unlock(&inode_lock);
    wake_up_inode(inode);
    BUG_ON(inode->i_state != I_CLEAR);
    destroy_inode(inode);
}


调用vfs删除节点函数。

proc_delete_inode函数如下:

static void proc_delete_inode(struct inode *inode)
{
    struct proc_dir_entry *de;

    truncate_inode_pages(&inode->i_data, 0);

    /* Stop tracking associated processes */
    put_pid(PROC_I(inode)->pid);

    /* Let go of any associated proc directory entry */
    de = PROC_I(inode)->pde;
    if (de)
        de_put(de);
    if (PROC_I(inode)->sysctl)
        sysctl_head_put(PROC_I(inode)->sysctl);
    clear_inode(inode);
}

此函数在索引节点的引用计数减少的时候调用,对于proc文件系统来说,在每一次inode引用计数减少之前,都要检查引用计数会不会减少至零,如果是,那么就将改索引节点的链接数直接设置为零。

simple_statfs函数如下:

int simple_statfs(struct dentry *dentry, struct kstatfs *buf)
{
    buf->f_type = dentry->d_sb->s_magic;
    buf->f_bsize = PAGE_CACHE_SIZE;
    buf->f_namelen = NAME_MAX;
    return 0;
}

当一个索引节点的引用计数和链接数都到零的时候调用,它将文件系统的统计数据填充到一个buf中,文件系统类型为PROC_SUPER_MAGIC,在文件系统中的空闲块以及文件系统中的文件节点都设置为0,因此对于只存在于内存中的proc文件系统来说,这些统计数据是没有意义的。



   再回到proc_fill_super(struct super_block *s)函数来:


de_get(&proc_root); //增加proc_dir_entry结构proc_root 的引用计数
    root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);

proc_get_inode函数用来获得proc文件系统的inode,其中s是proc超级块,proc_root是一个以初始化完成的proc_dir_entry结构体:

struct proc_dir_entry proc_root = {
    .low_ino    = PROC_ROOT_INO, 
    .namelen    = 5, 
    .name        = "/proc",   //目录名字
    .mode        = S_IFDIR | S_IRUGO | S_IXUGO,  
    .nlink        = 2, 
    .count        = ATOMIC_INIT(1),
    .proc_iops    = &proc_root_inode_operations,  //proc的inode操作函数
    .proc_fops    = &proc_root_operations,  //proc的file操作函数
    .parent        = &proc_root,
}

进入proc_get_inode函数:


struct inode *proc_get_inode(struct super_block *sb, unsigned int ino,
                struct proc_dir_entry *de)
{
    struct inode * inode;

    inode = iget_locked(sb, ino);//从sb指定的文件系统中得到节点号为ino的索引节点,并使用指针inode指向它
    if (!inode)
        return NULL;
    if (inode->i_state & I_NEW) { //得到新的索引
        inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; 
        PROC_I(inode)->fd = 0;
        PROC_I(inode)->pde = de; //使用container机制获取inode结构,把de与成员proc_dir_entry结构联系起来

        if (de->mode) {
            inode->i_mode = de->mode;
            inode->i_uid = de->uid;
            inode->i_gid = de->gid;
        }
        if (de->size)
            inode->i_size = de->size;
        if (de->nlink)
            inode->i_nlink = de->nlink;
        if (de->proc_iops)
            inode->i_op = de->proc_iops;  //把proc_root的inode操作函数来填充inode->i_op
        if (de->proc_fops) {
            if (S_ISREG(inode->i_mode)) {
#ifdef CONFIG_COMPAT
                if (!de->proc_fops->compat_ioctl)
                    inode->i_fop =
                        &proc_reg_file_ops_no_compat;
                else
#endif
                    inode->i_fop = &proc_reg_file_ops;
            } else {
                inode->i_fop = de->proc_fops;//把proc_root的file操作函数来填充inode->i_fop
            }
        }
        unlock_new_inode(inode);
    } else
     de_put(de);//减少引计数
    return inode;
}

函数返回值是用de初始化inode的一个结点, PROC_I(inode)->pde = de,是内核一般机制,使用container机制获取inode结构,把de与成员proc_dir_entry结构联系起来;而de的实际参数就是proc_root(proc的根目录),也就是用proc_root中相应的域,来初始化完成inode,最后返回inode.

此proc文件系统已经初始完成,相关子文件目录也都完成,成功创建proc目录并得到inode,及设置相关操作权限.


proc文件系统的相关文件操作函数:

创建文件函数:
1.struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent);
作用:直接创建proc型文件
参数:name名称,mode权限,parent所属的父目录(为NULL,在/proc目录下建立)
函数源码:

struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
                     struct proc_dir_entry *parent)
{
    struct proc_dir_entry *ent;
    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;
    }

    ent = __proc_create(&parent, name, mode, nlink);//单独创建目录项
    if (ent) {
        if (proc_register(parent, ent) < 0) {  //注册了proc_dir_entry,使用默认的proc_file_operations
            kfree(ent);
            ent = NULL;
        }
    }
    return ent;
}

此函数是使用了默proc_file_operations,进入proc_register:     


static int proc_register(struct proc_dir_entry * dir, structproc_dir_entry * dp)
{
    unsigned int i;
    struct proc_dir_entry *tmp;

//生成一个inode号    
    i = get_inode_number();
    if (== 0)
        return -EAGAIN;
    dp->low_ino = i;

//设置dp操作函数
    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) {
            WARN(1, KERN_WARNING "proc_dir_entry '%s/%s' already registered/n",
                dir->name, dp->name);
            break;
        }
//将创建的目录项加入到proc目录树中
    dp->next = dir->subdir;
    dp->parent = dir;
    dir->subdir = dp;
    spin_unlock(&proc_subdir_lock);

    return 0;
}


proc文件系统没有定义创建文件/目录项域,只有通过函数在proc创建文件/目录,而调用create_proc_entry创建,都会使用proc_register来注册定义的proc_dir_entry结构,从而设置了默认的dp操作函数.

2.static inline struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base,read_proc_t  *read_proc, void * data)
功能:创建一只读的 proc 文件.
参数:name名称,mode权限,base指定建立proc文件所在的目录,read_proc自定处理函数指针,data传入给函数指针的参数
3.static struct proc_dir_entry *proc_create(struct proc_dir_entry **parent  const char *name, mode_t mode, nlink_t nlink)
说明:proc_create创建文件不会调用默认proc_file_operations,一般在创建使用 seq_file 接口的 proc 文件时会使用。

 

原创粉丝点击