操作文件,除了使用C接口(库函数),我们还可以采用系统接口来进行文件访问
open close read write lseek都属于系统提供的接口,称之为系统调用接口
举例介绍open接口
#include
#include
#include
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 : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。0,1,2对应的物理设备一般是:键盘,显示器,显示器
一个进程可能打开多个文件,文件描述符就是从0开始的小整数,本质是数组下标。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
文件描述符的规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
#include
#include
#include
#include
#include
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);
}
我们关闭1,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出
重定向。常见的重定向有:>, >>, <。
请问fopen做了什么?给调用的用户申请file_struct结构体变量,并返回地址(file*);在底层通过open打开文件,并返回fd,把fd填充进file变量中的inode
为什么在代码尾处要加 fflush(stdout)呢?以下解释是在没有加 fflush(stdout)情况下。我们先关闭了描述符1(1指向显示器),现在关闭后1号文件描述符空出来,打开一个新的文件myfile,1号描述符本来指向显示器,现在指向myfile。file_struct内部我们只关闭了1号描述符,stdout还在,所以stdout对应的文件描述符还是1,接下来直接打印时,这个数据并没有直接刷新到操作系统底层,而是把数据放到了C语言提供的file结构的缓冲区里,还没来得及进程退出刷新,关闭了fd,此时fd就是1,没了1之后,进程退出时消息无法由用户从语言层刷新到操作系统底层,所以我们看不到数据的结果。 加上fflush(stdout)就可以了。但是 fflush(stdout)之前一条printf(“fd: %d\n”, fd);明明已经有\n为什么没有刷新呢?是因为显示器为行刷新,当close(1),又打开新文件时普通文件为全缓冲。
重定向的本质是修改,文件描述符fd下标对应的struct file*的内容
函数原型如下:
#include
int dup2(int oldfd, int newfd);
注:不是拷贝fd!而是拷贝fd对应的数组中的内容,new是old的一份拷贝,数组内容最后和old一致
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd
#include
#include
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
(1)一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
(2)printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
(3)而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
(4)但是进程退出之后,会统一刷新,写入文件当中。
(5)但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
(5)write 没有变化,说明没有所谓的缓冲。
综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,
都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统
调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是
C,所以由C标准库提供。