【Linux】基础IO(万字详解) —— 系统文件IO | 文件描述符fd | 重定向原理(上)

简介: 【Linux】基础IO(万字详解) —— 系统文件IO | 文件描述符fd | 重定向原理(上)

0.感性认识一切皆文件


linux认为,一切皆文件。

对文件而言:


曾经理解的文件:read 、 write

显示器:printf/cout ——》 一种write

键盘:scanf/cin ——》一种read

0a2653c851af460fa595bd959398a8f1.png

🧐什么叫做文件呢?


站在系统的角度,能够被input读取,或者能够被output写出的设备就叫文件!

侠义上的文件:普通的磁盘文件

广义上的文件:显示器,键盘,网卡,磁盘,显卡,声卡,几乎所有的外设,都可以称为文件


1. 回顾C中的文件操作


🥑C读写文件


文件操作:


首先要打开文件:打开成功,返回文件指针;打开失败,返回NULL

最后要关闭文件

FILE *fopen(const char *path, const char *mode);//路径 + 打开方式
int fclose(FILE *fp);


💦C写文件


们可以fputs/fgets以字符串形式读写;也可以fprintf/fscanf格式化读写


int fputs(const char *s, FILE *stream);  向特定文件流写入字符串


int fprintf(FILE *stream, const char *format, ...);


什么叫做当前路径?


当一个进程运行起来的时候,每个进程都会记录自己当前所处的工作路径


0a2653c851af460fa595bd959398a8f1.png


如果以w模式打开文件,默认把原始内容清掉,再写入(类似输入重定向)


2d65d23f6d4748949b924e4057485923.png


如果要以追加方式写,则要以"a" append模式打开文件


4cebaac233b3433da32a72337a77fc60.png


💢细节提问: 此处的strlen要不要+1?


//进行文件操作
   const char* s1 = "hello fwrite\n";
   fwrite(s1, strlen(s1), 1, fp);


不要!,\0结尾是C语言的规定,文件用遵守吗?文件保存的是有效数据!

否则就会出现乱码


0a2653c851af460fa595bd959398a8f1.png


tips:快速清空文件


>log.txt  //输入前已经清空文件,输入的又为空白


💦C读文件

fgets从特定文件流中按行读取,内容放在缓冲区。读取成功返回字符串起始地址,读失败返回NULL


char *fgets(char *s, int size, FILE *stream); //size:为缓冲区大小


2d65d23f6d4748949b924e4057485923.png


🥑关于stdin stdout stderr


C语言默认会打开三个输入输出流:stdin、stdout、stderr, 它们的类型都是FILE*,这三个东西是什么呢?


C语言把它们当做文件看待;站在系统角度,stdin对应的硬件设备是键盘、stdout对应显示器、stderr对应显示器,本质上我们最终都是访问硬件。C++中也有cin、cout、cerr,几乎所有语言都提供标准输入、标准输出、标准错误

既然fputs是向文件写入,stdout也是FILE*类型,我们是不是可以向显示器标准输出打印了?这说明显示器被看做文件(有那味了) 喊出那句话:Linux下,一切皆文件


2.系统文件 I / O


通过之前的学习,这些文件操作最终都是访问硬件(显示器、键盘、磁盘)。众所周知,OS是硬件的管理者。所有语言上对“文件”的操作,都必须贯穿操作系统。然而OS不相信任何人,访问操作系统,就必须要通过系统接口!!


open/fclose,fread/fwrite,fputs/fgets,fgets/fputs 等库函数一定需要使用OS提供的系统调用接口,接下来我们就来学习文件的系统调用接口,才能做到万变不离其宗!!


0a2653c851af460fa595bd959398a8f1.png


🌈open & close


💢通过手册查找 man 2 open


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);//路径 + 选项
int open(const char *pathname, int flags, mode_t mode);


参数说明


pathname: 要打开或创建的目标文件文件名
flags:    打开方式。传递多个标志位,下面的一个或者多个常量进行“或”运算,构成flags.
             O_RDONLY: 只读打开
             O_WRONLY: 只写打开
             O_RDWR  : 读写打开
          以上这三个常量,必须指定一个且只能指定一个
             O_CREAT : 若文件不存在,则创建它。同时需要使用mode选项,来指明新文件的访问权限
             O_APPEND: 追加写
mode:    设置默认权限信息


返回值说明:


return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
     成功: 新打开的文件描述符 
     失败: -1


💢man 2 close


#include <unistd.h>
int close(int fd);


话不多说,用起来。open如果以写入方式打开且文件不存在,需要或|上O_CREAT,这与C中以"w"模式打开完全一样 (为什么是|呢,继续看下去吧)。如果我们先不带第三个参数去实现


0a2653c851af460fa595bd959398a8f1.png


发现open并没有帮我创建新文件,因为我刚刚用的是C语言的接口,C语言的接口创建难道在系统也一样?


2d65d23f6d4748949b924e4057485923.png


可以看见文件的权限为什么是这样子的?有这个文件,要创建它,系统层面就必须指定权限是多少!,也是要用到我们的第三个参数了,我们采用权限设置的八进制方案——


我们在应用层看到一个很简单的动作,在系统接口层面甚至OS层面,可能做了非常多的动作! 我根本就不关心什么只写、创建、权限这些与系统相关的概念。语言为我们做了封装,我用就好了。

也就是:哪有什么岁月静好,有人负重前行罢了

fopen("./log.txt", "w");
int fd = open("./log.txt", O_WRONLY | O_CREAT, 0666);//umask过滤权限,可以把umask设为0


💢那第二个参数flags(int)为什么要把模式|在一起呢?


这是一种用户层给内核传递标志位的常用做法。

int有32个比特位,不重复的一个bit,就可以表示不同状态,就可以传递多个标志位且位运算效率较高。这些O_RDONLY、O_WRONLY、O_RDWR都是只有一个比特位是1的宏,并且相互不重复,这样|在一起,就能传递多个标志位

//用int中的不重复的一个bit,就可以表示不同状态
#define ONE 0x1     //0000 0001
#define TWO 0X2     //0000 0010
#define THREE 0X4   //0000 0100                                                                                
//系统内部用 & 来验证标志位是否为1
void show(int flags)//0000 0011
{
  if(flags & ONE)  printf("hello one\n");//0000 0011 & 0000 0001 为真
  if(flags & TWO)  printf("hello two\n");
  if(flags & THREE) printf("hello three\n");
}
int main()
{
  show(ONE);
  show(ONE | TWO); //0000 0001 | 0000 0010
  show(ONE | TWO | THREE);
}


进入fcntl-linux.h此文件,可以看到


0a2653c851af460fa595bd959398a8f1.png


🌈read & write


找辣个男人问问 哈哈哈

💦man 2 write


#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数:

   buf: 用户缓冲区

   count: 期望写的字节数

返回值:实际写入的字节数


又一次写入时,我们发现:


2d65d23f6d4748949b924e4057485923.png


O_TRUNC: 打开文件的时候直接清空文件


O_TRUNC:

   如果文件已经存在并且是一个常规文件,并且开放模式允许写入(即是0_RDNRor O_MwRONLY),那么它将被截断为长度为0(也就是清空文件)


4cebaac233b3433da32a72337a77fc60.png


O_APPEND: 追加文件


6de278e6d6694ce5bb08e7e842b7e74b.png


注意注意:写入文件的过程中,不需要写入\0!因为\0是C语言层面上规定字符串的结束标志,而写入文件关心的是字符串的内容,文件和语言不要搞混了


💦 man 2 read


#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
    buf: 读到的内容放在用户层缓冲区中,也就是自己定义缓冲区
    count: 期望读多少个字节
返回值:实际读多少个字节


读文件的前提:文件已经存在,不涉及创建及权限的问题,那么用两个参数的open打开文件即可


0a2653c851af460fa595bd959398a8f1.png


3.文件描述符(fd)


通过上面的练习,我发现每次成功open的fd都是3


2d65d23f6d4748949b924e4057485923.png


接下来,我们连续的打开文件,观察fd。我们知道打开文件失败返回-1,那么012去哪了呢?012消失的原因,要么是不让用,要么是被别人占用


4cebaac233b3433da32a72337a77fc60.png


事实上,当我们的程序运行起来变成进程,默认情况下,OS会帮助我们打开三个标准输入输出,012其实分别对应的就是标准输入、标准输出、标准错误


实践出真知:


0a2653c851af460fa595bd959398a8f1.png


c语言上的stdin标准输入、stdout标准输出、stderr标准错误,对应硬件设备也是键盘、显示器、显示器,这有什么关联呢?


话说回来,我们还是一直没搞懂FILE*是什么东西,FILE*open


FILE其实是一个struct结构体!是C语言库提供的,一般内部有多种成员

C文件 库函数内部 一定要调用 系统调用

在系统角度,只认识fd

所以FILE结构体里面,必定封装了fd

怎么样证明FILE结构体里面,必定封装了fd?


2d65d23f6d4748949b924e4057485923.png


🎨file descriptor(fd文件描述符)


所有的文件操作都是进程执行对应的函数,即本质上是进程对文件的操作


🔸 如果一个文件没有被打开,这个文件是在磁盘上。如果我创建一个空文件,该文件也是要占用磁盘空间的,因为文件的属性早就存在了(包括名称、时间、类型、大小、权限、用户名所属组等等),属性也是数据,所谓“空文件”是指文件内容为空


🥑文件 = 内容 + 属性。对文件的操作也是分成两类的:对文件内容的操作 + 对文件属性的操作


🔸 要操作文件,必须打开文件(C语言fopen、系统上open),本质上,就是文件相关的属性信息从磁盘加载到内存的过程


文件:被进程打开的文件(内存文件),没有被打开的文件(磁盘文件)


操作系统中存在大量进程,一个进程可以打开多个文件:进程:文件 = 1:1。系统中会存在大量的被打开的文件!所以OS要不要把如此之多的文件在内存中也管理起来呢? 必须管理! 先描述再组织


我们操作系统是C语言写的,OS内部要为了管理每一个被打开的文件,构建struct file


//创建struct file 对象,充当一个被打开的文件
struct file
{
    struct file * next;
    struct file * prev;
    //包含了一个被打开的文件的几乎所有的内容(不仅仅包含属性,权限,缓冲区)
}


🌍文件描述符:01234567,从0开始,连续的小整数,会让我们联想到数组下标!

打开的这么多文件,怎么知道哪些是我们进程的呢?操作系统为了让进程和文件之间产生关联,进程在内核创建struct files_struct 的结构,这个结构包含了一个数组 struct file* fd_array[] ,也就是一个指针数组,把表述文件的结构体地址填入到特定下标中。



相关文章
|
监控 Linux
Linux命令大全:echo与tail实现输出重定向。
这样,我们实现了使用echo和tail命令进行输出重定向的目的。在实际应用中,输出重定向技巧可节省时间,提高工作效率。希望本文内容对您了解和掌握Linux系统中echo与tail命令以及输出重定向的操作有所帮助。
444 27
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
799 34
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第17天】重定向在Linux中改变命令I/O流向,默认有&quot;&gt;&quot;覆盖输出至文件及&quot;&gt;&gt;&quot;追加输出至文件末尾,便于保存结果;使用&quot;&lt;&quot;从文件读取输入而非键盘,高效处理数据。文件描述符如0(stdin)、1(stdout)、2(stderr)标识I/O资源,支持读写操作。管道以&quot;|&quot;连接命令,使前一命令输出成为后一命令输入,如排序用户或找出CPU占用最高的进程,构建复杂数据处理流程。
379 9
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
1644 3
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
crash —— 获取系统的磁盘IO统计数据
crash —— 获取系统的磁盘IO统计数据
|
运维 Rust 监控
Linux高效运维必备:fd命令深度解析,文件描述符管理从此得心应手!
【8月更文挑战第23天】本文介绍了一款名为fd的命令行工具,该工具基于Rust语言开发,旨在以更直观的语法和更快的速度替代传统的`find`命令。通过本文,您可以了解到如何安装fd以及一些基本用法示例,比如使用正则表达式匹配文件名、排除特定目录等。此外,文章还展示了如何结合`ps`和`lsof`命令来查找特定文件并显示其文件描述符,从而帮助您更好地管理和监控Linux系统中的文件与进程。
1419 0
|
存储 Linux 数据处理
在Linux中,管道(pipe)和重定向(redirection)的是什么?
在Linux中,管道(pipe)和重定向(redirection)的是什么?
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
10月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
386 1