目录
一、open函数
1.介绍
2.open函数返回值
二、重定向
1.文件描述符的分配规则
2.重定向的本质
3.dup2系统调用
三、C语言库函数中的缓冲区及不同刷新模式
前言:
我们先来简单回顾一下C语言中的文件相关知识
● 打开文件的方式
r 打开文本文件进行读取。流位于文件的开头。r+ 用于读写。流位于文件的开头。w 将文件截断为零长度或创建用于写入的文本文件。流位于文件的开头。w+ 开放用于读写。
a 打开以附加(在文件末尾写入)。如果不存在,则创建该文件。流位于文件末尾。a+ 可读取和附加(在文件末尾写入)。如果不存在,则创建文件。读取的初始文件位置在文件的开头,但输出总是附加到文件的末尾。
C语言 默认会打开三个输入输出流,分别是 stdin, stdout, stderr这三个流的类型都是 FILE*, fopen 返回值类型,文件指针
一、open函数
1.介绍
open man 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 。mode:权限掩码,对创建的文件进行权限管理。参数 :O_RDONLY : 只读打开O_WRONLY: 只写打开O_RDWR : 读,写打开这三个常量,必须指定一个且只能指定一个O_CREAT : 若文件不存在,则创建它。需要使用 mode 选项,来指明新文件的访问权限O_APPEND: 追加写返回值:成功:新打开的文件描述符失败:-1
2.open函数返回值
fopen fclose fread fwrite 都是 C 标准库当中的函数,我们称之为库函数( lib )。而, open close read write 都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图
● Linux 进程默认情况下会有 3 个打开的文件描述符,分别是标准输入 0 , 标准输出 1 , 标准错误 2.● 0,1,2 对应的物理设备一般是:键盘,显示器,显示器
所以输入输出还可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
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;
}
那么文件描述符是用来做什么的呢,为什么open函数返回的是文件描述符呢?
我面先看下面图:
根据上面图片,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
那么对于其他系统调用接口read,write等大家直接man学习即可。
二、重定向
1.文件描述符的分配规则,重定向的本质
根据上图,在进程中打开一个文件就会在依次在fd_array指针数组中进行添加创建,我们看下面代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}
2.重定向的本质
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
重定向的本质是在指针数组中将对应下标位置的的指针所指向的内容进行修改,使其指向另一个文件,那么再向原先的文件下标中进行写入操作,操作就会打印到所指向的另一个文件中。
3.dup2系统调用
函数原型:
#include <unistd.h>int dup2(int oldfd, int newfd);
代码示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = open("log", O_CREAT | O_RDWR,0666);
if (fd < 0)
{
perror("open");
return 1;
}
close(1);
dup2(fd, 1);//将下标为1的数组内容更改成fd所指的内容,是1指向fd所指的文件
printf("hello Linux!\n");
return 0;
}
三、C语言库函数中的缓冲区
● 因为 IO 相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过 fd 访问的。● 所以 C 库当中的 FILE 结构体内部,必定封装了 fd。
#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;
}
hello printf
hello fwrite
hello write
hello write
hello printf
hello fwrite
hello printf
hello fwrite
● 一般 C 库函数写入文件时是全缓冲的,而写入显示器是行缓冲。● printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据 的缓冲方式由行缓冲变成了全缓冲。● 而我们放在缓冲区中的数据,就不会被立即刷新,甚至是 fork 之后● 但是进程退出之后,会统一刷新,写入文件当中。● 但是 fork 的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。● write 没有变化,说明没有所谓的缓冲区。