理论是灰色的,实践之树长青🌲 ——恩格斯


前述

近期在看Linux内核相关,且之前面试有遇到问**“open函数调用原理”**的问题,今天在这里做一下总结记录。

open函数

open函数主要包含以下两类:

int open(const char *pathName, int flags); // 打开文件
int open(const char *pathName, int flags, mode_t mode); // 创建文件

open函数参数介绍:
  • pathname:表示要打开的文件路径。
  • flags:用于指示打开文件的选项,常用的有O_RDONLY、O_WRONLY和O_RDWR。这三个选项必须有且只能有一个被指定。为什么O_RDWR!=O_RDONLY|O_WRONLY呢?Linux环境中,O_RDONLY被定义为0,O_WRONLY被定义为1,而O_RDWR却被定义为2。除了以上三个选项,Linux平台还支持更多的选项,APUE中对此也进行了介绍。
  • ·mode:只在创建文件时需要,用于指定所创建文件的权限位(还要受到umask环境变量的影响)。
open操作流程

open函数就是打开一个文件,在操作系统中就是确定进程操作哪个文件,这个过程主要由两件事构成:

  1. 将用户进程 task_struct 中的filp[20] 与内核中的file_table[64] 进行挂接。
  2. 将用户进程需要打开的文件对应的inode节点在file_table[64] 中进行登记。

task_struct :进程结构
// 硬编码部分(一般不做更改)
long state;
long counter;
long priority;
long signal;
struct sigaction sigaction[32];
long blocked; // 信号的位图
// 各种类型字段
int exit_code;
unsigned long start_code, end_code, end_data, brk, start_stack;
long pid, father, pgrp, session, leader;
unsigned short uid, euid, suid;
unsigned short gid, egid, sgid;
long alarm;
long utime, stime, cutime, cstime, start_time;
unsigned short used_match;
// 文件系统信息
int tty;
unsigned short umask;
struct m_inode * pwd;
struct m_inode * root;
struct m_inode * executable;
unsigned long close_on_exec;
struct file * filp[NR_OPEN]
// tss字段
struct tss_struct tss;

  • 操作系统根据用户进程的需求来操作文件,内核通过 *filp[20] 掌控每一个进程可以打开的文件,既可以打开多个不同的文件,也可以多次打开同一个文件,每打开一次文件(不论是否是同一个文件),就要在 *filp[20] 占用一个项记录指针,所以每一个进程可以同时打开文件的次数不能超过20次;
  • 操作系统中的file_table[64] 是管理所有进程打开文件的数据结构, 不但记录了不同进程打开不同的文件,也记录了不同进程打开同一个文件,甚至记录了同一个进程多次打开一个文件。与filp[20] 类似,只要打开一次文件,就要在file_table[64] 中记录;
  • 文件中的i节点是记载文件属性的最关键的数据结构,在操作系统中i节点和文件是一一对应的,找到i节点,就意味着找到唯一的文件。内核通过inode_table[32] 掌控正在使用的文件i节点,每个被使用的文件i节点都要记录在其中。

所以打开文件的本质就是要建立 *filp[20] file_table[64] inode_table[32] 三者之间的关系。

在这里插入图片描述

open源码

打开文件的具体操作是在进程中调用open()函数实现,该函数最终映射到sys_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(file_table[64])
			struct file *f = do_filp_open(dfd, tmp, &op, lookup);
			if(IS_ERR(f)){
				put_unused_fd(fd);
				fd = PTR_ERR(fd);
			} else {
				// 打开文件的通知事件
				fsnotify_open(f);
				// 将文件描述符和文件管理结构file对应起来(file_table[64])
				fd_install(fd, f);
			}
		}
		putname(tmp);
	}
	return fd;
}

// 这里的struct file结构如下:
struct file {
	unsigned short f_mode;  // 文件操作模式
	unsigned short f_flags;  //文件打开、控制标志
	unsigned short f_count;  //文件句柄数
	struct m_inode * f_inode;  //指向文件对应的i节点
	off_t f_pos;   // 文件位置(读写偏移值) 
}

所以sys_open 函数返回的是文件描述符fd,当用户使用fd与内核交互时,内核可以用fd从fdt->fd[fd]中得到内部管理文件的结构struct file,然后通过i节点进一步获取到对应的文件;

欢迎大家一起关注交流学习哈!
个人GitHub:https://github.com/SpecialAll

Logo

更多推荐