您现在的位置是:首页 > 数码 > 

Linux中的open和close

2025-07-26 16:01:38
Linux中的open和close 一、Linux中的open用户空间的open()如下/* 返回值为int类型的fd; 参数flags用户指定文件的打开或者创建模式 参数mode只在创建一个新文件时使用,用于指定新建文件时的访问权限,比如可读、可写及可执行权限*/ int open(ct char *pathname, int flags); int open

Linux中的open和close

一、Linux中的open

用户空间的open()如下

/*
返回值为int类型的fd;
参数flags用户指定文件的打开或者创建模式
参数mode只在创建一个新文件时使用,用于指定新建文件时的访问权限,比如可读、可写及可执行权限*/
int open(ct char *pathname, int flags);
int open(ct char *pathname, int flags, mode_t mode);

do_filp_open

内核中的文件操作函数的参数都有struct file* filp,显然内核需要在打开设备文件时为fd与filp建立某种联系,其次是为filp与驱动程序中的fops建立关联。在系统调用流程中会调用如下函数:

struct file *do_filp_open(int dfd, struct filename *pathname,ct struct open_flags *op)
{struct nameidata nd;int flags = op->lookup_flags;struct file *filp;set_nameidata(&nd, dfd, pathname, ULL);filp = path_openat(&nd, op, flags | LOOKUP_RCU);if (unlikely(filp == ERR_PTR(-ECHILD)))filp = path_openat(&nd, op, flags);if (unlikely(filp == ERR_PTR(-ESTALE)))filp = path_openat(&nd, op, flags | LOOKUP_REVAL);restore_nameidata();return filp;
}long do_sys_open(int dfd, ct char __user *filename, int flags, umode_t mode)|--do_sys_openat2(dfd, filename, &how);/*为本次的open操作分配一个未使用的文件描述符fd。为了跟踪对文件的读写等操作,内核对于每一个打开的文件都会分配一个fd和一个struct file类型的实例filp;作为一种系统资源,一个进程可以分配多少个fd决定了一个进程可以open多少个文件*/|--get_unused_fd_flags(how->flags);/*查到设备文件对应的inode;会为每个打开的文件分配一个新的struct file类型的内存空间*/|--do_filp_open(dfd, tmp, &op);

进程为文件操作维护一个文件描述表(current->files->fdt),对设备文件的打开,最终会得到一个文件描述符fd,然后用该描述符fd作为进程维护的文件描述符表(指向struct file *类型的数组)的索引值,将之前新分配的struct file空间地址赋值给它(current->files->fdt->fd[fd] = filp),这样用户空间程序在后续的read/write/ioctl等函数调用用利用fd就可以得到对应的filp。

在上述do_filp_open()最后会调用到inode->i_fop->open函数,即chrdev_open()函数,该函数非常重要。它首先通过kboj_lookup在cdev_map中用inode->i_rdev来查设备号所对应的设备new,这里展示了设备号的作用。成功查到设备后通过replace_fops()来将设备对象new中的ops指针(驱动程序通过调用cdev_init将其实现的file_operati对象的指针赋值给cdev的ops成员)赋值给filp对象中的f_op成员,此处展示了如何将驱动程序中实现的struct file_operati与filp关联起来,从此之后上图中的filp->f_op将指向驱动程序中实现的struct_fileoperati对象。

接下来chrdev_open函数将检查驱动程序中是否实现了open函数,如果实现的话则调用驱动程序中实现的filp->f_op->open函数。

static int chrdev_open(struct inode *inode, struct file *filp)
{ct struct file_operati *fops;struct cdev *p;struct cdev *new = ULL;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 -EXIO;new = container_of(kobj, struct cdev, kobj);spin_lock(&cdev_lock);/* Check i_cdev again in case somebody beat us to it whilewe dropped the lock. */p = inode->i_cdev;if (!p) {inode->i_cdev = p = new;list_add(&inode->i_devices, &p->list);new = ULL;} else if (!cdev_get(p))ret = -EXIO;} else if (!cdev_get(p))ret = -EXIO;spin_unlock(&cdev_lock);cdev_put(new);if (ret)return ret;ret = -EXIO;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;
}

总结

打开一个字符设备的流程大致如下

do_sys_open函数会获得要打开设备文件对应的inode,然后调用其中的i_fop函数,对于字符设备节点的inode而言,i_fops函数就是chrdev_open,后者通过inode中的i_rdev成员在cdev_map中查该设备文件所对应的设备对象cdev(图中标号2的线段),在成功到了该设备对象之后,将inode的i_cdev成员指向该字符设备对象(图中标号的线段),这样下次再对该设备文件节点进行操作时,就可以直接通过i_cdev成员得到设备节点所对应的字符设备对象,而无需通过cdev_map查。

内核在每次打开一个设备文件时,都会产生一个整型的文件描述符fd和一个新的struct file对象filp来跟踪对该文件的这一次操作,在打开设备文件时,内核会将filp和fd关联起来,同时会将cdev中的ops赋值给filp->f_op(图中标号4的线段)。最后,sys_open系统调用将设备文件描述符fd返回到用户空间,如此在用户空间对后续的文件操作read/write/ioctl等函数的调用都会通过fd获得文件所对应的filp,根据filp中的f_op就可以调用到该文件所对应的设备驱动上实现的函数。

二、Linux中的close

在close()系统调用的流程中会调用到如下函数

int filp_close(struct file *filp, fl_owner_t id)
{int retval = 0;/*用来判断filp中的f_count成员是否为0,如果针对同一个设备文件close的次数多于open的次数,就会出现这种情况,此时函数直接返回0,因为实质性的工作都被前面的close做完了。*/if (!file_count(filp)) {printk(KER_ERR VFS: Close: file count is 0\n);return 0;}/*如果设备驱动定义了flush函数,那么在release函数被调用前,会首先调用flush,这就是为了确保在把文件关闭前缓存在系统中的数据被写回到硬件中;块设备会和系统进行大量数据传输,为此内核为块设备驱动程序设计了高速缓存机制,这种情况下为欸了保证文件数据的完整性必须在文件关闭前将高速缓存中的数据写回到块设备中。*/if (filp->f_op->flush)retval = filp->f_op->flush(filp, id);if (likely(!(filp->f_mode & FMODE_PATH))) {dnotify_flush(filp, id);locks_remove_posix(filp, id);}fput(filp);return retval;
}

在该函数的最后会调用fput

void fput(struct file *file)
{/*和体系结构相关的原子测试操作,如果file->f_count的值为1,那么返回true,这就以为着可以真正关闭当前的文件了,所以接下来最终完成文件的关系操作*/if (atomic_long_dec_and_test(&file->f_count)) {struct task_struct *task = current;if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {init_task_work(&file->f_rcuhead, ____fput);if (!task_work_add(task, &file->f_rcuhead, TWA_RESUME))return;/** After this task has run exit_task_work(),* task_work_add() will fail.  Fall through to delayed* fput to avoid leaking *file.*/}if (llist_add(&file->f_llist, &delayed_fput_list))schedule_delayed_work(&delayed_fput_work, 1);}
}

上述文件的关闭操作以及一些系统资源的释放是在delayed_fput_work中进行的,在该work中最终调用到如下函数。

static void __fput(struct file *file)
{struct dentry *dentry = file->f_path.dentry;struct vfsmount *mnt = file->f_;struct inode *inode = file->f_inode;fmode_t mode = file->f_mode;if (unlikely(!(file->f_mode & FMODE_OPEED)))goto out;might_sleep();fsnotify_close(file);/** The function eventpoll_release() should be the first called* in the file cleanup chain.*/eventpoll_release(file);locks_remove_file(file);ima_file_free(file);/*注意这里的FASYC标志*/if (unlikely(file->f_flags & FASYC)) {if (file->f_op->fasync)file->f_op->fasync(-1, file, 0);}/*调用到设备驱动中的release操作*/if (file->f_op->release)file->f_op->release(inode, file);if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != ULL &&!(mode & FMODE_PATH))) {cdev_put(inode->i_cdev);}fops_put(file->f_op);put_pid(file->f_owner.pid);put_file_access(file);dput(dentry);if (unlikely(mode & FMODE_EED_UMOUT))dissolve_on_fput(mnt);mntput(mnt);
out:file_free(file);
}

三、设备驱动程序的设计中,会关心file和inode这两个结构体

1)struct file:

内核用struct file对象来描述进程打开的一个文件的试图,即使是打开同一个文件,内核也会为之生成一个新的struct file对象,用来表示当前操作的文件的相关信息;在文件的所有实例都关闭后, 内核释放这个数据结构。

struct file {...struct inode        *f_inode;    /* cached value */ct struct file_operati    *f_op;spinlock_t        f_lock;/*用于对struct file对象的引用计数,当close一个文件时,只有struct file对象中f_count值为0才真正执行关闭操作*/atomic_long_t        f_count;/*用于记录当前文件被open时所指定的打开模式*/unsigned int         f_flags;fmode_t            f_mode;.../*驱动中使用时大多被指向自定义以用于描述设备的结构体*/void            *private_data;...
} __randomize_layout__attribute__((aligned(4)));

比如下面的代码可用于判断以阻塞还是非阻塞方式打开设备文件:

if(file->f_flags & O_OBLOCKE)  //非阻塞pr_info(open with non-blocking\n);
else     //阻塞pr_info(“open with blocking\n”);

2)struct inode:

VFS inode包含文件访问权限、 属主、 组、 大小、 生成时间、 访问时间、 最后修改时间等信息。 它是Linux管理文件系统的最基本单位, 也是文件系统连接任何子目录、 文件的桥梁。每个文件都有一个inode与之对应。

对于表示设备文件的inode结构, i_rdev字段包含设备编号。 设备号例如对于字符设备当设备驱动通过cdev_add把一个字符设备对象加入到系统时,需要一个设备号来标记该对象在cdev_map中的位置信息;当在应用程序中打开一个设备文件时,系统将会根据设备文件对应的inode->i_rdev信息在cdev_map中寻设备。所以务必要保证设备文件节点中的inode->i_rdev数据和设备驱动程序中使用的设备号完全一致。

Linux内核设备编号分为主设备编号和次设备编号, 前者为dev_t的高12位, 后者为dev_t的低20位。 比如下列操作用于从一个inode中获得主设备号和次设备号:

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

在用户层查看/proc/devices文件可以获知系统中注册的设备, 第1列为主设备号, 第2列为设备名。当然查看/dev目录可以获知系统中包含的设备文件, 日期的前两列给出了对应设备的主设备号和次设备号,比如:

主设备号是与驱动对应的概念, 同一类设备一般使用相同的主设备号, 不同类的设备一般使用不同的主设备号(但是也不排除在同一主设备号下包含有一定差异的设备) 。 因为同一驱动可支持多个同类设备, 因此用次设备号来描述使用该驱动的设备的序号, 序号一般从0开始。

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/shuma/857208.html

相关标签:无
上传时间: 2024-02-10 09:41:10
留言与评论(共有 10 条评论)
本站网友 东方小镇
19分钟前 发表
inode->i_rdev
本站网友 瀚银科技
16分钟前 发表
flags | LOOKUP_REVAL);restore_nameidata();return filp; }long do_sys_open(int dfd
本站网友 南山丽景度假酒店
8分钟前 发表
它首先通过kboj_lookup在cdev_map中用inode->i_rdev来查设备号所对应的设备new,这里展示了设备号的作用
本站网友 南山茂业
23分钟前 发表
最后修改时间等信息
本站网友 妊娠期糖尿病食谱
16分钟前 发表
int flags
本站网友 mmmpp
1分钟前 发表
在系统调用流程中会调用如下函数:struct file *do_filp_open(int dfd
本站网友 吴志阳
27分钟前 发表
static void __fput(struct file *file) {struct dentry *dentry = file->f_path.dentry;struct vfsmount *mnt = file->f_;struct inode *inode = file->f_inode;fmode_t mode = file->f_mode;if (unlikely(!(file->f_mode & FMODE_OPEED)))goto out;might_sleep();fsnotify_close(file);/** The function eventpoll_release() should be the first called* in the file cleanup chain.*/eventpoll_release(file);locks_remove_file(file);ima_file_free(file);/*注意这里的FASYC标志*/if (unlikely(file->f_flags & FASYC)) {if (file->f_op->fasync)file->f_op->fasync(-1
本站网友 糙米的功效与作用
30分钟前 发表
在上述do_filp_open()最后会调用到inode->i_fop->open函数,即chrdev_open()函数,该函数非常重要
本站网友 艾菲迪克是什么
0秒前 发表
op