目录
[0 & 1 & 2](#0 & 1 & 2)
文件的理解
文件 = 文件内容 + 文件属性
文件是存储在磁盘中的,磁盘是外设,磁盘上文件所有的操作本质都是对外设的输入和输出,简称IO
Linux下一切皆文件
所有的文件操作本质是文件内容操作和文件属性操作
open函数
cpp
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname表示要打开或创建的目标文件
flags表示打开文件时传入需要的参数,具体如下:
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:读,写打开
- (以上只能且必须三选一)
- O_CREAT:若文件不存在,则创建。创建的文件权限需要用到mode选项
- O_APPEND:追加写
mode表示若创建文件则权限的大小
返回值:
- 成功:新打开的文件描述符
- 失败:-1
mode_t类型是一个整数类型
文件描述符
文件描述符fd就是一个小整数,从0开始
0 & 1 & 2
Linux进程默认情况下会有3个缺省打开的文件描述符fd,分别是标准输入0,标准输出1,标准错误2
0,1,2分别对应的物理设备为:键盘,显示器,显示器
当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体
每个进程都有一个*files指针,它指向了一张表files_struct,该表包含了一个指针数组,这个指针数组每一个元素都是指向打开文件的指针,本质上文件描述符就是该指针数组的下标,这样有文件描述符就能找到相应的文件了
文件描述符的分配规则:
在flies_struct数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
所以0,1,2关闭后是可以被替代的,这也是重定向的原理
重定向
常见的重定向有:>,>>,<
我们正常的echo后面的内容是直接打印到显示器上
但是当我们重定向后显示器上就没有打印该文字了,而是将文字直接写到了重定向的文件中
本质上是把文件描述符1关闭,变成了我们新创建的test.txt文件,这样只要是即将在显示器1号文件描述符上输出的就都会写到test.txt文件中
dup2系统调用
cpp
#include <unistd.h>
int dup2(int oldfd, int newfd);
我们在代码中也想重定向就可以使用dup2系统调用
echo "hello world" > test.txt就相当于dup2(fd, 1); fd表示test.txt的文件描述符,1是我们要替换的文件描述符
缓冲区
缓冲区的概念
缓冲区是内存空间的一部分,在内存空间中预留了一定的空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫缓冲区
缓冲区分为输入缓冲区和输出缓冲区
为什么要引入缓冲区机制?
如果没有缓冲区,直接通过系统调用对磁盘进行操作,那么每一次对文件进行一次读或写的时候都需要调用读写系统调用来处理,执行一次系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这样频繁的磁盘访问对程序的执行效率会造成很大的影响
为了解决以上问题,我们就可以引入缓冲区机制,我们可以一次从文件中读出大量的数据到缓冲区中,这部分的访问都不需要系统调用,等缓冲区的数据读取完成后再去磁盘中读取,这样可以极大的减少调用系统调用的次数,大大提高了计算机的运行速度
缓冲类型
标准I/O提供了3种类型的缓冲区
- 全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行I/O系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。这也是效率最高的一种类型
- 行缓冲区:当输入和输出种遇到换行符时,标准I/O库函数将会执行系统调用操作。当所操作的流涉及一个终端时,使用行缓冲方式
- 无缓冲区:无缓冲区是指标准I/O库不对字符进行缓存,直接调用系统调用
如果我们想强制刷新缓冲区,可以使用fflush强制刷新缓冲区
cpp
fflush(stdout);
有如下代码:
cpp
#include <stdio.h>
#include <string.h>
int main()
{
const char *msg0="hello printf\n";
const char *msg1="hello fwrite\n";
const char *msg2="hello write\n";
printf("%s", msg0);
fwrite(msg1, strlen(msg0), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
return 0;
}
运行结果:
cpp
hello printf
hello fwrite
hello write
但当我们重定向到file文件后,结果变成了:
cpp
hello write
hello printf
hello fwrite
hello printf
hello fwrite
这是因为:
printf和fwrite这些库函数会自带缓冲区,当重定向到普通文件时,缓冲方式由行缓冲变成了全缓冲,所以我们放在缓冲区的数据并不会被立即刷新
当我们fork的时候,父子数据会发生写时拷贝,父进程准备刷新的时候,子进程也具有相同的数据(缓冲区),所以就刷新了两份数据到内核级缓冲区中,write没有变化,write是没有缓冲区的
库函数在系统调用的上层,是对系统调用的封装
完