C库函数写入显示器属于行缓冲,顾名思义,也就是遇到换行符’\n’就会立即刷新出去。
C库函数写入文件时是全缓冲,也就是写满缓冲区才能刷新,如果发生重定向时(关闭1号文件)数据的缓冲方式就会由行缓冲变为全缓冲
但是如果关闭1号文件,则printf与fprintf都没有输出任何东西,因为发生重定向了描述符1指向的是其他文件,变成了全缓冲。
但是为什么write生效了呢,这就说明write,也就是系统调用是没有缓冲区的
全缓冲内容需要被刷新的话,需要用fflush函数进行刷新缓冲区。
fflush与fork
当行缓冲变成全缓冲并用fork()创建进程,但是当用fflush()函数的时候,printf和fprintf输出了两次,为什么?
在全缓冲模式下,我们放在缓冲区中的数据不会立即刷新,当进程退出后,会统一刷新,这里我们使用了fflush强制刷新。fork创建时会发生写时拷贝,子进程自然会获得父进程的缓冲区数据,所以两个进程执行到强制刷新时各自刷新,自然就有了两份数据,而write前面说过没有缓冲区,所以在这里还是没有变化
其实如fprintf,fgets,printf等这类函数都是有自带的缓冲区的,而系统调用write没有缓冲区。
缓冲区是在用户层面上的,是库函数为了提高性能而创立的,二次开发的。
在FILE结构体中,其实就有关于缓冲区的维护
等待进程结束或者遇到了强制刷新,这些维护缓冲区的指针就仍会调用系统结构将数据刷新给操作系统
在用户层,stdout这个文件指针指向的文件已经封装了,并且它的fd就是1,这是不能修改的,所以我们一上来关闭了1号文件,然后新创建了一个文件它的文件描述符就会分配为被1,同时此时写入时,像printf这类函数默认使用的输出流就是stdout,但是我们知道它的1指向的已经是我们新生成的那个文件了,所以这就重定向的本质
通俗易懂点,就是将原本应该打印在描述符为n文件的玩意儿输入到了描述符为m的文件里头。
函数原型如下
#include
int dup2(int oldfd, int newfd);
像前面直接关闭1号文件似乎不太合乎情理,因为标准输入,标准输出和标准错误作为默认的文件,不应该被关闭。
但是用dup2函数很好的解决这一问题,我们都知道描述符都是以指针的形式指向所需文件,那么newfd和oldfd可以这么理解,newfd指向了oldfd所指向的文件。
例如:要将1的内容重定向到fd,那么就输入dup2(fd,1)
磁头是硬盘中对盘片进行读写工作的工具,是硬盘中最精密最关键的部位之一。最初的磁头是读写二合一的,后来逐渐分离出读磁头和写磁头两个部分
一个磁盘(如一个 1T 的机械硬盘)由多个盘片(如下图中的 0 号盘片)叠加而成。
盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面
每个盘片被划分为一个个磁道,每个磁道又划分为一个个扇区。
其中,最内侧磁道上的扇区面积最小,因此数据密度最大。
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。
所有盘面中相对位置相同的磁道组成柱面。
硬盘结构复杂,操作系统对这些扇区进行寻址时将会变得很麻烦,所以一般是用文件系统把硬盘的若干扇区组合成簇或者树形结构,方便数据的增删查改
在逻辑上的表现则为,区域划分,根据边界等其他条件确定位置。
其实这种划分方式可以这么理解:中国划分由省、市、县、区等等来进行划分管理
硬盘容量动辄1T,2T,也不是很容易管理,所以硬盘分区被划定为一个个的block,一个block的大小是由格式化的时候确定的,并且不可以更改。但是不像真实生活中那样,管理河北省和四川省还是有区别的,因为还会涉及到其他一些因素,但是管理块就没有那么大的麻烦了,只要把一个块管理好,剩余的全部进行“复制粘贴”就可以了
文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构,即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。文件系统由三部分组成:文件系统的接口,对对象操纵和管理的软件集合,对象及属性。
从系统角度来看,文件系统是对文件存储设备的空间进行组织和分配,负责文件存储并对存入的文件进行保护和检索的系统。具体地说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等
文件=属性+内容,当创建一个空目录时,虽然无内容,但是其属性也是需要占据一定空间的。
所以我们把文件的属性的集合称之为inode,把文件的内容的集合称之为block,这些文件可以删除,可以复制,可以粘贴,所以我们把实现它的一些方法以及前面的inode和block等其他信息组装在文件系统里,以此方便对文件进行管理
例如:ls -al
其实,当我们输入ls -l后这条命令就成为了进程,这条进程就会根据一定的方法去拿到对应的文件信息,然后将其加载进内存,显示在我们的面前
而它是怎样找到对应的文件的呢,其实就和inode有关,使用stat命令查看文件的一些信息时,会发现其中就有一个叫做inode的属性
如下可以看做是一个分区(比如上图中的0-500),一个分区搞好了,其余分区也是这样,需要注意的是这里我们讲的是硬盘上的结构,当进入内存后,内存里也有其相应的映像
每个分区中又会被划分,也就是咋们上面说过的块(block group),同样这个分区中只要管好了一个块组 其余也都是是复制粘贴了
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。
而上图中启动块(Boot Block)的大小是确定的。它包含了一个硬盘的一些基本信息,比如放在D盘中的软件,引导操作系统知道软件在D盘中,当然这一部分损坏的话,这个分区也就完了。
块中内容
例子:
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc
文件创建
创建文件,首先就要给这个文件分配inode,所以在inode位图中找到不为0的位置,置为1,然后根据这个分配inode在inode Table中写入文件的创建信息;接着写入内容,在block位图中也去遍历,找到不为0的位置,置为1,然后在Data blocks中分配空间,同时让inode和Data Blocks建立映射关系,这样inode就对应了一个或多个数据块。
文件查找
同时,如果一个文件的inode是1234,那么查找这个文件的过程:首先肯定要找到对应的分区和块组,然后遍历inode Table找到对应的值,拿到属性之后,根据映射关系,就可以找到数据块内容
文件删除
删除数据时,无需将inode Table和Data block中的内容抹去,直接在inode位图中将对应的1置为0即可。因为硬盘本质就是存放的是0和1,在这里没必要去做一些无用操作,这也就是为什么删除文件会很快的原因 。同时既然删除文件时,没有抹去inode,所以恢复文件时知道当时的inode就可以遍历inode table实现恢复,所以一些数据恢复软件的本质也是基于此的。
所以删文件比下载文件快很多很多
前面的文件大家应该能够理解了,那么这里目录究竟干了什么。首先能够确定的一点,Linux下一切皆文件,所以目录是一种文件。
ls -l xxxxxxx
如下:列出某个文件的具体信息
这些文件的信息没有保存在目录里面,操作系统也没有必要这样做。目录只维护了文件名和其对应的inode的映射关系 ls -l test.txt
,它首先找到目录的数据块,这些文件名和映射关系就存储在这里,然后根据映射关系再去列出文件的具体信息
我们先创建一个硬链接hard_log.txt
它们的inode相同,在修改log.txt
的内容后,对应的hard_log.txt
也会发生相应变化
目录保存的是文件名和映射关系,如果删除了原来的文件,它删除的只是文件名和映射关系,而不是文件本身,相当于这里进行重命名
创建软链接soft_log.txt
发现并不是相同的inode,这表明软连接是一个独立的文件,这个文件的内容里保存的是路径等其他信息,软链接也就是我们windows下常说的快捷方式。
红色框所指的就是链接数,很多文件的默认链接数为1,因为只有一个链接,也就是只有一个映射关系。但是为什么目录dir1有两个呢?
这涉及到隐藏文件。
其中一个肯定是自身,还有一个(这里我们进入dir3)就是隐藏文件 点(.)
这些文件都有一个共同特点也就是同属于硬链接,即inode相同,只是一个文件名和inode的映射关系而已。
库都是现有的,已经写好的可复用的代码,试想如果没有库,那么所以程序都需要从零开始,成本实在太大
其本质是可执行代码的二进制形式,可以被操作系统载入内存,主要分为静态库(.a)和动态库(.so)
静态和动态指的就是链接。我们知道编译一个C程序需要经过预处理,编译,汇编和链接这4个步骤。在链接这个步骤,会将obj文件与系统库进行链接生成可执行文件。
#include
int main()
{
printf("hello\n");
return 0;
}
静态库在链接阶段,会将.o文件与用到库一起链接打包到可执行文件中
编译只需假设-static则可使用静态链接(默认情况是动态链接)。
使用file命令查询文件信息
这段程序使用的静态库的名字是libc.a
,其真正名字的是c,也就是c库
静态库体积较大,你可以将静态库看成一组目标文件的集合,也就是很多目标文件经过压缩打包后形成的一个文件
静态库在链接时在预处理阶段就被添加到了代码中,所以会使得形成的可执行文件过大,但是程序在运行过程中就再也无需依赖库了,所以其移植性很强,当然会很浪费资源和空间
静态库最大的问题就是空间浪费,而且库与库之间也是有依懒性的,所以有时只需要一个库的代码,但是由于依赖性导致可能会链接到多个库,那么空间的浪费程度是很恐怖的。
例如:静态库占用1M内存,有2000个这样的程序,将占用近2GB的空间
接下来我们创建动态库。
PS:进程地址空间中在堆和栈之间有一部分共享区域,这是因为动态库可以在多个程序之间共享,所以动态链接就会使得可执行程序很小
我们都知道使用printf
,scanf
需要引入头文件#include
。printf
,scanf
就是库函数,一个标准的库包含两个东西:头文件和对应的.a
或. so
文件
C语言提供的头文件就在/usr/include
目录下
C语言提供的库则在/usr/lib
目录下
如果我要提供一个库供他人使用,那么我编写好众多.c
文件和对应.h
文件后,将.c
文件编译为.o
文件,然后将众多.o
文件打包为上面的.a
或.so
文件,然后将.a
或.so
文件再次打包后,就能提供给别人使用了。
头文件是告诉编译器有哪些方法,以及对应的参数是什么,.a文件.so文件则封装了它们的实现方法,相当于源文件