关于系统调用,我们必须清楚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怎么办呢?如果大于当前支持的最大文件