Linux之文件基础IO详解

基础IO


首先我们先理解以下文件的几个基础知识:

  1. 文件的宏观理解:文件是在硬盘上存储的,对文件的所有的操作,都是对外设的输入输出,简称IO
  2. 磁盘上的空文件,占不占用磁盘空间呢?
image-20220126095502603

我们看到新建文件的属性上写的大小和占用空间是0字节,但是它依旧占用磁盘空间,因为文件 = 内容+属性(元数据),这里只是说的内容的大小和占用空间,而文件的属性也是占用空间的,所以一个空文件依旧占用磁盘空间,所以我们所学的文件操作,本质上不是对属性的操作就是对内容的操作

  1. 代码变成进程的过程:文件操作代码->.exe文件->加载到内存中->进程

对文件的操作,本质都是进程对文件的操作,C语言提供的库函数是在用户层,不能直接访问硬件,库函数去调用系统调用接口去访问硬件

下面我们来看一下C语言中对文件的操作:

include<stdio.h>
int main()
{
    FILE *fp = fopen("log.txt","w");
    if(NULL = fp)
    {
        perror("fopen error!\n");
      	return 1;
    }
    char c = 'A';
    for(;c <= 'Z';c++)
    {
        fputc(c,fp);
    }
    fclose(fp);
    return 0;
}

上面的代码执行后。就会在log.txt这个文件中写入A-Z,26个英文字母:

image-20220126101542550

那么站在系统的角度上,进程是怎么打开文件的呢?我们下面会慢慢进行讲解,fputc是一个字符字符写,我们看一个一次写很多的函数fwrite,这个函数也可以给文件里写:

#include<stdio.h>
#include<string.h>
int main()
{
    FILE* fp = fopen("./log.txt","a");
    if(NULL = fp)
    {
        perror("fopen");
        return 1;
    }
    const char *msg = "hello world";
    fwrite(msg,strlen(msg),1,fp);//这里strlen不要+1,不需要将\0写入,这本质是C的规定,并不是文件的规定
    
    fclose(fp);
    return 0;
}

w方式写:写入,每次写入都是重新写入,意味着之前的文件内容会被清空!

a方式写:append,追加写,本质也是写入,不清空原始文件,在文件的最后写入

image-20220126103238498

看到我们打开文件时./log.txt指明了在当前路径下找log.txt,什么是当前路径?每个进程都有一个内置的属性cwd,当前路径就是进程的当前路径。

stdin & stdout &stderror

任何C程序,默认打开的三个"文件":

stdin:标准输入,键盘文件

stdout:标准输出,显示器文件

stderr:标准错误,显示器文件

image-20220118150646232

我们可以看到stdin、stdout、stderr都是FILE*类型的,我们知道键盘,显示器都是硬件,那么这些硬件怎么和文件有关联呢?所有的外设硬件输入输出,本质对应的核心操作无外乎是read和write,他们都有对应的读方法和写方法,不同的硬件,对应的读写方式肯定是不一样的

每个硬件(磁盘、键盘、显示器、网卡,显卡)都有自己的read和write,而每个文件的信息,属性,操作存储在结构体file当中,在struct file结构体中有文件的属性,文件的操作,键盘、磁盘、显示器、网卡显卡的各自的struct file有指向各自硬件的读写方法的指针:

image-20220126114433252

这就是Linux下一切皆文件的原理,中间封装了软件层,通过函数指针去调用方法,在软件层以同样的方式来看待文件,都有一样的文件操作。

我们可以通过曾经的C语言接口,直接对stdin,stdout,stderr进行读写:

#include<stdio.h>
#include<string.h>
int main()
{
    const char *msg = "hello world!\n";
    fwrite(msg,strlen(msg),1,stdout);
    return 0;
}

image-20220126115851618

#include<stdio.h>
#include<string.h>
int main()
{
    char buff[11] = {0};
    fread(buff,10,1,stdin);
    printf("%s",buff);
    return 0;
}

image-20220126121228638

那么为什么所有语言都要提供默认打开的标准输入,标准输出,标准错误这些窗口呢?

计算机发明出来,如何交互?语言。那么语言也是需要进行交互的,scanf->键盘,printf->显示器,perror->显示器,如果没有打开,不可以直接调用这些接口,默认打开可以便于语言进行上手使用,都有输入输出的需求,所以所有语言都有默认打开的标准输入,标准输出,标准错误

站在系统角度如何理解文件?

接下来我们理解两个东西:

1.文件和进程的关系

2.系统调用

下面我们来看系统中的文件IO:

系统文件I/O

操作文件,除了上面的C接口(当然,C++等语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的功能:

对应于上面的函数,系统接口是open,close,read,write,所有的语言都是去使用系统调用接口去操作文件的。

open

image-20220112130951882

第一个参数是你要打开的文件名,第二个参数是打开方式:其中有只读,只写,还有读写:

image-20220112132128831

pathname: 要打开或创建的目标文件

flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

参数:

O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写

比如:

open("log.txt",O_RDONLY|O_CREAT,0664)//只读打开,若文件不存在,则创建它。设置创建的文件权限为664
open("log.txt",O_WRONLY|O_APPEND)//只写打开,追加写
open("log.txt",O_RDWR|O_APPEND)//读写打开,追加写

返回值

成功:新打开的文件描述符
失败:-1

open函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

close

image-20220112131021510

关闭文件的参数是所要关闭文件的文件描述符,文件描述符的相关细节下面讲解

read

image-20220112131048088

image-20220126124819704

返回值:返回读的字节数

image-20220126124438011

参数:

第一个参数是文件描述符,将文件描述符指向的文件内容读到buf当中,第二个参数就是保存数据的存储单元,第三个参数是读的个数

read的使用:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
    int fd = open("log.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    char buffer[1024];
    ssize_t s = read(fd,buffer,sizeof(buffer)-1);//返回读的个数
    if(s > 0)
    {
        buffer[s] = '\0';
        printf("%s",buffer);
    }
    close(fd);
    return 0;
}

image-20220126125535444

write

image-20220112131109301

write是写到一个文件当中,第一个参数是文件描述符,第二个是需要写的内容,第三个参数是写的字节数

返回值:

image-20220126130318071

如果写成功,返回写的字节数,如果失败,返回-1

系统接口write的使用

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd = open("log.txt",O_WRONLY|O_APPEND);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
        
    const char *msg = "hello!\n";
    write(fd,msg,strlen(msg));
    write(fd,msg,strlen(msg));
    write(fd,msg,strlen(msg));
    write(fd,msg,strlen(msg));
    close(fd);
    return 0;
}

image-20220126133543408

为什么所有语言要封装read,open等接口呢?

兼容自身语法特征,系统调用使用成本较高,而且不具备可移植性,read、open等接口只有在Linux平台下才能使用,在windows平台不能使用,并且方便二次开发

open函数返回值

文件描述符fd

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    int fd1 = open("log1.txt",O_WRONLY|O_CREAT);
    int fd2= open("log2.txt",O_WRONLY|O_CREAT);
    int fd3 = open("log3.txt",O_WRONLY|O_CREAT);
    int fd4 = open("log4.txt",O_WRONLY);
    int fd5 = open("log5.txt",O_WRONLY);
    
    printf("%d\n",fd1);
    printf("%d\n",fd2);
    printf("%d\n",fd3);
    printf("%d\n",fd4);
    printf("%d\n",fd5);
    
    return 0;
}

image-20220126134736647

image-20220126134933221

3,4,5这些都是文件描述符,fd4和fd5是因为不存在这两个文件打开失败,所以返回-1,在OS层面文件描述符就是一个整数,这看起来就是数组的下标,数组不应该在0开始吗?为什么从3开始呢?是因为0,1,2,已经被占用了,他们分别对应:0:标准输入,1:标准输出,2:标准错误,所谓的默认打开文件,标准输入,标准输出,标准错误,其实是有底层系统支持的。默认一个进程在运行的时候,就打开了0,1,2

0,1,2,3,4…其实本质是数组下标,他们是文件描述符

所有的文件,如果要被使用,首先必须被打开,一个进程可不可以打开多个文件呢?答案是可以的,那么当大量文件加载到内存当中时,操作系统一定要把打开的文件管理起来,怎么管理呢?先描述再组织,在Linux内核当中有一个struct file结构体,里面描述了文件的属性信息,文件的操作方法,文件的缓冲与存储位置等等,一个文件有一个struct file,所有文件的struct file用链表组织起来,文件是由进程打开的,一个进程能够打开多个文件,一个进程有一个PCB,那么进程的PCB就要和文件的struct file关联起来,在Linux内核当中是用一个结构体指针关联起来的,它指向struct files_struct,这个结构体里面有个struct file* fd_array[],这个数组存放结构体指针,这些指针指向struct file结构体,所以方法可以访问,就可以对磁盘进行读写了:

image-20220126142046267

对所有的文件进行操作,统一使用一套接口,各自的硬件都有自己的一套读写方法:

image-20220126114433252

对于进程来讲,对所有的文件进行操作,统一使用一套接口(一组函数指针),本质上利用了多态原理

我们看一看linux内核中的task_struct中有struct files_struct *files,这个指针指向files_struct结构体:

image-20220112213858573

我们再来看看files_struct这个结构体,我们可以看到里面确实有一个数组,数组的元素是指向struct file结构体的指针:

image-20220112214121599

我们可以看到struct file结构体中有文件操作:

image-20220126145302849

再转到文件操作里面,可以发现里面有各个读写方法的函数指针:

image-20220126145448281

下面我们来看文件描述符的分配规则:

文件描述符的分配规则

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>

int main()
{
	int fd = open("log.txt",O_WRONLY | O_CREAT);
    if(fd < 0)
    {
        perror("open error!\n");
        return 0;
    }
    printf("%d\n",fd);
    close(fd);
    return 0;
}

image-20220118195439761

当我们打开一个文件时,将文件的描述符输出,输出发现是fd是3,那么当我们关闭0时:

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    close(0);
	int fd = open("log.txt",O_WRONLY | O_CREAT);//我们在log.txt文件中写了aaaaaaaaaa
    if(fd < 0)
    {
        perror("open error!\n");
        return 0;
    }
    printf("%d\n",fd);
    close(fd);
    return 0;
}

image-20220112153537390

发现文件描述符结果是: fd是0

当我们关闭2时:

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    close(2);
	int fd = open("log.txt",O_WRONLY | O_CREAT);//我们在log.txt文件中写了aaaaaaaaaa
    if(fd < 0)
    {
        perror("open error!\n");
        return 0;
    }
    printf("%d\n",fd);
    close(fd);
    return 0;
}

image-20220118195606518

发现文件描述符结果是: fd是2

所以得出结论:

文件描述符的分配规则:在files_struct的fd_array数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。对进程来讲,默认的文件描述符从3开始,可以从最小的没有被分配的fd给进程

重定向

那我们如果关闭1呢?看代码:

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    close(1);
    int fd = open("log.txt",O_WRONLY | O_CREAT);
    if(fd < 0)
    {
        perror("open error!\n");
        return 0;
    }
    printf("%d\n",fd);
    close(fd);
    return 0;
}

image-20220118195945911

我们发现并没有打印,因为1号下标已经不是显示器了,1号下标而是新打开的文件,按道理应该会写在这个打开的文件当中,但是我们查看这个文件发现也没有内容:

image-20220118200027115

为了解决这个疑问,我们想一下这个问题:C语言中的fopen和系统接口open是怎么进行耦合的呢?

我们C语言中,打开文件这样写:

FILE *fp = fopen("log.txt","w");

那么C语言中FILE的是什么呢?它是一个结构体—struct _IO_FILE{},在这个结构体里面有一个_fileno,这个_fileno其实就是文件描述符,有了这个_fileno我们就可以和read进行耦合了,比如我们要读文件,系统接口这样调用:

FILE *fp = fopen("log.txt","w");
read(fp->_fileno)

通过fp->_fileno就拿到了文件描述符,这样就可以使用read函数了。所以C语言中的库函数和系统调用接口是通过FILE结构体里面的_fileno进行耦合的

我们来验证一下存在_fileno:

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    printf("%d\n",stdin->_fileno); 
    printf("%d\n",stdout->_fileno);
    printf("%d\n",stderr->_fileno);
    FILE* fp = fopen("log.txt","r");
    printf("%d\n",fp->_fileno);
    return 0;
}

image-20220126151045930

可以看到确实是有的,而且他打印出来确实是文件描述符

那么有一个问题:close(1),我们在关闭标准输出文件时,FILE* stdout还存在吗?

close(1)//FILE* stdout还存在吗?存在

答案是存在的,close仅仅是将文件描述符对应的结构体指针指向的struct file释放或者设置为失效了,并没有将FILE清理

在FILE结构体内部包含:

  1. 底层对应的文件描述符下标fileno

  2. 应用层C语言提供的缓冲区数据

在前面的那个文件中没有写入内容这个问题本质上是因为数据在缓冲区中没有刷新,因为新打开的文件是普通文件,它是缓冲区刷新策略是全刷新策略,所以数据在缓冲区中没有刷新,我们这里fflush刷新一下就可以了:

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    close(1);
	int fd = open("log.txt",O_WRONLY | O_CREAT);//我们在log.txt文件中写了aaaaaaaaaa
    if(fd < 0)
    {
        perror("open error!\n");
        return 0;
    }
    printf("%d\n",fd);
    fflush(stdout);//刷新标准输出文件的缓冲区
    close(fd);
    return 0;
}

image-20220118205443538

printf后面有\n为什么内容没有刷新到文件里面呢?因为文件变了,显示器文件变成普通文件了,刷新策略变了。显示器是行刷新,普通文件是全刷新

用fprintf函数向显示器文件中打印:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    fprintf(stdout,"heelo world!\n");
    return 0;
}

image-20220126153950840

用fprintf函数打印解释重定向:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    close(1);
    int fd = open("log.txt",O_WRONLY|O_CREAT,0644);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    fprintf(stdout,"heelo world!: %d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}

这里可以看到的是我们将1关闭,close仅仅是将文件描述符对应的结构体指针指向的struct file释放或者设置为失效了,并没有将FILE清理,我们用fprintf向显示器文件打印东西,其实是重定向到了新打开的文件log.txt中:

image-20220126154709165

int main()
{
    //C接口
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");
    fputs("hello fputs\n",stdout);
    
    //system call接口
    const char *msg = "hello write\n";
    write(1,msg,strlen(msg));
    
    fork();//创建子进程
    return 0;
}

我们写了这么一段代码,分别用C语言接口和系统调用接口来向显示器中写,并且在代码的结束口创建一个子进程:

image-20220126160055317

我们编译然后运行发现很正常的写入,这些都是我们代码中所想要写入的,那么再看下面,我们将myfile.c写到显示器的内容重定向到log.txt:

image-20220126160236661

我们发现多写入了hello printf,hello fprintf,hello fputs,我们仔细一看,这些都是C语言的接口写入的,为什么会写入两份呢?

毫无疑问是fork创建子进程搞得鬼,./myfile > log.txt是重定向,相当于是打开普通文件log.txt,向普通文件中写入,普通文件的刷新策略是全刷新,所以C语言的接口将自己的数据写在了自己的缓冲区里,fork之后,创建了子进程,父子在不发生写入操作时共享代码和数据,父子进程在退出之前需要将缓冲区的数据强制刷新到文件,往文件写数据时,父子进程发生了写时拷贝。而./myfile是向显示器文件写入,是行刷新,缓冲区里是没有这些数据的,和fork创建子进程没有关系,所以直接就写到了显示器文件当中。

使用dup2系统调用

image-20220126164352937

它的作用是用来复制一个文件的描述符。它们经常用来重定向进程的stdin、stdout和stderr。

int dup2(int oldfd,int newfd);

image-20220126164538000

注意:这个函数是使newfd成为oldfd的副本,也就是new是old的一份拷贝,不是拷贝fd,而是拷贝fd对应的数组中的内容,数组内容,最终都指向fd的结构体:

image-20220126170506322

如果重定向stdout:

dup2(fd,1);//1是fd的副本,相当于1指向了fd所指向的struct file

dup2函数的使用:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#incldue<fcntl.h>
int main()
{
    int fd = open("101.txt",O_WRONLY|O_CREAT,0644);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    dup2(fd,1);vid
    const char* msg = "hello dup2\n";
    int i = 0;
    while(i<10)
    {
        write(1,msg,strlen(msg));
        i++;
    }
    close(fd);
    return 0;
}

image-20220126171646437

可以看到原本往显示器文件中去写的内容,写到了101.txt当中

程序替换的时候,会不会影响重定向对应的数据结构数据?

不会影响,重定向本质是通过修改文件描述符对应的struct_file,而程序替换是将代码和数据进行替换,它们直接是被进程地址空间隔离开的,所以程序替换并不会对重定向有影响

image-20220126183130020

理解文件系统

文件:打开的文件(进程打开的,属性与操作方法的表现就是struct file,内存级文件),普通的未打开的文件

(就在磁盘上面,未被加载到内存的),打开的文件需要被管理,未打开的文件也要被管理起来,在OS中有一个文件系统

磁盘上的文件系统,认识磁盘

内存在OS的角度,使用的时候是有基本单位的:4KB,申请内存时,底层是以4KB为单位给的,磁盘存储也有基本单位:扇区(512字节),内存和磁盘之间需要交互,通过文件系统完成,通过OS完成,IO的时候,基本单位是多少呢?一般是4KB,4KB等于8个扇区

我们定位硬盘当中的位置一般是由三个东西确定:盘面,磁道,扇区,read(盘面,磁道,扇区),万一结构发生变化,这样的接口就不能用了

我们可以将磁盘空间想象成为线性结构:

image-20220310105303314

假设有500CB,OS怎么管理它呢?

空间区域太大,进行分区划分管理,将500GB划分成两个100GB,两个150GB

管理不只是划分区域,划分好之后还需要格式化:写入文件系统(数据和方法)

管理磁盘现在缩小到管理一个区域100GB,叫做分区

将这分区划分成10个10GB,叫做块组结构

我们对一个块组进行管理,这一个块组管理好其他块组的管理可以照搬管理好的这个块组,只要把所有的块组管理好了就把一个分区管理好了

前面的boot block称为启动块

image-20220217163812217

Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相
同的结构组成。政府管理各区的例子
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有多少inode被使用了
块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
inode表:里面有很多inode,inode里存放文件属性 如 文件大小,所有者,最近修改时间等,里面有很多inode,因为一个块组里有很多文件。
数据区:存放文件数据,我们找文件需要通过inode找,再通过inode找数据区,所以inode和数据区是有关联的

1、基本上,一个文件一个inode(包括目录)

2、inode是一个文件的所有的属性集合(没有文件名),属性也是数据,也要占据空间

3、真正标识文件的不是文件名,而是文件的inode编号

如何查看inode编号?

ls -ali//+i选项

image-20220228100433810

4、inode是可以和特定的数据块产生关联的

程序员是怎么定位一个文件呢?

路径定位的,目录

如何理解目录?

目录也是文件,目录也有inode,也有自己的数据块,目录里面的数据块存储的是文件名和inode的映射关系

在一个目录中ls -al,那么怎么显示文件的权限等属性的呢?这个问题我们下面回答。

那么块组是怎么知道哪些inode被使用,哪些没被使用,哪些block被使用,哪些没被使用?

并不会去遍历式的找哪些inode被使用,哪些没被使用,而是通过位图来确认当前磁盘的使用情况,那么位图也是要遍历的,通过遍历位图哪些位是使用了的,哪些是没使用的即可,1表示使用了的,0表示没有使用的

我们创建一个文件,ext*文件系统,做了什么工作?

下面我们来说明这个问题:

文件系统的核心结构,描述文件系统的属性,文件 = 内容(Date blocks)+属性(inode Table),每一个文件都对应一个inode节点

inode{
	//属性集合
}
block{
    //内容
}

inode里面保存的是属性集合,block里面保存的是文件内容,一个文件有一个inode,但是一个文件可能有多个block,当有多个文件时就会有多个inode和多个block,此时就需要管理起来:

filesystem
{
    //分区的基本情况
    //空间一共是多大
    //有多少空间已经使用&&没有使用
    //inode
    //block
    //group
}

文件系统包含的内容:描述整个分区的空间使用情况,以及还有方法

文件与data blocks是一对多的,文件与inode是一对一的,inode必须包含data blocks对应的映射关系

要找到一个文件就要先找到这个文件的inode,inode id是用来标识一个inode,通过inode编号就可以找到inode

块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。

i位图:1000100…00000(inode位图)

d位图:000000…00000(块位图)

硬盘当中怎么创建一个文件,过程是什么?

先找到分区和块组,给这个文件分配inode,在inode位图里面找一个没有被使用的,将文件的属性写入inode,再在块位图里面找一个没有被使用的,将文件数据写入到datablock中,再将块位图找到的那个位置写入到文件的inode中的映射关系中

目录也是文件,他也是由inode+数据块组成,那么目录的数据块存储的是什么呢?文件名到inode id的映射关系

我们在一个目录下进行ls命令,其实就是去找目录的inode,找到后再找到目录的数据块,目录的数据块里面存储的是文件名到inode id的映射关系,就能找到目录下的文件

所以,为什么大多数OS同一个目录中,不允许存在同名文件?

因为目录的数据块存储的是文件名到inode id的映射,如果文件名相同就会出现二义性,不知道找到是哪个映射关系。

创建文件我们清楚了,那么如何理解删除文件呢?

删除文件是否需要清空该文件占据的所有的空间数据呢?不会的,比如我们要删除inode数据和数据块的内容,我们只需要将位图对应位置改为0,就证明它为无效了,下一次申请直接覆盖就好了

linux下属性和内容是分离的,属性inode保存的,内容data block保存的

软硬链接

如何软链接?

ln -s log.txt s_link

image-20220228102811947

我们发现log.txt文件和s_link文件的inode编号不一样

软链接:具有独立的inode,有自己独立的inode编号,是一个独立的文件,它的datablock内容是文件的路径,类似于windows下的快捷方式

我们向log.txt里面写内容,显示s_link,发现写进去的内容也在s_link文件中,为什么呢?

image-20220228103123848

因为datablock内容是被软链接的文件的路径,cat s_link访问s_link的数据块,就会顺着这个路径找到log.txt,从而显示log.txt文件的内容

如何硬链接?

ln new.txt h_link

image-20220228103529856

我们发现h_link文件的inode和new.txt文件的inode一样

硬链接:和指向的文件共享同一个inode,不是一个独立的文件

硬链接是什么呢?相当于一个文件的别名,硬链接本质就是在链接的文件的目录下添加文件名和inode的映射关系

硬链接数

为什么创建一个目录的硬链接数是2呢?而创建一个普通文件的硬链接数为1呢?

image-20220218222917463

我们进入test目录:

image-20220218223227239

我们发现里面有一个.文件,这个.文件表示当前路径,它的inode编号和test的inode的编号是一样的,所以硬链接数为2

test文件的路径是/Linux/2-18/test/,我们进入到2-18这个目录的上一个目录Linux目录,我们在这个目录下查看2-18目录的属性信息,发现2-18这个文件的硬链接数为3,为什么呢?

image-20220218223715067

因为2-18本身这个文件算1个,进入2-18目录有一个.文件是当前目录算1个,因为2-18目录里还有一个目录test,我们进入test目录,test里面有一个…文件叫上级目录,test的上级目录是2-18,所以也算1个,一共有三个

一个文件具有三个时间:

image-20220228105228577

文件的时间属性

一个文件相关的时间有三种:

Access:文件最近被访问的时间

Modify:文件内容最近被修改时间

Change:文件属性最近被修改时间

我们修改一个文件的内容看一下,这三个时间如何变化:

image-20220228105635368

我们发现修改文件的内容后,只有文件的Modify时间和Change时间改变了,文件的Access时间却没有变,我们修改了文件的内容,为什么文件属性最近被修改的时间发生了变化?因为内容发生了变化,文件的大小等属性也会发生变化

我们再修改一下文件的权限:

image-20220228110141598

我们发现Change时间发生了改变

我们cat一下该文件,再看一下文件的Access时间会不会发生变化:

image-20220228110321265

我们发现此时Access时间发生了变化,我们多cat几次:

image-20220228110446841

我们发现Access时间不再变化了,为什么Access没有变化呢?

修改文件的内容和属性是不高频的,文件的访问是高频的,一直更新的话效率会不好,所以时间刷新策略有不同点

此时有一个问题:如果我们把一个C语言文件的代码修改了一下,然后进行make,除了第一次会进行编译,为什么后面会不进行编译,为什么呢?

image-20220228134554036

因为make时会检查文件的最后修改时间是不是晚于可执行程序的生成时间,如果晚于就可以成功make。如果不晚于就会make失败

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小赵小赵福星高照~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值