进程与文件
当我们对文件进行操作时,文件必须要被加载到内存中,然后CUP从内存中拿到此文件进行操作,没有打开的文件放在磁盘中存储。
文件的打开其实也是设计到内部某个进程。无论是系统调用,还是专有库中的函数,都是启动进程来进行打开。进程会自动记录目前启动时的当前路径,平常所说的相对路径就是指相对于当前进程路径下的路径。当我们没有特意说明文件路径在此进程中对文件操作时,默认会在此进程的路径下进行。比如我们使用C语言新建文件使用绝对路径,默认就会在此进程的路径下进行,若是此进程的路径发生改变,新建的文件会在改变后的路径下进行,这就是相对路径的原理。
[zhu@VM-16-10-centos day3]$ ll ..
.........
[zhu@VM-16-10-centos day3]$ cat myfile.cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
using namespace std;int main()
{
chdir("..");
FILE* fp = fopen("test.txt", "w");
if (!fp)
{
perror("fopen");
exit(1);
}
char* str = "file system";
int n = fwrite(str, strlen(str), 1, fp);
cout << "write block: " << n << " " << "pid = " << getpid() << endl;
fclose(fp);
return 0;
}
[zhu@VM-16-10-centos day3]$ ./myfile
write block: 1 pid = 12291
[zhu@VM-16-10-centos day3]$ ll ..
...........
-rw-rw-r-- 1 zhu zhu 11 Mar 21 10:52 test.txt //创建的文件
总的来说是进程在打开文件。我们对文件的操作都是由系统下的某个进程进行的。
Linux下的文件系统
在Linux系统下,我们可以把一切都看成文件(包括硬件)。Linux系统有一个重要特性,即“一切皆文件”的原则。无论是普通的磁盘文件、目录,还是网络套接字、硬件设备,在Linux中都被抽象为文件。
我们平常使用高级语言进行底层文件的调用,本质上是封装了系统调用。因为用户不能直接调用系统硬件,本质上是操作系统进行调用的。我们通常使用语言进行调用实际就是操作系统提供了相应的接口供用户使用。比如以C语言为例,C语言的库函数接口fopen、fclose、fread、fwrite 在某种意义上来讲调用的是系统接口open、close、read、write(这些函数运用跟C中的文件操作相似,不懂的可用man指令查看文档,这里不再过多解释),只不过对系统调用进行了封装。系统调用接口和库函数的关系如下:
下面我们来认识一下文件流操作。程序在启动时,默认会打开三个文件流:stdin、stdout、stderr。这三种流的类型都是文件指针FILE*。
stdin:标准输入——默认是键盘设备。计算机系统从此文件流中获取数据信息,即从此文件中读取数据。
stdout:标准输出——默认是显示器设备。将数据输出到此文件流中,即从此文件中输出数据。
stderr:标准错误——默认是显示器设备。用于输出程序或命令的错误信息,与stdout原理相似。
正因有了标准输入输出流操作(I/O设备操作),才能使得程序能够与用户和其他程序进行有效的交互。
那么问题来了,系统下的所有都是文件,程序系统又是如何找到对应的文件?其实每个文件都有一个对应的文件描述符进行标志。文件描述符是一个非负整数,与文件名形成了一种索引关系,使得程序可以通过这个整数来访问和操作对应的文件。
文件描述符的范围是0到N,其中0、1、2是特殊文件的文件描述符:0代表标准输入(stdin),1代表标准输出(stdout),2代表标准错误输出(stderr)。一般情况下,文件描述符从3开始数往后分配。因为内部的文件描述符其实就是存放管理文件结构体(struct file)的指针数组 fd_array 的下标,此指针数组每个元素都是一个指向打开文件的结构体指针,而task_struct内部指向存放此指针数组的结构体(struct files_struct)。总的来说文件描述符就是数组的下标,当使用一个文件时就必须找到此文件的文件描述符,通过文件描述符来找到对应的文件。
C标准库中的FILE其实就是自己封装的一个结构体,里面封装了 stdin、stdout、stderr 的文件描述符0,1,2。之所以系统不直接封装而让语言单独封装是为了保证可移植性。若是系统直接封装,一旦换了平台系统可能就会出问题。导致不可移植。其实不仅仅是流操作,很多有关系统接口也一样,为了保证可移植性,都是在不同语言内部封装不同系统调用的接口和相关的文件接口。
文件描述符及重定向
从上可看出,每个文件都有唯一的文件描述符。每个文件的文件描述符的分配规则是最小的没有被使用的数组下标会分配给最新打开的文件。我们先来看以下代码。
[zhu@VM-16-10-centos day3]$ cat open.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main()
{
close(1);
int fd = open("myfile", O_WRONLY | O_CREAT | O_TRUNC, 0666); //打开文件,成功后返回文件的文件描述符,失败返回-1
cout << "fd = " << fd << endl;
close(fd);
return 0;
}
[zhu@VM-16-10-centos day3]$ ./open //发现在标准输出流中什么也没有输出
[zhu@VM-16-10-centos day3]$ ll
total 32
-rw-rw-r-- 1 zhu zhu 7 Mar 21 20:31 myfile
............
[zhu@VM-16-10-centos day3]$ cat myfile //发现输出到文件log.txt中
fd = 1
注意,文件描述符会按照最小下标分配,以上程序中关掉了标准输出流的文件描述符1,当我们创建文件log.txt时会给此文件分配描述符1。我们平常输出的数据都是往标准输出流stdout中输出的,也就是系统会自动往文件描述符为1的文件中输出。这里log.txt的文件描述符为1,所以就会出现以上输出重定向。输入重定向同理,将文件描述符为0的进行覆盖。
重定向运用的就是以上的原理——系统将有关文件的文件描述符进行修改为指定的文件描述符或指针数组元素的重新指向。以下是输入重定向的原理示例。
[zhu@VM-16-10-centos day3]$ cat log.txt
123456
[zhu@VM-16-10-centos day3]$ cat myfile.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;int main()
{
close(0); //将标准输入重定向(键盘)去除
open("log.txt", O_RDONLY); //此时文件log.txt的文件描述符为0,即成为了输入流
int a = 0;
cin >> a; //从输入流中读取数据
cout << a << endl;
return 0;
}
[zhu@VM-16-10-centos day3]$ ./myfile
123456
不难发现,以上类似的程序完成重定向功能比较麻烦。说白了,重定向功能就是分配到指定的文件描述符,而文件描述符对应指定文件的功能,这里我们可直接让指定文件的文件描述符指向对应功能的文件描述符所指向文件的功能即可。比如将文件描述符为3的指向文件描述符为1所对应的系统文件,即指针数组元素之间的浅拷贝,fd_array[1]=fd_array[3]。
系统提供了相应接口函数dup来完成这种浅拷贝。
头文件
#include <unistd.h>
接口形式
int dup2(int oldfd, int newfd);
将所对应的oldfd拷贝到newfd,即fd_array[newfd]=fd_array[oldfd],此时文件描述符newfd所对应的文件结构将被覆盖。
dup2用法
[zhu@VM-16-10-centos day3]$ cat myfile.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;int main()
{
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd, 1);
cout << "fd = " << fd << endl;
return 0;
}[zhu@VM-16-10-centos day3]$ ./myfile
[zhu@VM-16-10-centos day3]$ cat log.txt
fd = 3