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.rhkb.cn/news/269020.html

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

相关文章

Linux笔记-3

软件安装 概述 在Linux中&#xff0c;软件安装分为3种方式&#xff1a;绿色安装(压缩包解压之后就能直接使用)&#xff0c;rpm安装(类似于Windows中的exe或者msi文件)&#xff0c;yum安装 RPM(Red Hat Package Manager)&#xff1a;红帽提供的软件包的管理工具。可以通过rpm命…

Github项目推荐-LightMirrors

项目地址 https://github.com/NoCLin/LightMirrors 项目简述 “LightMirrors是一个开源的缓存镜像站服务&#xff0c;用于加速软件包下载和镜像拉取。目前支持DockerHub、PyPI、PyTorch、NPM等镜像缓存服务。 当前项目仍处于早期阶段。”–来自项目说明。 也就是说&#xff…

vue中使用prettier

前言&#xff1a;prettier是一款有态度的代码格式化工具&#xff0c;它可以集成在IDE中&#xff0c;如VS Code、Web Storm等&#xff0c;也可以安装到我们开发的项目里面。本文主要讲解在Vue中集成prettier的过程&#xff0c;可以便于代码检测和格式化。 prettier官网 从官网的…

ardupilot 及PX4姿态误差计算算法对比分析

目录 文章目录 目录摘要1.APM姿态误差计算算法2.PX4姿态误差计算算法3.结论摘要 本节主要记录ardupilot 及PX4姿态误差计算算法差异对比过程,欢迎批评指正。 备注: 1.创作不易,有问题急时反馈 2.需要理解四元物理含义、叉乘及点乘含义、方向余弦矩阵含义、四元数乘法物理含…

vue+element ui上传图片到七牛云服务器

本来打算做一个全部都是前端完成的资源上传到七牛云的demo&#xff0c;但是需要获取token&#xff0c;经历了九九八十一难&#xff0c;最终还是选择放弃&#xff0c;token从后端获取&#xff08;springboot&#xff09;。如果你们有前端直接能解决的麻烦记得私我哦&#xff01;…

【最新】如何将idea上的项目推送到gitee

1.打开Gitee&#xff0c;在首页&#xff0c;点击“”&#xff0c;创建一个仓库 2.填写仓库基本信息 3.下拉&#xff0c;点击“创建”&#xff0c;出现下方页面&#xff0c;证明仓库创建成功。 4.打开idea&#xff0c;下载gitee的插件&#xff08;此处默认已经下载git&#xff0…

布隆过滤器实战

一、背景 本篇文章以解决实际需求的问题的角度进行切入&#xff0c;探讨了如果使用布隆过滤器快速丢弃无效请求&#xff0c;降低了系统的负载以及不必要的流量。 我们都知道布隆过滤器是以占用内存小&#xff0c;同时也能够实现快速的过滤从而满足我们的需求&#xff0c;本篇…

termux上安装Python

Termux是一款Android平台下的终端模拟器和Linux环境应用&#xff0c;它允许用户在移动设备上访问Linux命令行界面&#xff0c;以便使用命令行工具、脚本、开发环境等功能。 要在Termux上安装Python&#xff0c;请按照以下步骤进行操作&#xff1a; 一&#xff0c;下载termux …

温湿度传感器SHT21

SHT21是一款基于IIC的温湿度传感器&#xff0c;它的引脚及定义如下&#xff1a; 标准的IIC器件&#xff0c;没有其他多余的引脚&#xff0c;应用框图如下&#xff1a; 温度的测量范围是-40到125℃&#xff0c;湿度测量范围0-100%RH&#xff0c;具体参数及采样精度见下图&#x…

如何限制一个账号只在一处登陆

大家好&#xff0c;我是广漂程序员DevinRock&#xff01; 1. 需求分析 前阵子&#xff0c;和问答群里一个前端朋友&#xff0c;随便唠了唠。期间他问了我一个问题&#xff0c;让我印象深刻。 他问的是&#xff0c;限制同一账号只能在一处设备上登录&#xff0c;是如何实现的…

C语言操作符详解(一)

一、操作符的分类 • 算术操作符&#xff1a; 、- 、* 、/ 、% • 移位操作符:<< >> • 位操作符: & | ^ • 赋值操作符: 、 、 - 、 * 、 / 、% 、<< 、>> 、& 、| 、^ • 单⽬操作符&#xff1a; &#xff01;、、--、&、*、、…

嵌入式基础知识-信号量,PV原语与前趋图

本篇来介绍信号量与PV原语的一些知识&#xff0c;并介绍其在前趋图上的应用分析。本篇的知识属于操作系统部分的通用知识&#xff0c;在嵌入式软件开发中&#xff0c;同样会用到这些知识。 1 信号量 信号量是最早出现的用来解决进程同步与互斥问题的机制&#xff08;可以把信…

深入了解 Android 中的 FrameLayout 布局

FrameLayout 是 Android 中常用的布局之一&#xff0c;它允许子视图堆叠在一起&#xff0c;可以在不同位置放置子视图。在这篇博客中&#xff0c;我们将详细介绍 FrameLayout 的属性及其作用。 <FrameLayout xmlns:android"http://schemas.android.com/apk/res/androi…

计算机组成原理(超详解!!) 第一节 导论

1.计算机的性能指标 1.字长 一般大型计算机字长为32位或64位&#xff1b; 小型计算机字长为16位或32位&#xff1b;微型计算机字长有1位、4位、8位、16位&#xff1b; 高档微型计算机字长为32位和64位。对于字长短的计算机&#xff0c;为了提高计算精度&#xff0c;采用多字…

基于SSM的农业电商服务系统(农产品销售管理系统)(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的农业电商服务系统&#xff08;农产品销售管理系统&#xff09;&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#…

可视化大屏实现屏幕自适应和自动全屏的实现

前言 在可视化大屏项目中&#xff0c;屏幕适配是绕不过去的一个问题&#xff08;ps&#xff1a;如果知道大屏展示的屏幕是固定的&#xff0c;当我没说&#xff09;。这里简单介绍通过 css的transform属性 里面的 scal() 实现常规屏幕适配。 常规屏幕&#xff1a; 1366 * 768…

【蓝桥备赛】双指针

日志统计 双指针在算法中也是经常会用到的&#xff0c;比如原地交换数组中的元素就可以用双指针来做&#xff0c;但是有的时候可能看不出来是双指针的思想。 对于一对数字可以用pair类型&#xff0c;cnt表示类型的次数&#xff0c;bool数组表示当前是否符合大于等于k的条件。 …

Vue.js+SpringBoot开发高校实验室管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实验管理模块2.4 实验设备模块2.5 实验订单模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示五、样例代码5.1 查询实验室设备5.2 实验放号5.3 实验预定 六、免责说明 一、摘…

Unity游戏输入系统(新版+旧版)

使用新版还是旧版 旧版 using System.Collections; using System.Collections.Generic; using UnityEngine;public class c5 : MonoBehaviour {void Start(){}void Update(){// 注意要在游戏中 点鼠标键盘进行测试// 鼠标// 0左键 1右键 2滚轮if (Input.GetMouseButtonDown(0)…

【千字总结】爬虫学习指南-2024最新版

介绍 如何自学爬虫&#xff1f;今天有一个兄弟这样问我&#xff0c;可以看到打了很多字&#xff0c;诚意肯定是很足的&#xff0c;也是对我的内容给予了肯定&#xff0c;让我非常的开心。既然难得有人问我&#xff0c;那我一定要好好做一个回答。 我下面将要说的内容没有任何话…