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



相关文章
|
6天前
|
资源调度 JavaScript 搜索推荐
Linux系统之部署envlinks极简个人导航页
【4月更文挑战第11天】Linux系统之部署envlinks极简个人导航页
40 2
|
9天前
|
缓存 Linux 测试技术
安装【银河麒麟V10】linux系统--并挂载镜像
安装【银河麒麟V10】linux系统--并挂载镜像
59 0
|
7天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
20 6
|
2天前
|
Linux Shell 开发工具
Linux文件常用操作
Linux文件常用操作(几乎覆盖所有日常使用)
55 0
|
2天前
|
运维 网络协议 Unix
18.系统知识-Linux常用命令
18.系统知识-Linux常用命令
|
3天前
|
Linux 内存技术 Perl
【ZYNQ】制作从 QSPI Flash 启动 Linux 的启动文件
【ZYNQ】制作从 QSPI Flash 启动 Linux 的启动文件
|
9天前
|
Linux 索引
linux 文件查找 和文件管理常用命令
linux 文件查找 和文件管理常用命令
19 0
|
1月前
|
存储 Java 数据处理
|
1月前
|
Java API
java中IO与NIO有什么不同
java中IO与NIO有什么不同
|
3月前
|
存储 Java 数据安全/隐私保护
从零开始学习 Java:简单易懂的入门指南之IO字符流(三十一)
从零开始学习 Java:简单易懂的入门指南之IO字符流(三十一)