时间过的真的很快,上一次写关于c语言文件操作时还是个菜鸟(虽然现在也还是菜鸟,emmm),转眼半年过去,笔者也已经学到了Linux的基础IO阶段,关于Linux下的基础IO我们又要接触到很多的文件操作。如果你对于c语言的文件操作不是很熟悉,介意你可以戳上面的链接,之前写的文件操作非常适合小白阅读。
怎么说呢,今天要和大家聊的东西算是比较无聊的一个课题,但是划重点,无聊不代表不重要,恰恰相反,我们本篇博客会讲解很多系统调用函数都非常的重要。我们之前使用的c文件操作函数都是今天所讲系统调用函数的封装,那么话不多说,进入我们的主题。
由于我们本博客还是会讲到很多关于文件操作的函数,所以我们非常有必要一起来回顾一下c语言中的文件操作,这里非常简单的函数我就不在提了,有兴趣的同学点击上面的链接就可以了。
在c语言中我们向一个文件中写入数据使用的是fwrite函数:(忽略c++的函数)
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5
6 int main()
7 {
8 FILE* fp = fopen("log.txt","w");
9 if(fp == NULL)
10 {
11 cout << "open error"<<endl;
12 return 1;
13 }
14 const char* msg = "hello bit\n";
15 int count = 5;
16 while(count--)
17 {
18 fwrite(msg,strlen(msg),1,fp);
19 }
20 fclose(fp);
21 return 0;
22 }
在c语言中我们从一个文件中读出数据使用的是fread函数:
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5
6 int main()
7 {
8 FILE* fp = fopen("log.txt","r");
9 if(fp == NULL)
10 {
11 cout << "open error"<<endl;
12 return 1;
13 }
14 const char* msg = "hello bit\n";
15 char buff[1024] = {0};
16 ssize_t s = fread(buff,strlen(msg),1,stdout);
17 printf("%s",buff);
18 fclose(fp);
19 return 0;
20 }
我们使用fread函数时第四个参数使用了stdout,这里提一下一个C程序默认会打开三个输入输出流,分别是stdin, stdout, stderr ,man手册发现,这三个流的类型都是FILE*,这一点很重要,我们会在后面用到这一概念。我们这里着重就回顾这两个函数,后面我们使用的系统调用函数会和这两个函数对照起来使用。
不管是c语言的文件操作还是系统文件的操作,我们都少不了打开一个文件,关闭一个文件,向一个文件写入,那么一起来看看系统函数是怎么实现和我们上面同样的操作的呢?
open函数
close函数
这里参数很简单,把要关闭文件的文件描述符传给close函数就行
write函数
现在我们使用上面介绍的三个函数尝试创建一个文件并且写入:
umask每次都将文件的权限设置为0,同时使用O_WRONLY(只写)和O_CREAT创建并向文件中写入数据,这里要注意的是我们使用俩种方式组合起来操作文件时使用按位或,因为这里的O_WRONLY和O_CREAT其实是两个宏,这两个宏实际上是俩个数字,他们不同之处就在于他们的bit位至少有一位肯定不相同。0644是我们文件的8进制权限码。
实际上我们的重点不在于怎么使用上面的函数对文件进行写入,我们来讨论一些更有趣的东西,上面提到open函数的返回值是文件描述符,文件描述符就是一些大于0的整数,但是说了这么多,文件描述符到底有什么用呢?接着看
打印我们刚才创建文件的文件描述符,为3.此时大家都一脸懵逼,为什么是3.
为了搞清楚原因,我们不如接着创建几个文件打印他们的文件描述符来研究研究:
我们发现,这次得到的结果是3 4 5 ,此时我们更懵逼了,这到底是什么意思?并且还有一个问题是0 1 2 哪里去了?
还记得我们上面说过一句话么一个C程序默认会打开三个输入输出流,分别是stdin, stdout, stderr ,而这里我们再记一句话,一个进程默认打开三个文件描述符,0对应标准输入,1对应标准输出,2对应标准错误
那么既然这三个文件描述符对应了输入输出流,那么我们能向文件种写入数据,是不是也能用write函数向屏幕写入数据。有句话不是说Linux下一切皆文件么。
正如我们所猜想的,我们成功的将数据写入到了显示器上。现在我们就清楚了0 1 2代表什么了,所以我们也就明白为什么我们创建的文件描述符从3开始。
现在回到我们之前的主题,这些文件描述符有什么用呢?其实是这样的,大家都知道操作系统管理进程时需要用结构体先描述,然后再将他们组织起来,同样的道理,我们的一个进程可能会与相关的上百个文件有关联,那么我们管理这些文件是同样也要先把他们描述起来再进行组织。
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来 描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数 组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件 描述符,就可以找到对应的文件 。
我们将0或者2文件关闭,接着打开一个新文件。
int main() {
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的 最小的一个下标,作为新的文件描述符。
现在我们要使用我们上面的知识来给大家解释一个之前你肯定不明白的现象重定向,我们以前发现使用命令 数据 > 文件,我们的数据就被重定向到了相对应的文件种,那么它是怎么实现的呢?
我们将stdout文件描述符1关闭,打开一个新的文件,此时新文件的文件描述符为1,也就代表他现在是stdout文件,所以数据不在打印到屏幕,而是已经被打印到了文件中
而 **>>**的追加重定向只不过是将文件的打开方式更换为追加。
但是我们实现重定向一定要先关闭文件描述符1么?并不是,来看看下面的dup2函数
函数描述:makes newfd be the copy of oldfd, closing newfd first if necessary(使newfd成为oldfd的副本,必要时先关闭newfd)
什么意思呢?来通过图分析,的意思是直接将源文件地址赋值给目标地址(数组中实际上存放的就是文件结构体地址)
所以我们只需要在写入数据前给函数前加上dup(fd,1)函数调用就完成了重定向。