【Linux】C文件系统详解(二)——什么是fd文件描述符以及理解“一切皆文件“

文章目录

fd-文件描述符

任何一个进程,在启动的时候,默认会打开当前进程的三个文件

标准输入 标准输出 标准错误 本质都是文件
stdin stdout stderr 文件在语言层的表现
cin cout cerr 同上,但是他是一个类
0 1 2 <-fd ,数组下标

文件描述符,即open对应的返回值,本质就是:数组下标

标准输出和标准错误都会向显示器打印,但是其实是不一样的

类型 设备文件
标准输入 键盘文件
标准输出 显示器文件
标准错误 显示器文件
cpp 复制代码
#include<iostream>
#include<cstdio>

int main()
{
	//因为linux一切皆文件,所以,向显示器打印,本质就是向文件中写入
	printf("hello printf->stdout\n");	
	fprintf(stdout,"hello fprintf->stdout\n");
	fprintf(stderr,"hello fprintf->stderr\n");

	std::cout << "hello cout -> cout" << std::endl;
	std::cerr << "hello cerr -> cerr" << std::endl;
}
cpp 复制代码
int fd1 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//3
int fd2 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//4
int fd3 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//5
int fd4 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//6
int fd5 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//7
int fd6 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);//8

如何深度理解"一切皆文件"

我们使用OS的本质:

都是通过进程的方式进行操作系统的访问,在进程的角度,只能看到文件对象,而看不到底层的设备的区别,所以我们才说"Linux下一切皆文件".

FILE

操作系统层面,我们必须使用fd才能找到文件!

任何语言层面访问外设或者文件,都必须经历OS

FILE是什么?谁提供的?和我们刚刚讲的内核的struct有关系吗

cpp 复制代码
#include<stdio.h>
FILE* fopen(const char *path,const char* mode);

答案:

FILE是一个结构体.该结构体内部一定要有以下字段:
cpp 复制代码
fd

证明:

cpp 复制代码
int main()
{
	printf("%d\n",stdin->_fileno);
	printf("%d\n",stdout->_fileno);
	printf("%d\n",stderr->_fileno);
	FILE* fp = fopen(LOG,"w");
	printf("%d\n",fp->_fileno);
}
FILE是C语言标准库提供的.

我们平时安装VS2019,是在安装IDE环境以及对应语言的库和头文件

FILE和我们刚刚讲的内核的struct没有关系,最多就是上下层的关系

做实验->重定向的本质

第一个实验->文件描述符的分配规则

把fd为3的文件关闭以后,新的文件fd应该是什么

cpp 复制代码
int main()
{
	close(0);//fclose(stdin)
	close(2);//fclose(stderr)
	
	int fd1 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
	int fd2 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
	int fd3 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
	int fd4 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
	int fd5 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);
	int fd6 = open(LOG,O_WRONLY | O_CREAT | O_TRUNC,0666);

	printf("%d\n",fd1);0
	printf("%d\n",fd2);2
	printf("%d\n",fd3);3
	printf("%d\n",fd4);4
	printf("%d\n",fd5);5

	return 0;
}

进程中,文件描述符的分配规则:

最小的,没有被使用的数组元素,分配给新文件

第二个实验->输出重定向

cpp 复制代码
int main()
{
	fclose(1);
	int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUC, 0666);
	//此时log.txt的fd是'1'
	//但是上层结构不知道这个变化,他只知道要写进fd=1的文件中
	printf("you can see me!\n");//本来是指向stdout -> 1的,但是stdout变成了log.txt
	printf("you can see me!\n");
	printf("you can see me!\n");
	printf("you can see me!\n");
	printf("you can see me!\n");
	
	return 0;
}

结果:打印不到屏幕,但是打印到了log.txt
printf("",);不是认stdout,而是认fd==1的文件描述符

重定向的原理

在上层无法感知的情况下,在OS内部,更改进程对应的文件描述符表中,特定下标的指向!!

第三个实验->输入重定向

现在log.txt中写入:

txt 复制代码
123 456
cpp 复制代码
int main()
{
	fclose(0);
	int fd = open(LOG, O_RDONLY | O_CREAT | O_TRUC, 0666);//fd=0
	int a,b;
	scanf("%d %d",&a,&b);
	printf("a=%d , b=%d\n",a,b);
	return 0;
}

结果: cat log.txt:
a=123 , b=456

第四个实验->追加重定向

cpp 复制代码
int main()
{
	close(1);//标准输出
	int fd = open(LOG, O_RDONLY | O_CREAT | O_APPEND, 0666);
	printf("you can see me!\n");//从屏幕(stdout)上追加到fd中
	printf("you can see me!\n");
	printf("you can see me!\n");
	printf("you can see me!\n");
}

结果: cat log.txt:

txt 复制代码
a=123 , b=456
you can see me!
you can see me!
you can see me!
you can see me!

结论

所以stdout cout->1,他们都是向1号文件描述符对应的文件打印

stderr cerr ->2 ,他们都是向2号文件描述符对应的文件打印
输出重定向,只改的是1号对应的指向,对2号不影响

需求:把常规消息放一个文件,错误消息放在另一个文件
cpp 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#define LOG "log.txt"
#define LOG_NORMAL "logNormal.txt"
#define LOG_ERROR "logError.txt"

int main()
{
	close(1);
	int fd = open(LOG_NORMAL, O_WRONLY | O_CREAT | O_APPEND, 0666);
	close(2);
	int fd = open(LOG_ERROR, O_WRONLY | O_CREAT | O_APPEND, 0666);

	printf("hello printf->stdout\n");	
	fprintf(stdout,"hello fprintf->stdout\n");
	fprintf(stderr,"hello fprintf->stderr\n");

}

所以为什么要默认把1和2打开:

就是为了把常规消息和错误消息分类开来,便于后面的调试!

bash中重定向操作

cpp 复制代码
a.out > log.txt 2 > &1
或者
a.out 1>log.txt 2>err.txt

2 > &1

把1里面的内容,写到2下标的内容里

更好的写法

int dup2(int oldfd, int newfd)

是对数组对应下标的内容进行拷贝

new要成为old的拷贝

所以最终只有oldfd的内容了

而我们最后正确重定向肯定是剩下3啊

所以oldfd 是3

newfd 是1

所以代码
dup2(fd,1)

重定向写法:

cpp 复制代码
int main ()
{
	int fd = open(LOG_NORMAL, O_WRONLY | O_CREAT | O_APPEND, 0666);
	if(fd < 0)
	{
		perrer("open");
		return 1;
	}
	dup2(fd,1);
	printf("hello world,hello lx\n");
	close(fd);
}

就是打开文件,之后使用dup2就行

让我们自己的程序支持重定向:

cpp 复制代码
enum redir{
	REDIR_INPUT = 0,
	REDIR_OUTPUT,
	REDIR_APPEND,
	REDIR_NONE
};

char* checkdir(char commandstr[],redir &redir_type);
{
	//1.监测是否有 > < >> 
	//2.如果有,要根据> < >> 设置redir_type = ?
	//3.将符号改成\0,分成两部分
	//保存文件名,并返回
	//如果不满足,直接返回
}

int main()
{
	while(1)
	{
			redir redir_type = REDIR_NONE
		//...
		char* filename = NULL;
		char* filename = checkdir(commandstr,&redir_type);
		if(*filename)
		{
			//1.存在文件
			//2.获取redir_type
		}
		//遍历,看是否有> < >>,这三个字符
		//前半部分执行后续
		//把这三个字符变成\0,将后面的字符串打开文件
		//dup2(fd,1);
	
	
		//...
		if(id == 0)
		{
			if(redir_typr != REDIR_NONE)
			{
				dup2();
			}
		}
	}
}

未完待续...

相关推荐
重生之我在20年代敲代码36 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
爱上语文37 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people41 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
qmx_072 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
小安运维日记2 小时前
Linux云计算 |【第四阶段】NOSQL-DAY1
linux·运维·redis·sql·云计算·nosql
为风而战2 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
技术无疆4 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
2401_858286114 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
CoolTiger、5 小时前
【Vmware16安装教程】
linux·虚拟机·vmware16
jiao000015 小时前
数据结构——队列
c语言·数据结构·算法