文件操作,这个熟悉而又陌生的词汇。在我们在图形化的电脑上,不断的双击打开很多不同的文件,且不关闭,你是否会好奇,OS它是如何打开管理这些不同的文件呢?
在不同的代码语言中,都会有文件操作的库函数来提供使用,它们的功能相仿,会不会有什么联系呢?
文件这个在计算机里最常见的玩意,我们常用但是其概念很少被我们规范。
文件的基本构成粗略的概括可分为两部分:内容 + 属性。
内容不用解释大家都懂,属性一般包括该文件的创建、修改、类型、使用人、所在路径等一系列关于其的信息,用于记录和查看。
注:空文件当然也是有大小的,没有内容有属性
一般来说,我们第一次接触代码的文件操作是在C语言,例如:fopen、fclose、fread、fwrite等接口即是文件操作的函数。文件操作时我们必须open打开对应文件才能访问,而打开的文件会在相应的进程进行相应的操作。这就意味着 文件操作 = 打开的文件 + 相应的进程操作。
正如前言里说的,我们发现所有语言中都有相关的文件操作的库函数,且他们的用法和作用都大同小异,那是因为它们都是由基础的系统调用接口二次封装而来。
open close read write lseek这几个即为最初的系统调用接口。
我们拿open来做简绍
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
在open的返回值我们发现了一个有趣的名字“文件描述符”,而且根据返回值的类型我们知道了为int,为一个数。好神奇,为了了解它我们写了如下代码。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define FILE_NAME(number) "log.txt"#number
int main()
{
umask(0);//给创建文件的掩码
int fd0 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd1 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd2 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd3 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd4 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);
printf("fd: %d\n", fd0);
printf("fd: %d\n", fd1);
printf("fd: %d\n", fd2);
printf("fd: %d\n", fd3);
printf("fd: %d\n", fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
运行如下
如图中的这个文件描述符的本来面貌,我们也能猜出来应该是下表。
但是有个问题,0、1、2不在了。我们加几行代码再运行
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define FILE_NAME(number) "log.txt"#number
int main()
{
printf("stdin->fd: %d\n", stdin->_fileno);
printf("stdout->fd: %d\n", stdout->_fileno);
printf("stderr->fd: %d\n", stderr->_fileno);
umask(0);//给创建文件的掩码
int fd0 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd1 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd2 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd3 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd4 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);
printf("fd: %d\n", fd0);
printf("fd: %d\n", fd1);
printf("fd: %d\n", fd2);
printf("fd: %d\n", fd3);
printf("fd: %d\n", fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
结果如下:
我们找到了,0、1、2,它们分别是标准输入流、标准输出流和标准错误流,这三个文件是自动创建且打开的,它们规定了对应文件的输入输出和报错的方式,在不同类型的文件上(由相应的函数)替换这三个流即可,对不同的文件访问。而这个方式也叫“重定向”。
由于上面3个流的存在,我们可以将所有的设备也当作不同的文件来进行不同的管理,而它们也有一个我们熟知的名字——驱动。也由于这个特性,就有一种思想,万物皆可文件。