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. 关闭成功,返回0
	2. 关闭失败,返回-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. 成功,返回newfd
	2. 失败,返回-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. 物理内存到磁盘
    物理内存到磁盘

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

百度AI,能否“投”出未来?

图片&#xff5c;freeflo.ai ©自象限原创 作者丨程心、罗辑 2月28日&#xff0c;百度发布了2023年四季度财报及全年未经审计的财务报告&#xff0c;AI大模型带来的收入和利润成为最大的亮点。 财报显示&#xff0c;2023年百度集团总营收达1345.98亿元&#xff0c;同比增…

java数据结构与算法刷题-----LeetCode337. 打家劫舍 III

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 动态规划深度优先1.1 解题思路和细节2.2 代码实现 很多人觉得…

告别信息搜寻烦恼:用fastgpt快速部署国内大模型知识库助手

Docker Compose 快速部署 使用 Docker Compose 快速部署 FastGPT 推荐配置 环境最低配置&#xff08;单节点&#xff09;推荐配置测试2c2g2c4g100w 组向量4c8g 50GB4c16g 50GB500w 组向量8c32g16c64g 200GB 部署架构图 1. 准备好代理环境&#xff08;国外服务器可忽略&…

web游戏-飞机大战

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境&#xff0c;解压后浏览器直接打开。有需要的订阅后&#xff0c;私信本人&#xff0c;发源码&#xff0c;含60小游戏源码。如五子棋、象棋、植物大战僵尸、贪吃蛇、飞机大战、坦克大战、开心消消乐、扑鱼达人、扫雷…

STM32自学☞I2C

这里只是大体介绍&#xff0c;具体的可参考STM32数据手册

Python算法100例-3.2 水仙花数

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.问题拓展7.巧用字符串技巧 1&#xff0e;问题描述 输出所有的“水仙花数”。所谓的“水仙花数”是指一个三位数&#xff0c;其各位数字的立方和等于该…

[C语言]——C语言常见概念(3)

目录 一.字符和ASCII编码 二.字符串和\0 三.转义字符 四.语句和语句分类 1.空语句 2.表达式语句 3.函数调用语句 4.复合语句 5.控制语句 五.注释 1.注释的2种形式 1.1 /**/ 的形式 1.2 // 的形式 2.注释会被替换 一.字符和ASCII编码 在键盘上可以敲出各种字符&am…

【UE Niagara】纳米蠕虫效果

效果 步骤 1. 新建一个Niagara系统&#xff0c;选择一个空模板&#xff0c;这里命名为“NS_Worm” 打开“NS_Worm”&#xff0c;重命名发射器为“Leader” 先添加“Spawn Burst Instantaneous”模块来单次生成粒子 为了让粒子持续停留在关卡中&#xff0c;需要在“Particle St…

MybatisPlus的使用(一)--基本配置与无条件查询

创建测试用的数据库 CREATE DATABASE mybatis_plus /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use mybatis_plus; CREATE TABLE user ( id bigint(20) NOT NULL COMMENT 主键ID, name varchar(30) DEFAULT NULL COMMENT 姓名 , age int(11) DEFAULT NULL COMMENT 年龄 , em…

JavaWeb - 1 - 概述

一.什么是Web&#xff1f; Web&#xff1a;全球广域网&#xff0c;也称为万维网&#xff08;www World Wide Web&#xff09;&#xff0c;能够通过浏览器访问的网站 二.Web网站的工作流程 三.Web网站的开发模式 3.1 前后端分离开发&#xff08;主流&#xff09; 3.2 混合开发…

thymeleaf 一个莫名其妙的错误提示 org.attoparser.ParseException

thymeleaf 一个莫名其妙的错误提示 介绍 开发过程中遇到一个莫名奇妙的错误&#xff0c;一时竟然不知道怎么解决&#xff0c;找官网也没有找到 问题 页面显示 错误日志 org.attoparser.ParseException: (Line 96, Column 5) Malformed markup: Attribute “}” appears m…

羊大师揭秘羊奶将成为,健康新选择

羊大师揭秘羊奶将成为&#xff0c;健康新选择 羊奶作为一种传统的营养食品&#xff0c;已经在全球范围内受到了广泛的关注和认可。随着人们对健康生活的追求和对食品安全的重视&#xff0c;羊奶正逐渐成为健康的新选择。 羊奶的营养价值得到了科学的验证。羊奶中含有丰富的蛋…

【深度学习笔记】5_5 LeNet

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 5.5 卷积神经网络&#xff08;LeNet&#xff09; 在3.9节&#xff08;多层感知机的从零开始实现&#xff09;里我们构造了一个含单隐藏…

飞书文档批量导出

背景需求 最近所参与的项目即将结项&#xff0c;需要将飞书中的产品需求文档&#xff08;PRD&#xff09;交付给甲方&#xff0c;由于文档较多&#xff0c;大概有两百多个&#xff0c;一个一个的下载导出&#xff0c;太麻烦了&#xff08;PS&#xff1a;本人比较懒&#xff09;…

ruoyi-nbcio-plus的Vue3前端升级组件后出现的问题(一)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a; http://122.227.135.243:9666 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbach…

Jenkins 安装

目录 1、部署 Jenkins 安装配置 Jenkins 解锁 Jenkins 安装 Jenkins 插件 创建管理员账号 手动安装插件 2、Jenkins 从 GitLat 拉取代码 安装 Jenkins 插件 在 node-16 上生成密钥对 把公钥配置到 gitlab 上 把 root 用户私钥配置到 jenkins 上 Jenkins 创建一个任务…

PCSA时钟控制集成之时钟门控集成

1.4 时钟门控集成 高级时钟门控是使用每个时钟域的时钟控制器组件实现的。时钟控制器支持多个组件的时钟门控&#xff0c;并为每个组件提供一个Q-Channel接口。 大多数Arm组件都支持这种类型的时钟门控。大多数组件使用Q-Channel。一些较早的组件使用AXI LPI&#xff0c;但在…

2024全国护网行动HW行动招聘/收人!!!

2024全国护网行动HW行动招聘 溯蓉信创开始收人啦&#xff01;&#xff01;&#xff01;现在开始收录2024HW简历&#xff0c;感兴趣的小伙伴扫码二维码添加微信 我们签约后&#xff0c;入场即预付款3k&#xff0c;签约后我们会在HW之前对我们的人员进行HW培训&#xff0c;保证上…

AI加速引擎PAI-TorchAcc:整体介绍与性能概述

作者&#xff1a;沈雯婷、黄奕桐、艾宝乐、王昂、李永 1、简介 PAI-TorchAcc(Torch Accelerator)是阿里云人工智能平台开发的Pytorch上的大模型训练加速框架。 PAI-TorchAcc提供了一套基于Pytorch的简洁、易用的接口&#xff0c;无需进行模型转换就可以无缝地接入HuggingFac…

HTTP笔记(五)

个人学习笔记&#xff08;整理不易&#xff0c;有帮助点个赞&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客 目录 一&#xff1a;HTTP报文首部 &#xff08;1&#xff09;HTTP请求报文 &#xff08;2&#xff09…