在学C语言时期,当我们需要对文件进行操作时,使用的是 fopen、fclose、fwrite、fread 等库函数,而这些库函数实际上是对于系统调用接口进行了一层封装,更方便我们的使用。在了解这些系统调用接口之前,我们先来认识一下这两个概念:系统调用 和 库函数。
上面的 fopen fclose fwrite fread 等都是C语言标准库当中的函数,我们称之为库函数(libc)。而 open close read write lseek 等都属于系统提供的接口,称之为系统调用接口,看看下面这张图,系统调用和库函数之间的关系就一目了然了。
接口介绍
这里以open为例进行介绍,其余系统调用接口如 write read close lseek等可类比库函数中的相关接口,或者直接在man手册中查看。
#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:打开文件时,可以传入多个参数选项,用下面的一个或是多个常量进行 "或" 运算,构成flags
// 参数:
// O_RDONLY:只读打开
// O_WRONLY:只写打开
// O_RDWR:读写打开
// 以上三个常量必须指定一个切只能指定一个
// O_CREAT:若文件不存在则创建。需要用mode选项指明新文件的访问权限
// O_APPEND:追加写
// 返回值:
// 成功:新打开的文件描述符
// 失败:-1
其中mode参数在创建一个新文件时是必须要写的,否则系统会随机赋给文件权限,很有可能会造成创建出来的文件是文件所属用户也无法访问和操作的。而当打开一个已有的文件时,是不需要添加mode选项的。
文件描述符fd
对于open,它返回的是新打开文件的文件描述符,那么文件描述符是什么呢?事实上,文件描述符就是一个整数。
Linux系统将所有设备都当作文件来处理,并且用文件描述符来标识每个文件对象。Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。其实我们可以想象我们电脑的显示器和键盘在Linux系统中都被看作是文件,而它们都有相应的文件描述符与之对应:0,1,2 对应的物理设备一般就是 键盘,显示器,显示器,也就是说,虽然标准输出和标准错误最终都是打印屏幕上,实际上它也有一个短暂的被写入文件的过程。
文件描述符的分配规则
现在我们知道了文件描述符其实就是一个整数,那我们如何通过这个整数找到对应的文件呢?其实在每个进程的task_struct结构体中,有一个文件描述符表,文件描述符就是文件描述符表的下标,通过这个下标就可以找到对应的文件。并且文件描述符表的数量是有上限的,不能无限打开,所以一个文件如果不再使用就应该及时关闭(close),从而释放文件描述符表的对应项。如果文件一直打开不关闭,就会造成文件描述符泄露,从而无法正确打开新的文件。
为了可以最大限度的使用文件表描述符表,我们就应该有一个对应的分配规则:从0开始查找,找到第一个空闲的位置作为新文件对应的表项,并把这个下标当做文件描述符返回。也就是说,操作系统每次会寻找文件描述符表中最小未被使用的文件描述符返回。
用代码来验证,已经知道系统会默认打开三个文件描述符,那么我们新打开的文件的文件描述符就应该是3。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("hello_sys", O_CREAT|O_RDWR);
if(fd < 0) {
perror("open hello_sys failed!\n");
return 1;
}
printf("%d\n", fd);
close(fd);
return 0;
}
前面我们说过,系统会有三个默认打开的文件描述符,而这三个文件描述符并不是不可以更改的,看代码
int main()
{
close(2);
int fd = open("hello_sys", O_CREAT|O_RDWR, 00644);
if(fd < 0) {
perror("open hello_sys failed!\n");
return 1;
}
printf("%d\n", fd);
close(fd);
return 0;
}
我们先关闭文件描述符2,并且打开一个新的文件,看看新打开文件的文件描述符是多少。
通过运行结果我们可以看到,因为我们关闭了文件描述符2,此时它就是最小未被使用的文件描述符,当然就分配给了新打开的文件。那么如果改变了默认的文件描述符,那么它们的功能还在吗,我们再来试一下
int main()
{
close(1);
int fd = open("hello_sys", O_CREAT|O_RDWR, 00644);
if(fd < 0) {
perror("open hello_sys failed\n");
return 1;
}
printf("%d\n", fd);
char buf[] = "hello world!\n";
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
这一次我们关闭了文件描述符1,根据文件描述符的分配规则,新打开的文件就被分到了文件描述符1,那么我们来打印一下,可以看到屏幕上是没有任何输出的,再来看看 hello_sys 文件,可以看到我们打印的函数被写入到了这个文件中,这样就实现了输出的重定向。