Linux先进的系统-基础IO
文章目录
-
- Linux先进的系统-基础IO
-
- C语言中的文件接口
-
- 写入文件
- 读取文件
- 当前路径是什么?
- 三流默认打开
-
- stdout & stderr
- 系统文件IO
- 文件描述符fd
- 文件描述符的分配规则
- 重定向
-
- 输出重定向
- 追加重定向
- 输入重定向
- dup2
- FILE
-
- FILE文件描述符
- FILE的缓冲区
- 操作系统有缓冲区吗?
- 了解文件系统
-
- 磁盘
- 磁盘搜索方案
- 磁盘分区和格式化
- EXT2文件系统的存储方案
- 什么是inode
- 如何理解创建空文件
- 如何理解如何将信息写入文件
- 如何理解删除文件
- 为什么复制文件时间慢,删除文件时间快?
- 如何理解目录
- 软链接
- 硬链接
- 软硬链接的区别
- 三次文件
- 动态库和静态库
-
- 动态库和静态库的原理
- 初步识别动态库和静态库
- 动静态库各有特点
- 静态库的打包
- 包装和使用动态库
C语言中的文件接口
打开读写文件,C语言中用的FILE结构体。
写入文件
//写操作 #include <stdio.h> #include <string.h> int main() {
FILE *fp = fopen("log.txt", "w"); ///这里可以r w a 相应的阅读、写作和额外操作 if(!fp){
printf("fopen error!\n"); } const char *msg = "hello bit!\n"; int count = 5; while(count--){
fwrite(msg, strlen(msg), 1, fp);///二进制写入字符串 } fclose(fp); return
0
;
}
对文件进行读取
//读操作
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("log.txt", "r");
if(!fp){
printf("fopen error!\n");
}
char buf[1024];
const char *msg = "hello bit!\n";
while(1){
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
ssize_t s = fread(buf, 1, strlen(msg), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
这里有一个小的细节,
fwrite(msg, strlen(msg), 1, fp)
中的msg不需要加\0
,因为这是C语言的规定,操作系统并不认识。
什么是当前路径
我们知道,当以fopen写入的方式打开一个文件是,若该文件不存在,则会在当前路径下创建该文件,那么这里所说的当前路径是什么呢?
举个例子,我们在mycode2目录下创建一个可执行程序myproc,那么该可执行程序创建的log.txt文件会出现在mycode2目录下。
但是当我们退回上一级目录后,再次执行./mycode2/myproc
后,打开进程信息。
我们可以看到同样的软连接文件指向的文件不一样了。
cwd就是进程运行时我们所处的路径,而exe就是该可执行程序的所处路径。
实际上,我们这里所说的当前路径不是指可执行程序所处的路径,而是。
默认打开的三个流
,也就是说Linux下的任何东西都可以看作是文件,那么显示器和键盘当然也可以看作是文件。我们能看到显示器上的数据,是因为我们向“显示器文件”写入了数据,电脑能获取到我们敲击键盘时对应的字符,是因为电脑从“键盘文件”读取了数据。
为什么我们向“显示器文件”写入数据以及从“键盘文件”读取数据前,不需要进行打开“显示器文件”和“键盘文件”的相应操作?
需要注意的是,打开文件一定是进程运行的时候打开的,而任何进程在运行的时候都会默认打开三个输入输出流,即标准输入流、标准输出流以及标准错误流,对应到C语言当中就是stdin、stdout以及stderr。 其中,标准输入流对应的设备就是键盘,标准输出流和标准错误流对应的设备都是显示器。
查看man手册我们就可以发现,stdin、stdout以及stderr这三个家伙实际上都是FILE*
类型的。
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
当我们的C程序被运行起来时,操作系统就会默认使用C语言的相关接口将这三个输入输出流打开,之后我们才能调用类似于scanf和printf之类的函数向键盘和显示器进行相应的输入输出操作。
也就是说,、以及与我们打开某一文件时获取到的文件指针是同一个概念,试想我们使用fputs函数时,将其第二个参数设置为stdout,此时fputs函数会不会之间将数据显示到显示器上呢?
#include <stdio.h>
int main()
{
fputs("hello stdin\n", stdout);
fputs("hello stdout\n", stdout);
fputs("hello stderr\n", stdout);
return 0;
}
答案是肯定的,此时我们相当于使用fputs函数向“显示器文件”写入数据,也就是显示到显示器上。
不止是C语言当中有标准输入流、标准输出流和标准错误流,C++当中也有对应的cin、cout和cerr,其他所有语言当中都有类似的概念。实际上这种特性并不是某种语言所特有的,而是由操作系统所支持的。
stdout & stderr
我们来看看下面这段代码,代码中分别向标准输出流和标准错误流输出了两行字符串。
#include <stdio.h>
int main()
{
printf("hello printf\n"); //stdout
perror("perror"); //stderr
fprintf(stdout, "stdout:hello fprintf\n"); //stdout
fprintf(stderr, "stderr:hello fprintf\n"); //stderr
return 0;
}
直接运行程序,结果很显然就是在显示器上输出四行字符串。
这样看起来标准输出流和标准错误流并没有区别,都是向显示器输出数据。但我们若是将程序运行结果重定向输出到文件log.txt当中,我们会发现log.txt文件当中只有向标准输出流输出的两行字符串,而向标准错误流输出的两行数据并没有重定向到文件当中,而是仍然输出到了显示器上。
实际上我们使用重定向时,重定向的是文件描述符是1的标准输出流,而并不会对文件描述符是2的标准错误流进行重定向。
系统文件IO
操作文件除了C语言接口、C++接口或是其他语言的接口外,操作系统也有一套系统接口来进行文件的访问。 相比于C库函数或其他语言的库函数而言,系统调用接口更贴近底层,实际上这些语言的库函数都是对系统接口进行了封装。
我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,正是由于C语言对不通过操作系统的系统调用接口进行了封装,这种行为使得C语言具有了跨平台性,所以我们使用C语言才能放之四海任我行。
文件描述符fd
文件是由进程运行时打开的,一个进程可以打开多个文件,而系统当中又存在大量进程,也就是说,在系统中任何时刻都可能存在大量已经打开的文件。因此,操作系统务必要对这些已经打开的文件进行管理,操作系统会为每个已经打开的文件创建各自的struct file结构体,然后将这些结构体以双链表的形式连接起来,之后操作系统对文件的管理也就变成了对这张双链表的增删查改等操作。而为了区分已经打开的文件哪些属于特定的某一个进程,我们就还需要建立进程和文件之间的对应关系。
进程和文件之间的对应关系是如何建立的?
当一个程序运行起来时,操作系统会将该程序的代码和数据加载到内存,然后为其创建对应的task_struct、mm_struct、页表等相关的数据结构,并通过页表建立虚拟内存和物理内存之间的映射关系。
task_struct当中有一个指针file,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。 当进程打开log.txt文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file插入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可。
因此,我们只要有某一文件的文件描述符,就可以找到与该文件相关的文件信息,进而对文件进行一系列输入输出操作。
向文件写入数据时,是先将数据写入到对应文件的缓冲区当中,然后定期将缓冲区数据刷新到磁盘当中。
为什么进程创建的时候会默认打开0、1、2?
0就是标准输入流,对应键盘;1就是标准输出流,对应显示器;2就是标准错误流,也是对应显示器。 而键盘和显示器都属于硬件,属于硬件就意味着操作系统能够识别到,当某一进程创建时,操作系统就会根据键盘、显示器、显示器形成各自的struct file,将这3个struct file连入文件双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流。
什么是磁盘文件?什么是内存文件?
当文件存储在磁盘当中时,我们将其称之为磁盘文件。当磁盘文件被加载到内存当中后,我们将加载到内存当中的文件称之为内存文件。
磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序运行起来后便成了进程,而当磁盘文件加载到内存后便成了内存文件。 磁盘文件由两部分构成,分别是文件内容和文件属性(元信息)。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性。 文件加载到内存时,一般先加载文件的属性信息,当需要对文件内容进行读取、输入或输出等操作时,再延后式加载文件数据。
文件描述符的分配规则
尝试连续打开五个文件,看看这五个打开后获取到的文件描述符。
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
umask(0);
int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666);
int fd2 = open("log2.txt", O_RDONLY | O_CREAT, 0666);
int fd3 = open("log3.txt", O_RDONLY | O_CREAT, 0666);
int fd4 = open("log4.txt", O_RDONLY | O_CREAT, 0666);
int fd5 = open("log5.txt", O_RDONLY | O_CREAT, 0666);
printf("fd1:%d\n", fd1);
printf("fd2:%d\n", fd2);
printf("fd3:%d\n", fd3);
printf("fd4:%d\n", fd4);
printf("fd5:%d\n", fd5);
return 0;
}
可以看到这五个文件获取到的文件描述符都是从3开始连续递增的。因为文件描述符本质就是数组的下标,而当进程创建时就默认打开了标准输入流、标准输出流和标准错误流,也就是数组下标0、1、2已经被占用了,所以只能从3开始进行分配。
若我们在打开这五个文件前,先关闭文件描述符为0的文件,此后文件描述符的分配又会是怎样的呢?
即close(0)
。
可以看到,第一个打开的文件获取到的文件描述符变成了0,而之后打开文件获取到的文件描述符还是从3开始依次递增的。
再试试在打开这五个文件前,将文件描述符为0和2的文件都关闭(不要将文件描述符为1的文件关闭,因为这意味着关闭了显示器文件,此时运行程序将不会有任何输出)。
重定向
输出重定向
输出重定向就是,将我们本应该输出到一个文件的数据重定向输出到另一个文件中。
例如,如果我们想让本应该输出到“显示器文件”的数据输出到log.txt文件当中,那么我们可以在打开log.txt文件之前将文件描述符为1的文件关闭,也就是将“显示器文件”关闭,这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是1。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0){
perror("open");
return 1;
}
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
printf("hello world\n");
fflush(stdout);
close(fd);
return 0;
}
运行结果后,我们发现显示器上并没有输出数据,对应数据输出到了log.txt文件当中。
这里需要注意的是,对于C语言提高的printf()
函数,它默认是将内容输出到stdout中的,stdout指向一个struct FILE类型的对象,该对象中包含一个变量fd
(存储文件描述符),操作系统根据文件描述符去找到对应下表为1的结构体指针struct file*
,此时这个指针指向的是显示器文件,但是如果关闭显示器文件(操作系统将下标为1的结构体指针指向新的文件),根据文件描述符的分配规则,操作系统为log.txt创建新的struct FILE
结构体变量,下标为1的struct file*
指向log.txt的struct FILE
,此时printf()
函数只会将内容输出到文件描述符为1的文件中,至于这个文件是谁,它并不关心,所以在显示器上我们不会看见任何内容,它将内容直接输出到log.txt中。
C语言的printf()
函数会将内容先输出到C语言的缓冲区中,所以使用fflush(stdout)
才能将内容打印到log.txt中。
追加重定向
追加重定向和输出重定向的唯一区别就是,输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据。
例如,如果我们想让本应该输出到“显示器文件”的数据输出到log.txt文件当中,那么我们可以在打开log.txt文件之前将文件描述符为1的文件关闭,也就是将“显示器文件”关闭,这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是1。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY | O_APPEND, 0666);
if (fd < 0){
perror("open");
return 1;
}
printf("hello Linux\n");
printf("hello Linux\n");
printf("hello Linux\n");
printf("hello Linux\n");
printf("hello Linux\n");
fflush(stdout);
close(fd);
return 0;
}
运行结果后,我们发现显示器上并没有输出数据,对应数据输出到了log.txt文件当中。
输入重定向
输入重定向就是,将我们本应该从一个文件读取数据,现在重定向为从另一个文件读取数据。
例如,如果我们想让本应该从“键盘文件”读取数据的scanf函数,改为从log.txt文件当中读取数据,那么我们可以在打开log.txt文件之前将文件描述符为0的文件关闭,也就是将“键盘文件”关闭,这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是0。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
int fd = open("log.txt", O_RDONLY | O_CREAT, 0666);
if (fd < 0){
perror("open");
return 1;
}
char str[40];
while (scanf("%s", str) != EOF){
printf("%s\n", str);
}
close(fd);
return 0;
}
运行结果后,我们发现scanf函数将log.txt文件当中的数据都读取出来了。
dup2
要完成重定向我们只需进行fd_array数组当中元素的拷贝即可。例如,我们若是将fd_array[3]当中的内容拷贝到fd_array[1]当中,因为C语言当中的stdout就是向文件描述符为1文件输出数据,那么此时我们就将输出重定向到了文件log.txt。
在Linux操作系统中提供了系统接口dup2,我们可以使用该函数完成重定向。dup2的函数原型如下:
int dup2(int oldfd, int newfd);
: dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中,如果有必要的话我们需要先使用关闭文件描述符为newfd的文件。
: dup2如果调用成功,返回newfd,否则返回-1。
使用dup2时,我们需要注意以下两点:
-
如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。
-
如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。
例如,我们将打开文件log.txt时获取到的文件描述符和1传入dup2函数,那么dup2将会把fd_arrya[fd]的内容拷贝到fd_array[1]中,在代码中我们向stdout输出数据,而stdout是向文件描述符为1的文件输出数据,因此,本应该输出到显示器的数据就会重定向输出到log.txt文件当中。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0){
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
return 0;
}
代码运行后,我们即可发现数据被输出到了log.txt文件当中。
FILE
FILE当中的文件描述符
因为库函数是对系统调用接口的封装,本质上访问文件都是通过文件描述符fd进行访问的,所以C库当中的FILE结构体内部必定封装了文件描述符fd。
首先,我们在/usr/include/stdio.h
头文件中可以看到下面这句代码,也就是说FILE实际上就是struct _IO_FILE
结构体的一个别名。
typedef struct _IO_FILE FILE;
而我们在/usr/include/libio.h
头文件中可以找到struct _IO_FILE
结构体的定义,在该结构体的众多成员当中,我们可以看到一个名为_fileno
的成员,这个成员实际上就是封装的文件描述符。
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
那么C语言中的fopen
函数究竟在做什么?
fopen函数在上层为用户申请FILE结构体变量,并返回该结构体的地址(FILE*),在底层通过系统接口open打开对应的文件,得到文件描述符fd,并把fd填充到file结构体当中的_fileno
变量中,至此便完成了文件的打开操作。
FILE的缓冲区
我们来看看下面这段代码,代码当中分别用了两个C库函数和一个系统接口向显示器输出内容,在代码最后还调用了fork函数。
#include <stdio.h>
#include <unistd.h>
int main()
{
//c
printf("hello printf\n");
fputs("hello fputs\n", stdout);
//system
write(1, "hello write\n", 12);
fork();
return 0;
}
但是,当我们将程序的结果重定向到log.txt文件当中后,我们发现文件当中的内容与我们直接打印输出到显示器的内容是不一样的。
那为什么C库函数打印的内容重定向到文件后就变成了两份,而系统接口打印的内容还是原来的一份呢?
首先我们应该知道的是,缓冲的方式有以下三种:
- 无缓冲
- 行缓冲(遇见
\0
后刷新,常见与对显示器文件进行数据刷新) - 全缓冲(当文件缓冲区写满后在进行刷新,常见于对磁盘文件的刷新)
当我们直接执行可执行程序,将数据打印到显示器时所采用的就是行缓冲,因为代码当中每句话后面都有\n,所以当我们执行完对应代码后就立即将数据刷新到了显示器上。 而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf
和fputs
函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf
和puts
函数打印的数据就有两份。但由于write函数是系统接口,我们可以将write
函数看作是没有缓冲区的,因此write
函数打印的数据就只打印了一份。
?
实际上这个缓冲区是C语言自带的,如果说这个缓冲区是操作系统提供的,那么printf、fputs和write函数打印的数据重定向到文件后都应该打印两次。
?
我们常说
printf
是将数据打印到stdout
里面,而stdout
就是一个FILE*
的指针,在FILE结构体当中还有一大部分成员是用于记录缓冲区相关的信息的。
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
也就是说,这里的缓冲区是由C语言提供,在FILE结构体当中进行维护的,FILE结构体当中不仅保存了对应文件的文件描述符还保存了用户缓冲区的相关信息。
操作系统有缓冲区吗
操作系统实际上也是有缓冲区的,当我们刷新用户缓冲区的数据时,并不是直接将用户缓冲区的数据刷新到磁盘或是显示器上,而是先将数据刷新到操作系统缓冲区,然后再由操作系统将数据刷新到磁盘或是显示器上。(操作系统有自己的刷新机制,我们不必关系操作系统缓冲区的刷新规则)
对于操作系统,它不信任任何人,多以数据不会直接由用户缓冲区到磁盘,而是经由它手才能刷新到磁盘上(操作系统是软硬件交互的桥梁)。
理解文件系统
我们知道文件可以分为磁盘文件和内存文件,内存文件前面我们已经谈过了,下面我们来谈谈磁盘文件。
磁盘
磁盘是一种永久性存储介质,在计算机中,磁盘几乎是唯一的机械设备。与磁盘相对应的就是内存,内存是掉电易失存储介质,目前所有的普通文件都是在磁盘中存储的。
磁盘在冯诺依曼体系结构当中既可以充当输入设备,又可以充当输出设备。
硬盘一般有一个或多个盘片,每个盘片可以有两面,即第一个盘片的正面为0面,反面为1面然后依次类推。
每个盘片的盘面在出厂的时候被划分出了多个同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道(Track),每个盘面可以划分多个磁道。但肉业不可见。
在硬盘出厂时会对磁盘进行一次低格,其实就是再每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储512字节数据已经成了业界的约定。
柱面实际上就是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为 柱面 ,即各盘面上面相同位置磁道的集合。这样数据如果存储到相同半径磁道上的同一扇区,这样可以实现并行读取,主要是减少磁头寻道时间。
读取磁盘磁道上面金属块,主要负责读或写入数据。
磁盘的寻找方案
对磁盘进行读写操作时,一般有以下几个步骤:
- 确定读写信息在磁盘的哪个盘面。
- 确定读写信息在磁盘的哪个柱面。
- 确定读写信息在磁盘的哪个扇区。
通过以上三个步骤,最终确定信息在磁盘的读写位置。
磁盘分区与格式化
理解文件系统,首先我们必须将磁盘想象成一个线性的存储介质,想想磁带,当磁带被卷起来时,其就像磁盘一样是圆形的,但当我们把磁带拉直后,其就是线性的。
磁盘通常被称为块设备,一般以扇区为单位,一个扇区的大小通常为512字节。我们若以大小为512G的磁盘为例,该磁盘就可被分为十亿多个扇区。
计算机为了更好的管理磁盘,于是对磁盘进行了分区。磁盘分区就是使用分区编辑器在磁盘上划分几个逻辑部分,盘片一旦划分成数个分区,不同的目录与文件就可以存储进不同的分区,分区越多,就可以将文件的性质区分得越细,按照更为细分的性质,存储在不同的地方以管理文件,例如在Windows下磁盘一般被分为C盘和D盘两个区域。
在Linux操作系统中,我们也可以通过以下命令查看我们磁盘的分区信息:
ls /dev/vda* -l
当磁盘完成分区后,我们还需要对磁盘进行格式化。磁盘格式化就是对磁盘中的分区进行初始化的一种操作,这种操作通常会导致现有的磁盘或分区中所有的文件被清除。 简单来说,磁盘格式化就是对分区后的各个区域写入对应的管理信息。
其中,写入的管理信息是什么是由文件系统决定的,不同的文件系统格式化时写入的管理信息是不同的,常见的文件系统有EXT2、EXT3、XFS、NTFS等。
EXT2文件系统的存储方案
计算机为了更好的管理磁盘,会对磁盘进行分区。而对于每一个分区来说,分区的头部会包括一个启动块(Boot Block),对于该分区的其余区域,EXT2文件系统会根据分区的大小将其划分为一个个的块组(Block Group)。
: 启动块的大小是确定的,而块组的大小是由格式化的时候确定的,并且不可以更改。
其次,每个组块都有着相同的组成结构,每个组块都由超级块(Super Block)、块组描述符表(Group Descriptor Table)、块位图(Block Bitmap)、inode位图(inode Bitmap)、inode表(inode Table)以及数据表(Data Block)组成。
- Super Block: 存放文件系统本身的结构信息。记录的信息主要有:Data Block和inode的总量、未使用的Data Block和inode的数量、一个Data Block和inode的大小、最近一次挂载的时间、最近一次写入数据的时间、最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。
- Group Descriptor Table: 块组描述符表,描述该分区当中块组的属性信息。
- Block Bitmap: 块位图当中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
- inode Bitmap: inode位图当中记录着每个inode是否空闲可用。
- inode Table: 存放文件属性,即每个文件的inode(不保存文件名,文件名在目录的Block Group中)。
- Data Blocks: 存放文件内容。
- 其他块组当中可能会存在冗余的Super Block,当某一Super Block被破坏后可以通过其他Super Block进行恢复。
- 磁盘分区并格式化后,每个分区的inode个数就确定了。
什么是inode
经过前面的学习,我们得出一个结论,一个磁盘文件包含文件内容与文件属性(元信息)。文件内容就是文件中存储的数据,文件属性就是文件的基本信息。
在Linux命令行中输入ls -l
亦或是ll
,即可查看当前目录下的文件信息。
在Linux操作系统中,文件的元信息和内容是分离存储的,其中保存元信息的结构称之为inode,因为系统当中可能存在大量的文件,所以我们需要给每个文件的属性集起一个唯一的编号,即inode号。也就是说,inode是一个文件的属性集合,Linux中几乎每个文件都有一个inode,为了区分系统当中大量的inode,我们为每个inode设置了inode编号。