C标准库的文件IO接口
FILE * fopen(const char * path,const char * mode);
fopen函数为打开一个文件,path是要打开的文件名,mode则是打开文件的方式,返回类型为文件流指针,若打开失败则返回NULL
mode的几种形式:
r | 以只读方式打开文件,该文件必须存在 |
r+ | 以读/写方式打开文件,该文件必须存在 |
w | 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件 |
w+ | 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件 |
a | 以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留 |
a+ | 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留 |
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
fwrite函数功能是向一个文件写入数据,成功返回实际写入的数据个数(小于或等于count)。buffer是一个指针,对fwrite来说,是要获取数据的地址。size为要写入内容的单字节数。count为要进行写入多少个size字节数。stream是目标文件的文件流指针。
size_t fread( void * buffer , size_t size , size_t count , FILE * stream );
fread从文件流中读数据,最多读取count个项,每个项size个字节,如果调用成功返回实际读取到的项个数(小于或等于count),如果不成功或读到文件末尾返回 0。
关闭文件fclose(FILE* stream);
系统调用的文件IO接口
open:
int open(const char *pathname,int flags,mode_t mode);
int open(const char *pathname,int flags);
open函数为系统调用,以指定方式打开一个文件,成功返回文件描述符,失败返回-1。pathname为要打开的文件名,flags为打开的方式,mode为文件权限(非必选项)
几种flags形式:
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
O_CREAT | 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 |
O_APPEND | 追加写 |
write:
ssize_t write (int fd,const void * buf,size_t count);
write函数为系统调用,将buf指针指向的数据写入count个字节到fd文件描述符所代表的文件中。成功返回实际写入的字节数,失败返回-1。
read:
ssize_t read (int fd, void *buf, size_t count);
read将读取文件描述符fd所代表的文件,读取count个字节到buf中,成功返回实际读取到的字节数,失败或已经读到末尾则返回0。
关闭文件 close(int fd);
文件描述符
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
一个进程会默认打开三个文件描述符:0标准输入,1标准输出,2标准错误输出。
文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
文件重定向
< 标准输入重定向
> 标准输出重定向 清空原有内容后添加新内容
>> 标准输出重定向 向原有内容下方添加新内容
dup2系统调用:
int dup2(int oldfd,int newfd);
将newfd重定向到oldfd
dup2(fd,1)---将标准输出重定向到fd描述符(文件)。
C库函数和系统调用的一个重要区别
我们使用C标准库函数进行文件操作时,使用的是文件流指针FILE,因为库函数是对系统调用的封装,所以本质上都是通过文件描述符fd来访问文件。所以C库中的FILE结构体中,必定封装了文件描述符。
#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
我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。
一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。
进程退出之后,会统一刷新,写入文件当中。但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。write 没有变化,说明没有所谓的缓冲。
上面程序,当调用write时,因为没有自带缓冲区,数据被直接输出,而之后printf和fwrite都自带缓冲区,在fork之后才会刷新,子进程拷贝了父进程缓冲区数据。所以我们看到第一行输出了write,而后输出printf和fwrite。