2. 关于文件(第三部分)

2.4 打开文件

先来整理一下打开文件的基本过程:

 以上是打开文件时候需要用到的函数的基本调用流程,从系统调用开始,这些函数集中在fs/open.c 和 fs/namei.c中。

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;
	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

当在用户空间调用open的时候,通过系统调用转到内核态时会调用到以上函数。

int vfs_open(const struct path *path, struct file *file)
{
    file->f_path = *path;
    return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
}

略过中间的那些函数,当调用到vfs_open的时候,会有一个path对象和file对象。path的结构不复杂:

struct path {
    struct vfsmount *mnt;
    struct dentry *dentry;
} __randomize_layout;

这里的dentry就是我们要打开的文件所属的dentry,d_backing_inode(path->dentry)用来得到文件的inode对象。

而这儿的file,到面前为止还是一个刚刚初始化的空对象。

static int do_dentry_open(struct file *f,
     struct inode *inode,
     int (*open)(struct inode *, struct file *))
{
.......
    f->f_op = fops_get(inode->i_fop);
    if (WARN_ON(!f->f_op)) {
        error = -ENODEV;
        goto cleanup_all;
    }
......

    if (!open)
        open = f->f_op->open;
    if (open) {
         error = open(inode, f);
         if (error)
             goto cleanup_all;
    }
......
}

这里会通过fops_get(inode->i_fop)得到inode中的file_operations对象,而后用它指向的open函数来打开文件。

file_operations是个函数指针的集合,不同的文件系统都有自己的实现,比如在ext2中的定义为:

const struct file_operations ext2_file_operations = {
	.llseek		= generic_file_llseek,
	.read_iter	= ext2_file_read_iter,
	.write_iter	= ext2_file_write_iter,
	.unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext2_compat_ioctl,
#endif
	.mmap		= ext2_file_mmap,
	.open		= dquot_file_open,
	.release	= ext2_release_file,
	.fsync		= ext2_fsync,
	.get_unmapped_area = thp_get_unmapped_area,
	.splice_read	= generic_file_splice_read,
	.splice_write	= iter_file_splice_write,
};

而在内存中生成文件类型的inode的时候,都会将它设置给inode的i_fop对象:

inode->i_fop = &ext2_file_operations;

而在ramfs中也有类似的实现,不过它是通过调用file/inode.c中的init_special_inode来实现的。

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
	inode->i_mode = mode;
	if (S_ISCHR(mode)) {
		inode->i_fop = &def_chr_fops;
		inode->i_rdev = rdev;
	} else if (S_ISBLK(mode)) {
		inode->i_fop = &def_blk_fops;
		inode->i_rdev = rdev;
	} else if (S_ISFIFO(mode))
		inode->i_fop = &pipefifo_fops;
	else if (S_ISSOCK(mode))
		;	/* leave it no_open_fops */
	else
		printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
				  " inode %s:%lu\n", mode, inode->i_sb->s_id,
				  inode->i_ino);
}

这段函数通过判断设备类型来确定具体的操作,如果是字符设备(S_ISCHR(mode))的话:

inode->i_fop = &def_chr_fops;

inode->i_rdev = rdev;

音频设备属于字符设备,所以它的i_fop被赋予def_chr_fops:

const struct file_operations def_chr_fops = {
	.open = chrdev_open,
	.llseek = noop_llseek,
};

而这里的open指向了chrdev_open。可以看到这里并没有实现read,write等函数,后面会看到事实上在打开文件的时候,file中的f_op会被替换掉,那儿会有这些函数的实现。

static int chrdev_open(struct inode *inode, struct file *filp)
{
	const struct file_operations *fops;
	struct cdev *p;
	struct cdev *new = NULL;
	int ret = 0;

	spin_lock(&cdev_lock);
	p = inode->i_cdev;
	if (!p) {
		struct kobject *kobj;
		int idx;
		spin_unlock(&cdev_lock);
		kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
		if (!kobj)
			return -ENXIO;
		new = container_of(kobj, struct cdev, kobj);
		spin_lock(&cdev_lock);
		/* Check i_cdev again in case somebody beat us to it while
		   we dropped the lock. */
		p = inode->i_cdev;
		if (!p) {
			inode->i_cdev = p = new;
			list_add(&inode->i_devices, &p->list);
			new = NULL;
		} else if (!cdev_get(p))
			ret = -ENXIO;
	} else if (!cdev_get(p))
		ret = -ENXIO;
	spin_unlock(&cdev_lock);
	cdev_put(new);
	if (ret)
		return ret;

	ret = -ENXIO;
	fops = fops_get(p->ops);
	if (!fops)
		goto out_cdev_put;

	replace_fops(filp, fops);
	if (filp->f_op->open) {
		ret = filp->f_op->open(inode, filp);
		if (ret)
			goto out_cdev_put;
	}

	return 0;

 out_cdev_put:
	cdev_put(p);
	return ret;
}

chrdev_open的实现在fs/char_dev.c中。

这段函数中定义了变量struct cdev *p,再看函数结尾处的几行代码:

	fops = fops_get(p->ops);
	if (!fops)
		goto out_cdev_put;

	replace_fops(filp, fops);

通过fops_get得到p指向的file_operations对象ops,然后再通过replace_fops用它替换掉file中的f_op。后续就是通过它来操作文件了。

再看函数的开头处:

p = inode->i_cdev;

p来自于indoe的i_cdev,它又是如何来的呢?这里是字符设备需要特殊处理的地方。

先看cdev的定义:

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
} __randomize_layout;

它包含一个file_operations对象,还有一个dev_t对象。另外再char_dev.c中定义了全局变量:

static struct kobj_map *cdev_map;

再回到chardev_open中:

kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
if (!kobj)
	return -ENXIO;
new = container_of(kobj, struct cdev, kobj);

if (!p) {
	inode->i_cdev = p = new;

这里通过kobj_lookup通过inode中的设备号得到一个kobj,再从中取出一个cdev对象new,然乎将它赋值给inode->i_cdev。

如上所述,inode中最初并没有给i_cdev赋值,而留到open的时候。不过也就是第一次open的时候需要,后续打开的时候inode中已经保留该值了。

再看一个函数:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	if (WARN_ON(dev == WHITEOUT_DEV))
		return -EBUSY;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

这段函数将cdev 对象保存到cdev_map中。也就是说要使用open功能,需要事先调用过cdev_add函数,对于alsa,这部分代码在sound/core/sound.c中实现:

static int __init alsa_sound_init(void)
{
	snd_major = major;
	snd_ecards_limit = cards_limit;
	if (register_chrdev(major, "alsa", &snd_fops)) {
		pr_err("ALSA core: unable to register native major device number %d\n", major);
		return -EIO;
	}
 ......

register_chrdev 中传入了主设备号,名字和snd_fops。register_chrdev将会负责生成cdev 对象,并调用cdev_add函数。看一下snd_fops:

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

当打开一个音频设备时,最后会调用到snd_open函数。

后续的事情就由alsa模块自己处理了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值