【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[] ,也就是一个指针数组,把表述文件的结构体地址填入到特定下标中。



相关文章
|
2月前
|
监控 应用服务中间件 Linux
掌握并发模型:深度揭露网络IO复用并发模型的原理。
总结,网络 I/O 复用并发模型通过实现非阻塞 I/O、引入 I/O 复用技术如 select、poll 和 epoll,以及采用 Reactor 模式等技巧,为多任务并发提供了有效的解决方案。这样的模型有效提高了系统资源利用率,以及保证了并发任务的高效执行。在现实中,这种模型在许多网络应用程序和分布式系统中都取得了很好的应用成果。
96 35
|
2月前
|
监控 Linux
Linux命令大全:echo与tail实现输出重定向。
这样,我们实现了使用echo和tail命令进行输出重定向的目的。在实际应用中,输出重定向技巧可节省时间,提高工作效率。希望本文内容对您了解和掌握Linux系统中echo与tail命令以及输出重定向的操作有所帮助。
120 27
|
1月前
|
Linux C语言 网络架构
Linux的基础IO内容补充-FILE
而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fwrite函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和fwrite函数打印的数据就有两份。此时我们就可以知道,
34 0
|
1月前
|
存储 Linux Shell
Linux的基础IO
那么,这里我们温习一下操作系统的概念我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发。这就是因为在根本上操作系统确实像银行一样,并不完全信任用户程序,因为直接开放底层资源(如内存、磁盘、硬件访问权限)给用户程序会带来巨大的风险。所以就向银行一样他的服务是由工作人员隔着一层玻璃,然后对顾客进行服务的。
34 0
|
9月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
359 1
Linux C/C++之IO多路复用(aio)
|
7月前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
2710 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
5月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
219 34
|
4月前
|
消息中间件 监控 算法
用Apifox调试Socket.IO接口,从原理到实践
传统HTTP协议"请求-响应"的离散式通信机制已难以满足需求,这正是Socket.IO这类实时通信框架的价值所在。
用Apifox调试Socket.IO接口,从原理到实践
|
7月前
|
Linux API C语言
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
141 14
|
9月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
229 0
Linux C/C++之IO多路复用(poll,epoll)