文件相关的系统调用函数
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。
O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个 O_CREAT : 若文件不存在,则创建它。 O_APPEND: 追加写
为什么这个flags参数的返回值是int类型呢?
其实这些选项都是宏定义的,int一共有32个bite位,每一种选项转换为二进制都只有一个bite位是1,他们按位或就可以得到唯一的结果,这么做的意义就是通过这一个参数可以融合更多的功能。
③需要使用mode选项,来指明新文件的访问权限**
函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
文件描述符
通过对open函数的学习,我们知道了文件描述符就是open的返回值,是一个整数。
Linux进程默认情况下会有3个打开的文件描述符,分别是标准输入0,标准输出1,标准错误2 0,1,2对应的物理设备一般是:键盘,显示器,显示器
那么究竟什么是文件描述符呢 ?
每当创建一个文件,就要创建一个struct_file的结构体用来描述这个文件,并且文件还要被组织起来,但是文件又是由进程来创建的,所以要让进程和文件联系起来,在pcb中有一个struct files_struct* 的指针指向struct files_struct这个结构体,在这个结构体中有一个struct_file* fd_array[]的数组,就是通过这个数组的下标和对应的文件建立关系。所以在本质上文件描述符就是该数组的下标,只要拿着这个文件描述符就可以找到对应的文件,然后进行操作。
文件描述符的分配原则:在数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符在struct files_struct中有一个next_fd的指针,他来帮我们实现去找到数组中那个最小且没有被使用的下标
输出重定向
在默认的情况下,文件描述符都是从3开始的,但是一旦0,1,2其中一个被关闭,那么此时新打开的文件的文件描述符就会是0,1,2中被关闭的哪一个,其中关闭1(stdout)的时候,会发生重定向。
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(1);
int fd=open("log.txt",O_WRONLY|O_CREAT,0644);
if(fd<0)
{
perror("open error\n");
return 1;
}
printf("hello world ...\n");
fflush(stdout);
close(fd);
return 0;
}
对于上述代码来说,通俗的说本来应该打印在标准输出的内容,写到了我打开的文件当中,这个过程就叫做重定向。
重定向准确描述:重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件
常见的重定向还有“>>” (追加重定向)“<” ,但是本质上就是改变open中的第二个参数选项。
O_APPEND
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(1);
int fd=open("log.txt",O_WRONLY|O_APPEND,0644);
if(fd<0)
{
perror("open error\n");
return 1;
}
printf("hello world ...\n");
fflush(stdout);
close(fd); //如果不关闭会导致文件描述符泄漏
return 0;
}
O_TRUNC
每次都会把原先文件里面的内容清空,在输出。这是显示清空文件选项,原来的那种O_WRONLY|O_CREAT是隐式的清空,每次都会先创建这个文件,再写入
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(1);
int fd=open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0644);
if(fd<0)
{
perror("open error\n");
return 1;
}
printf("hello world ...\n");
fflush(stdout);
close(fd); //如果不关闭会导致文件描述符泄漏
return 0;
}
dup2系统调用
但是你每次都要先关闭一个文件描述符然后在进行相应的操作,太过于麻烦,所以这里就引入了一个int dup2(int oldfd, int newfd);这里的oldfd可以理解为我才打开文件的描述符,newfd理解为我要往哪里重定向的描述符,此时我希望本来应该打印在标准输出的内容打印到我的普通文件里面dup2(fd,1)
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd=open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0644);
if(fd<0)
{
perror("open error\n");
return 1;
}
dup2(fd,1);
close(fd);
const char *msg1="hello! : write\n";
const char *msg2="hello! : printf\n";
const char *msg3="hello! : fprintf\n";
write(1,msg1,strlen(msg1));
printf("%s",msg2);
fprintf(stdout,"%s",msg3);
fflush(stdout);
return 0;
}
重定向以后找到文件就可以通过文件描述符1,但是此时你的fd就不在使用了,就要及时的close(fd);因为默认的情况下这个文件描述符数组只分配了32个,只打开不关闭,会造成文件描述符泄漏的问题。那么这里数组就只给分配了32吗?struct files_struct中有一个 fd_table的指针,当你锁打开的文件超过32个的时候,也是有办法继续给其分配数组下标的。
对比fd和FILE,理解系统调用和库函数
-
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。
-
其中FILE结构体是被typedef struct _IO_FILE FILE
-
在结构体中有 int _fileno; 这个就是FILE结构体中封装的文件描述符
-
在FILE这个结构体中,还包含了大量的缓冲区
所以调用库函数在底层也是去调用系统调用接口去帮你实现操作。
缓冲区
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd=open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0644);
if(fd<0)
{
perror("open error\n");
return 1;
}
const char *msg1="hello! : write\n";
const char *msg2="hello! : printf\n";
const char *msg3="hello! : fprintf\n";
write(1,msg1,strlen(msg1));
printf("%s",msg2);
fprintf(stdout,"%s",msg3);
fork();
fflush(stdout);
close(fd);
return 0;
}
运行结果: hello : write hello : printf hello : fprintf
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
close(1);
int fd=open("log.txt",O_WRONLY|O_TRUNC|O_CREAT,0644);
if(fd<0)
{
perror("open error\n");
return 1;
}
const char *msg1="hello! : write\n";
const char *msg2="hello! : printf\n";
const char *msg3="hello! : fprintf\n";
write(1,msg1,strlen(msg1));
printf("%s",msg2);
fprintf(stdout,"%s",msg3);
fork();
fflush(stdout);
close(fd);
return 0;
}
运行结果: cat test.txt hello : write hello : printf hello : fprintf hello : printf hello : fprintf
我们发现printf和fwrite(库函数)都输出了2次,而write只输出了一次(系统调用)。这里的重定向使我们所写的目的地发生了变化,缓冲方式发生了改变
显示器:行缓冲方式
文件:全缓冲方式(只有把缓冲区写满了之后,才会刷新出去)
他们都属于用户级别缓冲区,是由C标准库所提供的
-
一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
-
printf/fwrite库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
-
而我们放在缓冲区中的数据,就不会被立即刷新,但是进程退出之后,会统一刷新,写入文件当中。
-
其中fork创建子进程的时候的时候,父子数据会发生写时拷贝,此时代码共享,数据各自私有一份,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。 write没有变化,说明没有所谓的缓冲。从这里也就知道了缓冲区是存在C标准库里面的,系统在这里是没有提供缓冲区