linux3.10 proc文件系统实现原理

1 主要数据结构

我们列举某个proc目录,其与虚拟文件系统的数据结构关系如下:

文件或者目录打开的时候会为虚拟文件系统创建inode,对于proc文件系统,inode结构包含于结构体proc_inode,用于连接vfs:

struct proc_inode {
	struct pid *pid;
	int fd;
	union proc_op op;
	struct proc_dir_entry *pde; //指向该目录/文件对应的proc_dir_entry
	struct ctl_table_header *sysctl;
	struct ctl_table *sysctl_entry;
	struct proc_ns ns;
	struct inode vfs_inode;//Vfs的inode,用宏PROC_I(inode)可以通过inode得到对应的proc_inode
};

 每一个文件或者目录创建的时候都会构建一个结构体proc_dir_entry用于管理这个文件或者目录,与proc_inode不同的是,proc_dir_entry在文件或者目录创建的时候就会产生,但是proc_inode只有在打开的时候才会产生:

struct proc_dir_entry {
	unsigned int low_ino;
	umode_t mode;  //文件类型:包含S_IFDIR(目录) 或者S_IFREG(普通文件)等等
	nlink_t nlink;
	kuid_t uid;
	kgid_t gid;
	loff_t size;
	const struct inode_operations *proc_iops; //文件或者目录打开的时候会赋给vfs_inode->i_op
	const struct file_operations *proc_fops;//如果是目录在打开的时候付给vfs_inode->i_fop,如果是普通文件会赋值为proc_reg_file_ops
	struct proc_dir_entry *next, *parent, *subdir;
	void *data;
	atomic_t count;		/* use count */
	atomic_t in_use;	/* number of callers into module in progress; */
			/* negative -> it's going away RSN */
	struct completion *pde_unload_completion;
	struct list_head pde_openers;	/* who did ->open, but not ->release */
	spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
	u8 namelen;
	char name[];//存放目录项名
};

2 文件系统挂载

proc文件系统挂载会调用proc_mount函数:

static struct file_system_type proc_fs_type = {
	.name		= "proc",
	.mount		= proc_mount,
	.kill_sb	= proc_kill_sb,
	.fs_flags	= FS_USERNS_MOUNT,
};
static struct dentry *proc_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
{
	int err;
	struct super_block *sb;
	struct pid_namespace *ns;
	char *options;
	if (flags & MS_KERNMOUNT) {
		ns = (struct pid_namespace *)data;
		options = NULL;
	} else {
		ns = task_active_pid_ns(current);
		options = data;

		if (!current_user_ns()->may_mount_proc)
			return ERR_PTR(-EPERM);
	}
    //分配并设置proc 文件系统的super_block
	sb = sget(fs_type, proc_test_super, proc_set_super, flags, ns);
	if (IS_ERR(sb))
		return ERR_CAST(sb);

	if (!proc_parse_options(options, ns)) {
		deactivate_locked_super(sb);
		return ERR_PTR(-EINVAL);
	}

	if (!sb->s_root) {
		err = proc_fill_super(sb);//在该函数中分配dentry和inode结构
		if (err) {
			deactivate_locked_super(sb);
			return ERR_PTR(err);
		}

		sb->s_flags |= MS_ACTIVE;
	}

	return dget(sb->s_root);
}

看一下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;
	s->s_blocksize_bits = 10;
	s->s_magic = PROC_SUPER_MAGIC;
	s->s_op = &proc_sops; //设置super_block的操作函数,后面分配inode的时候,会调用里面的proc_alloc_inode
	s->s_time_gran = 1;
	
	pde_get(&proc_root);
	root_inode = proc_get_inode(s, &proc_root);//分配inode
	if (!root_inode) {
		pr_err("proc_fill_super: get root inode failed\n");
		return -ENOMEM;
	}

	s->s_root = d_make_root(root_inode);//建立文件系统根目录的dentry
	if (!s->s_root) {
		pr_err("proc_fill_super: allocate dentry failed\n");
		return -ENOMEM;
	}

	return proc_setup_self(s);//这个在根目录下创建了名字叫self的dentry和inode节点,具体用途不知。。
}

在proc_get_inode函数中分配了inode

struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
{
	struct inode *inode = new_inode_pseudo(sb);//调用super_block的alloc_inode函数,为其分配一个proc_inode结构,其中包含inode

	if (inode) {
		inode->i_ino = de->low_ino;
		inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
		PROC_I(inode)->pde = de; //把proc_dir_entry结构赋值给proc_inode->pde,对于根目录,该结构是静态的proc_root

		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)
			set_nlink(inode, de->nlink);
		WARN_ON(!de->proc_iops);
		inode->i_op = de->proc_iops;//proc的操作函数,具体节点的操作函数就是他
		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;//该操作函数在文件打开的时候会赋值给file结构体,如果是普通文件
			} else {
				inode->i_fop = de->proc_fops; //如果是目录,
			}
		}
	} else
	       pde_put(de);
	return inode;
}
struct proc_dir_entry proc_root = {
	.low_ino	= PROC_ROOT_INO, 
	.namelen	= 5, 
	.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,
	.name		= "/proc",
};

上面的new_inode_pseudo函数会调用super_block的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;
	ei->sysctl = NULL;
	ei->sysctl_entry = NULL;
	ei->ns.ns = NULL;
	ei->ns.ns_ops = NULL;
	inode = &ei->vfs_inode;
	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
	return inode;
}

可以看到,通过该函数分配proc_inode,并且返回其内部的inode结构,通过上面的函数,可以看到为文件系统的根目录创建了dentry,proc_inode,并且和proc_root关联起来。

3 proc文件注册

下面内容原文地址为:https://blog.csdn.net/chenying126/article/details/78069910

3.1创建目录/文件

proc_create用于创建proc文件;proc_mkdir用于创建proc目录。接口__proc_create用于完成目录/文件创建的主要工作。

3.2目录创建

void __init proc_root_init(void)
{
	……
    proc_mkdir("fs", NULL);
   	……
}
 
struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
        struct proc_dir_entry *parent, void *data)
{
    struct proc_dir_entry *ent;
 
    if (mode == 0)
        mode = S_IRUGO | S_IXUGO;
    
   // 目录或者文件都是由结构体proc_dir_entry 来描述,S_IFDIR表示创建的是目录
    ent = __proc_create(&parent, name, S_IFDIR | mode, 2); 
    if (ent) {
        ent->data = data;
        ent->proc_fops = &proc_dir_operations; // file_operations打开的时候会赋给inode
        ent->proc_iops = &proc_dir_inode_operations; // inode_operations打开的时候会赋给inode
        parent->nlink++;
//建立层次关系,proc_dir_entry->parent指向父目录,proc_dir_entry->subdir_node插入到parent->subdir中
        if (proc_register(parent, ent) < 0) { 
            kfree(ent);
            parent->nlink--;
            ent = NULL;
        }
    }
    return ent;
}

对于目录来说proc_dir_operations都是空实现

static const struct file_operations proc_dir_operations = {
    .llseek         = generic_file_llseek,
    .read           = generic_read_dir,
    .iterate_shared     = proc_readdir,
};

proc_dir_inode_operations会在open的时候赋给inode,后续会重点讲解函数proc_lookup

static const struct inode_operations proc_dir_inode_operations = {
    .lookup     = proc_lookup,
    .getattr    = proc_getattr,
    .setattr    = proc_notify_change,
};

3.3文件创建

如果在/proc目录下创建一个proc文件,具体应该怎么做,先申明一个file_operations,结构,然后注册:

static const struct file_operations version_proc_fops = {
    .open       = version_proc_open,
    .read       = seq_read,
    .llseek     = seq_lseek, 
    .release    = single_release, 
};                         
 
static int __init proc_version_init(void)
{
    proc_create("version", 0, NULL, &version_proc_fops);
    return 0;
}

上面函数最终会调用到proc_create_data,传递下来的参数&version_proc_fops对应到下面的proc_fops

struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
                    struct proc_dir_entry *parent,
                    const struct file_operations *proc_fops,
                    void *data)
{
    struct proc_dir_entry *pde;
    if ((mode & S_IFMT) == 0)
        mode |= S_IFREG; //表示创建的是普通文件,
        
	……
    pde = __proc_create(&parent, name, mode, 1); //分配文件对应的proc_dir_entry并初始化,这边
//需要注意,传入的parent参数,如果传进去的parent为null,则会寻找根节点,把parent置为根节点
    if (!pde)   
        goto out;
    pde->proc_fops = proc_fops; //也就是version_proc_fops,文件打开的时候会赋给inode
    pde->data = data; 
    pde->proc_iops = &proc_file_inode_operations; //对于普通文件来说很多接口都没有实现
    if (proc_register(parent, pde) < 0) //建立文件在proc中的层次关系
        goto out_free;
    return pde;
	……
static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp)
{
	struct proc_dir_entry *tmp;
	int ret;
	
	ret = proc_alloc_inum(&dp->low_ino);
	if (ret)
		return ret;

	if (S_ISDIR(dp->mode)) {
		dp->proc_fops = &proc_dir_operations;
		dp->proc_iops = &proc_dir_inode_operations;
		dir->nlink++;
	} else if (S_ISLNK(dp->mode)) {
		dp->proc_iops = &proc_link_inode_operations;
	} else if (S_ISREG(dp->mode)) {
		BUG_ON(dp->proc_fops == NULL);
		dp->proc_iops = &proc_file_inode_operations;
	} else {
		WARN_ON(1);
		return -EINVAL;
	}

	spin_lock(&proc_subdir_lock);

	for (tmp = dir->subdir; tmp; tmp = tmp->next)
		if (strcmp(tmp->name, dp->name) == 0) {
			WARN(1, "proc_dir_entry '%s/%s' already registered\n",
				dir->name, dp->name);
			break;
		}

	dp->next = dir->subdir;
	dp->parent = dir;
	dir->subdir = dp;//和父目录的proc_dir_entry关联起来
	spin_unlock(&proc_subdir_lock);

	return 0;
}

从上面的源码分析也可以看到,创建proc文件或者目录的时候,只创建了该节点的proc_dir_entry结构,并把该结构和上层目录相关联,dentry和inode只有在打开该文件的时候才会被创建。

4 proc读写原理

以上面新建的/proc/version文件为例,看一下具体是怎么读写该文件的。

proc文件夹一般来说,都存在于上级文件系统化中,但是proc文件系统挂载在该文件夹下面,所以在open version文件,往下搜索的时候,先搜索proc节点,最终会找到proc文件系统根节点‘/’的dentry和inode结构,这两个结构在上面一节文件系统挂载的时候可以看到,已经被创建。接着就再往下搜索,为version文件创建dentry和inode节点。具体的函数调用流程看下图:

4.1文件打开

文件搜索的源码分析,在之前的sysfs文件系统博文中已经讲过,这边不再分析了,主要分析几个重点函数。继续之前的例子,当搜索到version文件时,需要为其建立inode和dentry,上层目录就是proc文件系统的根目录。所以我们调用根目录的inode的lookup方法,上面文件系统挂载的时候可以看到inode->i_op = de->proc_iops;de是定义的静态的proc_root;对应的是proc_root_inode_operations操作集函数,具体lookup函数为proc_root_lookup:

proc_root_lookup

     -------------->proc_lookup

            --------------->proc_lookup_de

struct dentry *proc_lookup_de(struct proc_dir_entry *de, struct inode *dir,
		struct dentry *dentry)
{
	struct inode *inode;

	spin_lock(&proc_subdir_lock);
	for (de = de->subdir; de ; de = de->next) { //找父proc_dir_entry对应的子proc_dir_entry,主要根据文件名来比对
		if (de->namelen != dentry->d_name.len)
			continue;
		if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
			pde_get(de);
			spin_unlock(&proc_subdir_lock);
			inode = proc_get_inode(dir->i_sb, de);//找到子proc_dir_entry,为其分配inode结构,关联proc_inode和proc_dir_entry
			if (!inode)
				return ERR_PTR(-ENOMEM);
			d_set_d_op(dentry, &proc_dentry_operations);//为当前节点的dentry设置dentry_operations函数
			d_add(dentry, inode);//dentry->d_inode = inode;把dentry和indoe关联起来。
			return NULL;
		}
	}
	spin_unlock(&proc_subdir_lock);
	return ERR_PTR(-ENOENT);
}

 经过上面的函数,dentry,inode,proc_inode都有了,再看do_dentry_open后面的流程。

文件的file->f_op来源于inode->i_fop,而i_fop在之前初始化为proc_reg_file_ops:

static const struct file_operations proc_reg_file_ops = {
	.llseek		= proc_reg_llseek,
	.read		= proc_reg_read,
	.write		= proc_reg_write,
	.poll		= proc_reg_poll,
	.unlocked_ioctl	= proc_reg_unlocked_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= proc_reg_compat_ioctl,
#endif
	.mmap		= proc_reg_mmap,
	.open		= proc_reg_open,
	.release	= proc_reg_release,
};

proc_reg_open会调用到proc_dir_entry->proc_fops->open,proc_dir_entry就是version文件注册的proc结构,前面注册的时候已经看到,其proc_fops就是前面的version_proc_fops :

static int version_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, version_proc_show, NULL);
}
 
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
        void *data)
{
    struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL);
    int res = -ENOMEM;
    
    if (op) {   
        op->start = single_start;
        op->next = single_next;
        op->stop = single_stop;
        op->show = show;  //也就是前面提到的version_proc_show
        res = seq_open(file, op);
		……
    return res;
}
 
int seq_open(struct file *file, const struct seq_operations *op)
{       
    struct seq_file *p;    
    p = kzalloc(sizeof(*p), GFP_KERNEL);
 
    file->private_data = p;  //与vfs中的file连接
        
    p->op = op; //设置顺序文件系统的seq_operations
    p->file = file;
    
    file->f_version = 0;
 
    file->f_mode &= ~FMODE_PWRITE;
    return 0;
}

4.2文件读取

 函数proc_reg_read是vfs read进入proc的入口。


static ssize_t proc_reg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{   
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    struct proc_dir_entry *pde = PDE(file_inode(file));
    ssize_t rv = -EIO;
    if (use_pde(pde)) {
        read = pde->proc_fops->read;  //调用version文件注册的proc读函数
        if (read)
            rv = read(file, buf, count, ppos);
        unuse_pde(pde);
    }
    return rv;
}

pde->proc_fops->read函数就是seq_read函数:

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    struct seq_file *m = file->private_data;
    size_t copied = 0;
    loff_t pos;
    size_t n;
    void *p;
    int err = 0;
 
    if (!m->buf) {//因为这是一个通用函数,它并不知道会读取多少数据所以这里暂且分配一页数据
        m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
        if (!m->buf)
            goto Enomem;
    }
    /* if not empty - flush it first */
    if (m->count) {//如果buffer中有数据先将数据拷贝到用户空间
        n = min(m->count, size);
        err = copy_to_user(buf, m->buf + m->from, n);
        if (err)
            goto Efault;
        m->count -= n;
        m->from += n;
        size -= n;
        buf += n;
        copied += n;
        if (!m->count) {
            m->from = 0;
            m->index++;
        }
        if (!size)
            goto Done;
}
    /* we need at least one record in buffer */
    pos = m->index;
    p = m->op->start(m, &pos);
    while (1) {//测试先前分配的buffer够不够容纳将要读取的数据,如果不够就释放重新分配更大内存
        err = PTR_ERR(p);
        if (!p || IS_ERR(p))
            break;
        err = m->op->show(m, p); //尝试将数据拷贝到buffer中
        if (err < 0)
            break;
        if (unlikely(err))
            m->count = 0;
        if (unlikely(!m->count)) {
            p = m->op->next(m, p, &pos);
            m->index = pos;
            continue;
        }
        if (m->count < m->size) //如果m->count 等于 m->size就说明buffer满了,buffer不够用
            goto Fill;
        m->op->stop(m, p);
        kvfree(m->buf);//释放掉之前不够用的buffer
        m->count = 0;
        m->buf = seq_buf_alloc(m->size <<= 1);//分配更大内存
        if (!m->buf)
            goto Enomem;
        m->version = 0;
        pos = m->index;
        p = m->op->start(m, &pos);
    }
    m->op->stop(m, p);
    m->count = 0;
    goto Done;
Fill:
    /* they want more? let's try to get some more */
    while (m->count < size) {
        size_t offs = m->count;
        loff_t next = pos;
        p = m->op->next(m, p, &next);
        if (!p || IS_ERR(p)) {
            err = PTR_ERR(p);
            break;
        }
        err = m->op->show(m, p);//将剩余的数据拷贝到buffer
        if (seq_has_overflowed(m) || err) {
            m->count = offs;
            if (likely(err <= 0))
                break;
        }
        pos = next;
    }
    m->op->stop(m, p);
    n = min(m->count, size);
    err = copy_to_user(buf, m->buf, n);//将buffer中的数据拷贝到用户空间内存
    if (err)
        goto Efault;
    copied += n;
    m->count -= n;
    if (m->count)
        m->from = n;
    else
        pos++;
    m->index = pos;
Done:
    if (!copied)
        copied = err;
    else {
        *ppos += copied;
        m->read_pos += copied;
    }
    file->f_version = m->version;
    mutex_unlock(&m->lock);
    return copied;
}

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值