Linux IO基础

目录

一.C语言文件IO操作

1.写文件

2.读文件

3.默认打开的三个流

4.什么是 “当前路径”

二.系统文件IO操作接口 

1.open函数

2.write , read ,close

3.关于文件描述符fd 

 三.FILE结构

1.FILE中的文件描述符

2.FILE的缓冲区理解                

3.补充

四.重定向

1.重定向原理

2.dup2函数

五.Linux文件系统 

1.初识inode

2.理解硬件磁盘

3.磁盘分区

4.Linux ext2文件系统

六.软硬链接

1.软连接

2.硬链接

3.软硬连接的对比

文件的三个时间


 


 

一.C语言文件IO操作

1.写文件

Linux IO基础_第1张图片

                        

2.读文件

Linux IO基础_第2张图片

                                

3.默认打开的三个流

①任何进程在运行的时候都会默认打开三个输入输出流,即标准输入流、标准输出流以及标准错误流,对应到C语言当中就是stdin、stdout以及stderr。

②其中,标准输入流对应的设备就是键盘,标准输出流和标准错误流对应的设备都是显示器。

③查看man手册,stdin、stdout以及stderr这三个都是FILE*类型的

Linux IO基础_第3张图片

                        

④当我们的C程序被运行起来时,操作系统就会默认打开三个输入输出流,之后我们才能调用类似于scanf和printf之类的函数向键盘和显示器进行相应的输入输出操作。

⑤stdin、stdout以及stderr与我们打开某一文件时获取到的文件指针指向的是同一个类型的结构体

注意:不止是C语言当中有标准输入流、标准输出流和标准错误流,C++当中也有对应的cin、cout和cerr,其他所有语言当中都有类似的概念。实际上这种特性并不是某种语言所特有的,而是由操作系统所支持的。

                                                         

4.什么是 “当前路径”

使用fopen以写入的方式打开一个文件时,若该文件不存在,则会自动在当前路径创建该文件,那么这里所说的当前路径指的是什么呢?

示例:

(1)代码 

Linux IO基础_第4张图片

                                 

(2)第一次运行

Linux IO基础_第5张图片

                                         

 (3)第二次运行

Linux IO基础_第6张图片

 (4)结论:这里所说的当前路径不是指可执行程序所处的路径,而是指该可执行程序运行成为进程时我们所处的路径 ,即进程运行时所处的路径。

                                

                                                

二.系统文件IO操作接口 

  • 文件操作除了C/C++接口或是其他语言的接口外,操作系统也有一套系统接口来进行文件的访问。
  • 相对于语言级别的库函数,系统调用接口更贴近底层,实际上这些语言的库函数都是对系统接口进行了封装,更加方便开发者进行使用,降低使用成本。
  • 在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装, 在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,我们不用关注底层差异,更好的使用。

Linux IO基础_第7张图片

                        

1.open函数

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

(1)第一个参数:pathname

open函数的第一个参数是pathname,表示要打开或创建的目标文件。

  • 若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建。
  • 若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建。(注意当前路径的含义)

Linux IO基础_第8张图片

                                         

 (2)第二个参数:flags

open函数的第二个参数是flags,表示打开文件的方式。

①常用选项:         [   O_WRNOLY | O_CREAT =  W (C语言)   ]

参数选项 含义
O_RDONLY 以只读的方式打开文件
O_WRNOLY 以只写的方式打开文件
O_APPEND 以追加的方式打开文件
O_RDWR 以读写的方式打开文件
O_CREAT 当目标文件不存在时,创建文件

                        

②系统接口open的第二个参数flags是整型,有32比特位,若将一个比特位作为一个标志位,则理论上flags可以传递32种不同的标志位,每一个选项只有一位为1.

查看系统中的定义:  每一个选项都是以宏的方式定义的

Linux IO基础_第9张图片

                         

③将标志位分离,判断传入的flags的选项,底层再做下一步操作 

int open(xxx, arg, xxx){
	if (arg & O_RDONLY){
		//设置O_RDONLY
	}
	if (arg & O_WRONLY){
		//设置O_WRONLY
	}
	if (arg & O_CREAT){
		//设置O_CREAT
	}
	......
}

                                

(3)第三个参数: mode 

open函数的第三个参数是mode,表示创建文件的默认权限,只在创建文件的时候有用。

Linux IO基础_第10张图片

                        

(4)返回值int

①open函数的返回值是新打开文件的文件描述符fd

Linux IO基础_第11张图片

                                 

② 文件打开失败,返回-1、

Linux IO基础_第12张图片

                                                

③返回值的解释

  • 实际上这里所谓的文件描述符本质上是一个指针数组的下标,指针数组当中的每一个指针都指向一个被打开文件的文件信息,通过对应文件的文件描述符就可以找到对应的文件信息。
  • 当使用open函数打开文件成功时数组当中的信息增加1(可以理解为,如果该指针未被使用即没有指向文件,默认填NULL),然后将该指针在数组当中的下标进行返回,而当文件打开失败时直接返回-1,因此,成功打开多个文件时所获得的文件描述符就是连续且递增的。
  • 程序运行起来默认打开三个文件,分别就是标准输入0、标准输出1、标准错误2,这就是为什么成功打开文件时所得到的文件描述符是从3开始进程分配的

                                

2.write , read ,close

(1)close

int close(int fd);

使用close函数时传入需要关闭文件的文件描述符即可,若关闭文件成功则返回0,若关闭文件失败则返回-1;(文件描述符也是有限的,是一种资源,不使用要关闭,以便于给其他文件分配)

                                 

(2) write

ssize_t write(int fd, const void *buf, size_t count);

从buf位置开始向后count字节的数据写入文件描述符为fd的文件当中。

  • 如果数据写入成功,返回实际写入数据的字节个数。
  • 如果数据写入失败,-1被返回。

                                         

(3)read

ssize_t read(int fd, void *buf, size_t count);

 从文件描述符为fd的文件读取count字节的数据到buf位置当中。

  • 如果数据读取成功,返回实际读取数据的字节个数。
  • 如果数据读取失败,-1被返回。

(4)使用举例

  1 #include  //写文件                                                                        
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 int main()
  9 {
 10   int fd = open("log.txt" , O_WRONLY);
 11   if(fd < 0){
 12     perror("open error!\n");
 13     return 1;
 14   }
 15 
 16   const char* buf = "file test ...\n";
 17   int count = 5;
 18   while(count--){
 19     write(fd , buf , strlen(buf));
 20   }
 21 
 22   close(fd);
 23                                                                                           
 24   return 0;

Linux IO基础_第13张图片


  1 #include  //读文件                                                                       
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 
  8 int main()
  9 {
 10   int fd = open("log.txt" , O_RDONLY);
 11   if(fd < 0){
 12     perror("open error!\n");
 13     return 1;
 14   }
 15 
 16   //法1;
 17  // char buf[64];
 18  // int count = 5;
 19  // while(count--){
 20  //   read(fd , buf , strlen(buf));
 21  //   printf("%s" , buf);
 22  // }
 23 
 24   //法2;
 25   char ch;
 26   while(1){
 27     ssize_t ret = read(fd ,&ch , 1);
 28     printf("%c" , ch);
 29 
 30     if(ret <= 0){
 31       break;
 32     }
 33   }                                                                                       
 34 
 35   close(fd);
 36 
 37   return 0;

Linux IO基础_第14张图片

                

3.关于文件描述符fd 

① 文件是由进程运行时打开的,一个进程可以打开多个文件,而系统当中又存在大量进程,也就是说,在系统中任何时刻都可能存在大量已经打开的文件。因此,操作系统务必要对这些已经打开的文件进行管理,操作系统会为每个已经打开的文件创建各自的struct file结构体,然后将这些结构体以双链表的形式连接起来,之后操作系统对文件的管理也就变成了对这张双链表的增删查改等操作。

                                

 ②进程和文件建立联系

Linux IO基础_第15张图片

                                         

③内核对于files_struct 的描述 

Linux IO基础_第16张图片

④什么叫做创建进程打开0,1,2

当某一进程创建时,操作系统就会根据键盘、显示器、显示器形成各自的struct file,将这3个struct file连入文件双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流

                                        

⑤磁盘文件(程序)vs内存文件(进程)

  • 当文件存储在磁盘当中时,我们将其称之为磁盘文件,而当磁盘文件被加载到内存当中后,我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序运行起来后便成了进程,而当磁盘文件加载到内存后便成了内存文件。
  • 磁盘文件由两部分构成,分别是文件内容和文件属性。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等,文件属性又被称为元信息。
  • 文件加载到内存时,一般先加载文件的属性信息,当需要对文件内容进行读取、输入或输出等操作时,再延后式的加载文件数据。(你把文件打开了,是不是立马要进行数据的修改,并不是;所以磁盘文件,当你打开文件的时候并不是把属性和数据立马加载到内存,而是先把一部分属性加载到内存,当你进行文件修改/读取的时候才会慢慢进行加载,需要的时候再加载)
  • 用户和OS交互是进程交互,查看文件也是需要以进程为基础进程
     

                                                 

⑥文件描述符的分配 : 从最小的,没有被使用的下标开始分配 

Linux IO基础_第17张图片

⑦写的函数最终是在进程中调用,代码是要OS执行的。write 函数是进程调用,OS执行
当调用write函数时,OS知道当前进程的PCB找到文件指针数组,通过传入的fd找到管理该文件的数据结构。

 

 三.FILE结构

1.FILE中的文件描述符

(1)因为库函数是对系统调用接口的封装,本质上访问文件都是通过文件描述符fd进行访问的,所以C库当中的FILE结构体内部必定封装了文件描述符fd。

                 

(2)在/usr/include/stdio.h头文件中可以看到下面这句代码

typedef struct _IO_FILE FILE;

                         

(3)查看 struct _IO_FILE内部定义

int _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
};

                                         

(4)fopen做了什么

  • fopen函数在上层为用户申请FILE结构体变量,并返回该结构体的地址(FILE*),在底层通过系统接口open打开对应的文件,得到文件描述符fd,并把fd填充到FILE结构体当中的_fileno变量中,至此便完成了文件的打开操作。
  • C语言当中的其他文件操作函数,比如fread、fwrite、fputs、fgets等,都是先根据我们传入的文件指针找到对应的FILE结构体,然后在FILE结构体当中找到文件描述符,最后通过文件描述符对文件进行的一系列操作。
  • stdout vs 1 -> 1更靠近底层

                                

2.FILE的缓冲区理解                

实验:Linux IO基础_第18张图片

Linux IO基础_第19张图片 

 三种缓冲方式 :

  • 无缓冲
  • 行缓冲(常见的对显示器进行刷新数据)
  • 全缓冲(常见的对磁盘文件写入数据)

                                 

(1)现象解释

①当我们直接执行可执行程序,将数据打印到显示器时所采用的就是行缓冲,因为代码当中每句话后面都有\n,所以当我们执行完对应代码后就立即将数据刷新到了显示器上。

②当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fprintf函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,fork之后父子进程代码共享数据私有,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时(刷新之后数据没了,相当于写入了),本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,,父子进程各自有一份独立的数据,所以最终打印了两次。所以重定向到log.txt文件当中printf和fprintf函数打印的数据就有两份。

③write函数是系统接口,没有缓冲区,因此write函数打印的数据就只打印了一份。

                        

(2)C语言缓冲区

①这个缓冲区是谁提供的?  C语言,用户级概念

②这个缓冲区在哪里? 

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. */

(3)为什么文件是全缓冲,显示器是行缓冲?
计算机遵守冯诺依曼体系,文件本身在系统当中属于外设/外设之上,我们把数据刷新到磁盘上/显示器上都叫做往外设上面写,外设效率低,所以我们把数据积累到内存缓冲区里,积累足够多的时候刷新,这样的话就可以提高效率(送快递,一个一个送,一批送)

                        

(4)全缓冲理论上效率最高,为什么显示器还要行刷新?
磁盘上的文件人在写的时候是不会读的,显示器不一样,当一个人往显示器上写数据时,这个人想要尽快拿到这个数据,不要缓冲效率就太低了,全缓冲人看到的消息就不及时,这样倒不如设置成行缓冲;本质行缓冲是在效率和可用性做的平衡
                                

(5)OS的缓冲区 : 不受缓冲方式的影响

操作系统实际上也是有缓冲区的,当我们刷新用户缓冲区的数据时,并不是直接将用户缓冲区的数据刷新到磁盘或是显示器上,而是先将数据刷新到操作系统缓冲区,然后再由操作系统在合适的时候将数据刷新到磁盘或是显示器上。(操作系统有自己的刷新机制)

因为操作系统是进行软硬件资源管理的软件,用户区的数据要刷新到外设必须经过操作系统,数据最终是由OS刷新到外设。

                        

3.补充

(1)数据刷新

Linux IO基础_第20张图片

 ①C语言的函数把数据写到用户级缓冲区全缓冲,如果close,把文件描述符1关了,最后进程退出的时候想刷到显示器但是fd 关了,没办法刷新到1了,只有在close前fflush强制刷新到内核缓冲区才能最终显示

Linux IO基础_第21张图片

                         

 ②调用fcolse会先把用户级缓冲区的数据刷新到内核缓冲区,然后调用close关闭文件描述符。  

③如果是长时间运行的程序文件使用完最好fclose,文件描述符有限,创建struct file需要消耗内存,防止内存泄漏;但是程序退出时会自动清空用户缓冲区,将数据刷新出来。

                        

(2)stdout ,stderr都是将数据打印到显示器

Linux IO基础_第22张图片

(3)理解Linux下一切皆文件

Linux IO基础_第23张图片

 ①当上层要进行读写文件时,它根本不关心底层指向什么方法,只需要调用该函数指针指向的方法;所有的函数都不关心底层的差异,只需要调用read,write就完成了统—的读写,站在应用层就可以认为一切皆文件。

②所有的外设硬件,都有自己的read,write方法 ,绝对不一样。
                        

 (4)凡是显示到显示器上面的内容,都是字符;  凡是从键盘读取的内容都是字符 。所以,键盘和显示器一般被称之为“字符设备”

 

                

                

四.重定向

1.重定向原理

(1)输出重定向: 应该显示到显示器的内容显示到文件中,close(1),打开新文件时分配文件描述符1

Linux IO基础_第24张图片

 代码+结果:

Linux IO基础_第25张图片

  •  printf函数是默认向stdout输出数据的,而stdout指向的是一个struct FILE类型的结构体,该结构体当中有一个存储文件描述符的变量,而stdout指向的FILE结构体中存储的文件描述符就是1,因此printf实际上就是向文件描述符为1的文件输出数据。
  • C语言的数据并不是立马写到了内核缓冲区,而是写到了C语言的缓冲区当中而且我们最后主动close(fd),重定向之后缓冲方式发生了改变,变成了全缓冲,所以使用printf打印完后需要使用fflush将C语言缓冲区当中的数据刷新到文件中。

                         

(2)追加重定向 : 只需要在open时添加O_APPEND字段

Linux IO基础_第26张图片

                                         

(3)输入重定向 : 应该从键盘中读取数据,close(0),新打开文件时分配文件描述符0,从文件中读取

Linux IO基础_第27张图片

代码+数据: 

Linux IO基础_第28张图片

  •  scanf函数是默认从stdin读取数据的,而stdin指向的FILE结构体中存储的文件描述符是0,因此scanf实际上就是向文件描述符为0的文件读取数据。

2.dup2函数

(1)函数介绍

int dup2(int oldfd, int newfd);     

①函数功能: dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中 , newfd指向的文件不再使用时close(newfd) .

②函数返回值: dup2如果调用成功,返回newfd,否则返回-1。

③使用dup2时,我们需要注意以下两点:

  • 如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。
  • 如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。              

④函数本质是修改下标空间里面指向file的地址,两个下标都指向同一个文件

Linux IO基础_第29张图片

                         

(2)函数使用示例

Linux IO基础_第30张图片

                        

(3)简易shell增加重定向功能

代码:

    1 #include                                                                       
    2 #include 
    3 #include 
    4 #include 
    5 #include 
    6 #include 
    7 #include 
    8 #include 
    9 #include 
   10 
   11 #define LEN 1024 //输入命令最大长度
   12 #define NUM 32 //命令拆分后的最大个数
   13 
   14 int main()
   15 {
   16 
   17   int type = 0;//0:> , 1:>> , 2:< 
   18   char cmd[LEN]; //存储命令
   19   char* myargv[NUM]; //存储命令拆分后的结果
   20   char hostname[32]; //主机名
   21   char pwd[128]; //当前目录
   22 
   23 
   24   while (1){
   25     //获取命令提示信息
   26     struct passwd* pass = getpwuid(getuid());
   27     gethostname(hostname, sizeof(hostname)-1); //获取主机名
   28     getcwd(pwd, sizeof(pwd)-1);//获取绝对地址
   29     int len = strlen(pwd);
   30     char* p = pwd + len - 1; //获取当前目录
   31     while (*p != '/'){
   32       p--;                                                                              
   33     }
   34     p++;
   35 
   36     //打印命令提示信息                                                                  
   37     printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);
   38     
   39     //从标准输入读取命令
   40     fgets(cmd, LEN, stdin);
   41     cmd[strlen(cmd) - 1] = '\0';
   42 
   43 
   44    //实现重定向功能
   45    char* start = cmd;
   46    while (*start != '\0'){
   47      if (*start == '>'){
   48        type = 0; //遇到一个'>',输出重定向
   49        *start = '\0';
   50        start++;
   51        if (*start == '>'){                                                              
   52          type = 1; //遇到第二个'>',追加重定向
   53          start++;
   54        }
   55        break;
   56      }
   57                                       
   58      if (*start == '<'){              
   59        type = 2; //遇到'<',输入重定向
   60        *start = '\0';
   61        start++;
   62        break;                                                                           
   63      }
   64      start++;
   65    }
   66 
   67    if (*start != '\0'){  //start位置不为'\0',说明命令包含重定向内容
   68      while (isspace(*start)) //跳过重定向符号后面的空格
   69        start++;
   70    }
   71    else{
   72      start = NULL; //start设置为NULL,标识命令当中不含重定向内容
   73    }
   74 
   75 
   76     //拆分命令
   77     myargv[0] = strtok(cmd, " ");
   78     int i = 1;
   79     while (myargv[i] = strtok(NULL, " ")){                                              
   80       i++;
   81     }
   82 
   83     pid_t id = fork(); //创建子进程执行命令
   84     if (id == 0){
   85       //child
   86       
   87       if(start != NULL){
   88         if(type == 0){ //输出重定向                                                     
   89         int fd = open(start ,O_WRONLY | O_CREAT | O_TRUNC, 0664); //O_TRUNC清空内容
   90         if(fd < 0){
   91           perror("open error!\n");
   92           exit(2);
   93         }
   94 
   95         close(1);
   96         dup2(fd , 1);
   97         }                                                                               
   98         else if(type == 1){ //追加重定向
   99         int fd = open(start ,O_WRONLY | O_CREAT | O_APPEND , 0664);
  100         if(fd < 0){
  101           perror("open error!\n");
  102           exit(2);
  103         }
  104 
  105         close(1);
  106         dup2(fd , 1);
  107         }
  108         else if(type == 2){ //输入重定向
  109         int fd = open(start , O_RDONLY);
  110         if(fd < 0){
  111           perror("open error!\n");
  112           exit(2);
  113         }
  114 
  115          close(0);
  116          dup2(fd , 0);
  117 
  118         }
  119       }
  120 
  121       execvp(myargv[0], myargv); //进行程序替换
  122       exit(1);
  123     }
  124                                                                                         
  125     //父进程等待获取子进程的退出信息
  126     int status = 0;
  127     pid_t ret = waitpid(id, &status, 0);
  128     if (ret > 0){
  129       printf("exit code:%d\n", WEXITSTATUS(status)); 
  130     }
  131   }
  132   return 0;
  133 } 

 结果:

Linux IO基础_第31张图片

                

                

                                        

五.Linux文件系统 

1.初识inode

(1)查看文件的inode

Linux IO基础_第32张图片

                 

 (2)Linux把文件的属性和内容进行分离存储

 文件 = 内容 + 属性 , 属性又叫做元信息,在inode中保存

                         

(3)inode是任何一个文件的属性集合,linux中几乎每一个文件都有一个inode ; 可能存在大量的inode,区分inode,用inode编号

                                

(4) ls看属性 , cat看内容

Linux IO基础_第33张图片

                                

2.理解硬件磁盘

(1)什么是磁盘?

磁盘是一种永久性存储介质,在计算机中,磁盘几乎是唯一的机械设备。与磁盘相对应的就是内存,内存是掉电易失存储介质,目前所有的普通文件都是在磁盘中存储的

Linux IO基础_第34张图片

                                

(2)磁盘基本概念

Linux IO基础_第35张图片
 

Linux IO基础_第36张图片

                                         

 (3)磁盘如何找到数据

  • 确定数据信息在磁盘的哪个盘面。
  • 确定数据信息在磁盘的哪个柱面。
  • 确定数据信息在磁盘的哪个扇区。

                        

3.磁盘分区

(1)理解文件系统,首先我们必须将磁盘想象成一个线性的存储介质,那么对硬盘的管理就转化为对数组的管理.

(2)磁盘通常被称为块设备,磁盘访问的最小单位为扇区,一个扇区的大小通常为512字节。

(3)磁盘分区理解

Linux IO基础_第37张图片①为什么要分区? 划分区域,为了更好的管理(中国有很多的省)

只要能把一个区域管好,剩下的区域复制黏贴式,用相同的方法管理

③分区1,2,3 就像Windows的 C , D ,E盘

(4)磁盘格式化

磁盘格式化就是对磁盘中的分区进行初始化的一种操作,这种操作通常会导致现有的磁盘或分区中所有的文件被清除;磁盘格式化就是对分区后的各个区域写入对应的管理信息 , 写入的管理信息是什么是由文件系统决定的,不同的文件系统格式化时写入的管理信息是不同的,常见的文件系统有EXT2、EXT3、XFS、NTFS等。

                                

4.Linux ext2文件系统

(1)为了更好的管理磁盘,会对磁盘每一个分区再次划分

Linux IO基础_第38张图片

 注意: 启动块的大小是确定的,而块组的大小是由格式化的时候确定的,并且不可以更改。

                        

(2)而每个Block Group都有着相同的结构组成 ,块组里面也划分了很多区域

Linux IO基础_第39张图片

  1. Super Block: 存放文件系统本身的结构信息。记录的信息主要有:Data Block和inode的总量、未使用的Data Block和inode的数量、一个Data Block和inode的大小、最近一次挂载的时间、最近一次写入数据的时间、最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。
  2. Group Descriptor Table: 块组描述符表,描述该分区当中块组的属性信息。
  3. Block Bitmap: 块位图当中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
  4. inode Bitmap: inode位图当中记录着每个inode是否空闲可用。
  5. inode Table: 存放文件属性,即每个文件的inode。
  6. Data Blocks: 存放文件内容。

Linux IO基础_第40张图片

①其他块组当中可能会存在冗余的Super Block,当某一Super Block被破坏后可以通过其他Super Block进行恢复。

②磁盘分区并格式化后,每个分区的inode个数就确定了。

比特位的位置 : 哪一个inode  ; 比特位的内容 : 是否被占用(0,1)

(3)更多理解

①如何理解目录?

  • 目录也是文件 , 目录文件 = inode + 数据块 ,数据块中存储的是(inode指针 : 文件名)的映射关系 
  • 每个文件的文件名并没有存储在自己的inode结构当中,而是存储在该文件所处目录文件的文件内容当中。因为计算机并不关注文件的文件名,计算机只关注文件的inode号,而文件名和文件的inode指针存储在其目录文件的文件内容当中后,目录通过文件名和文件的inode指针即可将文件名和文件内容及其属性连接起来 

                                         

②如何理解ls 和 ls -l 和cat

  • ls的时候就是找到该目录文件的inode,然后找到inode结构中存储的数据块位置,只要数据块中inode指针和文件名映射的文件名
  • ls -I首先定位到当前所处目录,找到这个目录文件的inode,属性信息在inode结构中保存,目录文件的数据块也能找到,数据块中有inode指针和数据块的映射关系,就能找到目录下其他文件的inode结构,把它们的属性信息加载出来。
  • 先找到当前目录的inode结构,inode表里面记录了数据块的位置,找到数据块里面存的是文件名和inode指针的映射关系,找到文件名Makefile对应的inode指针,根据inode结构找到数据信息显示出来。

                                 

③再次理解目录权限

不管是对目录下文件的读还是写,其他文件是存在目录文件的数据区,只有目录有x权限才能访问目录文件的数据

                                

④如何理解创建文件

  1. 先找到这个文件的分区和块组(这个由该文件的目录决定)
  2. 给这个文件分配inode,在inode位图中找到未被使用的位,由0->1
  3. 将文件对应的属性信息填到对应的inode中
  4. 将inode指针和文件名的映射关系填到当前目录的数据块中

 

⑤如何理解对文件写入

  1. 在当前目录的数据块中通过文件名找到inode指针进而找到inode结构
  2. 通过inode结构找到存储该文件内容的数据块,并将数据写入数据块。
  3. 若不存在数据块或申请的数据块已被写满,则通过遍历块位图的方式找到一个空闲的块号,并在数据区当中找到对应的空闲块,再将数据写入数据块,最后在inode结构中填充该数据块的位置。

                 

 ⑥关于inode结构中如何记录数据块

一个文件使用的数据块和inode结构的对应关系,是通过一个数组进行维护的,该数组一般可以存储15个元素,其中前12个元素分别对应该文件使用的12个数据块,剩余的三个元素分别是一级索引、二级索引和三级索引,当该文件使用数据块的个数超过12个时,可以用这三个索引进行数据块扩充
                        

⑦如何理解删除文件

  1. 在当前目录的数据块中通过文件名找到inode指针进而找到inode结构
  2. 通过inode结构中的记录已申请数据块的数组,将数据位图对应的位置 1->0
  3. 将该文件对应的inode在inode位图中由1->0 
  • 因为此操作并不会真正将文件对应的信息删除,而只是将其inode号和数据块号置为了无效,所以当我们删除文件后短时间内是可以恢复的。
  • 为什么说是短时间内呢,因为该文件对应的inode号和数据块号已经被置为了无效,因此后续创建其他文件或是对其他文件进行写入操作申请inode号和数据块号时,可能会将该置为无效了的inode号和数据块号分配出去,此时删除文件的数据就会被覆盖,也就无法恢复文件了。
     

                         

⑧如何理解复制文件慢 , 删除文件快 

因为拷贝文件需要先创建文件,然后再对该文件进行写入操作,该过程需要先申请inode号并填入文件的属性信息,之后还需要再申请数据块号,最后才能进行文件内容的数据拷贝,而删除文件只需将对应文件的inode号和数据块号置为无效即可,无需真正的删除文件,因此拷贝文件是很慢的,而删除文件是很快的。

                                

⑨面试:为什么我的磁盘还有空间,却不能创建文件?

一个磁盘的inode在初始化的时候已经设定好了,inode用完了,无法再创建文件。

        

                        

                        

六.软硬链接

1.软连接

(1)创建软连接

[gsx@VM-0-2-centos 220618]$ ln -s test test-s

Linux IO基础_第41张图片

                

(2)软链接文件的inode号与源文件的inode号是不同的,并且软链接文件的大小比源文件的大小要小得多。 Linux IO基础_第42张图片

                        

 (3)软链接又叫做符号链接,软链接文件相对于源文件来说是一个独立的文件,该文件有自己的inode号但是该文件只包含了源文件的路径名所以软链接文件的大小要比源文件小得多。软链接就类似于Windows操作系统当中的快捷方式。

Linux IO基础_第43张图片

                

2.硬链接

(1)创建硬链接

[gsx@VM-0-2-centos 220618]$ ln test test-h

Linux IO基础_第44张图片
                        

 (2)硬链接文件的inode号与源文件的inode号是相同的,并且硬链接文件的大小与源文件的大小也是相同的,特别注意的是,当创建了一个硬链接文件后,该硬链接文件和源文件的硬链接数都变成了2。

Linux IO基础_第45张图片

                                 

(3) 硬链接原理

  • 硬链接文件就是源文件的一个别名,一个inode对应几个文件名,该文件的硬链接数就是几。创建硬链接的本质是在当前目录的数据块中添加一组inode指针和文件名的映射,inode与链接文件的inode相同
  • 与软连接不同的是,当硬链接的源文件被删除后,硬链接文件仍能正常执行,只是文件的链接数减少了一个,因为此时该文件的文件名少了一个。
  • inode结构中一定有一个 int count记录硬链接个数(引用计数) ,最后count = 0时再删除该inode
    结构和数据。

 

(4)新创建的目录默认硬连接数是2

Linux IO基础_第46张图片

因为每个目录创建后,该目录下默认会有两个隐含文件.和..,它们分别代表当前目录和上级目录,因此这里创建的目录有两个名字,一个是dir另一个就是该目录下的.,所以刚创建的目录硬链接数是2。
                

3.软硬连接的对比

(1)软链接是一个独立的文件,有独立的inode,而硬链接没有独立的inode。

(2)软链接相当于快捷方式,硬链接本质没有创建文件,只是建立了一个文件名和已有的inode的映射关系,并写入当前目录.

(3)硬链接用处:方便目录之间通过相对路径方式进行跳转

                

文件的三个时间

在Linux当中,使用命令stat 文件名来查看对应文件的信息。

Linux IO基础_第47张图片

 三个时间:

  • Access: 文件最后被访问的时间。
  • Modify: 文件内容最后的修改时间。
  • Change: 文件属性最后的修改时间。

 ①当我们修改文件内容时,文件的大小一般也会随之改变,所以一般情况下Modify的改变会带动Change一起改变,但修改文件属性一般不会影响到文件内容,所以一般情况下Change的改变不会带动Modify的改变。

 ②touch一个已经存在的文件只会更改该文件的时间信息,三个时间都会改变

Linux IO基础_第48张图片

你可能感兴趣的:(Linux系统,linux,服务器,文件IO)