四、重定向
1、概念及演示
Linux 中标准的输入设备默认指的是键盘,标准的输出设备默认指的是显示器
输入/输出重定向:
输入重定向:指的是重新指定设备来代替键盘作为新的输入设备
输出重定向:指的是重新指定设备来代替显示器作为新的输出设备
注:通常是用文件或命令的执行结果来代替键盘作为新的输入设备,而新的输出设备通常指的就是文件
常用重定向:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.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); }
注:本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中fd=1,这种现象叫做输出重定向
重定向本质:
从上述示例来看,输出重定向是将进程中的文件指针数组中的标准输出stdout文件给关闭(并非真正关闭,而是将指针数组对应下标的内容置空),再将新打开文件分配到标准输出文件对应的下标上,再输出时,系统不知道文件已经替换,依旧输出到stdout文件对应的数组下标为1的文件上,但是此时文件已经被替换了
示图:
2、dup2系统调用
- 函数原型:
#include <unistd.h> int dup2(int oldfd, int newfd);//将文件地址oldfd替换到newfd上
- 示例:
#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("log.txt", O_CREAT | O_RDWR); if (fd < 0) { perror("open"); return 1; } close(1); dup2(fd, 1);//输出重定向 char buf[1024] = {0}; ssize_t read_size = read(0, buf, sizeof(buf) - 1); if (read_size < 0) { perror("read"); } printf("%s", buf); fflush(stdout); return 0; }
3、重定向的原理
注:重定向与程序替换是可以同时进行,重定向改变的是进程PCB中的文件指针数组中的文件地址信息,而程序替换则是触发写时拷贝将进程地址空间的代码和数据进行替换,这之间没有影响
输出重定向示例:命令 cat test.c > myfile
系统创建子进程exec替换程序执行cat test.c命令之前,先将标准输出文件关闭,并打开myfile文件(如果不存在则创建,对应的open选项则是O_WRONLY|O_CREAT)
追加重定向示例:命令 cat test.c >> myfile
这里大致和输出重定向一样,只不过open的选项改为O_APPEND|O_CREAT
输入重定向示例:命令 mycmd > test.c
系统创建子进程exec替换程序执行 test.c 命令之前,先将标准输入文件关闭,并打开 mycmd 文件(对应的open选项则是O_RDONLY)
4、缓冲区和刷新策略
- 示例:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.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); }
解释:
这里明明将输出结果重定向到文件myfile中,但是myfile文件并没有内容,与上面示例的区别是在文件关闭之前并没有将结果给强制刷新
对于文件结构体来说,里面除了读写方法外,还存在着缓冲区,再正式刷新到磁盘上对应的文件之前,数据先是由文件缓冲区保存着
对于标准输出的刷新策略是行缓冲,当遇到\n时触发刷新机制,对于普通文件来说则是全缓冲,当缓冲满时就进行刷新,而强制刷新以及进程结束刷新对两者都有效
这里输出重定向之后指针数组对应的原标准输出文件的替换成了普通文件,数据写到对应文件缓冲区里,同时对应刷新策略也改变成全缓冲,关闭文件之前没有强制刷新,则数据也就没写到对应磁盘上的文件里
刷新策略:
无缓冲:无缓冲的意思是说,直接对数据进行操作,无需经过数据缓冲区缓存,系统调用接口采用此方式
行缓存:缓冲区的数据每满一行即对数据进行操作,而通常情况下向屏幕打印数据就是行缓存方式
全缓冲:缓冲区的数据满时才对数据进行操作,通常向文件中写数据使用的就是全缓冲方式
五、文件及文件系统
1、FILE
- 概念:
因为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 > file
hello write hello printf hello fwrite hello printf hello fwrite
区别:这里 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用),而这与就和fork有关
解释:
printf fwrite 库函数是C语言上的函数,这些库函数在实现输出时必定通过调用C语言的文件IO函数实现。C语言文件IO函数的返回类型是FILE*,这里的FILE是C语言上的文件结构体,其中为了实现语言与系统层面的相连,FILE结构体里也存在着_fileno(对应fd)以及用户层面的缓冲区,所以库函数输出数据是先输出到FILE文件结构体里的缓冲区
如果是直接运行,即没有发生输出重定向时,向显示屏文件的刷新机制是行缓冲(遇到\n则刷新),即立即将缓冲数据给刷新,fork之后没有什么作用
当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲(普通文件是全缓冲的,缓冲满则刷新),即FILE中缓冲区存有数据,当fork之后,子进程会与父进程代码共享,数据各有一份(刷新就是写入,发生写时拷贝),程序结束退出时强制刷新数据,所以库函数调用的都输出了两次
write 为系统接口无缓冲机制,就直接将数据刷新
注意:
OS内核区实际上也是有缓冲区的,当我们刷新用户缓冲区的数据时,并不是直接将用户缓冲区的数据刷新到磁盘或是显示器上,而是先将数据刷新到操作系统缓冲区,然后再由操作系统将数据刷新到磁盘或是显示器上
操作系统也有自己的刷新机制,这样的分用户层面和内核层面的缓冲区,便于用户层面与内核层面进行解耦
FILE结构体:
//在/usr/include/stdio.h typedef struct _IO_FILE FILE; //在/usr/include/libio.h struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags //缓冲区相关 /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields di rectly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; //封装的文件描述符 #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };