IO(Linux)

文件系统

  • 前言
    • 1. 回顾关于C文件部分函数
    • 2. 一些文件知识的共识
    • 3. 相对路径
    • 4. fwrite中的'\0'
  • 一、文件描述符fd
    • 1. 概念
    • 2. 系统调用
      • ① open 和 close
      • ② write
      • ③ read 和 lseek
    • 3. 缺省打开的fd
  • 二、重定向
    • 1. 原理
    • 2. 系统调用dup2
    • 3. stdout和stderr的区别
    • 4. 进程替换和原来进程文件
  • 三、一切皆文件
  • 四、文件缓冲区
    • 1. 认识缓冲区
    • 2. 缓冲区刷新方式
    • 3. 小结
  • 五、文件系统
    • 1. 硬件
      • ①磁盘
      • ②存储构成和CHS寻址方式
      • ③磁盘——逻辑结构
      • ④磁盘寄存器
    • 2. 文件系统 —— ext2
  • 五、软硬连接
    • 1. 认识软硬链接
      • 软链接
      • 硬链接
    • 2. 实际应用
  • 六、打开的文件和文件系统的文件关联

前言

1. 回顾关于C文件部分函数

C语言中关于文件的博客:函数和C文件操作和部分函数 下面在复习两个函数

写文件 (fwrite) 和读 (fread) 文件:(被注释的是写文件的方法)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{//FILE *fp = fopen("myfile", "w");  写文件FILE *fp = fopen("myfile", "r");   //读文件if(fp == NULL){perror("fopen");exit(EXIT_FAILURE);}char buf[1024];   //把内容读到这个数组中const char *msg = "hello Linux!\n";while(1)                                                                                                                                                                                                                                  {size_t s = fread(buf, 1, strlen(msg), fp);  //读取内容的大小是 第二个参数 * 第三个参数if(s > 0){buf[s] = 0;printf("%s", buf);}if(feof(fp))break;}//写文件 //const char *msg = "hello Linux!\n";//int conut = 5;//while(conut--)//{//  fwrite(msg, strlen(msg), 1, fp);     //fread和fwrite的返回值,是实际写的块个数,如果完整的写完了,就返回的是第三个参数值//}fclose(fp);return 0;
}

输出到显示器(fprintf、fwrite和printf):(其中参数是stdout或者stderr)

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    int main()    
{    printf("pid: %d\n", getpid());    const char *msg = "hello Linux!\n";    fwrite(msg, strlen(msg), 1, stdout);    fprintf(stdout, "%s fprintf:stdout\n", msg);    fprintf(stderr, "%s fprintf:stderr\n", msg);                                                                                                                                                                                              return 0;    
} 

运行结果:
运行结果

stdin && stdout && stderr:

C语言默认打开的三个输入输出流,上面的实验也证明了,stdout和stderr都可以向屏幕输出。stdin是用来进行输入。
并且类型都是FILE*
stdin ------- 标准输入 —— 键盘
stdout ------- 标准输出 —— 屏幕
stderr ------- 标准错误 —— 屏幕
所以可以直接使用标准输入输出函数

2. 一些文件知识的共识

  1. 文件 = 文件内容 + 文件属性
    对文件的操作 = 对文件内容的操作 + 对文件属性的操作
  2. 文件:分为打开的文件与未被打开的文件 —— (下面会根据这两种分类进行详细介绍)
  3. 一个进程可能会打开多个文件,多个进程可能也都会用到同一个文件,而且OS中有很多进程。所以就需要进行管理。
  4. 文件也必须先描述再组织。(下面详细介绍)

根据上面所述:所以需要对文件进行管理,因此引出文件系统。

3. 相对路径

问题:fopen用相对路径打开文件是如何找到路径

#include <stdio.h>    int main()    
{    FILE *fp = fopen("myfile", "w");  //以写的方式打开文件    while(1);    fclose(fp);                                                                                                      return 0;    
}

查看上述代码执行的进程信息:
查看进程信息

进程的文件创建路径就是与cwd有关。接下来进行测试

接口:chdir更改当前工作目录

头文件:#include <unistd.h>
函数声明:int chdir(char *path);
参数:所要更改的目录,相对、绝对都可以
返回值:成功返回0。失败返回-1,并且错误码被设置

更改后的代码:

#include <stdio.h>    
#include <unistd.h>    int main()    
{    chdir("/home/kpl_2023/linux/basisIO");    FILE *fp = fopen("myfile", "w");  //以写的方式打开文件    while(1);    fclose(fp);    return 0;    
}

运行信息

小结: 使用相对路径创建文件受cwd影响

4. fwrite中的’\0’

//fwrite '\0'    
#include <stdio.h>    
#include <unistd.h>    
#include <string.h>    int main()    
{    FILE *fp = fopen("myfile", "w");    const char *msg = "hello fwrite\n";    fwrite(msg, strlen(msg), 1, fp);         //测试的目的在这里                                                                                                                   fclose(fp);    return 0;    
} 

strlen加不加1的运行结果:
运行结果

小结:

  1. 字符串以\0结尾只是语言方面设置的标记
  2. 对于OS和一些文本编辑器而言,这个标记位就是多余的

一、文件描述符fd

1. 概念

文件操作符本质就是数组的下标。这个数组就是文件描述符表。通过这个数组下标就可以实现对该文件的控制

文件如何被先描述再组织呢?
答:在进程中PCB中有个结构体files_struct指针,指向进程打开的文件描述符表。文件描述符表是一个数组,每个下标对应一个文件描述符fd(fd中的内容就是一个结构体指针),而所指向的结构体,包含了位置,基本属性,权限,大小,文件打开模式、文件的内核缓冲区,struct file *next这种指针(每个文件描述符结构体是用链表链起来的),引用计数等

结构图:
结构图

2. 系统调用

在前文也说过系统调用和库函数之间的关系:

  1. 上面介绍的一些关于文件的函数,例如fopen fclose fread fwrite等,都是C标准库中的函数 —— 库函数(libc)
  2. open close read write 都属于系统提供的接口 —— 系统调用

在说初始进程的操作系统部分时,提到了这张图。通过观察下面这张图,所以f#系列的函数底层对系统调用一定进行了封装,为了便于开发
在这里插入图片描述

① open 和 close

open:

头文件:#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>函数声明:int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);函数参数:1. pathname:文件路径2. flags:文件打开方式(以下几种常见方式)(1)、O_CREAT:文件没有就创建(2)、O_TRUNC:文件打开就清空(3)、O_APPEND:文件以追加的形式打开(4)、O_WRONLY:文件以只写的方式打开(5)、O_RDONLY:文件以只读的方式打开(6)、O_RDWR:文件以读和写的方式打开注:多种方式可以用 | 相连3. mode:文件打开的权限,一般文件设置666,目录设置777返回值:1. 打开成功,返回fd(所打开的文件描述符)2. 打开失败,返回-1,并设置错误码

close:

头文件:#include <unistd.h>函数声明:int close(int fd);参数:fd:文件描述符返回值:1. 关闭成功,返回02. 关闭失败,返回-1,并设置错误码

umask: 权限掩码

头文件:#include <sys/types.h>#include <sys/stat.h>函数声明:mode_t umask(mode_t mask);参数:mask:八进制位掩码值返回值:总是成功,返回上一个掩码值

简单使用:

//open    
#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <stdlib.h>                                                                                                                                       int main()    
{    umask(2);   //掩码修改    int fd = open("myfile.txt", O_WRONLY | O_CREAT, 0666);    //以写的方式打开文件,如果文件不存在创建文件if(fd < 0)    {    perror("open"); //如果打卡失败,打印错误    exit(1);    }    close(fd);    return 0;    
} 

② write

头文件:#include <unistd.h>函数声明:ssize_t write(int fd, const void *buf, size_t count);参数:1. fd:文件描述符2. buf:要写入文件内容的指针3. count:写入文件内容的大小返回值:1. 写入成功,返回写入文件的字节数,0表示什么也没写2. 写入失败,返回-1,错误码被设置

简单使用:

//write    
#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <string.h>    int main()    
{    umask(2);   //掩码修改    int fd = open("myfile", O_TRUNC | O_WRONLY | O_CREAT, 0666);    if(fd < 0)    {    perror("open"); //打印错误    exit(-1);    }    const char *msg = "hello write!\n";    write(fd, msg, strlen(msg)); //msg:缓冲区首地址。 strlen(msg):本次读取期望写入多少个字节的数据。 返回值:实际写了多少字节的数据                                                                                                                        close(fd);    return 0;    
} 

③ read 和 lseek

read:

头文件:#include <unistd.h>函数声明:ssize_t read(int fd, void *buf, size_t count);参数:1. fd:文件描述符2. buf:存放读取内容空间的指针3. count:读取的字节数返回值:1. 成功,返回读入的字节数,0意味着读到了文件尾2. 失败,返回-1,并设置合适的错误码

lseek:

头文件:#include <unistd.h>#include <sys/types.h>函数声明:off_t lseek(int fd, off_t offset, int whence);参数:1. fd:文件描述符2. offset:移动相对于whence偏移量offset的位置3. whence:固定位置。(三个可选的位置:SEEK_SET(文件开头)SEEK_CUR(文件当前位置)SEEK_END(文件末尾位置))几种常用:1. lseek(fd, 0, SEEK_SET); //移动文件指针到开始2. lseek(fd, 0, SEEK_CUR); //移动文件指针到当前位置3. lseek(fd, 0, SEEK_END); //移动文件指针到结束位置返回值:1. 成功:返回距离文件开头的字节数2. 失败:返回-1,错误码被设置

简单使用:

//read    
#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    #define MAX_SIZE 1024    int main()    
{    int fd = open("myfile", O_CREAT | O_TRUNC | O_RDWR, 0666);    if(fd < 0)    {    perror("open");    return -1;    }    //向文件写入    const char *msg = "hello write\n";    write(fd, msg, strlen(msg));    //这里需要使用系统调用lseek。因为经过上面的写入操作,文件指针指向了结束位置。    //如果直接读取会导致什么都读不到,所以要使用系统调用lseek把文件指针移到开始位置    lseek(fd, 0, SEEK_SET);    //移动文件指针到开始的位置                                                                                                   char buf[MAX_SIZE] = {0};    read(fd, buf, strlen(msg));    printf("read: %s\n", buf);    close(fd);    return 0;    
}

运行结果:
运行结果
因为msg字符串中有\n,然后我们printf的时候又加了\n所以会换行两次

3. 缺省打开的fd

在上文提到C语言默认打开三个输入输出流,但是这并不是C语言的特性,而是操作系统的特性,进程会默认缺省打开三个文件描述符(三个流)。分别是标准输入(0)、标准输出(1)、标准错误(2)。对应的物理设备是(一般是这样):键盘,显示器,显示器。

三个流:

typedef struct _IO_FILE FILE;
extern struct _IO_FILE *stdin;
extern struct _IO_FILE *stdout;
extern struct _IO_FILE *stderr;

这三个流在底层封装了文件描述符

#include <stdio.h>    int main()    
{    printf("stdin->fd : %d\n", stdin->_fileno);    printf("stdout->fd : %d\n", stdout->_fileno);    printf("stderr->fd : %d\n", stderr->_fileno);                                                                                                           return 0;                                                                                
} 

运行结果:
运行结果

系统调用write和read的第一个参数就是文件描述符。
验证:
从文件描述符0(也就是stdin)读入数据,再向文件描述符1(stdout)和2(stderr)中写入从0读入的数据
预期结果:从键盘读入一段数据,在显示器中打印两端读入的数据

#include <stdio.h>      
#include <string.h>      
#include <unistd.h>                                                                                                                                       #define MAX_SIZE 1024                                int main()                                           
{                                                    char buf[MAX_SIZE] = {0};                          read(0, buf, MAX_SIZE - 1);   //在读取数据时,通常会将读取的数据存储到一个缓冲区中。在这段代码中,定义了一个大小为MAX_SIZE的缓冲区buf,//为了确保在读取数据时不会发生缓冲区溢出,需要在读取数据时保留一个字节用于存储字符串结束符’\0’。//因此,在读取数据时使用MAX_SIZE-1,以确保在读取数据后能够在缓冲区末尾添加’\0’                                                                                                                write(1, buf, strlen(buf));                                                                                                                    write(2, buf, strlen(buf));                                                                                                                    return 0;                                                                                                                                      
}

运行结果:符合预期,输入一次hello IO 之后打印出来两次
运行结果

结论: stdin、stdout、stderr三个流中对应的文件描述符分别是0、1、2

二、重定向

1. 原理

先来看一段代码:

#include <stdio.h>                                                                                                                                        
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <stdlib.h>    
#include <unistd.h>    int main()    
{    close(1);   //关闭文件描述符1    int fd = open("myfile", O_WRONLY | O_CREAT, 0666);    if(fd < 0)    {    perror("open");    return 1;    }    printf("fd: %d\n", fd);    fflush(stdout);    close(fd);    return 0;    
} 

运行结果:
运行结果

前文提到OS会默认打开三个流,0、1、2。这里我们把文件描述符1关闭了,所以本来应该输出到显示器的内容,输出到myfile文件中。—— 这就是输出重定向

底层:
底层结构

stdout只是上层的概念表示1号文件描述符,底层文件描述符里的内容可能会发生变化。
所以打开文件本质就是给它在文件描述符表中遍历找一个空位置,并将所打开的文件指针放入,而该位置的下标就是所打开的文件描述符。

2. 系统调用dup2

头文件:#include <unistd.h>函数声明:int dup2(int oldfd, int newfd);参数:1. oldfd:当前使用的文件描述符2. newfd:调用接口成功使用的文件描述符返回值:1. 成功,返回newfd2. 失败,返回-1,错误码被设置

简单使用:

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    int main()    
{    int fd = open("myfile", O_TRUNC | O_CREAT | O_RDWR, 0666);    dup2(fd, 1);  //将fd中所指向文件的指针覆盖到1号文件描述符中    printf("hello Linux\n");                                                                                                                                close(fd);    return 0;    
} 

实验结果:
实验结果

如果文件指针在多个文件描述符中,控制引用计数即可。如果觉得重定向后原来的文件描述符多余,可以关闭

3. stdout和stderr的区别

先看一段代码:

#include <unistd.h>    
#include <string.h>    int main()    
{    const char *msg = "hello Linux\n";    write(1, msg, strlen(msg)); //stdout    write(2, msg, strlen(msg)); //stderr                                                                                                                    return 0;    
}

运行结果:
运行结果

通过前面的学习,可以理解1和2两个文件描述符的是向显示器输出的。

拓展:

将执行的结果重定向到文件myfile中:
运行结果
发现一半显示在屏幕上,另一半重定向到了文件当中
原因:当使用 > 符号将输出重定向到文件时,只有标准输出流(stdout)的内容会被重定向到指定的文件中,而标准错误流(stderr)的内容仍然会显示在终端上。这样做为了让用户能够及时看到程序的错误信息,而不会导致混淆

当然stdout和stderr两个流也可以重定向同一个文件中。两种方法

  1. ./test 1 > myfile 2>> myfile 注:>>要与前面挨着,哪怕换成>也是要与前面挨着。 这里使用了>>(重加重定向)因为再使用输出重定向会清空前一个重定向的内容
    执行结果
  2. ./test 1 > myfile 2>& 1 注意>&要与前面挨着不能有空格
    执行结果
    -./test 1 > myfile执行后,已经完成重定向动作,1中的内容已经是myfile文件的指针了。使用2>& 1这个就是把1的内容写到2里面

4. 进程替换和原来进程文件

进程替换,不会对进程的文件进行替换。
进程替换:替换页表中虚拟地址和物理地址的联系并且在内存中加载相应进程的内容,是与mm_struct对象有关。而文件是另一部分files_struct相关。

结构图

三、一切皆文件

一切皆文件

显然在这里就可以体现出了,继承和多态的思想

所以我们在调用系统调用read这样的接口时候。表面可能只是传了fd之类的参数。但是实际调用是由进程来实现的。(进程是我们对计算机操作的主要手段)
ssize_t read(int fd,...)
{task_struct -> files -> fd_asrray[fd] -> f_ops -> (*write)()然后根据初始化是write指针指向那个硬件的方法,就是那个方法

四、文件缓冲区

1. 认识缓冲区

先看两段段代码:

  1. 描述:只使用系统调用向1号文件描述符写入,然后关闭1号文件描述符
#include <stdio.h>                                                                                                                                        
#include <unistd.h>      
#include <string.h>      
int main()      
{      const char *str = "hello write";      write(1, str, strlen(str));      close(1);      return 0;      
}

运行结果:成功打印
运行结果

  1. 描述:只使用C接口向1号文件描述符写入,然后关闭1号文件描述符
#include <stdio.h>                                                                                                                                        
#include <unistd.h>  
#include <string.h>  int main()  
{  const char *fstr = "hello fwrite";  printf("hello printf");  fprintf(stdout, "hello fprintf");  fwrite(fstr, strlen(fstr), 1, stdout);  close(1);  return 0;  
} 

运行结果:什么都没有输出
运行结果

观察上面两个例子,发现了奇怪现象,第一段代码使用系统调用write结果正常,而第二段代码使用C式接口却什么都没有输出。
原因:

  1. 在进程控制讲exit和_exit时讲到了,C缓冲区不在内核空间,在用户空间。
  2. 在测试C接口和系统调用时,我都没有在字符串中加\n,代表我没有主动刷新缓冲区
  3. C语言的缓冲区不在内核中,在程序关闭前要刷新缓冲区时,但是前面把1号文件描述符关闭了,C语言的缓冲区刷不到内核中,所以看不到写入的结果,而使用系统调用write是直接写入到内核级的缓冲区,哪怕关闭了1号文件描述符也不会影响

注:

  1. C的写接口在底层必然封装了系统调用的写接口(eg:write)
  2. 目前我们认为只要数据刷新到内核了,数据就可以到硬件了

底层:
底层

2. 缓冲区刷新方式

回顾一个刷新缓冲区的接口fflush:

头文件:#include <stdio.h>函数声明:int fflush(FILE *stream);

简单使用:

#include <stdio.h>    
#include <unistd.h>                                                           int main()    
{    printf("hello printf");    fflush(stdout);    close(1);                                                                                                                                               return 0;                                                                                                                              
} 

运行结果:
运行结果

缓冲区刷新方式:

  1. 无缓冲 —— 直接刷新
  2. 行缓冲 —— 不刷新,直到碰到\n才刷新 —— 一般常用在显示器
  3. 全缓冲 —— 缓冲区满了,才刷新 —— 一般常用文件写入

注:进程退出的时候也会刷新,所以不局限上面情况

测试以下缓冲区大小,也就是全缓冲:

#include <stdio.h>    
#include <unistd.h>    
#include <string.h>    int main()    
{    FILE *fp = fopen("myfile", "w");    const char *msg = "hello Linux";    int cnt = 1;    while(cnt < 1000)    {    fprintf(fp, "%d %s", cnt, msg);    cnt++;    }    fclose(fp);                                                                                                                                                                                                                               return 0;    
}

测试结果可以自己测试一下,缓冲区的内容还是挺大的。

测试:刚刚说到写到普通文件中的内容都是全缓冲,我们用行缓冲测试

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    //需要三秒把内容都写到文件                                                                                                                                
int main()             
{                                     FILE *fp = fopen("myfile", "w");      const char *msg = "hello Linux\n";    fprintf(fp, "%s", msg);    sleep(1);                  fprintf(fp, "%s", msg);    sleep(1);                  fprintf(fp, "%s", msg);    sleep(1);            return 0;            
}  

运行结果:前面几秒都没有显示内容,可见向文件写是全缓冲
运行结果

通过上面的实验得出的结论,来进行接下来的测试:
描述:使用C接口和系统调用向1号文件描述符写入,并在最后fork()

#include <stdio.h>                                                                                                                                        
#include <unistd.h>      
#include <string.h>      int main()      
{      const char *fstr = "hello fwrite\n";      const char *str = "hello write\n";      printf("hello printf\n");      fprintf(stdout, "hello fprintf\n");      fwrite(fstr, strlen(fstr), 1, stdout);      //系统调用      write(1, str, strlen(str));      fork();      return 0;      
} 

运行结果:出现问题了,为什么直接运行和写入到文件中的结果不一致
运行结果

解释:

  1. 向屏幕显示的行缓冲变成了向文件写入的全缓冲
  2. 创建了子进程,发生了写时拷贝。注:创建十个文件,那就有十个缓冲区。
  3. 缓冲区也是数据,在程序关闭时会自动刷新,而无论那个进程先刷新都会发生更改,所以就会出现拷贝,所以也会拷贝两份

3. 小结

用户级缓冲区存在的意义:

  1. 解决用户的效率问题。
  2. 配合格式化

C缓冲区在FILE结构体中,其中包含缓冲区字段和维护信息

C接口和系统调用的底层关系:
C接口和系统调用

五、文件系统

以上的内容介绍,可以说都是围绕被打开的文件。那还有没有被打开的文件,这一部分主要理解文件在磁盘上如何存储的。

Linux的文件在磁盘中存储,文件的内容和属性是分开存储的
文件 = 文件内容 + 文件属性 -> 在磁盘上存储文件 = 存文件的内容 + 存文件的属性。

  1. 文件的内容是按数据块存储的
  2. 文件的属性存储在inode(是一个一般128字节的数据块)中

1. 硬件

①磁盘

是电脑中唯一的机械设备,也是一个外设

磁盘

盘面:存储二进制信号。表面光滑实际不是
磁头:每个盘面都要有一个磁头,一一对应的关系。两者不接触

注:

  1. 主轴旋转是定位扇区的过程,磁头臂(摇头臂)来回摆动是定位柱面(磁道)的过程
  2. 在软件设计上,一定要有意识的将数据放在一起。因为磁盘作为一个机械设备,运动越少效率越高。

②存储构成和CHS寻址方式

存储构成:
存储构成

磁道:就是一个盘面的任意同心圆
柱面:所有盘面上下相同位置的磁道构成的立体圆柱
扇区:磁盘被访问的最基本单元 —— 512字节 / 4KB。最外一层同心圆的扇区和最里面一层的同心圆扇区数量是一样的,外层的0、1序列稀疏,里面0、1序列稠密就可以。当然现在也可以让最外层的磁道划分更多的扇区,算法会发生变化

CHS寻址方式:如何找到数据或把数据存储到磁盘,首要解决的就是地址问题

  1. Header —— 先定位磁头(也就是确定盘面)
  2. Cylinder —— 定位磁道
  3. Sector —— 定位扇区

③磁盘——逻辑结构

以前的英语听力使用磁带,我们都知道磁带延展开,那在逻辑上我们看它就是线性的。磁盘也是同样的道理。

抽象一个线性磁盘:—— 对于磁盘的建模
丑相的线性磁盘

本质就是基于扇区的数组

LBA地址(逻辑扇区地址):任意一个扇区都有下标,属于该扇区的唯一标识。
只要有LBA地址,都可以通过计算得出CHS地址。

④磁盘寄存器

磁盘寄存器(端口/串口):用于快速接收地址
磁盘寄存器

2. 文件系统 —— ext2

上面我们抽象一个线性磁盘。但是一般来说,磁盘容量很大,所以要进行分区管理。(eg:电脑上的C、D盘)。主要采取的就是分治思想

文件系统

  1. 格式化:每个分区在被使用之前,都必须提前将部分文件系统的属性信息提前设置进对应的分区,方便后续分组等工作。
  2. Linux文件在磁盘中存储,是将属性和内容分开的,在这里得到证明
    Data Blocks:存的就是文件内容
    inode Table:存的就是文件属性,这个属性不包含文件名。
  3. 在Linux中标识文件的方式是inode编号
  4. Data Blocks:一般而言每个块只存放自己的数据

上文说到inode存的是文件属性:inode和数据块
数据块

删除数据:

删除一个文件,只需要在对应的inode Bitmap和Block Bitmap把映射数据的块由1置0,把对应的inode也由1置0即可。
删除 == 允许被覆盖

认识:
文件名不属于inode内的属性。关于文件的增删查改都是通过inode进行的。上文也说到Linux中标识文件的方式是inode。
但是这里就有个问题,使用者操作的一直都是文件名,但是前面的认识又说标识文件的方式是inode。下面进行解释

目录:

目录也是文件,有自己的inode,同时也有自己的属性和内容。

  1. 目录中存放的内容:文件名和对应的inode的映射关系。有点像kv结构,所以在同一目录下不能创建同名文件
  2. 目录权限
    • 目录下,没有w权限,无法创建文件
    • 目录下,没有r权限,无法查看文件
    • 目录下,没有x权限,无法进入目录

所谓的w权限,其实就是添加对应的文件名与inode的映射关系
所谓的r权限,就是查看文件名对应的映射
x权限,就是可以在进入目录前做一下判断,如果没有该权限,就限制更改环境变量
3. 文件可以通过命令获取其inode,但是目录的inode怎么获取,只有向上递归,一直到根目录。根目录的inode是确定的。同时这样无疑效率慢,所以也有dentry缓存,将经常访问的缓存起来

注:stat [文件名]也可以查看文件的属性

五、软硬连接

1. 认识软硬链接

软链接

软链接:是一个独立的文件,具有独立的inode,它的数据块中保存的是指向文件的路径(相当于Windows快捷方式)。
注:至于数据块里没有保存指向文件的inode而是路径,是因为inode有一定的限制,而直接使用路径可以指向任意位置的文件,甚至跨文件系统。

使用:

ln -s [指定文件] [目标文件]

对普通文件建立和删除软链接:

  • 对普通文件建立软链接
    建立软链接
  • 删除软链接 —— 两种方式
    第一种:删除软链接
    第二种:unlink [目标链接]
    取消软链接
  • 如果该文件已有软链接,但是该文件被删除
    删除指向的文件

对目录软链接:(这种操作,就介绍着玩,接下来这个就套娃了)

对目录创建软链接

软链接是独立的文件:
软链接和inode

硬链接

不是独立的文件,因为没有独立的inode。
本质就是在特定的目录的数据块新增文件名和指向文件的inode编号映射关系

每一个inode内部都有一个计数器,有多少文件名指向我
目录里面保存的是:文件名——inode编号的映射关系
文件名1——inode 1111
文件名2——inode 1111

  • 所以:(建立普通文件硬链接)
    建立硬链接
  • 删除操作
    rm和unlink都行,上面说软链接时已经演示

注:rm删除原指向的文件,硬链接依旧可以使用
hard-link

建立目录硬链接(不可行)
建立目录硬链接
不可行原因:
当使用find命令找文件时,此时有个路径下有个根目录的硬链接,此时查找时,就会陷入套娃。
注:.和…存在的原因是因为Linux系统在底层做了一定的工作,所以不会被影响

对硬链接或者文件其中任意一方修改,都会影响另一方
互相影响

2. 实际应用

软链接:可以用来创建快捷方式,方便用户访问文件。跨越不同的文件系统,可以指向任意位置的文件。指向目录,可以创建循环链接。

硬链接:通常用来进行路径定位,可以进行目录间切换。不能跨越不同的文件系统。用来节省存储空间,因为多个文件名指向同一个数据块。
目录间切换

六、打开的文件和文件系统的文件关联

认识1:
页框和页帧:
页框和页帧

物理内存的页框和磁盘的页帧都是用来管理内存的单位

  • 物理内存的页框和磁盘上的Data block块(也就是页帧)是对应的关系。当操作系统需要将物理内存中的数据换出到磁盘上时,会将数据存储到磁盘上的Data block块中。当需要将数据从磁盘换入到物理内存时,操作系统会将磁盘上的Data block块读取到物理内存的页框中,实现数据的交换和管理。

物理内存的页框和磁盘上的Data block块是一一对应的关系,用来实现虚拟内存的管理和数据交换。

在在物理内存和磁盘上划分4KB的理由:

  1. 硬件:减少IO次数 —— 减少访问外设的次数
  2. 软件:基于局部性原理,预加载机制。

当然也不必担心浪费或者说要很大的空间需要频繁IO,因为OS中还有slab分派器和伙伴系统算法

认识2: OS如何管理内存?
真理:先描述再组织

  1. 先描述:一般描述物理内存,肯定要有地址,状态,大小,引用计数等。因此这里使用一个结构体管理:
    struct page{ //page页必要的属性信息 }
  2. 再组织,在Linux系统中有个数组struct page array[SIZE]每个page管理4KB的内存,所有的page结构体由这个数组组织在一起,这个数组所占内存并不大,因为page结构体中采用联合体的形式。

所以对内存的管理变成了对数组的管理,存储page数组的大小是固定的,所以可以通过地址计算出页号,同时反过来,通过页号也能计算出地址。

  • 我们要访问内存,只需要找到这个对应的4KB的page,就能在系统中找到对应堆的物理页框。所以申请内存的操作,都是访问内存的page数组

认识3: 在Linux中,每一个进程打开的每一个文件都要有自己的inode属性和自己的文件页缓冲区
图解:
自己的inode和文件页缓冲区

  • 上图的树是基数树,再画个图介绍原理,可以发现通过地址有序地对page对象进行排列
    基数树

认识4:

  1. 加载文件系统
    加载文件系统
  2. 物理内存到磁盘
    物理内存到磁盘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/717067.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机考研】408学到什么程度才能考130?

408考130要比考研数学考130难的多 我想大部分考过408的考生都是这么认为的。408的难点在于他涉及的范围太广了&#xff0c;首先如果你要备考408&#xff0c;你要准备四门课程&#xff0c;分别是数据结构&#xff0c;计算机组成原理&#xff0c;操作系统和计算机网络。 这四门…

企业计算机服务器中了360勒索病毒如何解密,360后缀勒索病毒处理流程

对于众多的企业来说&#xff0c;企业的数据是企业发展的核心&#xff0c;越来越多的企业开始注重企业的数据安全问题&#xff0c;但随着网络技术的不断发展与应用&#xff0c;网络黑客的攻击加密手段也在不断升级。近期&#xff0c;云天数据恢复中心接到多家企业的求助&#xf…

设计模式—命令模式:探索【命令模式】的奥秘与应用实践!

命令模式 命令模式是一种行为设计模式&#xff0c;它的主要目的是将请求封装成一个对象&#xff0c;从而使得请求的发送者和接收者之间进行解耦。 在命令模式中&#xff0c;命令被封装为一个对象&#xff0c;包含了需要执行的操作以及执行这些操作所需的所有参数。 命令的发送者…

OpenGuass 之 where 1 = 0 处理流程代码走读

一. 前言 在OpenGuass中&#xff0c;如果where 条件中包含where 1 0 等固定为否条件的查询语句&#xff0c;在生成执行计划的时候&#xff0c;执行计划是BaseResult类型&#xff0c;此类型的执行计划不会进行物理数据扫描&#xff0c;如下所示&#xff1a; 对于非固定为否条件&…

【论文阅读】多传感器SLAM数据集

一、M2DGR 该数据集主要针对的是地面机器人&#xff0c;文章正文提到&#xff0c;现在许多机器人在进行定位时&#xff0c;其视角以及移动速度与车或者无人机有着较大的差异&#xff0c;这一差异导致在地面机器人完成SLAM任务时并不能直接套用类似的数据集。针对这一问题该团队…

latex中\documentclass[preprint,review,12pt]{elsarticle}的详细解释

在LaTeX中&#xff0c;\documentclass 是一个命令&#xff0c;用于指定文档所使用的文档类。文档类定义了文档的总体结构、格式和样式。elsarticle 是一个常用的文档类&#xff0c;它主要用于在Elsevier出版的期刊上提交论文。 详细解释 \documentclass[preprint,review,12pt…

Autosar Appl介绍

AUTOSAR架构中的应用层 AUTOSAR 应用层构成AUTOSAR 架构中的最顶层,被认为对所有车辆应用至关重要。AUTOSAR 标准使用“组件”概念指定应用层实现。 在谈论应用层实现时,应该考虑的三个最重要的部分是: AUTOSAR 应用软件组件这些组件的 AUTOSAR 端口AUTOSAR 端口接口 AUTOS…

浙江大学主办!2024年第7届信息通信与信号处理国际会议( ICICSP2024)征稿开启!

会议官网 IEEE | ICICSP 2024 学术会议查询-学术会议交流服务平台-爱科会易 (uconf.com)​www.uconf.com/

OpenChat:性能高达105.7%,第一个超越ChatGPT的开源模型?

OpenChat&#xff1a;性能高达105.7%&#xff0c;第一个超越ChatGPT的开源模型&#xff1f; 前几天开源模型第一还是是Vicuna-33B、WizardLM&#xff0c;这不又换人了。对于开源模型的风起云涌&#xff0c;大家见怪不怪&#xff0c;不断更新的LLM榜单似乎也没那么吸引人了。 …

在springboot项目中调用通义千问api多轮对话并实现流式输出

官网文档 阿里灵积提供了详细的官方文档 如何实现多轮对话 官方文档中提到只需要把每轮对话中返回结果添加到消息管理器中&#xff0c;就可以实现多轮对话。本质上就是将历史对话再次发送给接口。 如何实现流式输出 官方文档中提出使用streamCall()方法就可以实现流式输出&…

ViT的若干细节

之前只看了ViT的大概结构&#xff0c;具体的模型细节和代码实现知之甚少。随着ViT逐渐成为CV领域的backbone&#xff0c;有必要重新审视下。 patch -> token 为了将图片处理成序列格式&#xff0c;很自然地想到将图片分割成一个个patch&#xff0c;再把patch处理成token。 …

Linux学习:初始Linux

目录 1. 引子&#xff1a;1.1 简述&#xff1a;操作系统1.2 学习工具 2. Linux操作系统中的一些基础概念与指令2.1 简单指令2.2 ls指令与文件2.3 cd指令与目录2.4 文件目录的新建与删除指令2.5 补充指令1&#xff1a;2.6 文件编辑与拷贝剪切2.7 文件的查看2.8 时间相关指令2.9 …

【网站项目】202物流管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

不会代码的时候,如何使用Jmeter完成接口测试

1.接口测试简介 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 2.接口测试流程 接口测试的…

2024.3.1 小项目

1、机械臂 #include <myhead.h> #define SER_IP "192.168.125.32" //服务器端IP #define SER_PORT 8888 //服务器端端口号#define CLI_IP "192.168.68.148" //客户端IP #define CLI_PORT 9999 /…

Python matplotlib

目录 1、安装 matplotlib 2、绘制折线图 修改标签文字和线条粗细 校正图形 3、绘制散点图 绘制单点 绘制一系列点 自动计算数据 删除数据点的轮廓 自定义颜色 使用颜色映射 自动保存图表 4、随机漫步 创建 RandomWalk() 类 选择方向 绘制随机漫步图 给点着色 …

最简单的ubuntu远程桌面方法

最简单的ubuntu远程桌面方法 部署环境&#xff1a;Ubuntu 20.04 LTS 现在最常用的远程控制Linux系统的方法是通过XRDP、VNC等&#xff0c;但是安装配置过程繁琐复杂&#xff0c;经常出现各种问题导致连接失败&#xff0c;另外一方面延迟较高&#xff0c;操作卡顿。 经过我坚…

【Java项目介绍和界面搭建】拼图小游戏——键盘、鼠标事件

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

DDS数据分发服务——提升汽车领域数据传输效率

1.引言 随着智能化技术的快速发展&#xff0c;汽车行业正经历着一场革命性的变革。如今的分布式系统变得越来越复杂且庞大&#xff0c;对网络通信基数要求在功能和性能层面越来越高。数据分发服务&#xff08;DDS&#xff09;作为一项先进的数据传输解决方案&#xff0c;在汽车…

2369. 检查数组是否存在有效划分(动态规划)

2024-3-1 文章目录 [2369. 检查数组是否存在有效划分](https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/)思路&#xff1a;代码&#xff1a; 2369. 检查数组是否存在有效划分 思路&#xff1a; 1.状态定义:f[i]代表考虑将[0,i]是否能被有效划…