万字深剖 Linux I/O 原理

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

梅开二度🤔

在 C 语法下就早已知悉基础 IO ,其实就是耳熟能详的文件操作,说到文件操作脑子里又是一堆耳熟能详的函数接口:
在这里插入图片描述
以一个简单的写入操作为例,运行程序后当前路径下会生成对应文件,文件当中就是我们写入的内容:

#include <stdio.h>
int main()
{
   
	FILE* fp = fopen("log.txt", "w");
	if (fp == NULL){
   
		perror("fopen");
		return 1;
	}
	int count = 5;
	while (count){
   
		fputs("hello world\n", fp);
		count--;
	}
	fclose(fp);
	return 0;
}

在这里插入图片描述

当前路径🤔

文件操作我们打开文件时,如果 fopen 对象是一个未创建的对象,那么就会自动在当前路径生成一个该文件,这里就牵涉到一个 当前路径 \color{red} {当前路径} 当前路径的概念。

比如我们在刚刚写入后的 log.txt 文件进行读取:

#include <stdio.h>
int main()
{
   
	FILE* fp = fopen("log.txt", "r");
	if (fp == NULL){
   
		perror("fopen");
		return 1;
	}
	char buffer[64];
	for (int i = 0; i < 5; i++){
   
		fgets(buffer, sizeof(buffer), fp);
		printf("%s", buffer);
	}
	fclose(fp);
	return 0;
}

在这里插入图片描述
该情况下,我们在总目录下运行可执行程序 myproc,那么该可执行程序创建的 log.txt 文件会出现在总目录下:
在这里插入图片描述
这是否意味着 “当前路径” 就是指的 “当前可执行程序所处的路径”
我们不妨直接去查看他的路径对吧,我们用 ps -axj | head -1&&ps -axj | grep myproc | grep -v grep 可以查看可执行程序的 PID :

在这里插入图片描述
然后我们再利用 PID 来查看执行路径 sudo ls /proc/8189 -al,因为我在总目录 ~ 下,因此这里我使用弄了 sudo 命令进行管理员权限查找:

在这里插入图片描述
这里的 cwdexe软链接,我们下文细谈,所以实际上,当前路径不是指可执行程序所处的路径,而是指该可执行程序运行成为进程时我们所处的路径

三大输入输出流🤔

我们一直贯彻一个理念就是 Linux 下 一切皆文件,我们肉眼可见的显示屏输出的数据,本质是电脑读取键入的字符,电脑从“电脑文件” 读取字符,电脑再对“显示器文件”进行输出

那么问题来了,在我们对这些“文件”进行读写之前,为什么我们没有一个文件打开的操作呢?

要知道打开文件一定是进程运行的时候打开的,而任何进程在运行的时候都会默认打开三个输入输出流,即标准输入流、标准输出流、标准错误流,就是 C 当中的 stdin、stdout、stderr;C++当中的 cin、cout、cerr,其他所有语言都有类似的概念。实际上这种特性并不是某种语言所特有的,而是由操作系统所支持的

其中,标准输入流对应的设备就是键盘,标准输出流和标准错误流对应的设备都是显示器。查看 man 手册我们不难发现,stdin、stdout、stderr 这仨 byd 其实就是 FILE* 类型的

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

我们之所以可以调用 scanf 、printf 这类的函数向键盘显示器进行输入输出操作,其实就是程序运行时,操作系统默认使用 C 的接口将这三个输入输出流打开。试想我们使用 fputs 函数时,将其第二个参数设置为 stdout,此时 fputs 函数会不会直接将数据显示到显示器上呢?

fputs("hello stdin\n", stdout);

答案是肯定的,因为此时就是用 fputs 向显示器文件进行了写入操作

系统文件 I/O🤔

相比 C,C++ 这些语言的接口,操作系统也有一套文件操作的接口,而且操作系统的接口更加贴近底层,而其他语言的接口本质上也是对操作系统的接口的封装,我们在 Linux、Windows 平台下运行 C 代码时,C 库函数就是对 Linux、Windows 系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发

open😋

函数原型:

int open(const char *pathname, int flags, mode_t mode);
  1. pathname 表示要打开或创建的目标文件。

若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建。
若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建,注意当前路径的含义

  1. flags 表示打开文件的方式。

flags 的可调用参数有如下这些:
在这里插入图片描述
flags 可以同时传入多个参数选项,这些选项用 “或” 运算符连接。例如以只写的方式打开文件时,文件不存在就应该自动创建文件,则参数设置如下

O_WRONLY | O_CREAT

我们基于与运算的最根本原因是因为: 这些宏定义选项的共同点就是它们的二进制序列当中有且只有一个比特位是 1 \color{red} {这些宏定义选项的共同点就是它们的二进制序列当中有且只有一个比特位是 1} 这些宏定义选项的共同点就是它们的二进制序列当中有且只有一个比特位是1,除了 O_RDONLY 序列为全 0,表示他为默认选项,且为 1 的比特位是各不相同的,这样一来函数内部就可以通过使用与运算来判断是否设置了某一选项

int open(arg1, arg2, arg3){
   
	if (arg2&O_RDONLY){
   
		//设置了O_RDONLY选项
	}
	if (arg2&O_WRONLY){
   
		//设置了O_WRONLY选项
	}
	if (arg2&O_RDWR){
   
		//设置了O_RDWR选项
	}
	if (arg2&O_CREAT){
   
		//设置了O_CREAT选项
	}
	//...
}
  1. mode,表示创建文件的默认权限,在不创建文件时,此选项可以不设置。

我们将mode设置为 0666,则文件创建出来的权限如下,按理说本来应该是 :
在这里插入图片描述
但是不要忘了,Linux 系统设有 umask 权限掩码,文件的真正权限计算方法是:mode &( ~umask),umask 的默认值应该是 0002,所以在我们自己设置的权限下应该减去 umask 得到 0664,即:

在这里插入图片描述

当然,如果想绕开 umask ,直接使用我们第一手的设置,那么我们可以直接将 umask 进行置 0 操作

umask(0);

open 返回值🤔

open 的返回值其实是新打开文件的文件描述符 fd,我们这里尝试一次打开多个文件,然后分别打印它们的文件描述符:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
   
	umask(0);
	int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666)
  • 123
    点赞
  • 127
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔乔家的龙龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值