重定向
什么是重定向:
重定向的原理其实就是更改文件描述符中特定下标中的内容!
我们来验证一下这个结论:
输出重定向
如果我们关闭对应标准输出流的文件描述符1
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
close(1);//关闭标准输出流
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open fail:");
return 1;
}
//向屏幕打印信息
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");
fflush(stdout);//刷新缓冲区
close(fd);
return 0;
}
从上面我们可以看出,本来是向屏幕打印的数据,结果却重定向到了log.txt
。
输入重定向
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
close(0);//关闭标准输入流
int fd=open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open fail:");
return 1;
}
//向屏幕打印信息
char buf[128]={'\0'};
while (scanf("%s", buf) != EOF)
{
printf("%s\n", buf);
}
close(fd);
return 0;
}
从上面我们可以看出,本来是从屏幕上读取数据,结果从log.txt
里读取了。
dup2函数
dup2函数是操作系统专门给用户提供的一个系统调用函数,它的作用就是拷贝文件描述符
功能:dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中,如果newfd已经打开,dup2会先关闭它,然后再进行拷贝。
返回值: 如果调用成功,返回newfd,否则返回-1。
在使用dup2函数时,需要注意两个点:
oldfd:必须是一个有效的、已打开的文件描述符。如果 oldfd 无效,dup2 会返回 -1 并设置 errno。
如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open fail:");
return 1;
}
close(1);
dup2(fd,1);//进行重定向
printf("hello printf!\n");
fprintf(stdout,"hello fprintf!\n");
close(fd);
return 0;
}
缓冲区
语言缓冲区
我们先看一段代码演示:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
close(1);//关闭标准输出流
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open fail:");
return 1;
}
//向屏幕打印信息
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");
printf("hello world!\n");
close(fd);
return 0;
}
为啥我明明关闭标准输出流了log.txt
里还啥都没打印进去呢?
这里就跟缓冲区有关,常见的缓冲区刷新方法有三种:
行缓冲:对显示器进行刷新数据
全缓冲:对磁盘文件写入数据
无缓冲。
C语言的prinft
函数,如果加\n
就是行缓冲,没加就是全缓冲。
对文件重定向就是让本该打印在屏幕的数据输入进了一个磁盘文件,这时缓冲方法就从行缓冲变为了全缓冲,全缓冲需要程序结束后、缓冲区满时,调用刷新缓冲区的函数才会被刷新。
但是再次之前我们用close
关闭了对应的文件描述符(fd),所以在程序结束后就无法找到对应的文件,就不会对文件进行任何的写入,所以我们一般用刷新缓冲区的函数来提前刷新缓冲区(如fflush
).
系统缓冲区
我一样用一段代码演示:
#include <stdio.h>
#include <unistd.h>
int main()
{
//c
printf("hello printf\n");
fputs("hello fputs\n", stdout);
//system
write(1, "hello write\n", 12);
fork();
return 0;
}
我创建了一个子进程,为啥test.txt
里不是两份一样的数据呢?怎么少了一个write
?
这是因为我们执行可执行程序,打印到屏幕,默认是行缓冲,所以直接打印所有数据。但是如果我们对数据进行重定向的话,向磁盘写入数据,默认为全缓冲,此时数据都会存在语言缓冲区中。这时我们创建了一个子进程,父子进程代码数据共享,进程结束后语言缓冲区刷新,本质就是对数据进行修改,这时为了进程的独立性就会发生写时拷贝,那么因为是语言缓冲区刷新,C语言接口的数据才会打印两份,而
write
是直接写入系统缓冲区的,所以语言缓冲区和他没关系,他就不会发生写时拷贝,所以这就是为什么write只打印了一份。
磁盘与文件系统
磁盘
磁盘是一种永久性存储介质,在计算机中,磁盘几乎是唯一的机械设备。与磁盘相对应的就是内存,但是内存是掉电易失存储介质,所以目前所有的普通文件都是在磁盘中存储的。
磁盘的物理结构图:
我们如果需要找到磁盘的特定区域,只需要找到对应的扇区(sector)
,磁道(track)
,柱面(cylinder)
就行。
磁盘的逻辑结构类似于磁带:
磁带也是磁盘的一种,在磁带圈起来时,就类似一个盘面。将其拉直展开,就可以看成是由扇区(512字节)为单位组成的数组。
我们可以将磁盘想象成卷起来的磁带,那么就可以将磁盘的某一个盘片上的某一个磁道抽象为线性结构,类似于数组:
在计算机中,为实现高效磁盘管理,经常进行分区操作。分区编辑器可在磁盘上划分出多个逻辑部分,如 Windows下常见的C盘和D盘
。分区越多,文件管理越精细,不同性质文件可存储于不同分区。这样能更好地组织和管理文件,提升系统运行效率,便于用户快速找到所需文件,优化计算机使用体验。
我们Linux可以使用指令ll /dev/vda*
来查看磁盘的分区信息。
inode
因为文件 = 内容 + 属性
,文件的内容和属性又是分开存储的,文件内容放在Data blocks里,那么为了快速定位文件位置,所以保存文件属性的结构inode
得有编号以便于快速找到文件。
我们可以通过ls -i
,显示当前目录下的各文件编号。
EXT系列文件系统的分区结构
对于每一个分区来说,分区的头部会包括一个启动块(Boot Block),对于该分区的其余区域,EXT2文件系统会根据分区的大小将其划分为一个个的块组(Block Group)。其中启动块的大小是确定的,而块组的大小是由格式化的时候确定的,并且不可以更改。
组成部分:
一组中各区域分布:
inode table(节点表):存储了每个inode的详细信息,包括文件的大小、权限、时间戳、数据块位置等
Data blocks(数据区):存放文件内容,全都是划分好的4KB块
inode Bitmap(inode table位图):每个bit位表示inode table中的inode是否被占用。 inode号在inode table中早就设置好了,只是有没有被使用的问题。inode bitmap中的位置与inode table中的位置是一一对应的。
Block Bitmap(块位图):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
Group Descriptor Table:块组描述符,描述块组属性信息。例如:每个组的起始inode与block编号。
超级块(Super Block):存放当前分区文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。
Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。Super Block有多份拷贝,可在损坏时进行修复。
值得注意的是:
其他块组当中可能会存在冗余的Super Block,当某一Super Block被破坏后可以通过其他Super Block进行恢复。
磁盘分区并格式化后,每个分区的inode个数就确定了。
对于文件的几个问题,通过上面的学习基本可以回答出:
- 如何理解创建一个空文件?
- 遍历inode Bitmap,找到比特位为0的位置,申请一个未被使用的inode。
- 将inode表中找到对应的inode, 并将文件的属性信息填到inode结构当中。
- 将该文件的文件名和inode指针添加到目录文件的数据块当中。
- 如何理解向文件写入信息?
- 通过文件的inode编号找到对应的inode结构。 通过inode结构找到存储该文件内容的数据块,并将数据写入数据块。
- 若不存在数据块或者申请的数据块已经写满了,就需要遍历block
- Bitmap找到一个空的块号,并在数据区当中找到对应的空闲块,再把数据写入到数据块当中,最后还需要建立数据块和inode结构的对应关系。
- 删除文件做了些什么?
- 将该文件对应的inode在Inode map当中设置为无效。
- 将该文件申请过的Data Block在Block map当中置为无效。
为啥删除比下载快这么多的原因就在这里,因为文件内容并没有被删除,所以我们可以在对应文件内容被其他文件覆盖之前,通过技术手段复原已删除的文件。
- 如何理解目录?
目录也是一种文件,是文件就有对应的文件属性与文件内容,其中对应的文件属性就是我们的inode存储的就是目录的大小,目录的拥有者等。而对应的文件内容存储的就是该目录下的文件名以及对应文件的inode指针。
软硬链接
软链接
软链接就相当于Windows系统的快捷方式,软链接文件有自己的inode号。
建立软链接的方式:
ln -s 被链接的文件名 链接后的文件名
软链接文件是一个独立的文件,该文件有自己的inode。
软链接存储的是目标文件或目录的路径,而不是直接指向inode。
删除软链接不会影响目标文件,但如果目标文件被删除或移动,软链接将变为“悬空链接”(dangling link),无法访问目标。
取消软链接可以通过指令unlink 软连接名
。
硬链接
硬链接文件就是源文件的一个别名,它与源文件之间具有相同的inode,大小。一旦为某个文件建立硬链接,那么对应的硬链接数就会加一。
硬链接没有独立的inode,并不是一个独立的文件, 本质是在特定的目录下,添加一个文件名和inode编号的映射关系。
建立硬链接的方式:
ln 被链接的文件名 链接后的文件名
取消硬链接的方式和软链接一样都可以用unlink
。
我们普通的文件硬链接数量为1,为啥目录的硬链接都是2?
这是因为我们当前目录下还存在一个隐藏文件.
指向我们的当前目录,.
文件其实就是我们目录的硬链接文件。