标准输出和标准错误对应的都是显示器文件,其内容都会打印到显示器上。那么标准输出和标准错误有什么区别呢?标准输出和标准错误都打印到显示器上,我们怎么区分哪些是标准输出打印的信息,哪些是标准错误打印的信息呢?
我们用一段代码来对比一下标准输出和标准错误的区别:
我们分别往stdout文件和stderr文件里写入内容,标记为[1]号的内容是写入到stdout文件里的,标记为[2]号的内容是写入到stderr文件里的。
运行程序查看结果:
我们可以看到,向stdout文件写入的内容和向stderr文件写入的内容都打印到了显示器上。
输入指令:./myfile > stdout.txt
即将内容重定向到stdout.txt文件里,我们会发现结果只有标准输出文件里的内容被写入到了stdout.txt中,而标准错误文件里的内容并没有被写入,依然是打印在显示器上。
这个现象解释起来非常简单,我们的指令./myfile > stdout.txt
默认是将标准输出文件里的内容进行重定向,完整的指令应该是:./myfile 1 > stdout.txt
.
如果我们想要同时将标准输出文件重定向到一个新的文件,将标准错误文件重定向到另一个新的文件,应该输入这个指令:./myfile > stdout.txt 2>stderr.txt
,此时就能将标准输出的内容和标准错误的内容分开到两个不同的文件里了。
如果我们就想要将标准输出和标准错误的内容都重定向到一个文件里,那我们需要使用这个指令:./myfile > all.txt 2>&1
我们前面介绍过,在内存中的文件都是被打开的文件,我们一直进行的文件操作也是针对被打开文件的,但在磁盘上有大量的没有被打开的文件,这些文件就存储在磁盘里。所以对于文件系统的了解,我们不能还把视角放在内存上,而是应该把视角迁移到硬盘上。
如下是磁盘的内部结构图,磁盘的主轴串联着一摞盘片,每一个盘片都有两面,两面都可以存放数据,我们存储在磁盘上的数据就是在这个地方。传动轴的一端有磁头,磁头是在盘片上方的(并不与盘面接触),一方面盘片在旋转,另一方面磁头在摆动,盘片旋转决定的是磁头访问到的哪一块区域,磁头摆动决定的是访问盘片的内侧还是外侧,这就是磁盘的寻址过程。并且每一个盘面都配备一个磁头(即一个盘片的正反面都有磁头)。
磁盘盘片的盘面会被分成很多组同心圆,这些同心圆称为磁道。每一个磁道(即每一个同心圆)都会被划分为一组扇区,扇区之间由间隔隔开,如下图所示。
磁盘上存储的基本单位是扇区。
所以将来在进行磁盘寻址的时候,磁头找的是某一个面上的某一个磁道上的某一个扇区,这种寻址方式叫做CHS地址。
当操作系统要对磁盘进行写数据操作或者读数据操作,操作系统和磁盘之间又是怎么建立联系的呢?
实际上,在操作系统层面,磁盘的盘片就被逻辑抽象成为线性的数组,这个数组的每一个单位就是一个扇区,定义了这样的一个数组,操作系统只需要有数组的下标,就可以实现对磁盘的管理,这也是先描述再组织的思想。这种下标地址我们叫做LBA地址,即Logical Block Address,逻辑块地址。所以如果内存中有数据需要写入到磁盘里,操作系统会根据LBA地址映射转换成CHS地址,然后将内存中要写入到磁盘的数据配合着CHS地址写入到磁盘里,至此就完成了对应的写入操作。
我们的磁盘是一块很大的内存空间,管理这么大的一块内存空间成本是比较高的。因此操作系统对磁盘的管理采取的是分而治之的思想,即将磁盘分成几个区,然后分别对单独的一个区进行管理。磁盘分区其实非常常见,我们的电脑上一般会有所谓的C盘、D盘甚至是E盘F盘,这就是磁盘的分区。
将磁盘分成一个个的区以后,我们就可以针对一个区进行管理,其中每个区又可以继续分成若干个组,如下图所示:
一个分区里面最开始一般都有一个区域叫做Boot Block,这里存储的是开机信息,这些开机信息一般包含了分区表、操作系统软件在磁盘的什么位置等,所以在开机的时候硬件就可以通过这里的开机信息找到我们的操作系统。除了这个区域外剩下的区域就被分成了一个个的小组。
到这里,我们就将磁盘的管理转换成了一个组的管理。下面我们可以介绍针对一个组的管理方案,首先我们要明确一点:文件是由文件内容和文件属性组成的,文件内容和文件属性都是数据,它们都要被存储起来,在Linux中采用的是将内容和属性数据分开存储的方案。
其中文件内容被存储在block中,也就是我们所说的磁盘中的块,一个块是4KB大小的空间;文件属性被存储在inode中,inode就是磁盘上的另一份空间,一个inode空间的大小一般是128字节。
有了这两个概念以后,我们可以介绍磁盘分区中一个块组的内容:
ll -i
既然文件内容和文件属性是分开存储的,文件内容存储在Data blocks中,文件属性存储在inode Table中,那么文件属性和文件内容是如何关联起来的呢?
在inode Table中,一般情况下每一个文件就对应着inode Table里的一个inode,inode实际上是通过一个结构体struct inode来描述文件的所有属性的,在这个结构体中还有一个blocks[]数组,这个数组存储的就是该文件内容存储在Data blocks中对应的编号,通过blocks[]这个数组就可以找到文件内容所对应的编号,就可以到Data blocks中找到对应的文件内容。
这里还可以引出另一个概念:文件名也属于文件的属性,但是在inode里面并没有保存文件名。原因是在Linux下,底层实际上是通过inode编号来标识文件的,实际上Linux底层并没有文件名的概念。
但是我们在Linux操作系统下看到的文件都是具有文件名的,既然文件是由inode编号来标识的,那么我们又怎么能够通过文件名来找到文件呢?
我们说过在Linux下一切皆文件,那么目录也是属于文件,因此目录也具有文件内容和文件属性,目录的文件内容里存储的就是该目录下所有文件的inode编号和其文件名的映射关系。通过这个映射关系,我们上层看到的文件名就可以和底层的inode编号关联起来。也正是因为这个原因,在同一个目录下不能存在同名文件,因为文件名对应的是inode编号,如果文件名相同意味着inode编号相同,这是不可能的,每一个inode编号都是独一无二的。
接下来我们就可以回答一个问题:当我们创建一个文件的时候,操作系统做了什么?
首先操作系统会在分区内部的一个块组里的inode Bitmap查找没有被使用的inode,找到以后将inode Bitmap里对应的比特位由0置1,然后将创建的文件的属性值写入到inode Table对应的inode里;当我们往文件写入内容时,操作系统会到Block Bitmap里查找没有被使用的块,然后将要写入的数据写入到对应的块中,最后再在inode的blocks[]数组里填上对应的编号。这还没有完,我们创建一个文件一定是在一个目录下创建的,在创建文件的时候用户会给文件指定文件名,操作系统会根据目录的inode找到目录的Data blocks,将文件名和inode编号的映射关系写入到目录的数据块中。
那么当我们删除一个文件时,操作系统又做了什么呢?
操作系统根据目录的inode找到目录的Data blocks,再根据文件名和inode编号的映射关系拿到文件的inode编号,再通过inode编号找到文件的inode Bitmap和Block Bitmap,将inode Bitmap和Block Bitmap对应的比特位由1置0,就完成了删除工作。
软链接是通过名字引用另外一个文件,相当于Windows操作系统下的创建快捷方式,软链接创建出来的文件是一个独立文件,它和原来的目标文件不共用同一个inode编号。软链接文件并不是对原来文件的拷贝,而是将原来文件的路径存储到软链接文件的内容当中。我们可以看一个例子理解一下:
我们当前在dir1目录下,写了一个代码生成了一个可执行程序mytest,在当前dir1的目录下我们运行可执行程序只需要输入指令./mytest
即可。
但如果我们退出到上一层目录,想要运行刚才的可执行程序mytest就需要带上比较长的路径:./dir1/mytest
,假如我们的目录再深一点,运行可执行程序需要带上的路径就会更长,非常的不方便。
因此我们可以在上级目录建立可执行程序mytest的软链接,输入指令:ln -s ./dir1/mytest mytest
,这样我们就创建了目标文件的软链接,即在当前目录下的快捷方式,可以直接./运行起来了。
我们知道了在磁盘上找文件是通过inode编号而不是文件名,文件名和inode是通过映射关系关联起来的,其实在Linux中可以让多个文件名对应同一个inode编号。硬链接就是单纯地在目录下给指定的文件新增“文件名和inode编号的映射关系”。
我们可以测试一下:在当前目录下先创建一个文件叫做my.txt,然后输入指令:ln my.txt my.txt.hard
,创建该文件的硬链接,新的文件名设置为my.txt.hard,查看结果我们可以发现,两个文件的名字不同,但是inode编号相同。
我们输入指令ll
查看到文件属性,其中红框框起来的这一列数字代表的就是该文件的硬链接数。硬链接数本质上就是该文件属性中的一个计数器,用来标识有几个文件名和该inode编号建立了映射关系。
如果我们创建一个新的空目录和一个新的文件,查看它们的硬链接数会发现,目录的硬链接数是2,文件的硬链接数是1,这是为什么呢?
文件的硬链接数是1非常好理解,因为创建一个新的文件,文件名本来就已经和inode编号建立了映射关系,所以硬链接数当然是1。我们再查看一下目录里面的内容:我们发现空目录里面有一个 “.” 目录,它与dir1目录的inode编号相同,所以dir1的硬链接数是2,这个 “.” 目录就是当前目录。所以我们平时运行可执行程序时输入的 “./” 指令,这个 “.” 就是当前目录下的意思。
建议使用unlink来删除软硬链接,输入指令:unlink mytest
、unlink my.txt.hard