基础IO详解

2023-05-16

目录

一、系统文件IO

1.1 open

1.1.1 open的第一个参数

1.1.2 open的第二个参数

1.1.3 open的第三个参数

1.1.4 open的返回值

1.2 close

1.3 write

1.4 read

二、文件描述符

2.1 进程与文件描述符

2.2 文件描述符的分配规则

三、重定向

3.1 自实现重定向原理

3.1.1 输出重定向

3.1.2 追加重定向

3.1.3 输入重定向

3.2 dup2函数

四、C语言IO深入底层

4.1 C语言中默认打开的三个流

4.2 FILE

4.3 用户层缓冲区

五、理解"一切皆文件"思想

六、文件系统

6.1 认识inode

6.2 磁盘概念

6.3 磁盘分区与格式化

6.3.1 磁盘分区

6.3.2 磁盘格式化

6.4 EXT2文件系统的存储方案

6.5 软硬链接

6.5.1 软链接

6.5.2 硬链接


一、系统文件IO

操作文件除了使用C语言、C++以及其他的语言的接口之外,还可以使用操作系统提供的接口

1.1 open

1.1.1 open的第一个参数

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

  • 若pathname以路径的方式给出,则需要创建该文件时,在pathname路径下进行创建
  • 若pathname以文件名的方式给出,则需要创建该文件时,默认在当前路径下进行创建

1.1.2 open的第二个参数

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

上面提供的参数选项仅仅是较为常用的,具体可以man 2 open命令进行查看

打开文件时可以传入多个参数选项,当有多个选项传入时,将这些选项用"按位或"隔开

传参原理:

系统接口open的第二个参数flags为整型类型,在32位平台上有32个bit位。若将一个bit位作为一个标志位,则理论上flags可以传递32种不同的标志位。而实际上flags的参数选项都是宏

在oen函数内部就可以通过使用一系列的位运算来判断是否设置了某一选项。

1.1.3 open的第三个参数

open函数的第三个参数是mode,表示创建文件的默认权限

传入0666参数进行文件创建,按理应得到-rw-rw-rw-权限的文件,但却得到了如下图的文件

这是因为权限掩码的存在(默认为0002),可以在代码中设置权限掩码来避免上述情况发生。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    umask(0000);//设置文件掩码                                                                     
    int fd = open("./log.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    close(fd);
    return 0;
}

注意: 当不需创建文件时,可以不用设置第三个参数

1.1.4 open的返回值

open函数成功调用后返回打开文件的文件描述符,若调用失败则返回-1。文件描述符具体在后面进行讲解。

1.2 close

使用close函数时传入需要关闭文件的文件描述符(即调用open函数的返回值)即可。若关闭文件成功则返回0;若关闭文件失败则返回-1。

1.3 write

Linux系统接口中使用write函数向文件写入信息

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

  • 若数据写入成功,则返回实际写入数据的字节数。
  • 若数据写入失败,则返回-1。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd = open("./log.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    if(fd < 0){
        perror("open:");
        exit(1);
    }

    const char* buf = "hello Linux!\n";
    write(fd, buf, strlen(buf));                                                     
    close(fd);
    return 0;
}

1.4 read

Linux系统接口中使用write函数向文件写入信息

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

  • 若数据读取成功,则返回实际读取数据的字节数
  • 若数据读取失败,则返回-1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd = open("./log.txt",O_RDONLY);
    if(fd < 0){
        perror("open:");
        exit(1);
    }

    char buf[64] = {0};
    while(read(fd, buf, 63)){
        printf("%s",buf);                                                                                                                                                        
        memset(buf,0x00,64);
    }

    close(fd);
    return 0;
}

二、文件描述符

2.1 进程与文件描述符

文件被访问的前提是被加载到内存中打开,一个进程可以打开多个文件,而系统当中又存在大量进程。即在系统中任何时刻都可能存在大量已经打开的文件,所以操作系统内部提供了struct file结构体用于描述文件(先描述,再组织),再使用双链表进行管理。
而为了区分已经打开的文件哪些属于特定的某一个进程,我们就还需要建立进程和文件之间的对应关系。

进程与文件之间的对应关系是如何确定的呢?

task_struct(PCB)中有一个指针,该指针指向一个名为files_struct的结构体。在该结构体中就有一个名为fd_array指针数组,该数组的下标就是我们所谓的文件描述符。

当一个进程打开其第一个文件时,先将该文件从磁盘当中加载到内存,形成对应的struct file变量,将该struct file变量链入文件双链表中,并将该结构体的首地址填入到fd_array数组中下标为3的位置,最后返回该文件的文件描述符给调用进程。

因此只需要有某一文件的文件描述符,就可以找到与该文件相关的文件信息,进而对文件进行一系列输入输出操作。

内存文件 VS 磁盘文件

当文件存储在磁盘当中时,被称为磁盘文件;当磁盘文件被加载到内存中后,此时的文件被称为内存文件。磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序加载到内存运行起来后便成了进程,而当磁盘文件加载到内存后便成了内存文件。

磁盘文件由两部分构成,分别是文件内容文件属性。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为元信息。
文件加载到内存时,一般先加载文件的属性信息,当需要对文件内容进行读取、输入或输出等操作时,再延后式的加载文件数据

2.2 文件描述符的分配规则

Linux进程会默认打开3个文件描述符,分别是标准输入0、标准输出1、标准错误2
分别对应的物理设备一般为:键盘、显示器、显示器

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

#include <stdio.h>                                                                                                                                                               
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd1 = open("./log1.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd2 = open("./log2.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd3 = open("./log3.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd4 = open("./log4.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd5 = open("./log5.txt",O_RDWR | O_CREAT | O_TRUNC,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);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);
    return 0;
}

则后续打开的文件的文件描述符,从3开始逐个增加1。

在打开文件之前将文件描述符为0和2的文件关闭又会发生什么呢?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    close(0);
    close(2);                                                                                                                                                                    
    int fd1 = open("./log1.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd2 = open("./log2.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd3 = open("./log3.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd4 = open("./log4.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd5 = open("./log5.txt",O_RDWR | O_CREAT | O_TRUNC,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);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);
    return 0;
}

 结论: 文件描述符是从最小但是没有被使用的fd_array数组下标开始进行分配的

三、重定向

3.1 自实现重定向原理

3.1.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_RDWR | O_CREAT | O_TRUNC, 0666);
    if(fd < 0){
        perror("opern error:");
        return 1;
    }
    printf("hello world\n");
	fflush(stdout);//为什么需要刷新?阅读后续章节《缓冲区》
	close(fd);
	return 0;
}

3.1.2 追加重定向

输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据,并无其他区别

#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);
    if(fd < 0){
        perror("opern error:");
        return 1;
    }
    printf("hello world\n");                                                         
    fflush(stdout);
    return 0;
}

3.1.3 输入重定向

输入重定向,将本应该从一个文件读取数据,现在重定向为从另一个文件读取数据

#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);
    if(fd < 0){
        perror("opern error:");
        return 1;
    }
    char buf[64] = {0};
    while(scanf("%s",buf) != EOF){
        printf("%s\n",buf);                                                          
    }
    return 0;
}

3.2 dup2函数

在Linux环境下还可以使用dup2()系统调用来实现重定向。dup2()本质上是通过fd_array数组中地址元素的拷贝完成重定向的。

#include <unistd.h>
int dup2(int oldfd, int newfd);

函数功能: 将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中

函数返回值: 若调用成功则返回newfd,否则返回-1

  • 若oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。
  • 若oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作并直接返回newfd。
#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;
}

四、C语言IO深入底层

相较于C库函数或其他语言的库函数而言,系统调用接口更为底层,语言层的库函数实际上都是对系统接口进行的封装。

在Linux环境下的C库底层使用的就是Linux操作系统提供的接口,在Windows环境下的C库底层使用的便是Windows操作系统给提供的接口,再通过条件编译即可实现跨平台

4.1 C语言中默认打开的三个流

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

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

stdin、stdout以及stderr都是属于FILE*类型的,与使用fopen()打开文件时获得的文件指针相同

注意: 不止是C语言当中stdin、stdout和stderr,C++当中也有对应的cin、cout和cerr,其他语言中也有类似的概念。这种特性并不是某种语言所特有的,而是由操作系统所支持的。

标准输出流和标准错误流对应的都是显示器,它们有什么区别?

#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的标准错误流进行重定向。当然也可以显示指定重定向标准错误流。

4.2 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库文件IO函数的具体流程理解

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

4.3 用户层缓冲区

#include <stdio.h>
#include <unistd.h>
int main()
{
	printf("hello printf\n");
	fputs("hello fputs\n", stdout);
	write(1, "hello write\n", 12);
	fork();
	return 0;
}

查看上述代码,貌似并没有什么奇特的地方,甚至这个fork()十分的多余。但是将程序执行结果重定向到log.txt之后会得到这种情况。

 用户层的缓冲区,通常有以下三种缓冲策略:

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

当直接执行可执行程序,将数据打印到显示器时所采用的就是行缓冲,又因为代码当中每个字符串后都有'\n',所以当执行完对应代码后就立即将数据刷新到了显示器上。
但是将运行结果重定向到log.txt文件后,用户层缓冲区的刷新策略就变为了全缓冲,此时使用printf和fputs函数打印的数据都进入了C语言的缓冲区中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和puts函数打印的数据就有两份。但由于write函数是系统接口,不存在用户层缓冲区,因此write函数打印的数据就只有一份。

用户层缓冲区由谁提供?在哪?

由C语言库实现提供。用户层的输出缓冲区就存在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. */

操作系统是否存在缓冲区?

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

五、理解"一切皆文件"思想

"一切皆文件"是Linux的设计哲学,体现在os的软件设计层面。

Linux是C语言编写的,但是如何使用C语言实现面向对象?

面向对象语言中的类大致由成员属性和成员方法构成,C语言中可以包含成员属性但是并不可以包含成员方法,但是可以使用函数指针指向函数来代替成员方法。

通过这个设计方式,消除了上层处理硬件的差异,统一了看待文件的方式,一切皆文件。

六、文件系统

文件可以分为内存文件和磁盘文件,之前谈论的都是内存文件,接下开始讲解磁盘文件

6.1 认识inode

磁盘文件由两部分构成,分别是文件内容和文件属性(都存储在磁盘中)。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为元信息。

在Linux操作系统中,文件的元信息和内容是分离存储的,其中保存元信息的结构被称之为inode。因为系统当中存在大量的文件,所以需要给每个文件的属性集起一个唯一的编号,即inode号。即inode是一个文件的属性集合,Linux中每个文件都对应一个inode,为了区分系统中大量inode,我们为每个inode设置了inode编号。

ls命令添加 -i 选项即可查看文件的inode编号

struct inode {
	struct hlist_node	i_hash;
	struct list_head	i_list;		/* backing dev IO list */
	struct list_head	i_sb_list;
	struct list_head	i_dentry;
	unsigned long		i_ino;
	atomic_t		i_count;
	unsigned int		i_nlink;
	uid_t			i_uid;
	gid_t			i_gid;
	dev_t			i_rdev;
	u64			i_version;
	loff_t			i_size;
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	blkcnt_t		i_blocks;
	unsigned int		i_blkbits;
	unsigned short          i_bytes;
	umode_t			i_mode;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct mutex		i_mutex;
	struct rw_semaphore	i_alloc_sem;
	const struct inode_operations	*i_op;
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct super_block	*i_sb;
	struct file_lock	*i_flock;
	struct address_space	*i_mapping;
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
	};

	__u32			i_generation;

#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct hlist_head	i_fsnotify_mark_entries; /* fsnotify mark entries */
#endif

#ifdef CONFIG_INOTIFY
	struct list_head	inotify_watches; /* watches on this inode */
	struct mutex		inotify_mutex;	/* protects the watches list */
#endif

	unsigned long		i_state;
	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	unsigned int		i_flags;

	atomic_t		i_writecount;
#ifdef CONFIG_SECURITY
	void			*i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
	void			*i_private; /* fs or device private pointer */
};

6.2 磁盘概念

内存是掉电易失存储介质,而磁盘是一种永久性存储介质。在计算机中,磁盘几乎是唯一的机械设备。磁盘在冯诺依曼体系结构当中既可以充当输入设备,又可以充当输出设备。

磁盘寻址方案(CHS寻址)

  1. 确定读写信息在磁盘的哪个盘面
  2. 确定读写信息在磁盘的哪个柱面(磁道)
  3. 确定读写信息在磁盘的哪个扇区

6.3 磁盘分区与格式化

若要理解文件系统,先将磁盘想象成一个线性的存储介质。譬如磁带,当磁带被卷起来时就像磁盘一样是圆形的,但当将磁带拉直后就是线性的。

6.3.1 磁盘分区

磁盘通常被称为块设备,一般以扇区为单位,一个扇区的大小通常为512字节。计算机为了更好的管理磁盘,对磁盘进行了分区。磁盘分区就是使用分区编辑器在磁盘上划分几个逻辑部分,盘片一旦划分成数个分区,不同的目录与文件就可以存储进不同的分区。分区越多,就可以将文件的性质区分得越细,按照更为细分的性质,存储在不同的地方以管理文件。譬如在Windows操作系统下磁盘一般被分为C盘和D盘两个区域。

6.3.2 磁盘格式化

当磁盘完成分区后,还需对磁盘进行格式化。磁盘格式化就是对磁盘中的分区进行初始化的一种操作,这种操作通常会导致现有的磁盘或分区中所有的文件被清除。其实磁盘格式化就是对分区后的各个区域写入对应的管理信息。

写入的管理信息是什么是由文件系统决定的,不同的文件系统格式化时写入的管理信息是不同的,常见的文件系统有EXT2、EXT3、XFS、NTFS等。

6.4 EXT2文件系统的存储方案

对于每一个分区来说,分区的头部会包括一个启动块(Boot Block),对于该分区的其余区域,EXT2文件系统会根据分区的大小将其划分为一个个的块组(Block Group)

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

而每个组块都有着相同的组成结构,每个组块都由超级块(Super Block)、块组描述符表(Group Descriptor Table)、块位图(Block Bitmap)、inode位图(inode Bitmap)、inode表(inode Table)以及数据表(Data Block)组成

  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: 存放文件内容

注意:

  1. 组块中会存在冗余的Super Block,当某个Super Block被破坏后可以通过该块组中的其他Super Block进行恢复
  2. 磁盘分区并格式化后,每个分区的inode个数就确定了
  3. 一个文件使用的数据块和inode结构的对应关系,通过一个数组进行维护。该数组一般可以存储15个元素,其中前12个元素分别对应该文件使用的12个数据块,剩余的三个元素分别是一级索引、二级索引和三级索引。当该文件使用数据块的个数超过12个时,可以用这三个索引进行数据块扩充

如何创建一个文件?

  1. 通过遍历inode位图的方式,找到一个空闲的inode
  2. 在inode表当中找到对应的inode,并将文件的属性信息填充进inode结构中
  3. 将该文件的文件名和inode指针添加到目录文件的数据块中

如何对文件写入信息?

  1. 通过文件的inode编号找到对应的inode结构
  2. 通过inode结构找到存储该文件内容的数据块,并将数据写入数据块
  3. 若不存在数据块或申请的数据块已被写满,则通过遍历块位图的方式找到一个空闲的块号,并在数据区当中找到对应的空闲块,再将数据写入数据块,最后还需要建立数据块和inode结构的映射关系

如何删除文件?

  1. 将该文件对应的inode在inode位图当中置为无效。
  2. 将该文件申请过的数据块在块位图当中置为无效。

此操作并不会真正将文件对应的信息删除,而只是将其inode号和数据块号置为了无效,所以当删除文件后短时间内是可以恢复的。但后续创建其他文件或是对其他文件进行写入操作申请数据块号时,可能会将该置为无效了的inode号和数据块号分配出去,此时删除文件的数据就会被覆盖,就无法恢复文件了。

为什么拷贝文件较慢,而删除文件较快呢?

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

对目录的认识

  1. Linux中一切皆文件,目录也被看作是文件
  2. 目录有自己的属性信息,目录的inode结构当中存储的就是目录的属性信息
  3. 目录的数据块当中存储的就是该目录下的文件名以及对应文件的inode指针

6.5 软硬链接

6.5.1 软链接

Linux环境下的软链接就类似与Windows环境下的快捷方式

软链接又称符号链接,软链接文件相对于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件包含源文件的路径名。

但软链接文件只是其源文件的一个标记,当删除了源文件后,链接文件不能独立存在,虽然仍保留文件名,但却不能执行或是查看软链接的内容了。

6.5.2 硬链接

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

硬链接文件就是源文件的一个别名,一个文件有几个文件名,该文件的硬链接数就是几。且当硬链接的源文件被删除后,硬链接文件仍能正常执行,只是文件的链接数减少了一个,因为此时该文件的文件名少了一个。

为什么新建目录的的硬链接数是2?

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

通过上图也可以看出dir和该目录下的.的inode号相同,也可以说明它们代表的实际是同一个文件

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基础IO详解 的相关文章

  • 【踩坑专栏】lombok报错程序包org.slf4j不存在

    问题描述 xff1a 在Pom中引入了依赖 xff0c idea中也有lombok的插件 xff0c 之前使用lombok的 64 Slf4j注解没有问题 xff0c 最近在某一个项目中 xff0c 在编译时突然报错程序包org slf4j
  • Ubuntu1804_server 离线安装GCC_7.5

    本文利用一个比较简单方便的方法为Ubuntu1804 server的服务器离线安装GCC 7 5 之前写过一篇关于离线安装软件的文章 xff0c 有兴趣的同学请移步Ubuntu18 04 离线安装nginx 可是如果生产服务器有大量需要离线
  • 关于Java之IO流音乐拼接小项目

    需求 xff1a 做一个音乐串烧 分析 xff1a 1 有n个音乐 xff0c 找到高潮部分 xff0c 2 获取高潮部分的流对象 3 把这部分对象保存成一个mp3 4 把它们拼接起来 以下为源码供大家分享 xff1a 方法一 xff1a
  • 192.168.和10.0.开头的IP、内网IP段、IP简介、分类

    IP地址分为五大类 xff1a A类 B类 C类 D类和E类 xff0c 如下图所示 xff1a 在这五类IP地址中 xff0c 我们最常使用的是A类 B类和C类地址 在这三类地址中 xff0c 绝大多数的IP地址都是公有地址 xff0c
  • 牛客网. 跳跃游戏-II

    题目概述 解题思路 我开始想到的做法是贪心 首先维护两个指针i和cur xff0c i用于顺序遍历 xff0c cur用来指向上一次可以跳到的最远的位置 维护一个一维数组 xff0c 用来记录跳到每个位置需要的最短步数 然后考虑当前能跳到的
  • OpenEuler 20.03 LTS yum 安装redis后systemctl启动异常

    前言 通过命令yum install redis xff0c 安装redis后 xff0c 通过systemctl启动报如下错误 xff1a span class token punctuation span root 64 ecs e50
  • python ahttp:简单、高效、异步requests请求模块

    ahttp xff1a 简单 高效 异步requests请求模块 ahttp 是一个所有的http连接请求均使用协程的方式 使请求过程中 IO 操作交给其他硬件 xff0c 而CPU专注于处理计算型任务 xff0c 可以大量的节约等待的时间
  • uniapp 页面跳转出现闪屏、短暂白屏的解决办法

    在项目pages json里设置页面的背景色和页面中page或container的背景色一致即可解决这个问题 34 path 34 34 pages ecosystem index 34 34 style 34 34 navigationB
  • softmock-基于mitmproxy实现系统性管理maplocal

    softmock 介绍 softmock 是一个拦截 http https 到本地的工具 请求拦截到本地之后 xff0c 可以进行修改 新增等操作 xff0c 使下次请求直接返回到本地的数据 而不依赖远程服务器 softmock 是从 抓包
  • django设置samesite

    较新版本的chrome会因samesite策略而禁止跨域的cookie 解决方法在项目中的setting py设置 xff1a SESSION COOKIE SAMESITE span class token operator 61 spa
  • 使用python构造一个微信聊天机器人

    申请一个图灵的APIKEY http www tuling123 com python3环境下安装wxpy pip install wxpy linux下还需安装pillow pip install pillow 然后执行以下代码 xff1
  • aiohttp 简易使用教程

    0 前言 本文翻译自aiohttp的官方文档 xff0c 如有纰漏 xff0c 欢迎指出 aiohttp分为服务器端和客户端 xff0c 本文只介绍客户端 由于上下文的缘故 xff0c 请求代码必须在一个异步的函数中进行 xff1a asy
  • Zabbix 5.4 Server安装

    系统 xff1a ubuntu 1804 xff08 1804 server zabbix 5 4 mysql 5 7 x1f4d3 UTF 8是Zabbix支持的唯一编码 它可以正常工作而没有任何安全漏洞 用户应注意 xff0c 如果使用
  • vue基本格式

    MVVM模式 vue的基本步骤 数据绑定 v model name 数据渲染 xff0c 双向绑定
  • Activity中使用onNewIntent方法避免多次实例化同一个Activity

    最近写的项目中有一个搜索 搜索结果 搜索这样一个循环的过程 xff0c 发现了几个问题 xff1a 1 循环导致多次实例化这两个类 xff1b 解决方案 xff1a 在Manifest里面对应activity下面设置启动模式为singleT
  • Linux 无密码自动登录

    GNOME环境 etc gdm3 custom conf文件 xff0c 修改其中的AutomaticLoginEnable xff0c AutomaticLogin项 xff0c 具体如下所示 xff1a Configure Automa
  • FileItem类

    文件上传时需要用到FileItem类 xff0c FileItem是一个接口 xff0c 它的实现类是DiskFileItem 如图为FileItem 接口中定义的方法 xff1a 1 getInputStream xff1a 以流的形式返
  • mysql数据表中文乱码解决办法

    在往 mysql 数据库中插入数据的时候出现数据 汉字 乱码情况 xff1a 在把数据库 xff0c 数据表的编码改为UTF 8后 xff0c 还是乱码 Mysql 的默认编码方式是 Latin1 xff0c 不支持中文 xff0c 因此
  • Spring xml配置文件头解析

    Spring文档中默认的XML文件格式 xff1a lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt beans xmlns 61 34 http www springfra
  • SpringMVC请求静态资源异常

    问题描述 xff1a 使用 REST 风格的资源URL时 xff0c SpringMVC请求静态资源 图片 js等 发生异常 优雅的 REST 风格的资源URL 不希望带 html 或 do 等后缀 若将 DispatcherServlet

随机推荐

  • web项目异常A web application registered the JBDC driver [com.mysql.jdbc.Driver] but failed to unregister

    异常 xff1a A web application registered the JBDC driver com mysql jdbc Driver but failed to unregister it when the web app
  • @Controller和@RestController的区别?

    64 Controller和 64 RestController的区别 xff1f 官方文档 xff1a 64 RestController is a stereotype annotation that combines 64 Respo
  • get方式传值中文乱码

    如下情况 xff1a lt a id 61 span class hljs string 34 bookname 34 span title 61 span class hljs string 34 span class hljs vari
  • Zabbix 5.4 server安装后的相关操作

    通过前面的安装 xff0c 相信大家已经能够正常登录zabbix server的管理页面了 在进行正式的使用之前 xff0c 建议大家最好把下面这个管理页面中左侧的操作树中的每一项功能都打开看看 xff0c 这样心中对zabbix serv
  • Maven搭建的SSM项目中遇到的问题

    Maven搭建的SSM项目中遇到的问题 1 EL表达式失效 2 装配异常Unable to configure ssm 解决办法 其实这两个问题的出现都是因为servlet版本和java版本不合适造成的 xff0c EL表达式在servle
  • Java小工具Lombok的安装与使用

    1 Lombok简介 Lombok是一个代码生成器 xff0c 可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具 xff0c 通过使用对应的注解 xff0c 可以在编译源码的时候生成对应的方法 使用 lom
  • 日常记录(1)

    数据库连接池Druid Alibaba github地址 xff1a https github com alibaba druidBlog xff1a http blog csdn net pk490525 article details
  • vnc 设置自定义分辨率

    1 vnc设置分辨率 vncserver geometry 1600x900即可 xff0c 之后通过window下vnc连接后的ubuntu分辨率即为1600x900了 注意这里的X是小写的x而不是 2 但是 xff0c 登录后 xff0
  • 解决Vcenter Client启动Fault Tolerance辅助虚拟机被禁用保护的问题

    解决FT辅助虚拟机被禁用的问题 项目场景 xff1a 学习虚拟机中问题描述 xff1a 辅助虚拟机被禁用原因分析 xff1a 解决方案与结论 项目场景 xff1a 学习虚拟机中 最近这个学期在学习虚拟化技术 xff0c 由于课程是新开的 x
  • kali的重复登录与vnc灰屏

    云安全 xff08 二 xff09 VNC连接的一些小问题 文章目录 云安全 xff08 二 xff09 VNC连接的一些小问题 前言 xff1a 问题重现一 解决灰屏问题二 普通用户循环登录1 原因2 解决方法 三 原因分析四 总结五 项
  • sql注入闯关笔记【Less-1】基于错误的GET单引号字符型注入

    云安全之sql注入 xff08 sqli labs Less 1 文章目录 云安全之sql注入 xff08 sqli labs Less 1 前言一 闯关一 测试注入点二 手工注入三 sqlmap注入 二 总结三 思路与解惑 前言 这学期学
  • 写一个简单的爬虫,可直接复制学习!!

    简单爬虫直面代码 xff0c 可直接复制学习 这个代码的作用主要是用来获取到百度首页的数据 xff0c 只用来供理解学习 真 小白 福利 todo 首先导包requests 用于爬取数据 import requests todo 定义你要爬
  • 教你如何开发VR游戏系列教程五:UI 交互

    原文链接 xff1a 欢迎关注AR学院 上一篇介绍了ugui NGUI 以及普通3D模型的UI设计 这一讲主要介绍怎么样利用这些UI做交互 大家在VR游戏看到的UI以及UI交互 xff0c 主要有哪几种 xff1f 1 头控悬停 xff08
  • C/C++斐波那契数全解(哪种方法更好?)

    目录 一 递归思想 二 空间换时间 三 动态规划 四 通项公式 五 矩阵快速幂 六 总结 本文章参考leetcode斐波那契数官方题解 斐波那契的边界条件是 F 0 61 0 和 F 1 61 1 当 n gt 1 时 xff0c 每一项的
  • Zabbix 5.4客户端安装

    通过前面的操作 xff0c 相信大家的zabbix server已经能够正常的运行起来了 xff0c 但是仅有zabbix server是不完整的 xff0c server的目标是监控其他的主机 xff0c 而并非只监控自身 xff0c 所
  • Linux中kill -2、kill -9等区别 && kill signal汇总

    xfeff xfeff kill号令用于终止指定的过程 xff08 terminate a process xff09 xff0c 是Unix Linux下过程经管的常用号令 凡是 xff0c 我们在须要终止某个或某些过程时 xff0c 先
  • Linux下查找文件(find、grep命令)

    目录 一 find命令 1 按文件名 2 按文件类型查询 3 按照文件大小查找 4 按照文件日期查找 4 1按照创建日期查找 4 2按照修改日期查找 4 3按照访问日期查找 5 按深度查找 5 1查找起始点以下n层的目录 xff0c 不超过
  • 深度剖析数据在内存中的存储

    小编认为要想成为一个好的程序员 xff0c 不能仅仅只做到会使用 xff0c 而要做到理解其本质 做到可持续发展 接下来小编会向大家介绍数据在内存中究竟是如何存储与运算的 xff0c 也算是修炼内功了吧 目录 一 数据类型介绍 1 整型家族
  • Linux下进程控制详解

    目录 一 进程创建 1 1 初识fork 1 2 函数返回值 1 3 写时拷贝技术 1 4 fork函数的使用场景 1 5 fork函数的失败原因 二 进程终止 2 1 进程退出场景 2 2 进程退出码 2 3 进程正常退出方法 2 3 1
  • 基础IO详解

    目录 一 系统文件IO 1 1 open 1 1 1 open的第一个参数 1 1 2 open的第二个参数 1 1 3 open的第三个参数 1 1 4 open的返回值 1 2 close 1 3 write 1 4 read 二 文件