深度解析Linux中系统文件IO+文件描述符+输出重定向+缓冲区

系统文件IO

采用系统接口来进行文件访问

接口介绍

#include 
#include 
#include 
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: 追加写

系统调用和库函数

fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口。

文件描述符fd

0&1&2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器,所以输入输出还可以采用如下方式:

#include 
#include 
#include 
#include 
#include 
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}

深度解析Linux中系统文件IO+文件描述符+输出重定向+缓冲区_第1张图片
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

文件描述符的分配规则

#include 
#include 
#include 
#include 
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
//输出 fd :3
//关闭 0 
#include 
#include 
#include 
#include 
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
//结果 fd : 0

文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

重定向

深度解析Linux中系统文件IO+文件描述符+输出重定向+缓冲区_第2张图片
追加重定向,close(1)后,输出重定向到 log.txt中。

重定向的本质

深度解析Linux中系统文件IO+文件描述符+输出重定向+缓冲区_第3张图片

缓冲区

#include 
#include 
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;
}
//结果
hello printf
hello fwrite
hello write

当我们对进程实现输出重定向后:

//加上这两句代码
close(1);
int fd = open("log.txt",O_CREAT|O_APPEND|O_WRONLY,0644);

打开log.txt 结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

库函数fwrite、printf 都输出重定向到文件中的原因是
IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。
printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了log.txt的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。
我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?

  1. 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  2. printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  3. 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。
  4. 但是进程退出之后,会统一刷新,写入文件当中。
  5. 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  6. write 没有变化,说明没有所谓的缓冲。
    综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能, printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

你可能感兴趣的:(Linux,linux,操作系统)