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



相关文章
|
14天前
|
存储 Linux C语言
【Linux】文件描述符
【Linux】文件描述符
|
19天前
|
网络协议 Linux Shell
Linux重定向笔记
Linux重定向笔记
17 0
|
26天前
|
存储 Linux 开发工具
【Linux】基础 IO(文件描述符)-- 详解(下)
【Linux】基础 IO(文件描述符)-- 详解(下)
|
26天前
|
存储 Linux C语言
【Linux】基础 IO(文件描述符)-- 详解(上)
【Linux】基础 IO(文件描述符)-- 详解(上)
|
27天前
|
存储 缓存 Unix
【进程IO】详细讲解文件描述符fd
【进程IO】详细讲解文件描述符fd
|
1月前
|
Linux C语言 UED
【Linux】开始了解重定向
上一篇文章我们复习了C文件IO相关操作,了解了linux下的文件系统调用(open write read ),认识了文件描述符fd值,今天我们来学习重定向和缓冲区,这个缓冲区之前遇到过很多次,比如进度条项目的刷新缓冲区操作。然后我们可以来尝试封装一下系统调用,模拟C语言的文件库。
20 2
|
13小时前
|
消息中间件 运维 监控
Linux命令ipcs详解:IPC对象的全面洞察
`ipcs`命令详解:Linux下用于洞察IPC(消息队列、信号量、共享内存)对象的工具。它列出系统中的IPC资源,显示详细信息,如ID、所有者、权限等。参数如`-m`、`-q`、`-s`分别显示共享内存、消息队列和信号量信息。结合`-l`或`-c`可调整输出格式。定期检查IPC状态有助于系统管理和性能优化。需注意权限和谨慎操作。
|
13小时前
|
消息中间件 Linux 数据处理
Linux命令ipcrm详解:轻松管理IPC对象
`ipcrm`是Linux下用于删除IPC(进程间通信)对象的命令,如消息队列、共享内存和信号量。它通过指定对象ID或键值进行操作,如`-m ID`删除共享内存,`-q ID`删除消息队列,`-s ID`删除信号量。使用时需注意确认对象未被使用,以免影响系统运行。结合`ipcs`命令检查对象详情,并可定期清理不再需要的IPC对象以优化系统资源。
|
13小时前
|
监控 Linux Shell
Linux命令ionice:优化磁盘I/O优先级
`ionice`是Linux工具,用于调整进程的磁盘I/O优先级,改善系统响应。它设置三种I/O调度类:Idle(低优先级),Best-effort(默认)和Real-time(高优先级)。通过 `-c` 和 `-n` 参数分别设定调度类和优先级。示例:`ionice -c3 -n7 command`(低优先级I/O)和`ionice -c2 -p 1234`(改变PID为1234的进程为Idle类)。使用时注意平衡系统资源,避免干扰其他任务,并结合`iostat`、`iotop`监控性能。