携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情
💦 为什么要学习文件系统接口
根据之前所说,在 C 语言中要访问硬件,必须贯穿计算机体系结构,而 fopen、fclose 等系列的库函数,其底层都要调用系统接口,这里它们对应的系统接口也很好记 —— 去掉 " f " 即为系统接口。不同语言所提供的接口本质是对系统接口封装,学习封装的接口本质学的就是语言级别的接口,换言之要学习不同的语言,就得会不同语言操作文件的方法,但实际上对特定的操作系统,最终都会调用系统提供的接口。
所以接下来我们当然是要学习系统接口,我们要学习它的原因主要有两点:其一,只要把它学懂了,以后学习其它语言上的文件操作,只是学习它的语法,底层就不用学习了;其二,这些系统接口,更贴近于系统,所以我们就能通过接口,来学习文件的若干特性。
💦 测试用例一
#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_CREAT, 0644);//打开
if(fd < 0)
{
perror("open");
return 1;
}
//操作
const char* msg = "Hello System Call!\n";
write(fd, msg, strlen(msg));
write(fd, msg, strlen(msg));
write(fd, msg, strlen(msg));
write(fd, msg, strlen(msg));
close(fd);//关闭
return 0;
}
-
使用 open 需要包含三个头文件,它有两个版本。版本一:以 flags 方式打开 pathname;版本二:以 flags 方式打开 pathname,并设置 mode 权限。
flags 可以是 O_RDONLY(read-only)、O_WRONLY(write-only)、O_RDWR(read/write),且必须包含以上访问模式之一。此外访问模式还可以带上 |标志位,下面会介绍一两个标志位,实际还要看场景使用。
-
open("log.txt", O_WRONLY);
以写的方式打开一个存在的文件,它同 fopen 一样,如果没有写操作,原文件的内容不会被覆盖;如果写操作,原文件的内容会被覆盖成写的内容。
-
open("log.txt", O_WRONLY, 0644);
以写的方式打开不存在的文件,权限是 644,运行程序发现没有新建文件 。
这里我们看到概要,我们发现 open 的返回值不是 FILE*,而是 int,接着我们再看下返回值说明,它说 open 会返回一个新的文件描述符(file descriptor),如果打开失败,返回 -1。在 C 语言中我们把 FILE* 称为文件指针,FILE* 和 file descriptor 必然有联系,下面再谈。
我们发现它的返回值是 -1,所以这里打开文件失败了。
O_CREATE 发现文件不存在,将会新建文件,且必须指定 mode 权限(如果没有指定,那么新建的文件会变成可执行程序),如果没有 O_CREATE,说明文件是存在的,则可忽略 mode 权限(就算写了权限也不会对原来的文件更改权限)。
-
open("log.txt", O_WRONLY|O_CREAT, 0644);
很明显,它和 fopen 不一样,fopen 如果以写的方式打开,文件不存在,则会新建文件,而这里的 open 想做到类似的效果,需要带上 O_CREAT 标志位。这里我们运行程序,发现文件描述符是 3,说明文件打开成功,而且权限被设置成了 644。
C 接口对比系统接口 ❗
fopen 的底层一定调用 open 的,系统是怎么保证这里如果没有目标文件,会自动新建呢?—— w 的底层就是 O_WRONLY|O_CREAT;另外 fopen 默认创建文件的权限是系统默认的;关于 fopen 和 open 的返回值类型为什么是 FILE* 和 int 一会再谈。
-
使用 close 关闭文件,需要包含 unistd 头文件。fd 是 open 的返回值。
-
使用 write 写入文件,需要包含 unistd 头文件。write 向 fd 文件描述符中写入 buf,写 count 个字节,返回值是写了多少个。
💦 测试用例二
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
int fd = open("log.txt", O_RDONLY);//打开
if(fd < 0)
{
perror("open");
return 1;
}
//操作
char buffer[1024];
ssize_t sz = read(fd, buffer, sizeof(buffer) - 1);//期望读1023个,但实际可能只有100个,是从文件读,文件并不遵守字符串\0的规则,所以要主动\0
if(sz > 0)
{
buffer[sz] = '\0';//利用read的返回值,实际读到的个数就是该被\0的位置
printf("%s\n", buffer);
}
close(fd);//关闭
return 0;
}
-
要使用 read 读文件,需要包含 unistd 头文件。read 从 fd 文件描述符中读数据到 buf,读 count 个字节,返回值是实际读到的数据。
💦 测试用例三
#include<string.h>
#include<stdio.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_APPEND);//打开
if(fd < 0)
{
perror("open");
return 1;
}
//操作
const char* msg = "Hello System Call!\n";
write(fd, msg, strlen(msg));
write(fd, msg, strlen(msg));
close(fd);//关闭
return 0;
}
-
open("log.txt", O_WRONLY|O_APPEND);
以写的方式追加一个存在的文件。