当我们在C语言程序中调用一个库函数的时候,比如调用printf()函数,实际上它是通过文件指针来指向要打印的位置的。并且,printf()函数会调用Linux的系统函数write()函数(它是一个系统接口,也可以人工调用),write()函数再继续调用sys_write()函数(这个函数只能是操作系统去调用),sys_write()继续调用设备驱动,具体调用哪个驱动要看输出的位置,如果是printf()打印到显示器上,那么就调用显示器驱动并打印在屏幕上,如果是写到网络上,就会调用网卡驱动。我们所作的只有在C程序中调用printf()等库函数,其余操作都是操作系统帮我们做的。请看下面这张图。
printf()函数在打印的时候通过一个文件指针来实现打印到某个文件的某个位置。在文件在文件指针中,包含了一个文件描述符,这个文件描述符用于指定目标文件,默认情况下就是STDOUT_FILENO也就是标准输出1号描述符;f_pos指定了读写的位置,比如我们打印的时候他会不停的在上一次打印的末尾位置打印后面的内容,就是通过这个位置去实现的;在最后还有一个缓冲区buffer,那么为什么要有buffer缓冲区呢,其实这是为了提高读写的效率,把读写的内容先放到缓冲区,这样就可以实现一次读写更多的内容。并且,这个缓冲区需要刷新才能得到输入输出,我们可以通过下面程序来测试一下。
测试文件01_print_dm.c
/************************************************************ >File Name : 01_print_dm.c >Author : QQ >Company : QQ >Create Time: 2022年05月12日 星期四 15时28分15秒************************************************************/ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <unistd.h> int main() { close(1); int fd = open("test.log", O_CREAT | O_TRUNC | O_WRONLY, 0644); printf("hello ... \n"); fflush(stdout); close(fd); return 0; }
编译文件makefile
.PHONY:all clean CC=gcc CFLAGS=-Wall -g EXE=01_print_dm all:$(EXE) %.o:%.c $(CC) $(CFLAGS) -c $< -o $@ clean: -@rm -f *.o $(EXE)
在这个程序中close(1);表示关闭标准输出,在前面我们已经说过文件描述符1代表标准输出,这时候通过open()打开一个文件,我们知道,当打开一个文件的时候会使用一个当前空闲的最小文件描述符,因为前面我们把标准输出关闭了,所以当前空闲的最小文件描述符1分配给open()函数打开的文件。虽然1号文件描述符当前已经不是标准输出(终端显示屏)了,但是stdout依然是指向1号文件描述符的,实际上这就相当于把open()打开的文件当作标准输入输出,printf()打印的内容都会打印到test.log文件内。首先我们屏蔽fflush()函数试一下
这个test.log文件内是空的,也就是说,如果不刷新缓冲区的话,无法正常打印内容,我们把刷新函数fflush()加上就可以看到,printf()函数打印内容直接打印到test.log文件内了,而不会打印在终端。
实际上,在Linux下启动一个进程,就会默认打开三个文件描述符:0标准输入、1标准输出、2标准错误。它们分别对应C语言中的stdin、stdout、stderr。当我们每次打开一个文件,就会分配给这个文件一个当前空闲的最小文件描述符,如果此时标准输入0、标准输出1、标准错误2空闲,那么也会把这个文件描述符分配给新打开的文件但是这三个文件描述符0、1、2与stdin、stdout、stderr的对象关系不会变,并且在后续的操作中会把0、1、2指向的新文件当作标准输入输出和标准错误去处理,并将输入输出或错误信息打印到这个文件。