Linux:文件操作
在C语言中,我们可以使用fopen() fclose() fread() fwrite()等接口进行文件操作,而由于文件是储存于磁盘中的,且磁盘是由操作系统来管理的,因此在用户层面对文件的操作必然要调用操作系统对文件的操作。
我们接下来就要了解操作系统是如何描述、组织文件并进行操作的,以及C语言是如何对这些操作进行封装,把封装好的接口提供给用户的
操作系统获取文件路径:
下面这段代码我们要让进程打开文件myfile,但并没有指明文件地址:
#include <stdio.h>int main(){FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");}while(1);fclose(fp);return 0;}
执行结果:
在/home/nexus/daily_study/file下,myfile被创建
实际上,操作系统是在当前进程地址下寻找myfile文件,如果没有就创建该文件:
查询进程地址,发现与文件地址相同
Linux系统文件操作接口
文件的通用操作为:打开、关闭、读、写。对应 Linux 系统的 API 接口函数分别为 open(),close(),read(),write()。
这些函数通过文件描述符 File Descriptor(fd) 实现 IO 操作。
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:传⼊多个参数选项,⽤下⾯的⼀个或者多个常量进⾏ “ 或 ” 运算
- 访问方式标志:必须且只能包含其中一个:
- O_RDONLY:只读
- O_WRONLY:只写
- O_RDWR:读写
- 文件创建标志:可以包含。
- O_CREAT:如果文件不存在,则创建文件。此时必须指定第三个参数 mode。
- 文件状态标志:可以包含。
- O_APPEND:追加模式。
- 访问方式标志:必须且只能包含其中一个:
- mode:设置文件权限(使用权限码),仅在
O_CREAT
时生效。
返回值:报错时返回 -1,否则返回文件描述符fd。
close函数
int close(int fd);
其中fd是要关闭的文件描述符。如果close函数成功执行,它将返回0。如果执行失败,它将返回-1,并设置errno来指示错误。
read函数
头文件:
#include <unistd.h>
函数:
ssize_t read(int fd, void *buf, size_t count);
read 函数会尝试从文件描述符fd中读取 count 个字节到缓冲区 buf 中。
返回值:
成功时返回读到的字节数,0表示读到文件末尾了。如果返回字节数小于指定的字节数,不一定出错,有可能文件就剩这么多数据了。出错时返回 -1,并设置 errno 为合适值。
write函数
头文件:
#include <unistd.h>
函数:
ssize_t write(int fd, const void *buf, size_t count);
write 函数会尝试从缓冲区 buf 中读取 count 个字节,写到文件描述符 fd 中。
返回值:
成功时返回写入的字节数。如果返回字节数小于指定的字节数,不一定出错,有可能是磁盘满了。出错时返回 -1,并设置 errno 为合适值。
文件描述符和文件描述符表
⽂件描述符是一个从0非负整数
进程进行文件操作时,必须让进程和⽂件关联起来。因此,每个进程都有⼀个指针*files,指向⼀张表files_struct,该表最重要的部分就是包含⼀个指针数组fd,fd的每个元素都是⼀个指向打开⽂件的指针。所以,本质上⽂件描述符就是该数组的下标。只要拿着⽂件描述符,就可以找到对应的⽂件。
Linux 系统在启动时,标准 IO 会占用掉前 3 个文件描述符的位置:
0:标准输入 stdin
1:标准输出 stdout
2:标准错误stderr
⽂件描述符的分配规则:在files_struct数组当中,找到当前没有被使⽤的最⼩的⼀个下标,作为新的⽂件描述符。
利用上述规则,我们可以实现重定向:
如果我们调用close(1),再调用open(1,"myfile",0644),这时本应该输出到显⽰器上的内容,输出到了myfile中
Linux中,有三个重定向指令:>,>>,<,分别表示写入,追加写入和读入
理念:”一切皆文件“
Linux系统遵循”一切皆文件“的理念,这样做最明显的好处是,开发者仅需要使⽤⼀套接口和开发⼯具,即可调取Linux系统中绝⼤部分的资源。举个简单的例⼦,Linux中⼏乎所有读(读⽂件,读系统状态,读PIPE)的操作都可以⽤ read 函数来进⾏;⼏乎所有更改(更改⽂件,更改系统参数,写PIPE)的操作都可以⽤write函数来进⾏。