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


    fd-文件描述符

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

    标准输入标准输出标准错误本质都是文件
    stdinstdoutstderr文件在语言层的表现
    cincoutcerr同上,但是他是一个类
    012<-fd ,数组下标

    文件描述符,即open对应的返回值,本质就是:数组下标
    标准输出和标准错误都会向显示器打印,但是其实是不一样的

    类型设备文件
    标准输入键盘文件
    标准输出显示器文件
    标准错误显示器文件
    #include
    #include
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ![[Drawing 2023-11-08 11.12.05.excalidraw|900]]

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

    我们使用OS的本质:

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

    FILE

    操作系统层面,我们必须使用fd才能找到文件!
    任何语言层面访问外设或者文件,都必须经历OS

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

    #include
    FILE* fopen(const char *path,const char* mode);
    
    • 1
    • 2

    答案:

    FILE是一个结构体.该结构体内部一定要有以下字段:
    fd
    
    • 1

    证明:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    FILE是C语言标准库提供的.

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

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

    做实验->重定向的本质

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

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

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

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

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

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    重定向的原理

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

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

    现在log.txt中写入:

    123 456
    
    • 1
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果: cat log.txt:

    a=123 , b=456
    you can see me!
    you can see me!
    you can see me!
    you can see me!
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结论

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

    需求:把常规消息放一个文件,错误消息放在另一个文件
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    所以为什么要默认把1和2打开:
    就是为了把常规消息和错误消息分类开来,便于后面的调试!

    bash中重定向操作

    a.out > log.txt 2 > &1
    或者
    a.out 1>log.txt 2>err.txt
    
    • 1
    • 2
    • 3

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

    更好的写法

    int dup2(int oldfd, int newfd)
    是对数组对应下标的内容进行拷贝

    new要成为old的拷贝
    所以最终只有oldfd的内容了
    而我们最后正确重定向肯定是剩下3啊
    所以oldfd 是3
    newfd 是1

    所以代码
    dup2(fd,1)

    重定向写法:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

    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();
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    未完待续…

  • 相关阅读:
    Java类型转换、常用运算符
    【牛客网-公司真题-前端入门篇】——奇安信秋招笔试-前端-卷1
    530. 二叉搜索树的最小绝对差
    CSS选择器、修饰方式
    每天一道算法题:125. 验证回文串
    机器会思考吗?浅析ai智能体框架metagpt的思考机制
    Leetcode--剑指Offer
    【Go事】一眼看穿 Go 的集合和切片
    【9】c++设计模式——>开放封闭原则
    什么是 OpenJ9
  • 原文地址:https://blog.csdn.net/weixin_62712120/article/details/134467071