首页 > 系统相关 >【Linux】文件操作、系统IO相关操作、inode和输入输出重定向

【Linux】文件操作、系统IO相关操作、inode和输入输出重定向

时间:2025-01-23 18:58:11浏览次数:3  
标签:文件 stdout int Linux fd IO printf include inode

头像
⭐️个人主页:@小羊
⭐️所属专栏:Linux
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录


1、理解文件

1.1 狭义理解

  • 文件在磁盘里
  • 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
  • 磁盘是外设(即是输出设备也是输入设备)
  • 对磁盘上所有文件的操作本质都是对外设的输入和输出,简称IO

1.2 广义理解

  • Linux下一切皆文件(键盘、显示器、网卡、磁盘……这些都是抽象化的过程)。

1.3 文件操作

  • 对于OKB的空文件是占用磁盘空间的
  • 文件 = 文件属性 + 文件内容
  • 所有的文件操作本质是文件内容操作和文件属性操作

1.4 系统角度

  • 对文件的操作本质是进程对文件的操作
  • 磁盘的管理者是操作系统
  • 文件的读写本质不是通过C/C++的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的

2、系统文件IO

2.1 文件相关操作

C语言中文件操作,在操作一个文件之前我们首先要打开它,那么在学了一段时间操作系统后,你知道在操作一个文件之前为什么要先打开吗?
文件存储在磁盘上,CPU执行进程访问文件,而CPU不能直接访问磁盘,所以对于存储在磁盘上的文件如果要被进程访问,首先应该加载到内存中,所以打开文件的过程就是将文件从磁盘加载到内存。

以“w”方式打开文件,这个文件首先被清空然后再写入,也就是如果我们只打开文件但不写入就直接关闭,这个文件就会被清空。
前面我们学习过输出重定向操作符“>”,> file:也是先打开文件然后才操作,如果文件不存在则创建,如果文件存在则清空。

在学习C语言文件操作的时候我们就知道,任何一个程序在启动的时候默认要打开三个文件流stdinstdoutstderr,C++中也有cincoutcerr,其他语言也会支持类似的特性,那么是谁打开呢?通过前面的学习不难推测出是进程默认会打开三个输入输出流。

下面是几种往显示器上输出的方式:

在这里插入图片描述

我们所用的C文件接口,底层一定是封装对应的文件类系统调用,C库函数有:fopenfclosefwritefread,对应系统调用接口有:openclosewriteread。其中fopen底层封装的是系统调用接口openfclose封装的是close等。

在这里插入图片描述

通过一个参数可以传递多个信息:

#include <stdio.h>

#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)

void test(int flags)
{
	if (flags & ONE)
	{
		printf("one\n");
	}
    if (flags & TWO)
	{
		printf("two\n");
	}
	if (flags & THREE)
	{
		printf("three\n");
	}
	if (flags & FOUR)
	{
		printf("four\n");
	}
	if (flags & FIVE)
	{
		printf("five\n");
	}
}

int main()
{
	test(ONE);
	printf("\n");
	test(ONE | TWO);
	printf("\n");
	test(ONE | TWO | THREE);
	printf("\n");
	test(ONE | TWO | THREE | FOUR);
	printf("\n");
	test(ONE | TWO | THREE | FOUR | FIVE);
	printf("\n");
	return 0;
}

在这里插入图片描述

open选项:
在这里插入图片描述
在这里插入图片描述

简单touch命令:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	open(argv[1], O_CREAT | O_WRONLY, 0666);
    return 0;
}

在这里插入图片描述


2.2 文件描述符

open返回值(文件描述符):
在这里插入图片描述

在这里插入图片描述
从上面的测试中可以看到,默认打开几个文件,文件描述符为什么从3开始呢?
其中的原因文章开头就已经提到过,因为一个程序在启动前默认会打开三个文件流stdinstdoutstderr,怎么证明这件事呢?

yjz@hcss-ecs-8f13:~/linux/text_4.11.15$ ./filecode
hello world
hello world
Are you ok?
yjz@hcss-ecs-8f13:~/linux/text_4.11.15$ cat filecode.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
	char buffer[100];
	ssize_t s = read(0, buffer, sizeof(buffer));
	if (s > 0) //实际读到的字节大小
	{
		buffer[s - 1] = 0;
		printf("%s\n", buffer);
	}

	const char *str = "Are you ok?\n";
	ssize_t w = write(1, str, strlen(str)); 
	return 0;
}

可以看出stdin文件流的文件描述符是0, stdout文件流的文件描述符是1。

在这里插入图片描述
通过上面的草图,所以fd(文件描述符)究竟是什么呢?其实fd就是数组下标,我们通过这个下标来管理文件,在系统层面,fd是访问文件的唯一方式。

yjz@hcss-ecs-8f13:~/linux/text_4.11.15$ make
gcc -o filecode filecode.c
yjz@hcss-ecs-8f13:~/linux/text_4.11.15$ ./filecode
stdin : 0
stdout : 1
stderr : 2
pf : 3
yjz@hcss-ecs-8f13:~/linux/text_4.11.15$ cat filecode.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
	printf("stdin : %d\n", stdin->_fileno);
	printf("stdout : %d\n", stdout->_fileno);
	printf("stderr : %d\n", stderr->_fileno);

	FILE *pf = fopen("log.txt", "w");
	printf("pf : %d\n", pf->_fileno);
	
	return 0;
}

FILE是C语言封装的一个文件流类型的结构体。

系统调用接口writeread已经能实现往显示器文件中读和写,为什么语言(以C语言为例)还要做封装呢?
如果我们想在显示器上打印整型1234,而显示器只认字符,所以我们就需要先把1234转换为4个字符,在通过write写到显示器上,这样很麻烦,所以通过对write封装,得到一个printf函数,我们想打印任何类型都可以通过指定打印类型就可以完成打印,底层的复杂转换就不需要我们自己动手了。
在这里插入图片描述

进程打开文件,需要给文件分配新的fd,fd的分配规则是分配最小的、没有被使用的fd。

int main()
{
   	close(1);
	int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
	int fd2 = open("log2.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
	int fd3 = open("log3.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
	int fd4 = open("log4.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);

	printf("fd1 : %d\n", fd1);
	printf("fd2 : %d\n", fd2);
	printf("fd3 : %d\n", fd3);
	printf("fd4 : %d\n", fd4);

	fflush(stdout);

	close(fd1);
	close(fd2);
	close(fd3);
	close(fd4);
	
	return 0;
}

在这里插入图片描述


2.3 重定向

printf函数只认文件描述符fd,默认向显示器文件(fd为1)中写入,如果我们手动关闭显示器文件,再打开其他文件,则空的fd为1的这个位置就会分配给别的文件,所以printf就会写入到这个文件中。
向上面这种改变file_struct中文件指针的过程就是重定向的过程。

在这里插入图片描述

这是因为./mypipe默认是向stdout中写入,所以./mypipe > test.txt只是改为向test.txt中写入,而stderr本身也是标准文件流,不会被重定向到test.txt中。
那么这里就会有一个疑问,为什么C/C++标准输入是一个,而标准输出有两个呢?(stdout/stderrcout/cerr

在这里插入图片描述
注意:这里的2和>之间不可以有空格,2>在一起的时候才表示错误输出。

输出时可以将正确和错误的信息分离,方便我们做调式。

系统调用接口dup2可以实现输出重定向:
在这里插入图片描述

int main()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);

	dup2(fd, 1);

	printf("printf fd : %d\n", fd);
	fprintf(stdout, "fprintf fd : %d\n", fd);
	fputs("fputs fd\n", stdout);
	const char *str = "fwrite fd\n";
	fwrite(str, 1, strlen(str), stdout);

	return 0;
}

在这里插入图片描述

对于stdout来说它管只找fd为1的文件,本来fd为1的文件是显示器,通过dup2系统调用将fd为1的位置分配给文件log.txt,最终我们向stdout中输出就输出到了文件log.txt中。

dup2可以实现输入重定向:

int main()
{
    int fd = open("log.txt", O_RDONLY);
	
	dup2(fd, 0);

	char buffer[1024];
	size_t r = read(0, buffer, sizeof(buffer));
	if (r > 0)
	{
		buffer[r] = 0;
		printf("stdin redir: \n%s\n", buffer);
	}

	return 0;
}

在这里插入图片描述

本来0对应的是键盘,通过dup2重定向后从文件log.txt中读取。


本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

头像

标签:文件,stdout,int,Linux,fd,IO,printf,include,inode
From: https://blog.csdn.net/2301_78843337/article/details/145327972

相关文章

  • 【Linux】理解Linux中一切皆文件、缓冲区、ext2文件系统、软硬链接
    ⭐️个人主页:@小羊⭐️所属专栏:Linux很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎~目录1、如何理解在Linux中一切皆文件?1.1概述1.2文件类型1.3优势2、缓冲区2.1为什么要引入缓冲区?2.2缓冲类型3、Ext系列文件系统4、软硬链接1、如何理解在Linux......
  • 在使用prism的region跳转时,出现The region manager does not contain the MainViewReg
    在做新项目时,把原来的旧项目拷过来进行重构,上一个项目进行region填充是没有问题的,这次再次进行测试出现了这样的问题,于是在网上寻找答案。错误给出来的很明显,regionManager没有一个叫做MainViewRegionName的区域,想当然的就手动添加,进行刷新,这种方法参考Prism区域异常问题分析(......
  • pyinstaller package fastapi application with Gunicorn
    使用Gunicorn部署FastAPI应用程序:快速而强大的组合https://juejin.cn/post/7348003004123463717本地部署本地开发调试过程中,我通常是这样启动Fastapi服务的在终端中运行:uvicornmain:app--host0.0.0.0--port80当然,也可以python脚本启动:importuvicorn​uvi......
  • Linux 进程环境变量:深入理解与实践指南
      ......
  • 【Rust自学】13.9. 使用闭包和迭代器改进IO项目
    13.9.0.写在正文之前Rust语言在设计过程中收到了很多语言的启发,而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。在本章中,我们会讨论Rust的一些特性,这些特性与许多语......
  • 基于Python和uiautomation的Windows桌面自动化操作方案
    基于Python和uiautomation的Windows桌面自动化操作方案在日常开发和测试过程中,我们经常需要对Windows桌面应用程序进行自动化操作。本文将记录如何使用uiautomation库来实现这些操作,同时为了避免对主机的正常使用造成干扰,借助VMwareWorkstation虚拟机环境进行操作,并结合实际案例......
  • 蓝牙芯片HS6621CG-C丰富IO口资源集成红外编码语音采集功能超高性能已移植NFC例程支持
    2.4Ghz的soc蓝牙5.1芯片HS6621CC语音遥控/智能门锁M4F内核兼容NORDIC的2.4G私有协议超低功耗,丰富IO口资源集成红外编码语音采集功能超高性能已移植NFC例程支持语音蓝牙遥控智能门锁智能家居等应用简介:HS6621CxC是一种功耗优化的真正的片上系统(SOC)解决方案,既适用于蓝牙低能耗,也......
  • Linux捣鼓记录:使用 Preload 加快应用程序启动
    简介Preload是由BehdadEsfahbod编写的程序,它作为一个守护进程运行,并使用马尔可夫链统计程序的使用情况;在计算机空闲时,使用频率较高的程序的文件会加载到内存中。这会加快程序的启动时间,因为需要从磁盘读取的数据更少。安装终端执行以下命令安装Preload:sudoaptinstallpr......
  • 15 分布式锁和分布式session
    在java中一个进程里面使用synchronized在new出来对象头信息中加锁,如果是静态方法中在加载的类信息中加锁(我们在锁的原理中讲过)。如果使用lock加锁可以自己指定。这些都是在同一个进程空间中的操作。如果在分布式环境中由于程序不在一个进程空间,就没办法使用这些原子性的元......
  • 自学StableDiffusion,一般人我还是劝你算了吧
    本期将从以下4个模块逐步讲解:......