文章目录
前言
I/O输入/输出(Input/Output),分为IO设备和IO接口两个部分。 在POSIX兼容的系统上,例如Linux系统 ,I/O操作可以有多种方式,比如DIO(Direct I/O),AIO(Asynchronous I/O,异步I/O),Memory-Mapped I/O(内存映射I/O)等,不同的I/O方式有不同的实现方式和性能,在不同的应用中可以按情况选择不同的I/O方式。- -百度百科
预备知识:
先从语言层面上讲:
1.文件在哪里?
磁盘(“狭义”),磁盘是一个外设,在磁盘上的文件,对文件上的操作都是对外设的输入输出,简称IO。linux下一切皆文件, 所以IO随处可见。
2.当我们创建了一个0kb的文本,他是否占用磁盘空间呢?
答:是的,文件 = 属性 + 内容。 属性又称文件的元数据(文件大小,修改时间,类型等等)。
结论一:
所以我们所学的文件操作,无外乎是对属性或者内容的操作!
fread,fwrite,fhets,fgetc,fputs…都是对内容上的操作,
fseek,ftell,rewind…是对元数据的操作。
系统角度:
进程看待文件的方式:
fread等文件操作代码->通过编译链接生成exe文件->exe文件加载到内存->成为进程
实际上对文件的操作,本质都是进程对文件的操作!
系统调用接口看待文件的方式:
用户访问硬件的时候,通过进程调用fopen,fread/fwrite,fclose后,后调用系统接口,经过操作系统,驱动,最后才能在硬件*问,驱动层会访问硬件后把数据放到驱动的内存管理模块供操作系统取。
1.回顾C语言所学的接口
1.1 fopen
FILE * fopen ( const char * filename, const char * mode );
第二个参数支持a,w,r等等。
其中w每次写入,都是重新写入,意味着之前的文件会被清空。
a:append,追加,本质在写入的时候会往后面继续增多。
fopen的第一个参数filename的理解:
我们不用文件名,以w方式,默认在当前路径下会把文件创建出来。
对当前路径的理解:
每个进程,都有一个内置的属性(cwd),我们通过ps axj可以查看进程的pid,然后内部死循环。然后ls/proc/进程id,就可以看到它的路径资源。
1.2 fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
第二个参数就是元素的基本单元多大,第三个参数count是写入多少个基本单元。
返回值成功写入了n个size,大多数情况count == 返回值。
注意:尽量size设置大一点,count设置小一点
再往文件写入的时候不需要把字符串的'\0'
写入进去。若写入则文本当中会出现乱码。虽然\0显示出来是不可显的,但是我们还是要注意这点。
因为在字符串末尾加入\0是c语言的规定,但是不是文件中的规定。
1.3 fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
对于本地定义的缓冲区末尾要加\0的理解
当我们将第四个参数设置为stdin,即从标准输入流当中读取,也就是从文件里面读出,此时由于文件不是以’\0’结尾,我们这个时候ptr缓冲区当中通常会少读一个,往我们定长的缓冲区的返回值大小的索引处放’\0’!
代码:
测试结果:
1.4 fclose
int fclose ( FILE * stream );
对读写的重新认知
任何c程序,都会默认打开三个"文件"(实际上是文件指针),标准输入(stdin),标准输出(stdout),标准错误(stderr)。我们可以看到他们是FILE*类型。
他们一一对应一个文件
标准输入:键盘文件
标准输出:显示器文件
标准错误:显示器文件
所有的外设硬件,归根结底对应的核心操作无外乎是读和写!
对键盘读的操作:键盘当中获取数据到内存当中。
对键盘的写入操作:无
对显示器的写入操作:操作系统往显示器上数据写入(printf)
对显示器的读操作:无,系统不会往显示器上读数据。
例子:当我们su- 输入密码的时候实际上是键盘输入到系统,而不是系统从显示器上读的。
对于c程序为什么会默认打开stdin,stdout,stderr的理解:
如果我们没有打开这些文件,我们就无法直接调用上面三种接口,倘若我们刚学c语言需要进行文件的操作把文件打开,这样就不便于语言进行上手使用了,所以几乎任何语言都是这样的。由于几乎所有语言都这样,所以这不是语言层提供的功能,而是系统层面提供的了。
一切皆文件
一切皆文件,看到这句话其实会觉得比较诧异,我现在手上敲打的不是键盘吗,我鼠标进行点击,操作系统是如何把他们都当作文件来看待的?
答: 其实无论是什么硬件,最重要的两个属性就是读和写。我往网卡里面读,往网卡里面写,实际上就是在网上冲浪。
操作系统是如何做到的呢?系统设计一个结构体,这个结构体实际上就包括了文件的一些属性,和读写方法!!而当我们插入鼠标的usb接口时,相当于驱动层就会把对应文件的结构体给填充好,操作系统要对鼠标进行操作的时候,就是调用结构体内的函数指针。
struct file{
//文件属性 --变量
//文件操作 op--函数指针 int(*read)();
}
系统当中是不是有上万个struct file?
不是的,只有打开的文件有对应的struct file,而没有打开的文件都存放在对应的硬盘上面。我们前半段讲述打开的文件,后半段讲述未打开的文件。
对于Linux一切皆文件的理解
第一层理解:
每个硬件都有对应的读方法和写方法,磁盘,网卡,普通文件。这些信息OS就是用struct file这个结构体来进行描述。
有这么多的文件,自然需要组织起来,我们需要对应的数据结构(struct file实际上是用双链表组织的,进程要和打开的文件对应是用的是数组下标),我们的操作系统掌握着数据结构,对数据结构的信息进行管理,通过信息中函数指针就可以调用指定硬件对应的某些操作,站在Linux的眼光下,就能够做到一切皆文件了,在OS系统下,相当在这层软件层使得一切皆文件变得可能。
站在系统角度,理解文件
系统调用:
fopen,fclose,fread,fwrite这些库函数一定要与底层的系统调用相关,枚举的库函数去掉f就是对应的系统调用。
在特定的一款操作系统当中,在语言层调用的读写操作都是调用对应的系统调用来完成。
2.读写系统调用介绍
2.1 open
open的第三个参数mode是文件权限,当我们需要创建文件(O_CREATE)的时候可以设置这个字段。
第二个参数flags有O_RDONLY,O_WRONLY,和O_RDWR读写打开。以及O_APPEND,O_CREAT等等。
第一个参数pathname是文件的路径,可以采用绝对路径或者相对路径。
返回值是文件描述符。假如不给mode属性创建出来的会有T(粘滞位)的属性。
注意:open当中O_WRONLY若没有写入文件是要|O_CREAT,这个同c语言的不同。
返回值<0即读取失败。
2.2 close
colse即关闭对应的文件描述符。
2.3 write:
write将本地的缓冲区数据写到指定的fd文件描述符当中。
对write进行测试:
测试一:
代码:O_WRONLY|O_APPEND就可以在读写的时候进行往后追加。
结果:
测试2:
验证O_CREAT是在文件不存在的时候才会创建,文件存在不会覆盖该文件,并且O_WDONLY|O_APPEND的组合才能够完成文件的写入追加,单纯用O_APPEND并不能完成。
结果2:
2.4 read:
read就是将所读的数据放入buf当中。
小测试:
代码:由于read时我们是放在c当中的char的一个数组,并且我们想要打印这个数组里面的内容,所以我们需要知道我们读取了多少个字符,我们期望读sizeof(buf),实际上返回值s是我们实际上独到的。buf[s]就能让buf是一个字符串数组。
结果:
致此,文件的读写执行都完成了。
对于读写系统调用理解
对于open的第二个参数flags的使用的理解:
采用的位图的方式,做到对整形的一个利用最大化,0X1,0x2,0x4,0x8这样子就能让每一种功能都只占一个比特位。实际上我们上面使用的O_CREAT,O_RDONLY就是一个个的宏,每一个功能都占flags的一个比特位。
open的函数体内部就可以使用下面代码的方式,对我们需要什么功能进行一个判断。这样flags的利用率实际上就相当于高了。
if(flags & O_RDONLY){...}
if(flags & O_CREAT){...}
我们打开/usr/include/bits/fcntl-linux.h
这个文件就可以看到相关的定义:
语言上对open,write,read,close封装的原因:
兼容自身的语法特征,系统调用使用的成本太高,并且不具备有可移植性。
如:linux的open在windows下跑不了,但是windows下用c读写文件,在linux用c读写文件都是可以的。这是因为语言会自动根据平台,选择自己底层对应的文件接口。
即使系统底层的实现有差异,但是在用户调用对应接口的时候也就感知不到了。
深入理解fd
如何理解fd: 正式进入系统底层调用接口的细节!!
open的返回值,这个整数是什么?
我们通过测试,当我们open多个文件时,观察他们的fd,发现他们是连续的返回值。以往我们接触这种小而连续的整数时,不由自主会想到数组下标!
代码测试:
结果:连续的小整数
缺少的0,1,2实际上对应的c当中默认被打开的标准输入,标准输出,标准错误。
结论二:
因为所有的文件,如果要被使用,首先要被打开。一个进程是可以打开多个文件的,那么操作系统就要把文件给描述,组织起来。
描述文件的结构体正是struct file
,组织方式是用双链表,里面正是目标文件的基本操作和部分属性,结构体内部还有对应的缓冲区(struct address_space * f_mapping)。然后通过缓冲区可以把文件刷新到磁盘上面。
其中struct files_struct
是用来构造文件与进程直接的关系的,用户层使用的文件描述符,就是struct file_struct 当中的一个字段:struct file* fd_array[],然后fd就是当中的最小的数组下标。每次open后得到的返回值就是上面的过程拿到的。每个进程都有一个struct files_struct*
,可以理解它指向一个文件描述符表,用来存储进程与文件之间的对应关系,通过数组下标的方式来管理,既简单,并且无重复。
系统分配文件描述符的规则: 最小的,没有被使用的数组下标,进行分配。
一切皆文件的第二层理解
将软件层解开面纱,即一张图解释这张示意图。
关于缓冲区的理解
看下面代码,思考发生何种现象。
实验结果: 运行程序没有打印出来结果,在log.txt中也没有出现任何内容
分析: 站在内核角度,文件描述符数组fd_array[1]的内容指向新打开的文件(普通文件),printf原先是往显示屏打印更改为往普通文件打印,由于显示器文件的刷新策略是行刷新,而其他文件是全刷新,所以数据就会在C语言的缓冲区当中存放,后续进程结束C语言会拿到1号文件描述符放到对应的内核缓冲区当中,但是最后close fd文件描述符后,相当于数据从C语言缓冲区无法到达内核缓冲区,即使进程结束,也无法通过fd再去刷新到对应的硬盘文件当中。
解决方法:在printf后面加fflush(stdout),这样就会刷新stdout,即1号文件描述符,然后就从语言层的缓冲区到一直贯穿体系结构到内核缓冲区待合适时候刷新到硬盘。
注意:这里的fflush是将语言层的缓冲区的数据拷贝到内核缓冲区,此时采用什么样的刷新策略和struct files指向的读写方法有关,倘若是指向的是显示器文件,采用的就是行刷新。
3.对于刷新的理解
用户层缓冲区的作用
用户层printf是拷贝到语言层缓冲区后直接返回,这样减少用户到内核层的切换,在合适的时候刷新到内核层缓冲区即可。用户和内核就解耦了!!
用户层缓冲区的刷新:即通过内核,贯穿体系结构写到内核缓冲区中。
OS的刷新策略:OS根据自身的情况同步通过磁盘驱动到磁盘当中。
刷新策略:
由用户缓冲区和内核缓冲区共同决定的。当如果是显示器设备,只有用户层缓冲区数据往内核缓冲区里面送,内核缓冲区会马上拷贝到磁盘当中。
每一个文件都有一个内核缓冲区。
printf的\n是在C语言的缓冲区的,也就是下面的这些字段,也可以说除了_fileno外大多数都是缓冲区。
struct FILE = 底层对应的文件描述符下标 + 应用层 C语言提供的缓冲区数据。
当我们在c语言输出一段字符串时,并不是马上贯穿体系结构,而是放到c语言的缓冲区,printf认为他自己的文件描述符还 是1,而fd_array中1的内容指向已经改变了,这个就是输出重定向。
由此,用户只需要使用fd进行操作。文件的更多属性在inode当中
FILE*文件指针,file descriptor是文件描述符,他们之间的联系:
在/usr/include/libio.h当中底层当中的FILE实际上是由typedef struct _IO_FILE FILE; 269行能看到一个重要的字段 int _fileno,即文件描述符的字段。也就是我们c语言用FILE*的时候,也是会通过这个字段调用我们的系统接口的函数。
通过grep -ER ‘struct _IO_FILE’ /usr/include当中我们可以查找到在stdio.h当中进行了类型的重命名。
在libio.h当中我们可以看到FILE当中的种种变量。
fopen实现代码简化:
行刷新和全刷新的小栗子,执行下面的代码,我们观测到什么现象呢?
内核行刷新,用户层全缓冲,往显示屏就是行刷新
结果,我们的输出设备是显示屏,打印的顺序和次数都很正常,这就是因为我们语言层缓冲区和内核层的缓冲区都采用的是行刷新。
当我们输出设备换成文件,内核的刷新策略便是全缓冲,系统调用接口write不会在用户层缓冲区停留,马上到了内核缓冲区就刷新出来了。而C接口则都停留在用户层缓冲区,fork创建的子进程拿到这部分数据,在进程结束后也对这些数据进行拷贝到内核缓冲区的操作。
write打印了一次,说明内核区不会进行写时拷贝,写时拷贝指的清空缓冲区的数据。
写时拷贝,只要是数据都要拷贝,大部分拷贝的都是用户数据,内核的数据若是属于该进程的也是需要拷贝的,属于系统的则不需要拷贝。
4.重定向
dup2的原理:实际上就是文件描述符当中的内容的互相拷贝就可以完成重定向。
第一个参数oldfd的内容覆盖到newfd当中。
newfd是oldfd的一份拷贝,所以oldfd指向的是新打开的文件描述符。
dup2的运用代码: 输出重定向
在我们使用dup2时,用新打开的fd文件写入1号文件描述符的内容,当我们像1号文件描述符写入时,自然而然,我们就会像文件当中写入。
dup2的结果:
当在命令行当中输入echo “hello world” > log.txt当中实际上,echo "hello world"本质是想在1号描述符当中输出, > 号后对于log.txt进行O_WRONLY|O_CREATE的open打开,然后 > 相当于dup2(fd,1)!!!
输入重定向的理解:当我们使用dup2,把标准输入流的对应的文件描述符的内容设置为了文件的描述符,就会到从文件里面读取内容,而不是从键盘,这就是输入重定向。
代码测试:
结果:
问:
程序替换的时候,会不会影响文重定向对应的数据结构数据?
答:
不会,通过地址空间分隔开。将来创建子进程的时候,并不会涉及到struct files_struct这一条路线。
5.未打开的文件
打开的文件,普通的未打开的文件,属性与操作方法的表现就是struct file{},就是内存级文件,内存上的文件和磁盘上的文件并不一样,文件上更强调方法和数据结构,而磁盘上更强调的是数据,他们是通过缓冲区关联起来的。
而我们本章,主要讲述磁盘上面的文件,是未被加载到内存当中的文件。一个的目的是为了保存,一个是为了操作。并且很重要的一点是:磁盘上的数据也需要被管理。
stat * 查看更多信息,可以看到磁盘上当中的各种信息。
ls的底层是在磁盘当中的元信息放在操作系统内核,再由内核放到空间,我们才能看到对应的属性。
5.1对机械硬盘的简单理解
磁头来回摆动 ,盘面可以是正面反面的类型(第n个盘面),一个盘面上的一圈就是一个磁道,其中磁道上的白线分割开的是一个个的扇区。
站在操作系统的角度,使用的时候是有内存基本单位的:4KB(页框)。磁盘存储的基本单位就是扇区。
扇区大小一般是512个字节,8个扇区就是一个页帧,虽然现在较为先进的磁盘是4kb的。
但内存的基本单位是1字节,内存和磁盘的交互是以4KB为单位的,实际上的文件通过操作系统完成。
磁头和盘面是没有挨着的,磁头上下摆动的话会刮花盘面,就可能会造成蓝屏等等问题。
不同的分区可以用不同的文件系统。
磁盘的存储单位是扇区,所以磁盘读取的最小单元也是扇区
磁头的摆动:
数据是写在下图彩色的区域,磁盘的盘面是两面都可以读写的。
站在操作系统的角度,使用我们的内存的基本单位是4KB,而站在用户层,我们使用内存的基本单位是字节。
但是并不是我们用户层申请一字节的空间,操作系统就给我们申请4KB的内存空间,因为C语言实际上是有一些池化技术,等C语言的内存池中内存用完,才会再往操作系统申请内存空间,并且一次也不一定就要1个4KB,这些都是要看具体情况的。
磁盘存储的基本单位:扇区,扇区大小一般在512字节,有一些比较先进的磁盘是4KB,但是主流是512字节。
内存和磁盘的交互:output,input,通过文件系统完成。IO的时候,基本单位是4KB,也就是一次要读取8个扇区填满一个页框。而8个扇区组成一个页帧。
柱面的理解:
柱面就是每个盘面上的相同位置的同心圆构成的集合。(看下图)
从一个纵切图来看,实际上磁头并不是只有一个的,他是有多个,并且没有靠着盘面,有n个盘片就有2*n个磁头,因为每个盘片是有两个盘面的。
靠近圆心的比特位密集,反之稀疏,通过这种做法就能让每一个扇区的大小都是512字节,但是现在有些厂商已经能做到每个扇区的大小容量不一样了,但是我们这里暂不关心。
5.2操作系统如何看待磁盘?
对磁盘抽象化的原因
如果不对磁盘做抽象,那么对机械硬盘
read(盘面,磁道,扇区),那么我们操作系统的调用对应文件的读写方法时所要去找的这个位置就得是以硬编码的方式提前写入的。再说简单一点,今天我调用了printf("hello");
,操作系统就定位某某盘面,某某磁道,某某扇区进行写操作再刷新,倘若我们这个时候外设变了,我用固态硬盘,由于结构变化导致先前的读写逻辑都得发生变化就不合理了,这也是因为程序的编写与硬件强耦合了。所以我们就得对磁盘做抽象,将他抽象成为一个大数组。
站在操作系统上:
那么就是下图所示的样子,那么我们内存需要进行IO的时候就往磁盘取8个“格子”,这种逻辑地址也叫做LBA逻辑地址。
而由数组下标找到对应的所谓的盘面,磁道中扇区,柱面的工作交给的就是磁盘驱动来完成。现在我们即使将底层的磁盘硬件由机械改为固态硬盘时,实际上就由SSD驱动来完成由下标到对应磁盘的某个位置就可以啦!
操作系统要管理磁盘,实际上就是把磁盘抽象成线性的空间,然后实际上就是对这个线性空间的管理。实际上操作系统只关心LBA,逻辑地址,而盘面,扇区,柱面,操作系统都是不关心的。每一个下标对应空间都是512字节(一个扇区)。
由管理磁盘到管理数组的过程,但管理一个512GB的内存用一个数组有点太过大了,操作系统会将这个大块的数组切成一个个的块再做管理,每个块可以由不同的方法管理,500GB拆成一个个小块数组的过程就叫做分区,正是分区才有了C,D,E盘。
这个过程可以类比管理中国,切分成管理若干个每个省,管理每个省,又可以切分成管理若干个镇,而派分省长,管理团队的过程又称之为格式化。格式化即写入数据和方法,数据类比成省当中有多少河流,有多少亩地,而方法就是管理这些数据的方法。
格式化:写入文件系统,文件系统有自己的属性数据和他自己的一些特征,方法就是对对应文件的属性操作的方法。
最终问题就能演变成对一个个的块组(Blcok group的管理)
那么如何对块组Block group进行管理呢?
0号块组上的第一个扇区装的就是启动信息。
对文件系统字段分析:
SuperBlock:用于标识文件系统的核心结构,用于描述文件系统的属性,例如文件系统的名字,版本,块组的使用与否。当操作系统起来的时候会把superBlock加载到系统当中,系统就知道有这个文件系统的存在。假如有10个块组,可能就有3个有SuperBlock,算是一个备份。
Group Descriptor Table:当前那这个组里面还剩多少被使用。每一个块组都要有一个Group Descriptor Table。
Block Bitmap:标识哪些数据块被使用和未被使用
Data blocks:维护inode Table和Data block
inode:是一个文件所有属性的一个集合,除了文件名。
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
数据区:存放文件内容
每一个文件对应一个inode节点(不包括软硬连接的情况),那么在一个块组,一个inode节点是512字节,即使inode Table是1Gb,那么一个组块就能存在上千万个inode,但是由于Data blocks的容量有限,所以也不一定能到那么多。
进程的文件和这里的文件有什么区别?
进程的访问的文件是打开的文件,我们实际上所有打开的文件都是进程帮我们打开的。所以是内存级文件。
但是有很多暂时不用使用的文件,硬盘级文件,这里讲述的硬盘级文件。
inode是可以和特定的数据块产生关联的:
inode Table的数组大小是确定的,如果一个Block group有1T,那么我们的inode可能就不够用了,但是我们可以通过数据块携带其他数据块的编号,通过这种方式我们可以使用少量的inode表而映射到更多的Data blocks。
5.目录是文件,并且目录也有自己的inode,也有对应的数据块,数据块存放的是文件名与inode直接的映射关系,这样能方便系统去定位一个文件。echo "hello world" > test.c
实际上echo "hello"成为一个进程,重定向到test.c的过程就是在当前的进程的cwd标识的路径下面目录去找对应的test.c,从而实现对test.c的写入。也就完成一次IO操作。
- ll /proc/23654/cwd
- lrwxrwxrwx 1 ljh ljh 0 Feb 19 21:50 /proc/23654/cwd -> /home/ljh/1.24
对于验证cwd的实验:
如何定位一个文件
目录也是文件,要找到目录实际上要检索某个文件就得从根目录开始层层找,再把找到的结果缓存起来。
创建一个ext*文件系统,系统做了什么工作
当创建一个空目录的时候,我们会在inode位图当中找到一个未用的位置填入,然后在当前目录的数据区当中填入目录与inode的编号与文件名的映射关系。
写的时候就通过目录文件名找对应inode,然后就可以申请对应的blocks块。
小结论: 大多数的OS在同一个目录下不允许出现同名文件。因为key值冗余,在目录的映射当中存在相同的文件名会使不确定具体要哪一个value。
如何理解删除文件
有了上面的基础,结合我们平时的使用,下载东西耗费时间长,但是删除的时间快,其实删除文件并不会清空该文件所占据的所有的空间数据,其实仅仅是把该文件的inode位图和Block位图的1置0,还有当前目录文件名与inode的映射关系删除即可。那么下次创建文件的时候就会覆盖式的使用之前释放的空间。
windows下的回收站就是目录,当我们windows下删除一个文件,实际上如果没有清空回收站,我们发现磁盘的大小并没有发生变化,这是因为此时只需要更改当时的文件的目录的映射关系,更改到回收站的目录当中,而清空回收站才会重新更改之前GDTable的数据,我们直观看就是磁盘空间此时才会变大。
所以一些恢复软件的逻辑就是去遍历inode表,找到对应的数据内容,所以当丢失数据,不要动电脑,马上找维修厂商,他们有对应的恢复软件。
ps:inode编号在同一个分区下不重复,但是不同分区下有可能重复的。
一个区块对应的inode数量和数据块的数量有限,当数据块用完但是inode没用完这种情况没什么办法解决。
但是inode用完数据块没用完的情况我们可以通过删除空目录,减少文件(释放inode)的方式缓解。
Linux下支持Ext2,Ext3,fs,usb-fs,sysfs,proc,我们这里着重讲Ext系列的。
5.3软硬链接
软链接: ln -s
软连接就是一个普通文件,就有自己独立的inode编号,就是inode对应的数据块就是指向文件对应的路径。类似windows下的快捷方式。
硬链接:
硬链接相当于在当前目录下数据块创建一对文件名和inode的映射关系。
重命名,定位当前文件的时候会用到硬链接。
以下面的test为例,为什么这里的test的硬链接数是2呢,其实这里的test由于test包含了.这个文件,他的inode也是1051467,所以在test这个目录下,他的数据块就会记录两个文件名映射到inode中。
硬链接数的测试2:
软链接文件是一个独立的文件有自己的inode节点,通过数据中保存的源文件路径访问源文件
硬链接是文件的一个目录项,与源文件共用同一个inode节点,直接通过自己的inode节点访问源文件。
反思:
读取目录需要r权限?
读文件是在目录下的data block找到文件名与inode的映射关系。
符号链接:软链接的另一种说法
对于三个时间的理解:
Access:文件最近被访问的时间
Modify:文件内容最近被修改的时间
Change:文件属性最近被修改的时间
touch除了创建文件,还可以刷新文件的ACM原因。三个时间都会被刷新。
chmod 命令只会改变Change时间。
vim打开并对内容进行更改会同时改变Modify和Change两个时间,因为这个过程中文件的属性(文件的内容多少的标识),类似该字段可能也会发生变化。
Linux2.6后只对文件进行打开而不更改是不会更新Access值的,文件实际上读写的话读的概率是比较大,这时候更新的话会导致系统的效率会降低。
是否需要make?
make是如何通过时间来确定是否需要执行?
当我们编写程序test.c,生成可执行程序a.out,当我们文件进行修改的时候,再次make,系统会对比test.c和a.out的ACM时间判断是否需要执行make命令,如果a.out的时间早于test.c,则要,反之不要。
机灵的编译器:
所以,当一些大型项目需要编译时,我们的编译器也会十分机灵的去选择需要编译的文件进行编译,对于没有修改的程序编译器就不会去进行编译了。
最后一个小问题:
是否不分盘就只有一个文件系统?
答:
不是,如果仅仅是单从磁盘来说,那就是一个文件系统,但是很多临时文件,设备文件都会有对应的文件系统,不同的硬件可能都会有不同的文件系统。df -h可以查看文件系统
总结
IO就先这样啦!