1.回顾c语言的文件操作
#include <stdio.h>
int main()
{
// FILE* fp = fopen("log.txt", "r");
FILE* fp = fopen("log.txt", "a");//a追加 log.txt不存在就会在当前路径下创建一个
//所谓当前路径,不是指当前可执行程序所处的路径,而是进程运行时所处的路径
if(fp == NULL)//打开失败
{
perror("fopen");
return 1;
}
// char buffer[100];
// int ct = 5;
// while(ct--)
// {
// // fgets(buffer, sizeof(buffer), fp);
// // fgets(buffer, sizeof(buffer), stdin);//stdin--从键盘输入
// printf(buffer);
//
// }
int count = 5;
while(count--)
{
fputs("hello Linux\n", fp);
}
fclose(fp);
return 0;
}
-
stdin & stdout & stderr
C默认会打开三个输入输出流,分别是stdin, stdout, stderr。这三个流的类型都是FILE*, fopen返回值类型,文件指针
stdin:键盘 标准输入
stdout:显示器 标准输出
stderr:显示器 标准错误
2.系统文件I/O
在c语言里面,有fopen,fclose,fread,fwrite这些函数。而open,close,read,write都属于系统提供的接口,称之为系统调用接口。实际上,Linux环境使用fopen底层调用的还是open。那么c语言为什么还要弄出来一个fopen呢。这是为了保证c语言的跨平台性,比如在win下面,也有与fopen对应的系统调用接口。这样就可以实现c语言在Linux下掉Linux对应的系统调用接口,在win下调用win对应的系统调用接口。
3.文件描述符fd
文件描述符是一个整数,实际上是一个数组的下标
-
0 & 1 & 2
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。0,1,2对应的物理设备一般是:键盘,显示器,显示器。虽然1 2 都对应显示器,但是他们是不同的。标准错误是不能被重定向的,只会打印到显示器上。
- fopen到底在干什么?
- 给用户申请struct file结构体,并返回地址file*
- 在底层通过open打开文件,并返回fd,把fd填充进struct file结构体里面的fileno。
4.缓冲区
凡是显示到显示器上面的内容,都是字符,凡是键盘读取的内容都是字符,所以键盘和显示器一般被称为“字符设备”
缓冲:
-
无缓冲
-
行缓冲(常见的对显示器刷新数据时)
一行输入满了再一起刷新到显示器上面。所以我们在printf打印的时候,如果不加\n是不会立刻打印到显示器上面的,如果想立刻打印到显示器上面可以使用fflush
进程在退出的时候会将用户区的缓冲区内容刷新到os缓冲区
-
全缓冲(对文件写入的时候)
整个缓冲区满了再刷新到磁盘
以上所说的缓冲区是c语言自带的,其实操作系统也有缓冲区,上面说的刷新到显示器,刷新到磁盘,其实不是一步完成的,而是c语言的缓冲区刷新到os的缓冲区,再由os的缓冲区刷新到显示器/硬盘。这里我们不关心os的缓冲区是如何刷新的
下面我们看一下这段代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
//c语言函数
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
//system
const char* msg = "hello write\n";
write(1, msg, strlen(msg));
fork();
return 0;
}
我们对上面代码做以下4种测试
-
注释fork()不创建子进程,不重定向
-
注释fork()不创建子进程,重定向
-
创建子进程,不重定向
-
创建子进程,重定向
对比结果我们发现:
- 不重定向的打印顺序正常,而重定向之后先打印了hello write。
- 创建子进程并重定向,hello printf 和hello fprint打印两次
原因:重定向之后,由于hello write是系统调用,没有缓冲区,直接打印出来,而hello printf和 hello fprint重定向之后,由行缓冲变成全缓冲,不会立刻打印出来,要等到进程结束才刷新缓冲区。当创建子进程不重定向,子进程相当于什么也没做。而创建子进程并且重定向时,父进程的hello printf和hello fprintf还在缓冲区,子进程由于写时拷贝,也有一份hello printf和hello fprintf。当父子进程结束,刷新缓冲区,父子进程的hello printf和hello fprintf都写入到log.txt。
5.dup2
#include <unistd.h>
int dup2(int oldfd, int newfd);
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
* If oldfd is not a valid file descriptor, then the call fails, and
newfd is not closed.
* If oldfd is a valid file descriptor, and newfd has the same value as
oldfd, then dup2() does nothing, and returns newfd.
int main()
{
umask(0);
int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
dup2(fd, 1);
printf("haha\n");//原本打印到显示器就是写入到log.txt
close(fd);
return 0;
}