Linux文件操作-open函数(源码解析)


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


前述

近期在看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

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
adc_keys_probe函数Linux内核中与ADC按键相关的设备树探测函数。在设备树中,如果有ADC按键的相关信息(如所使用的ADC控制器、引脚等),Linux内核会自动调用该函数进行探测。 其主要功能包括: 1. 读取设备树中ADC按键节点的相关信息,如所用ADC控制器和引脚号; 2. 根据以上信息初始化ADC控制器,并将其与对应的GPIO引脚进行绑定; 3. 注册Linux输入子系统的按键输入设备,并将其与初始化好的ADC控制器进行关联。 下面是该函数的代码实现: static int adc_keys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct adc_keys_platform_data *pdata = dev_get_platdata(dev); const char *adc_name = pdata ? pdata->adc_name : NULL; struct input_dev *input_dev; struct adc_keys *keys; int ret, i; if (!adc_name) { dev_err(dev, "no ADC controller specified in platform data\n"); return -EINVAL; } input_dev = devm_input_allocate_device(dev); if (!input_dev) return -ENOMEM; keys = devm_kzalloc(dev, sizeof(*keys), GFP_KERNEL); if (!keys) return -ENOMEM; platform_set_drvdata(pdev, keys); keys->input = input_dev; keys->adc = devm_iio_channel_get(&pdev->dev, "iio"); if (IS_ERR(keys->adc)) { dev_err(dev, "failed to get ADC channel\n"); ret = PTR_ERR(keys->adc); goto err_free_mem; } input_dev->name = pdev->name; input_dev->phys = "keys/input0"; input_dev->id.bustype = BUS_HOST; input_dev->id.vendor = 0x0001; input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; input_set_capability(input_dev, EV_KEY, KEY_POWER); input_set_capability(input_dev, EV_KEY, KEY_VOLUMEUP); input_set_capability(input_dev, EV_KEY, KEY_VOLUMEDOWN); keys->min_val = pdata ? pdata->min_val : ADC_KEYS_DEFAULT_MAX; keys->max_val = pdata ? pdata->max_val : ADC_KEYS_DEFAULT_MIN; ret = input_register_device(input_dev); if (ret) { dev_err(dev, "failed to register input device\n"); goto err_free_mem; } ret = adc_keys_init_dev(keys, adc_name); if (ret) { dev_err(dev, "failed to init ADC controller\n"); goto err_free_dev; } for (i = 0; i < ARRAY_SIZE(keys->keymap); i++) { ret = input_register_keycode(input_dev, keys->keymap[i].type, keys->keymap[i].code, NULL); if (ret) { dev_err(dev, "failed to register input keycode\n"); goto err_free_dev; } } ret = adc_keys_set_timer_interval(keys); if (ret) dev_warn(dev, "Failed to initialize the polling timer\n"); dev_info(dev, "registered ADC keys input device\n"); return 0; err_free_dev: input_unregister_device(input_dev); err_free_mem: return ret; } 该函数通过dev_get_platdata函数读取设备树节点的平台数据信息,获取相关参数。接下来,分别进行输入子系统的相关初始化、iio_channel获取、ADC控制器的初始化、按键注册及关联操作,最终成功时输出相关信息并返回0,失败则进行相应的错误处理操作

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值