linux就该这么学【基础IO】

在这里插入图片描述

c语言IO基础操作:

在我们学习C语言的时候唯一对IO流有过的接触的就是c语言的文件操作了,回顾一下c语言对文件的操作

练习1:向一个文本文件写入5个hello word

#include <stdio.h>
#include <string.h>
int main()
{
  
    FILE *fp = fopen("word.txt", "w");
    if(!fp) printf("文件打开失败\n");
   
    int count = 5;
    const char* str = "hello word\n";
    while(count--){
        fwrite(str, strlen(str), 1, fp); //往文件中写入5个hello word
    }
    
	fclose(fp);
    fp = NULL;
    
	return 0;
}

将刚写入文件的hello word存储回buf 数组中

#include <stdio.h>
#include <string.h>
int main()
{
  
    FILE *	fp = fopen("word.txt", "r");
    if(!fp) printf("文件打开失败\n");
  
	char buf[12];
	while (fread(buf, 1, strlen(str), fp)) {
		buf[11] = 0;
		printf("%s", buf);
	}
	
	fclose(fp);
    fp = NULL;
    
	return 0;
}

系统文件I/O

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码

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);
pathname: 要打开或创建的目标文件

flags: 打开文件时,可以传入多个参数选项(创建文件的方式),
		用下面的一个或者多个常量进行“或”运算,构成flags。

mode:指明创建属性

//创建文件的三种方式
参数:
 O_RDONLY: 只读打开
 O_WRONLY: 只写打开
 O_RDWR : 读,写打开
 这三个常量,必须指定一个且只能指定一个
 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,
 			来指明新文件的访问权限
 			
 O_APPEND: 追加写(类似于c语言中的fopen()的”a“选项, 会追加信息)
 
返回值:
 成功:新打开的文件描述符
 失败:-1

flags参数:

grep -ER 'O_CREAT | O_APPEND | O_RDONLY)' /usr/include/
/usr/include/bits/fcntl-linux.h  
//flags对应参数所在头文件

系统标准头文件中:/usr/include/bits/fcntl-linux.h
在这里插入图片描述
如果理解flags

int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);

其实当我们写出这行代码的时候,会去调用系统调用open,open函数的第一个
参数代表的是要打开的目标文件,而 O_WRONLY | O_CREAT | O_APPEND
这是一个位运算表达式,而按位或(|)的结果值表示的是:需要使用open函数
打开目标文件后,执行的功能,其实每一个选项(O_WRONLY | O_CREAT | O_APPEND)他都有对应的一个值,这个值被包含在/usr/include/bits/fcntl-linux.h这个头文件中(也就是上述图)。

程序演示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SIZE 256

//使用系统调用接口完成文件读写操作
int main()
{
    int fd = open("word.txt", O_RDONLY);  //只读打开
    if(fd < 0) {
      perror("文件打开失败\n");
      return 0;
    }
    
    close(fd);  //关闭文件
    return 0;
}

read文档:
在这里插入图片描述
read接口介绍:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

Read()尝试从文件描述符fd中读取字节数  
从buf开始的缓冲区。  
 
对于支持查找的文件,读操作从当前位置开始  
租用文件偏移量,文件偏移量随数量递增  
字节读。 如果当前文件的偏移量位于或超过文件的末尾,  
不读取字节,read()返回零。  
 
如果count为零,read()可能会检测到下面描述的错误。 在  
没有任何错误,或者如果read()不检查错误,则read()  
计数为0时返回0,没有其他效果。  
 
如果count大于SSIZE_MAX,结果是不指定的。  

程序演示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SIZE 256

//使用系统调用接口完成文件读写操作
int main()
{
    int fd = open("word.txt", O_RDONLY);  //只读打开
    if(fd < 0) {
      perror("文件打开失败\n");
      return 0;
    }
    //文件打开成功
    char buf[SIZE]; 
    read(fd, buf ,SIZE); //从文件描述符fd中读取size个字节的字符到buf数组中。

    printf("%s", buf); //输出buf数组中的字符串
    close(fd);  //关闭文件
    return 0;
}

open函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图:
在这里插入图片描述
系统调用接口和库函数的关系,一目了然。所以,可以认为,在c语言中 f#系列的函数,都是对系统调用的封装,方便二次开发

文件描述符fd

代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SIZE 256

//使用系统调用接口完成文件读写操作
int main()
{
    int fd = open("word.txt", O_RDONLY);  //只读打开
    if(fd < 0) {
      perror("文件打开失败\n");
      return 0;
    }
    //文件打开成功
    char buf[SIZE]; 
    
    read(fd, buf ,SIZE); //从文件fd中读取size个字节的字符到buf数组中。

    printf("fileindex:%d , src: %s", fd, buf); 
    //打印fopen的返回值,以及buf数组中存放的字符串
    close(fd);  //关闭文件
    return 0;
}

程序运行结果:
在这里插入图片描述

从程序的运行结果来看,确实当我们的程序运行起来之后,会将会打开"word.txt"文件,并在执行read函数的时候将"word.txt"文件中的内容写入到buf这个字符数组中去,另外需要关心的是open在执行成功之后带回来一个val值为3的返回值。这是为什么呢?

为了一探究竟接下来我们可以写一个程序用来测试fd的返回值

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SIZE 256

//使用系统调用接口完成文件读写操作
int main()
{
    int fd = open("word.txt", O_RDONLY);  //只读打开
    if(fd < 0) {
      perror("文件打开失败\n");
      return 0;
    }
    //读取文件的信息存放到buf数组中
    char buf[SIZE]; 
    read(fd, buf ,SIZE); //从文件fd中读取size个字节的字符到buf数组中。
    printf("fileindex:%d , src: %s", fd, buf);
      

    /* 使用open()系统调用打开多个文件,观察fd的返回值结果 */
    int fd1 = open("word1.txt", O_WRONLY | O_CREAT); //打开文件只写,如果没有就创建
    int fd2 = open("word2.txt", O_WRONLY | O_CREAT); //只写 。。。。。。。。。。
    int fd3 = open("word3.txt", O_WRONLY | O_CREAT); //只写 。。。。。。。 。 。
    int fd4 = open("word4.txt", O_WRONLY | O_CREAT); //只写 。。 。 。。 。 。。 
    int fd5 = open("word5.txt", O_WRONLY | O_CREAT); //只写 。 。。 。 。。 。。
    /* 打印fd的返回值 */
    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);

    close(fd);  //关闭文件
    return 0;
}

程序运行结果:
在这里插入图片描述
现象1:从fd的返回值来看,他是连续打印的,fd每次的返回值也是顺序递增的,并且fd的返回值默认会从3开始打印

现象2:open("word1.txt", O_WRONLY | O_CREAT);执行打开文件的时候,带了两个选项,而我们使用c语言的fopen打开文件的时候只需要带”w“

文件描述符的分配规则

疑问?为什么fd的返回值默认从3开始?
在这里插入图片描述
其实c语言的程序一旦执行起来就会默认打开三个IO流,分别是stdio、stdin、stderr标准输入输出流。而后面的3、4、5、6、7、8其实是文件描述符,文件描述符在系统层面就是一个整数,这个整数的取值范围是【0,n】,
那么之前的0~2数字其实依次代表的是:
0:标准输入(stdin) 1:标准输出(stdout) 2:标准错误(stderr)
其实【0,n】的数字本质上是数组的下标。

对于进程来讲,默认的文件描述符从3开始分配(因为0~2是被标准输入输出和stderr占用了)。

总结:

1、open是属于封装出来的系统调用接口
2、open函数的返回值是一个文件描述符fd
3、文件描述符有他的分配规则,站在进程的角度,文件描述符默认只会分配最小值。

使用程序证明, 如果stdin流被关闭了,那么返回的fd会是多少?

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define SIZE 256

//使用系统调用接口完成文件读写操作
int main()
{
    close(0);  //将标准输入流关闭
    int fd = open("word.txt", O_RDONLY);  //只读打开
    if(fd < 0) {
      perror("文件打开失败\n");
      return 0;
    }
    /* 打印fd的返回值 */
    printf("fd:%d\n", fd);

    close(fd);  //关闭文件
    return 0;
}

程序运行结果:
在这里插入图片描述

结论:给文件描述符分配时,默认从最小开始分配。

重定向

有趣的实验:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

#define SIZE 256

//使用系统调用接口完成文件读写操作
int main()
{
    close(1);  //将标准输入流关闭
    int fd = open("word.txt", O_WRONLY | O_CREAT);  //只读打开
    if(fd < 0) {
      perror("文件打开失败\n");
      return 0;
    }
    /* 打印fd的返回值 */
    printf("hhhhhhh....%d\n", fd);
    fflush(stdout);  
    close(fd);  //关闭文件
    return 0;
}

程序运行结果:
在这里插入图片描述
我们会发现原本应该应该打印到显示屏的hhhhhhh....1却被输出到了文件中,那么为什么呢?其实一开始的标准输出流是被打开了的,可是我们却close关闭了stdout流,那么1这个文件标识符的值就会空着,而当使用open函数打开"word.txt"文件时,系统就会将原本属于stdout的文件标识符1分配给"word.txt"文件,所以导致原本应该输出到屏幕的值却输出到了文件"word.txt"上,而这也是重定向的原理

总结:

1、当fd等于1,这种现象也叫做重定向(常见的重定向有:>, >>, <)
2、重定向的本质其实是,当原本属于stdout的文件描述符1,被分配给了被open打开文件的fd,并在输出的过程将原本应该输出到stdout流的内容被重定向到了被open打开的文件

问题? 文件被打开了那么需不需要被管理?交给谁管理?

文件其实还是交给OS管理的,而OS管理的原则是:先描述,再组织,当文件被加载到内存中之后,操作系统会以某种数据结构的方式管理起文件,而PCB中有一个task_struct类型的结构体,里面会存放一个指针,该指针会指向一个指针数组,获得他的首地址。

在这里插入图片描述
那如果我们需要写入一些数据进入文件的话,操作系统会怎么做呢?

操作系统会使用task_struct中的数组指针通过获取struct file *arr[0]的地址,通过数组是线性存储的特性,通过下标索引找到对应的文件,再向该文件中写入数据

问题使用open()函数打开文件,返回的fd值又是谁的?

答案是指针数组的索引位置

下面的代码均摘自linux 2.6.24。
struct files_struct在<include/linux/fdtable.h>中定义如下:

struct files_struct {
    atomic_t count;
    struct fdtable *fdt;
    struct fdtable  fdtab;
   
    int next_fd;
    struct embedded_fd_set close_on_exec_init;
    struct embedded_fd_set open_fds_init;
    struct file * fd_array[NR_OPEN_DEFAULT];   
};

如何理解linux下一切皆文件?

在我们实际使用硬盘、显示器、键盘的时候,不管是读取还是写入的过程其实调用的接口分别是:
(h_read()、h_write() )硬盘读写
(x_read()、x_write() )显示器读写
(j_read()、j_write() )键盘读写

在这里插入图片描述

其实struct file结构体中会包含open、write、read的函数指针,而驱动层也会有对应的几个函数接口,由于他们的参数是相同的,所以可以通过struct file结构体中的函数指针指向驱动层的函数接口,达到open、write、read可以分别使用硬盘、显示器、键盘。(读者可以理解为是类似于多态,因为同一个函数可以实现不同的功能) ,由于操作系统站在上层的角度,所以并不需要关心底层的实现,操作系统真正关心的是使用了软件封装出来的虚拟层,所以在操作系统看来一切皆文件!

以下是该结构体 在内核2.6.5中看起来的样子:

struct file_operations {undefined

  struct module *owner;

  loff_t(*llseek) (struct file *, loff_t, int);

  ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);

  ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);

  ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);

  ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);

  int (*readdir) (struct file *, void *, filldir_t);

  unsigned int (*poll) (struct file *, struct poll_table_struct *);

  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

  int (*mmap) (struct file *, struct vm_area_struct *);

  int (*open) (struct inode *, struct file *);

  int (*flush) (struct file *);

  int (*release) (struct inode *, struct file *);

  int (*fsync) (struct file *, struct dentry *, int datasync);

  int (*aio_fsync) (struct kiocb *, int datasync);

  int (*fasync) (int, struct file *, int);

  int (*lock) (struct file *, int, struct file_lock *);

  ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

  ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

  ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);

  ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

  unsigned long (*get_unmapped_area) (struct file *, unsigned long,

         unsigned long, unsigned long,

         unsigned long);

};

FILE

有趣实验:

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main()
{
    close(1);
    int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
    if(fd < 0){
      printf("opend error\n");
      return 1; 
    }
    
    const char* str = "hello word!:write\n";
    const char *str1 = "hello word!:printf\n"; 
    const char *str2 = "hello word!:fprintf\n";

    write(1, str, strlen(str));
    printf(str1);
    fprintf(stdout, str2);
    
    fflush(stdout);
    close(fd);
    return 0;
}

//程序运行结果:
[mzt@VM-16-4-centos test]$ cat w.txt 
hello word!:write
hello word!:printf
hello word!:fprintf

当我们将标准输出流关闭之后,我们原本需要输出到屏幕的信息全都被输出到了文件中,其实这个现象博主上面也解释过了。那么还有一些知识需要大家掌握。

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。

  • 所以C库当中的FILE结构体内部,必定封装了fd

  • 其实struct FILE 结构体是被存放在系统头文件libio.h中的,而这个系统头文件的路径是:/usr/include/libio.h

  • FILE其实只是struct _IO_FILE 结构体类型的别名
    在这里插入图片描述
    其实stdout也是属于FILE类型结构体的指针,既然是FILE类型,那么stdout中一定会包含_fileno
    在这里插入图片描述
    对比write(1, str, strlen(str)) 这句代码能够向文件中写入可以理解,但是为什么 fprintf(stdout, str2);这句代码也能向文件中写入呢?因为stdout中存放的_fileno中的值并没有被改变他还是1!所以这两个代码的功能是一样的!

当我们看完他的相同点之后,再来看他的不同点,这里还是用一段小程序测试:

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main()
{
    close(1);
    int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
    if(fd < 0){
      printf("opend error\n");
      return 1; 
    }
    
    const char* str = "hello word!:write\n";
    const char *str1 = "hello word!:printf\n"; 
    const char *str2 = "hello word!:fprintf\n";

    write(1, str, strlen(str));
    printf(str1);
    fprintf(stdout, str2);
    

    fork();
    fflush(stdout);
    close(fd);
    return 0;
}

从程序的运行结果我们不难发现,write只被执行了一次,而printffprintf却被执行了两次
在这里插入图片描述

write和printf/fprintf他们之间的区别就是write是系统调用接口,而printf / fprintf确实库函数,其实向显示器上输出结果这种方式也是有缓冲区的,这种缓冲方式是行缓冲(遇到\n就刷新,因为需要被立马看到),而如果是往文件中输出内容并不需要被用户立马就看见,所以文件是以全缓冲(缓冲区存不下了才会往硬盘中的文件输出)
而这个重定向其实引起来的最大的变化是:在重定向的时候,我们写入数据的目的地发生了变化,没有重定向本身是要往显示器上写,而重定向之后就必须要往文件中写,所以一旦重定向之后是影响缓冲方式的!!

我们发现 printf 和 fprintf (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork执行后的fflush(stdout);有关!

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf fprintf 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲。

综上:
printf fwrite 库函数会自带缓冲区,而 write系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢?printf fwrite是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

使用dup2系统调用

在这里插入图片描述
Dup2()使newfd成为oldfd的副本(使用oldfd拷贝出newfd),必要时先关闭newfd‐sary。

dup2原理:

如果想要stdout(标准输出流)打印到屏幕的信息写入文件当中,完成标准输出重定向到文件中的操作,我们需要将指向stdou这个文件的指针去指向myfile这个被创建出来的file对象就行,中间的过程是将index位置为3的数组中的指针拷贝给index下标位置为1中,那么就会让index位置1处的*file去指向新打开的myfile文件所对应的对象(也就是所谓的oldfd拷贝给newfd,那么index位置为1处的指针就会被更新为指向myfile文件所关联的file对象)
在这里插入图片描述

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

int main()
{
    //close(1);
    int fd = open("w.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
    if(fd < 0){
      printf("opend error\n");
      return 1; 
    }
    
    dup2(fd, 1);  
    //将位于指针数组中index位置为fd的指针,拷贝给index位置为1处的指针
    const char* str = "hello word!:write\n";
    const char *str1 = "hello word!:printf\n"; 
    const char *str2 = "hello word!:fprintf\n";

    write(1, str, strlen(str));
    printf(str1);
    fprintf(stdout, str2);
    

    fork();
    fflush(stdout);
    close(fd);  //文件描述符是有一定的上限的,需要关闭节省资源
    return 0;
}

理解文件系统

我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据
在这里插入图片描述
每行包含7列:

  • 模式
  • 硬链接数
  • 文件所有者
  • 大小
  • 最后修改时间
  • 文件名

ls -l读取存储在磁盘上的文件信息,其实本质上是将文件的信息放到内存,然后显示出来。
在这里插入图片描述
在这里插入图片描述

其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
在这里插入图片描述
上面的执行结果有几个信息需要解释清楚

inode

为了能解释清楚inode我们先简单了解一下文件系统
在这里插入图片描述

Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。

Boot Block其实是一个启动块,启动块一般是第一个物理盘的扇区,这个启动块会包含硬盘的分区表这样的信息, 并且这个分区也会指明你的操作系统盘在哪个分区,并且他能找到操作系统

在这里插入图片描述

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子
  • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了,注意:Super Block 结构在每一个块组里面都有一个Super Block,并且是一样的,这样做的目的是为了防止Super Block 被破快导致剩下的空间块无法使用,
  • GDT,Group Descriptor Table:块组描述符,描述块组属性信息,并且里面记录了inode的使用情况和inode的总个数。
  • 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用(记录inode哪些被使用,哪些没有被使用)
  • inode位图(inode Bitmap):inode Bitmap它其实是一个位图,他是利用了一种哈希的原理将每个bit表示一个inode是否空闲可用。
  • i节点表(inode table):存放文件属性 如 文件大小,所有者,最近修改时间等, 其中inode:存放文件的属性, inode必须包含data blocks对应的映射关系,inode id会 标识一个inode
    在这里插入图片描述
  • 数据区(data blocks):存放文件内容
  • 文件和inode的关系是1对1,文件和data blocks的关系是1对n

文件创建、删除、查找过程

如何创建一个文件?

  • 其实创建一个文件的过程包含以下:先给文件分配inode,但是需要在inode Bitmap该表中找一个没有被分配的比特位,它代表的是一个inode,分配成功后会将它置为1,而文件的属性会包含大小、最后修改时间、文件名. 所以需要将文件的属性填入inode table,通过inode table和data blocks所对应的映射关系将数据写入到这个数据块中。

如何查找一个文价?

  • 先找到文件所在的块组,在inode table中找到对应的inode信息,找到inode对应的信息就能够找到属性,根据他的块组映射找到对应的数据块,将数据提取出来

如何删除一个文件?

  • 将需要删除的文件所对应的inode编号在位图inode Bitmap当中,将1置为0。

如何恢复一个文件?

  • 根据inode将对应的inode Bitmap位置置为1,再根据inode table和data blocks所对应的映射关系置为1,这个文件就被恢复了。

将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作。
在这里插入图片描述
我们将上图简化:
在这里插入图片描述

创建一个新文件主要有一下4个操作:

  1. 存储属性
    内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
  2. 存储数据
    该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据
    复制到300,下一块复制到500,以此类推。
  3. 记录分配情况
    文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录
    新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文
    件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来

问题? 目录算是文件吗?

是,目录也是由inode和数据块构成的,而一个数据块中保存的其实是文件名和inode id,所以目录中的数据存放的是文件名到inode的映射关系。

理解硬链接

了解硬链接:

[root@localhost linux]# touch abc
[root@localhost linux]# ln abc def
[root@localhost linux]# ls -1i
abc def 263466 abc 263466 def

我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个

  • abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 263466 的硬连接数为2。
  • 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。

软链接

在这里插入图片描述
具有独立的inode,因为log.txt和log.txt的软链接都是只有一个inode,既然各自都有一个inode那么它就是一个独立的文件,

硬链接

在这里插入图片描述

其实硬链接h_link和log. txt文件共享的是同一个inode,所以硬链接并不是一个独立的文件,那么硬链接是什么? 其实h_link在本质上还是一个文件名,只是它使用的是log. txt的inode编码,其实test这个当前目录中也是会存放文件名和inode的映射关系,而h_link只是一个特定文件名和inode的映射关系,

为什么目录的链接数是2?其实文件名dir和1835039( inode id )属于一组映射关系,还有的就是dir目录中的隐藏文件 . 会和1835039属于一组映射关系,所以dir的连接数是2
在这里插入图片描述
但是dir中也会存放当前目录和上级目录,如果dir的连接数为3,那么dir和1835039属于一组映射关系,而dir中的隐藏文件 .和1835039属于一组映射关系,tmp中的隐藏文件. .和1835039属于一组映射关系。所以dir中就会有3个连接数
在这里插入图片描述

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱生活,爱代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值