关于系统调用,我们必须清楚linux内存分为用户空间和内核空间,当我们进行程序应用时用的是用户空间,当我们要打开文件和设备的时候就会使用系统调用来访问内核空间,这时候相当中断一样,我们进入内核空间,当 处理完以后才回到用户空间。
以下分析个例子关于open的系统调用
int fd = open("abc.txt",O_CREAT);
if(fd != NULL)
{
printf("file open:fd\n",fd);
}
close(fd);
这时候,当我们使用open系统调用的时候,就会进入内核空间,访问结构体task_struct,而task_struct里面有一个files_struct,
files_struct
{
atomic_t count;// count为文件表files_struct的引用计数
fd[1024];
}
此时返回出来是一个fd[3]
而我们输出的结果正是file open: 3
总的来说,当我们使用系统调用的时候,它会去找task结构体,然后找到我们的file结构体,然后再返回出来fd。
下面来源: https://www.cnblogs.com/zengyiwen/p/5755186.html
每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行,但你可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改。Linux中专门用了一个数据 结构file来保存打开文件的文件位置,这个结构称为打开的文件描述(open file description)。这个数据结构的设置是煞费苦心的,因为它与进程的联系非常紧密,可以说这是VFS中一个比较难于理解的数据结构,file结构中主要保存了文件位置,此外,还把指向该文件索引节点的指针也放在其中。file结构形成一个双链表,称为系统打开文件表,其最大长度是NR_FILE,在fs.h中定义为8192。
struct file- {
struct list_head f_list; /*所有打开的文件形成一个链表*/struct dentry *f_dentry; /*指向相关目录项的指针*/struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/struct file_operations *f_op; /*指向文件操作表的指针*/mode_t f_mode; /*文件的打开模式*/loff_t f_pos; /*文件的当前位置*/unsigned short f_flags; /*打开文件时所指定的标志*/unsigned short f_count; /*使用该结构的进程数*/unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数*/int f_owner; /* 通过信号进行异步I/O数据的传送*/unsigned int f_uid, f_gid; /*用户的UID和GID*/int f_error; /*网络写操作的错误码*/unsigned long f_version; /*版本号*/void *private_data; /* tty驱动程序所需 */};
内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。文件描述表中每一项都是一个指针,指向一个用于 描述打开的文件的数据块———file对象,file对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的 file对象。需要注意的是,file对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的file对象,从而共享这个打开的文件。 file对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同 一个file对象的进程.
file对象中包含一个指针,指向dentry对象。dentry对象代表一个独立的文件路径,如果一个文件路径被打开多次,那么会建立多个file对象,但它们都指向同一个dentry对象。
dentry对象中又包含一个指向inode对象的指针。inode对象代表一个独立文件。因为存在硬链接与符号链接,因此不同的dentry 对象可以指向相同的inode对象.inode 对象包含了最终对文件进行操作所需的所有信息,如文件系统类型、文件的操作方法、文件的权限、访问日期等。
打开文件后,进程得到的文件描述符实质上就是文件描述符表的下标,内核根据这个下标值去访问相应的文件对象,从而实现对文件的操作。
注意,同一个进程多次打开同一个文件时,内核会创建多个file对象。
当进程使用fork系统调用创建一个子进程后,子进程将继承父进程的文件描述符表,因此在父进程中打开的文件可以在子进程中用同一个描述符访问。
---------------------------------------------------------------open解析---------------------------------------------------
int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);
前一个是glibc封装的函数,后一个是系统调用
open源码追踪:
long do_sys_open(int dfd, const char __user *filename, int flags, int mode){struct open_flags op;/* flags为用户层传递的参数, 内核会对flags进行合法性检查, 并根据mode生成新的flags值赋给 lookup */int lookup = build_open_flags(flags, mode, &op);/* 将用户空间的文件名参数复制到内核空间 */char *tmp = getname(filename);int fd = PTR_ERR(tmp);if (!IS_ERR(tmp)) {/* 未出错则申请新的文件描述符 */fd = get_unused_fd_flags(flags);if (fd >= 0) {/* 申请新的文件管理结构file */struct file *f = do_filp_open(dfd, tmp, &op, lookup);if (IS_ERR(f)) {put_unused_fd(fd);fd = PTR_ERR(f);} else {/* 产生文件打开的通知事件 */fsnotify_open(f);/* 将文件描述符fd与文件管理结构file对应起来, 即安装 */fd_install(fd, f);}}putname(tmp);}return fd;}
从上面来看,打开文件,内核消耗了2种资源:文件描述符跟内核管理文件结构file
根据POSIX标准,当获取一个新的文件描述符时,要返回最低的未使用的文件描述符。Linux是如何实现这一标准的呢?
在Linux中,通过do_sys_open->get_unused_fd_flags->alloc_fd(0,(flags))来选择文件描述符,代码如下
int alloc_fd(unsigned start, unsigned flags){struct files_struct *files = current->files;//获取当前进程的对应包含文件描述符表的结构unsigned int fd;int error;struct fdtable *fdt;/* files为进程的文件表, 下面需要更改文件表, 所以需要先锁文件表 */spin_lock(&files->file_lock);repeat:/* 得到文件描述符表 */fdt = files_fdtable(files);/* 从start开始, 查找未用的文件描述符。在打开文件时, start为0 */fd = start;/* files->next_fd为上一次成功找到的fd的下一个描述符。使用next_fd, 可以快速找到未用的文件描述符;*/if (fd < files->next_fd)fd = files->next_fd;/*当小于当前文件表支持的最大文件描述符个数时, 利用位图找到未用的文件描述符。如果大于max_fds怎么办呢?如果大于当前支持的最大文件描述符, 那它肯定是未

Linux系统调用是用户空间与内核空间交互的关键,尤其在文件操作中扮演重要角色。当我们使用open系统调用时,会进入内核空间访问task_struct和files_struct。每个文件都有一个文件描述符(fd),与file结构体相关联,保存文件位置等信息。file对象形成系统打开文件表,其引用计数决定何时释放。进程通过文件描述符表访问file对象,实现文件操作。在进程复制(如fork)或系统调用(如dup、dup2)时,文件描述符会被复制或重新分配。此外,close系统调用会根据文件类型执行不同操作,释放相关资源。通过对系统调用的深入剖析,我们可以更好地理解和管理Linux系统中的文件资源。
最低0.47元/天 解锁文章
2万+

被折叠的 条评论
为什么被折叠?



