MINIX 1.0 文件系统的实现(C/C++实现)

 MINIX 1.0 文件系统简介

Linux 0.11操作系统启动时需要加载一个根目录,此根目录使用的是MINIX 1.0文件系统,其保存在硬盘的第一个分区中。Linux 0.11操作系统将硬盘上的两个连续的物理扇区(大小为512字节)做为一个物理盘块(大小为1024字节),而且物理盘块是从1开始计数的。硬盘上的第一个物理盘块是主引导记录块(MBR)。Linux 0.11并未使用硬盘上的主引导记录块进行引导,而是使用软盘A上的引导扇区进行引导的。在硬盘的主引导记录块中除了没有用到的引导程序外,在其包含的第一个物理扇区的尾部(引导标识0x55aa的前面)是一个64字节的分区表(典型的IBM Partition Table),其中每个分区表项占用16字节,共有4个分区表项。每个分区表项都定义了一个分区的起始物理盘块号和分区大小(占用的物理盘块数量)等信息。硬盘上的物理盘块可以按照图12-1所示进行划分。

Linux 0.11使用的硬盘只包含了两个分区,在第一个分区中提供了Linux 0.11的根目录,并使用MINIX
1.0文件系统进行管理,第二个分区没有用到,内容为空。需要强调的是,在MINIX 1.0文件系统管理的硬
盘分区中就不再使用物理盘块作为划分的单位了,而是将一个物理盘块作为一个逻辑块来使用,并且逻辑
块号是从0开始计数的。
接下来,重点了解一下MINIX 1.0文件系统是如何管理硬盘上的第一个分区的。第一个分区包含了引
导块(占用逻辑块0)、超级块(占用逻辑块1)、i节点位图(占用逻辑块2-4)、逻辑块位图(占用逻辑块
5-12),以及i节点和数据区,如图12-2所示。

分区 1 开始处的引导块同样没有被使用。其后的超级块用于存放 MINIX 1.0 文件系统的结构信息,主要用于说明各部分的起始逻辑块号和大小(包含的逻辑块数量)。超级块中各个字段的含义在表 12-3 中进行了说明。在超级块的后面是占用了 3 个逻辑块的 i 节点位图,其中的每一位用于说明对应的 i 节点是否被使用。位的值为 0 时,表示其对应的 i 节点未被使用;位的值为 1 时,表示其对应的 i 节点已经被使用。在 i 节点位图的后面是占用了 8 个逻辑块的逻辑块位图,其中的每一位用于说明数据区中对应的逻辑块是否被使用。除第 1 位(位 0)未被使用外,逻辑块位图中每个位依次代表数据区中的一个逻辑块。因此,逻辑块位图的第 2 位(位 1)代表数据区中第一个逻辑块,第 3 位(位 2)代表数据区中的第二个逻辑块,依此类推。当数据区中的一个逻辑块被占用时,逻辑块位图中的对应位被置为 1,否则被置为 0。在逻辑块位图的后面是 i 节点部分,其中存放着 MINIX 1.0 文件系统中文件或目录的索引节点(简称i 节点)。注意,i 节点是从 1 开始计数的。每个文件或目录都有一个 i 节点,每个 i 节点结构中存放着对应文件或目录的相关信息,如文件宿主的 id(uid)、文件所属组 id(gid)、文件长度、访问修改时间以及文件数据在数据区中的位置等。i 节点共包含 32 个字节,其结构如表 12-4 所示。

i_mode 字段用来保存文件的类型和访问权限属性。其位 15~12 用于保存文件类型,位 11~9 保存执行文件时设置的信息,位 8~0 表示文件的访问权限。如图 12-5 所示。i_zone[]数组用于存放 i 节点在数据区中对应的逻辑块号。i_zone[0]到 i_zone[6]用于存放文件开始的 7 个块号,称为直接块。例如,若文件长度小于等于 7KB,则根据其 i 节点的 i_zone[0]到 i_zone[6]可以很快找到文件数据所占用的逻辑块。若文件大一些,就需要用到一次间接块 i_zone[7]了,其对应的逻辑块中的数据又是一组逻辑块号,所以称之为一次间接块。对于 MINIX 1.0 文件系统来说,i_zone[7]对应的逻辑块中可以存放 512 个逻辑块号,因此可以寻址 512 个逻辑块。若一次间接块提供的逻辑块仍然无法存储文件的全部数据,则需要使用二次间接块 i_zone[8]了,其可以寻址 512*512 个逻辑块。参见图 12-6。

如果 i 节点对应的是一个文件,那么这个文件的内容(例如文本文件中的文本数据)就会保存在由
i_zone 数组指定的若干个逻辑块中。但是,如果 i 节点对应的是一个目录,这个目录又包含了若干个子文件或子目录,情况就会稍微复杂一些。如果 i 节点对应的是一个目录,则在该 i 节点的 i_zone 数组对应的逻辑块中会保存所有子文件和子目录的目录项信息。MINIX 1.0 文件系统的目录项长度为 16 字节,开始的 2 个字节指定了子文件或子目录对应的 i 节点号,接下来的 14 个字节用于保存子文件或子目录的名称。由于整个目录项的长度是 16 个字节,因此一个逻辑块可以存放 1024/16 = 64 个目录项。子文件和子目录的其他信息被保存在由 i 节点号指定的 i 节点结构中。所以,当需要访问一个文件时,需要根据文件的全路径(例如/bin/sh)从根节点找到子目录的 i 节点,然后再从子目录的 i 节点找到文件的 i 节点。

由于在 Linux 0.11 的内核中访问硬盘上的 MINIX 1.0 文件系统的过程比较复杂,还会涉及到硬盘设
备的物理特性和驱动程序代码。为了简化实验内容,这里采用模拟的方式来访问 MINIX 1.0 文件系统,即使用 C 语言编写一个 Windows 控制台程序,直接访问 VSCode 提供的硬盘镜像文件中的 MINIX 1.0 文件系统。

初始程序解释:

初始程序包括了harddisk.img,main.c,readme.md和makefile文件

在当前项目的源代码文件 main.c 中,实现了打印输出 MINIX 1.0 文件系统的目录树的功能,仔细阅读其中的源代码,并着重理解下面的内容:
1.在源代码文件的开始位置定义了分区表项、超级块、i 节点和目录项的结构体,读者需要理解其
中每个字段的意义。
2.get_physical_block 函 数 的 作 用 是 将 一 个 物 理 块 的 内 容 读 取 到 缓 冲 区 中 。
get_partition_logical_block 函数的作用是将第一个分区中的一个逻辑块的内容读取到缓冲区
中。需要注意的是,物理块号是从 1 开始计数的,而逻辑块号是从 0 开始计数的。这两个函数实
现了对硬盘镜像文件物理层和逻辑层的分层访问,从而可以模拟出访问硬盘的过程。
3.load_inode_bitmap 函 数 将 硬 盘 镜 像 文 件 中 的 整 个 i 节 点 位 图 都 读 入 到 了 内 存 中 。
is_inode_valid 函数根据 i 节点位图中的内容判断一个 i 节点是否有效。需要注意的是,i 节点
是从 1 开始计数的。get_inode 函数根据 i 结点的 id 读取相对应的 i 结点。
4.print_inode 函数递归打印目录树。首先读取 i 节点位图的第一位,并找到根节点。然后判断 i
节点是否为目录,若是目录,则打印目录名(名称前加 Tab 键是为了有树的层次感),然后递归打
印其子目录和子文件;如果是常规文件则直接打印文件名。

#include <stdio.h>

#define PHYSICAL_BLOCK_SIZE 1024
#define LOGICAL_BLOCK_SIZE 1024
#define NAME_LEN 14
#define START_PARTITION_TABLE 0x1be

//分区表结构
struct par_table_entry {
	char boot_indicator;	//导入指示器,绝大多数情况都是0
	char start_chs_val[3];	//起始柱面磁头扇区值,3个字节分别对应柱面号、磁头号、扇区号
	char par_type;			//分区类型
	char end_chs_val[3];	//终止柱面磁头扇区值
	int start_sector;		//起始盘块号
	int par_size;			//分区大小
};

// 超级块结构体
struct super_block
{
  unsigned short s_ninodes;		// 节点数。
  unsigned short s_nzones;		// 逻辑块数。
  unsigned short s_imap_blocks;	// i 节点位图所占用的数据块数。
  unsigned short s_zmap_blocks;	// 逻辑块位图所占用的数据块数。
  unsigned short s_firstdatazone;	// 第一个数据逻辑块号。
  unsigned short s_log_zone_size;	// log(数据块数/逻辑块)。(以2 为底)。
  unsigned long s_max_size;		// 文件最大长度。
  unsigned short s_magic;		// 文件系统魔数。
};

// i节点结构体
struct d_inode
{
  unsigned short i_mode;		// 文件类型和属性(rwx 位)。
  unsigned short i_uid;			// 用户id(文件拥有者标识符)。
  unsigned long i_size;			// 文件大小(字节数)。
  unsigned long i_time;			// 修改时间(自1970.1.1:0 算起,秒)。
  unsigned char i_gid;			// 组id(文件拥有者所在的组)。
  unsigned char i_nlinks;		// 链接数(多少个文件目录项指向该i 节点)。
  unsigned short i_zone[9];		// 直接(0-6)、间接(7)或双重间接(8)逻辑块号。
								// zone 是区的意思,可译成区段,或逻辑块。
};

//目录项结构
struct dir_entry{
	unsigned short inode;	//i节点号
	char name[NAME_LEN];	//文件名
};

struct super_block sblock;		//超级块
struct par_table_entry pte[4];	//分区表数组
FILE* fd;						//文件指针
char physical_block[PHYSICAL_BLOCK_SIZE]; //存储物理块
char logical_block[LOGICAL_BLOCK_SIZE];  //存储逻辑块
char *inode_bitmap;		//i节点位图指针
//char *logical_bitmap;	//逻辑块位图指针,本实例未使用

//读取一个物理块
void get_physical_block(int block_num)
{
	//减1是因为物理盘块是从1开始计数
	fseek(fd, (block_num - 1) * PHYSICAL_BLOCK_SIZE, SEEK_SET);
	fread(physical_block, PHYSICAL_BLOCK_SIZE, 1, fd);
}

//读取第一个分区的一个逻辑块
void get_partition_logical_block(int block_num)
{
	//block_num前面加的1表示在第一个分区前还有一个主引导记录(MBR)块,
	//后面加的1是因为物理盘块是从1开始计数的,而逻辑块是从0开始计数的
	get_physical_block(1 + block_num + 1);
	memcpy(logical_block, physical_block, LOGICAL_BLOCK_SIZE);
}

//读取分区表
void get_partition_table()
{
	int i = 0;

	//分区表有4个16字节的表项组成,第一个表项的起始地址为START_PARTITION_TABLE
	get_physical_block( 1 );	//分区表在物理盘块的第1块
	memcpy(pte, &physical_block[START_PARTITION_TABLE], sizeof(pte));
	for(i = 0; i < 4; i++)
	{
		printf("**************pattition table%d****************\n", i+1);
		printf("Boot Indicator:%d\n", pte[i].boot_indicator);
		printf("start CHS value:0x%04x\n", pte[i].start_chs_val);
		printf("partition type:%ld\n", pte[i].par_type);
		printf("end CHS value:0x%04x\n", pte[i].end_chs_val);
		printf("start sector:%d\n", pte[i].start_sector);
		printf("partition size:%d\n", pte[i].par_size);
	}
}

//读取第一个分区的超级块
void get_super_block()
{	
	get_partition_logical_block( 1 );
	memcpy(&sblock, logical_block, sizeof(sblock));
	
	printf("**************super block****************\n");
	printf("ninodes:%d\n", sblock.s_ninodes);
	printf("nzones:%d\n", sblock.s_nzones);
	printf("imap_blocks:%d\n", sblock.s_imap_blocks);
	printf("zmap_blocks:%d\n", sblock.s_zmap_blocks);
	printf("firstdatazone:0x%04x\n", sblock.s_firstdatazone);
	printf("log_zone_size:%d\n", sblock.s_log_zone_size);
	printf("max_size:0x%x = %dByte\n", sblock.s_max_size,sblock.s_max_size);
	printf("magic:0x%x\n", sblock.s_magic);
}	


//加载i节点位图
void load_inode_bitmap()
{
	inode_bitmap = (char*)malloc(sblock.s_imap_blocks * LOGICAL_BLOCK_SIZE);
	int i = 0;
	for(i = 0; i < sblock.s_imap_blocks; i++)
	{
		//i节点位图前有1个引导块和一个超级块
		get_partition_logical_block(1 + 1 + i);	
		memcpy(&inode_bitmap[i * LOGICAL_BLOCK_SIZE], &logical_block, LOGICAL_BLOCK_SIZE);
	}
}

//根据i节点位图判断其对应的i节点是否有效
//参数inode_id为i节点的id
//有效返回1,无效返回0
int is_inode_valid(unsigned short inode_id)
{
	if(inode_id > sblock.s_ninodes)
		return 0;
		
	char byte = inode_bitmap[(inode_id - 1) / 8]; //inode_id减1是因为i节点是从1开始计数的
	return (byte >> (7 - (inode_id - 1) % 8) ) & 0x1;	//取一个字节中的某位与1做位运算
}

//根据i节点id读取i节点
void get_inode(unsigned short inode_id, struct d_inode* inode)
{
	//一个引导块,一个超级块,sblock.s_imap_blocks个i节点位图,sblock.s_zmap_blocks个逻辑块位图	
	//一个i节点占32个字节,一个盘块有LOGICAL_BLOCK_SIZE/32个节点,所以inode_id/(LOGICAL_BLOCK_SIZE/32)
	//减1是因为i节点号是从1开始计数的,而逻辑块号是从0开始计数的
	//inode_blocknum是i节点在逻辑块中的偏移块数
	int inode_blocknum = 1 + 1 + sblock.s_imap_blocks + sblock.s_zmap_blocks + (inode_id - 1) / (LOGICAL_BLOCK_SIZE/32) ;
	get_partition_logical_block(inode_blocknum);
	memcpy((char*)inode, &logical_block[((inode_id - 1) % sizeof(struct d_inode)) * sizeof(struct d_inode)], sizeof(struct d_inode));
}

//递归打印i节点下的目录
void print_inode(unsigned short id, int tab_count, const char* name)
{
	int i, m, n;
	struct d_inode inode;
	struct dir_entry dir;
	
	//如果i节点号对应在i节点位图相应位的值为1,说明此i节点已使用
	//否则说明此i节点无用或已被删除,则直接返回
	if(is_inode_valid(id) != 1)
		return;
		
	get_inode(id, &inode);
	tab_count++;
	unsigned short mode = inode.i_mode >> 12; //高4位存放的是文件类型
	
	//如果是目录文件
	if(mode == 4)
	{
		//打印tab键,为了使目录有层次感
		for(i=0; i<tab_count; i++)
		{
			printf("\t");
		}
		printf("%s\n", name);
		
		//循环读取i节点中的i_zones[]数组
		for(m = 0; m<7; m++)
		{
			//如果数组数据为0,则跳过
			if(inode.i_zone[m] == 0)
				continue;
			
			//一个逻辑块最多存储64个目录项,循环读取64个目录项
			//其中前两项分别为 . 和 .. 
			for(n = 0; n < 64; n++)
			{
				get_partition_logical_block(inode.i_zone[m]);
				//将逻辑块中的数据拷贝到目录项结构体
				memcpy((char*)&dir, &logical_block[n * sizeof(dir)], sizeof(dir));
				
				//如果是 .和..则继续循环
				if(n == 0 || n == 1)
					continue;
					
				//如果目录项中没有内容了则不再读取
				if(dir.inode == 0)
					break;
				
				//递归打印子目录
				print_inode(dir.inode, tab_count, dir.name);
			}
		}
	}
	
	//如果是常规文件
	else if(mode == 8)
	{
		for(i=0; i<tab_count; i++)
		{
			printf("\t");
		}
		printf("%s\n", name);
	}
	//如果块设备文件、字符设备文件等其他类型文件,请读者尝试自己实现
}


int main(int argc, char* argv[])
{
	int bit;
	struct d_inode* inode = (struct d_inode*)malloc(sizeof(struct d_inode));
	
	char* path = "harddisk.img";
	fd = fopen(path, "rb");
	if(fd==NULL)
		printf("open file failed!\n");
		
	//读取分区表
	get_partition_table();
	//读取超级块
	get_super_block();
	
	//加载i节点逻辑块位图
	load_inode_bitmap();
	//i节点位图的第一位对应文件系统的根节点
	//如果第一位为1,则打印根节点
	bit = is_inode_valid(1);
	if(bit == 1)
		print_inode(1, -1, "\\");
	else
		printf("root node lost!\n");
	
	return 0;
}

任务目录:

基于已有的harddisk.img硬盘镜像文件和main.c文件,完成以下9个任务:

1.打印输出 MINIX 1.0 文件系统的目录树

2. 参考 load_inode_bitmap 函数写出一个可以将硬盘镜像文件中的整个逻辑块位图都加载到内存中
的函数。
3. 打印输出类似于 Linux 0.11 内核命令“df”的输出内容。
4. 将“/usr/root”文件夹下的“hello.c”文件的内

容打印输出。
5. 将“/usr/src/linux-0.11.org”文件夹下的“memory.c”的内容打印输出。注意,此文件大于7KB,所以需要使用二次间接块才能访问所有的数据。
6. 删除“/usr/root/hello.c”文件。
7. 删除“/usr/root/shoe”文件夹。
8. 新建“/usr/root/dir”文件夹。
9. 新建“/usr/root/file.txt”文件,并设置初始大小为 10KB

任务一

打印输出 MINIX 1.0 文件系统的目录树

1. 选择 VSCode 的“Terminal”菜单中的“Run Build Task...”菜单项,在弹出的列表中选择“生
成项目(make)”,在项目生成的过程中,位于编辑器下方的“TERMINAL”窗口会实时显示生成的进
度和结果。如果生成项目成功,会在项目目录中生成应用程序的可执行文件 minix.exe,并在项
目目录中准备好硬盘镜像文件 harddisk.img,在此硬盘的第一个分区中提供了使用 MINIX 1.0 文
件系统管理的根目录。注意:如果在执行程序的过程中,导致 harddisk.img 硬盘镜像文件破怀,可以删除项目目录中的artifact.done 文件,重新生成项目,即可恢复初始的 harddisk.img 硬盘镜像文件。
2. 选择 VSCode 的“Terminal”菜单中的“New Terminal”菜单项,在 VSCode 底部的 TERMINAL 窗
中,输入命令“.\minix.exe > a.txt”后按回车,应用程序会读取项目目录中的 harddisk.img
硬盘镜像文件,并将打印输出的目录树重定向到文本文件 a.txt 中。
3. 在 VSCode 左侧的“文件资源管理器”窗口中双击 a.txt 文件,可以在编辑器中打开该文件,查看
MINIX 1.0 文件系统的目录树。

任务二:

参考 load_inode_bitmap 函数写出一个可以将硬盘镜像文件中的整个逻辑块位图都加载到内存中
的函数。

定义一个指针指向逻辑块位图:

char *zone_bitmap; // 用于指向逻辑块位图的指针

加载逻辑块位图到内存中,然后用调用辅助函数检验所有逻辑块的使用情况:


/**
 * 加载整个逻辑块位图到内存中
 */
void load_zone_bitmap()
{
    // 计算需要分配的内存大小:每个逻辑块1024字节,共s_zmap_blocks个逻辑块
    zone_bitmap = (char*)malloc(sblock.s_zmap_blocks * LOGICAL_BLOCK_SIZE);
    if (zone_bitmap == NULL) {
        printf("Memory allocation failed for zone bitmap.\n");
        return;
    }

    int i;
    // 逻辑块起始位置 = 引导块(1) + 超级块(1) + i节点位图占用的块数
    int start_block = 1 + 1 + sblock.s_imap_blocks;

    for (i = 0; i < sblock.s_zmap_blocks; i++) {
        get_partition_logical_block(start_block + i); // 读取当前逻辑块
        memcpy(&zone_bitmap[i * LOGICAL_BLOCK_SIZE], logical_block, LOGICAL_BLOCK_SIZE);
    }
}


/**
 * 判断指定的逻辑块是否被使用
 * @param zone_num - 数据区中的逻辑块号(从0开始)
 * @return 1: 已使用,0: 未使用
 */
int is_zone_used(int zone_num)
{
    // 检查逻辑块号是否合法
    if (zone_num < 0 || zone_num >= sblock.s_nzones) {
        return 0; // 越界无效
    }

    int bit_position = zone_num + 1; // 第0位保留不用,第一个数据块是第1位
    int byte_index = bit_position / 8; // 所在字节索引
    int bit_index = 7 - (bit_position % 8); // 所在字节中的位索引(高位在前)

    char byte = zone_bitmap[byte_index]; // 取出该字节

    return (byte >> bit_index) & 0x1; // 判断该位是否为1
}

/**
 * 检查并打印所有逻辑块的使用情况
 */
void check_all_zones_usage()
{
    printf("************** Logical Block (Zone) Usage **************\n");
    printf("Total zones: %d\n", sblock.s_nzones);
    printf("--------------------------------------------------------\n");

    int used_count = 0;
    int free_count = 0;

    for (int zone_num = 0; zone_num < sblock.s_nzones; zone_num++)
    {
        int is_used = is_zone_used(zone_num);

        if (is_used)
        {
            printf("Zone %5d : [USED]\n", zone_num);
            used_count++;
        }
        else
        {
            printf("Zone %5d : [FREE]\n", zone_num);
            free_count++;
        }
    }

    printf("--------------------------------------------------------\n");
    printf("Used zones:  %d\n", used_count);
    printf("Free zones:  %d\n", free_count);
    printf("********************************************************\n");
}

主函数:

int main(int argc, char* argv[])
{
	int bit;
	struct d_inode* inode = (struct d_inode*)malloc(sizeof(struct d_inode));
	
	char* path = "harddisk.img";
	fd = fopen(path, "rb");
	if(fd==NULL)
		printf("open file failed!\n");
		
	//读取分区表
	get_partition_table();
	//读取超级块
	get_super_block();
	
	//加载i节点逻辑块位图
	load_inode_bitmap();
	//i节点位图的第一位对应文件系统的根节点
	//如果第一位为1,则打印根节点
	bit = is_inode_valid(1);
	if(bit == 1)
		print_inode(1, -1, "\\");
	else
		printf("root node lost!\n");
	
    // 加载逻辑块位图
    load_zone_bitmap();

    // 检查并打印所有逻辑块使用情况
    check_all_zones_usage();

	return 0;
}

任务三:

打印输出类似于 Linux 0.11 内核命令“df”的输出内容。

需要计算并展示文件系统的使用情况,包括总块数、已用块数、可用块数以及使用百分比:

代码:

/**
 * 打印类似 df 命令的输出
 */
void print_df_output()
{
    int used_count = 0;
    int total_zones = sblock.s_nzones;

    // 遍历所有逻辑块,统计已使用的块数
    for (int zone_num = 0; zone_num < total_zones; zone_num++)
    {
        if (is_zone_used(zone_num))
        {
            used_count++;
        }
    }

    int free_count = total_zones - used_count;
    float use_percentage = ((float)used_count / total_zones) * 100;

    printf("Filesystem\tBlocks\tUsed\tAvailable\tUse%%\n");
    printf("MINIX_FS\t%d\t%d\t%d\t\t%.0f%%\n", 
           total_zones, used_count, free_count, use_percentage);
}

void print_file_content(unsigned short inode_id)
{
    struct d_inode inode;
    get_inode(inode_id, &inode);
    
    unsigned short mode = inode.i_mode >> 12; // 高4位存放的是文件类型
    if (mode != 8) { // 如果不是普通文件
        printf("Not a regular file.\n");
        return;
    }

    int size = inode.i_size;
    for (int i = 0; i < 9 && size > 0; i++) {
        if (inode.i_zone[i] == 0)
            break;

        get_partition_logical_block(inode.i_zone[i]);
        int block_size = size > LOGICAL_BLOCK_SIZE ? LOGICAL_BLOCK_SIZE : size;
        fwrite(logical_block, 1, block_size, stdout); // 输出到标准输出
        size -= block_size;
    }
}

主函数中调用:

    // 打印类似 df 命令的输出
    print_df_output();

 任务四:

将“/usr/root”文件夹下的“hello.c”文件的内容打印输出。

代码:

void print_file_content(unsigned short inode_id)
{
    struct d_inode inode;
    get_inode(inode_id, &inode);
    
    unsigned short mode = inode.i_mode >> 12; // 高4位存放的是文件类型
    if (mode != 8) { // 如果不是普通文件
        printf("Not a regular file.\n");
        return;
    }

    int size = inode.i_size;
    for (int i = 0; i < 9 && size > 0; i++) {
        if (inode.i_zone[i] == 0)
            break;

        get_partition_logical_block(inode.i_zone[i]);
        int block_size = size > LOGICAL_BLOCK_SIZE ? LOGICAL_BLOCK_SIZE : size;
        fwrite(logical_block, 1, block_size, stdout); // 输出到标准输出
        size -= block_size;
    }
}

// 根据路径查找文件的i节点id
unsigned short find_inode_by_path(const char* path)
{
    const char *current = path + 1; // 跳过第一个 '/'
    unsigned short current_inode = 1; // 根目录的i节点id为1

    while (*current) {
        if (*current == '/') {
            current++;
            continue;
        }

        // 找到下一个 '/' 或字符串结束的位置
        const char *next_slash = strchr(current, '/');
        int name_len = next_slash ? next_slash - current : strlen(current);

        struct dir_entry dir;
        struct d_inode inode;
        get_inode(current_inode, &inode);
        
        int found = 0;
        for (int i = 0; i < 7; i++) {
            if (inode.i_zone[i] == 0)
                break;

            get_partition_logical_block(inode.i_zone[i]);
            for (int j = 0; j < 64; j++) {
                memcpy(&dir, &logical_block[j * sizeof(struct dir_entry)], sizeof(struct dir_entry));
                if (strncmp(dir.name, current, name_len) == 0 && dir.name[name_len] == '\0') {
                    current_inode = dir.inode;
                    found = 1;
                    break;
                }
            }
            if (found) break;
        }

        if (!found) {
            printf("Directory or file not found: %.*s\n", name_len, current);
            return 0;
        }

        if (next_slash) {
            current = next_slash + 1;
        } else {
            break;
        }
    }
    return current_inode;
}

主函数调用:

	// 查找并打印 /usr/root/hello.c 文件的内容
    const char* target_path = "/usr/root/hello.c";
    unsigned short inode_id = find_inode_by_path(target_path);
    if (inode_id) {
        print_file_content(inode_id);
    } else {
        printf("File not found: %s\n", target_path);
    }

任务五:

将“/usr/src/linux-0.11.org”文件夹下的“memory.c”的内容打印输出。注意,此文件大于
7KB,所以需要使用二次间接块才能访问所有的数据。

过程中出现了地址有错码的情况:

这是由于MINIX文件系统中的目录项名称是固定长度的,并且可能会被填充字符(如空格 ' ' 或零 \0)填充到最大长度,因此需要小心处理这些名称以避免显示乱码字符。

safe_extract_filename() 函数能安全地截断和清理目录项名称:

void safe_extract_filename(const char* raw_name, char* out_name, size_t out_size) {
    const int name_len = 14; // MINIX 文件系统中的文件名最大长度
    strncpy(out_name, raw_name, name_len);
    out_name[name_len] = '\0'; // 强制结尾

    // 去除尾部的空格或非打印字符
    for (int i = strlen(out_name); i > 0; i--) {
        if (out_name[i - 1] == ' ' || !isprint((unsigned char)out_name[i - 1])) {
            out_name[i - 1] = '\0';
        } else {
            break;
        }
    }
}

并且在find_inode_by_path函数中使用,使其在寻找i节点时能正确阶段空值:

// 根据路径查找文件的i节点id
unsigned short find_inode_by_path(const char* path)
{
    const char *current = path + 1; // 跳过第一个 '/'
    unsigned short current_inode = 1; // 根目录的i节点id为1

    while (*current) {
        if (*current == '/') {
            current++;
            continue;
        }

        // 找到下一个 '/' 或字符串结束的位置
        const char *next_slash = strchr(current, '/');
        int name_len = next_slash ? next_slash - current : strlen(current);

        struct dir_entry dir;
        struct d_inode inode;
        get_inode(current_inode, &inode);
        
        int found = 0;
        for (int i = 0; i < 7; i++) { // 遍历每个区段
            if (inode.i_zone[i] == 0)
                break;

            get_partition_logical_block(inode.i_zone[i]);
            for (int j = 0; j < 64; j++) { // 每个逻辑块最多存储64个目录项
                memcpy(&dir, &logical_block[j * sizeof(struct dir_entry)], sizeof(struct dir_entry));
                
                // 处理文件名,确保它被正确截断
                char temp_name[NAME_LEN + 1];
                safe_extract_filename(dir.name, temp_name, NAME_LEN + 1);

                if (strncmp(temp_name, current, name_len) == 0 && 
                    (temp_name[name_len] == ' ' || temp_name[name_len] == '\0')) {
                    current_inode = dir.inode;
                    found = 1;
                    break;
                }
            }
            if (found) break;
        }

        if (!found) {
            printf("Directory or file not found: %.*s\n", name_len, current);
            return 0;
        }

        if (next_slash)
            current = next_slash + 1;
        else
            break;
    }

    return current_inode;
}

下面为打印memory.c的函数: 

/**
 * 打印指定 i 节点对应的文件内容(支持直接块、一次间接块、二次间接块)
 */
void print_file_content2(unsigned short inode_id)
{
    struct d_inode inode;
    get_inode(inode_id, &inode);

    unsigned short mode = inode.i_mode >> 12; // 高4位是文件类型
    if (mode != 8) {
        printf("Not a regular file.\n");
        return;
    }

    unsigned long remaining = inode.i_size;
    int block_num;

    // 处理直接块(i_zone[0] ~ i_zone[6])
    for (int i = 0; i < 7 && remaining > 0; i++) {
        block_num = inode.i_zone[i];
        if (block_num == 0) break;

        get_partition_logical_block(block_num);
        int bytes_to_read = (remaining < LOGICAL_BLOCK_SIZE) ? remaining : LOGICAL_BLOCK_SIZE;
        fwrite(logical_block, 1, bytes_to_read, stdout);
        remaining -= bytes_to_read;
    }

    // 处理一次间接块(i_zone[7])
    if (remaining > 0 && inode.i_zone[7] != 0) {
        char indirect_block[LOGICAL_BLOCK_SIZE];
        get_partition_logical_block(inode.i_zone[7]);
        memcpy(indirect_block, logical_block, LOGICAL_BLOCK_SIZE);

        for (int i = 0; i < LOGICAL_BLOCK_SIZE / sizeof(int) && remaining > 0; i++) {
            memcpy(&block_num, &indirect_block[i * sizeof(int)], sizeof(int));
            if (block_num == 0) break;

            get_partition_logical_block(block_num);
            int bytes_to_read = (remaining < LOGICAL_BLOCK_SIZE) ? remaining : LOGICAL_BLOCK_SIZE;
            fwrite(logical_block, 1, bytes_to_read, stdout);
            remaining -= bytes_to_read;
        }
    }

    // 处理二次间接块(i_zone[8])
    if (remaining > 0 && inode.i_zone[8] != 0) {
        char double_indirect_block[LOGICAL_BLOCK_SIZE];
        get_partition_logical_block(inode.i_zone[8]);
        memcpy(double_indirect_block, logical_block, LOGICAL_BLOCK_SIZE);

        for (int i = 0; i < LOGICAL_BLOCK_SIZE / sizeof(int) && remaining > 0; i++) {
            memcpy(&block_num, &double_indirect_block[i * sizeof(int)], sizeof(int));
            if (block_num == 0) break;

            // 读取一级间接块
            char indirect_block[LOGICAL_BLOCK_SIZE];
            get_partition_logical_block(block_num);
            memcpy(indirect_block, logical_block, LOGICAL_BLOCK_SIZE);

            for (int j = 0; j < LOGICAL_BLOCK_SIZE / sizeof(int) && remaining > 0; j++) {
                memcpy(&block_num, &indirect_block[j * sizeof(int)], sizeof(int));
                if (block_num == 0) break;

                get_partition_logical_block(block_num);
                int bytes_to_read = (remaining < LOGICAL_BLOCK_SIZE) ? remaining : LOGICAL_BLOCK_SIZE;
                fwrite(logical_block, 1, bytes_to_read, stdout);
                remaining -= bytes_to_read;
            }
        }
    }
}

 输出结果(部分):

任务六:

删除“/usr/root/hello.c”文件。

 这里包括的函数有很多:write_inode(),remove_dirent_from_parent()函数,free_inode_blocks()函数,mark_block_as_free()函数,mark_inode_as_free()函数,delete_file()函数

// 将指定 inode 写入到磁盘镜像中
void write_inode(unsigned short inode_num, struct d_inode* inode) {
    int inode_block = 2 + inode_num / (LOGICAL_BLOCK_SIZE / sizeof(struct d_inode));
    int inode_offset = (inode_num % (LOGICAL_BLOCK_SIZE / sizeof(struct d_inode))) * sizeof(struct d_inode);

    get_partition_logical_block(inode_block);  // 读取逻辑块到 logical_block 缓冲区

    // 更新内存缓冲区中的 inode 数据
    memcpy(logical_block + inode_offset, inode, sizeof(struct d_inode));

    // 将修改后的逻辑块写回磁盘
    write_partition_logical_block(inode_block);
} 



// 将路径拆分为父目录和文件名
int remove_dirent_from_parent(const char* parent_dir, const char* filename) {
    unsigned short parent_inode = find_inode_by_path(parent_dir);
    if (!parent_inode) {
        printf("父目录未找到: %s\n", parent_dir);
        return 0;
    }

    struct d_inode parent_inode_info;
    get_inode(parent_inode, &parent_inode_info);

    for (int i = 0; i < 7; i++) { // 遍历每个区段
        if (parent_inode_info.i_zone[i] == 0)
            break;

        get_partition_logical_block(parent_inode_info.i_zone[i]);
        for (int j = 0; j < 64; j++) { // 每个逻辑块最多存储64个目录项
            struct dir_entry dir;
            memcpy(&dir, &logical_block[j * sizeof(struct dir_entry)], sizeof(struct dir_entry));

            // 安全地提取和比较文件名
            char temp_name[NAME_LEN + 1];
            strncpy(temp_name, dir.name, NAME_LEN);
            temp_name[NAME_LEN] = '\0'; // 强制结尾

            // 去除尾部的空格或非打印字符
            for (int k = strlen(temp_name); k > 0; k--) {
                if (temp_name[k - 1] == ' ' || !isprint((unsigned char)temp_name[k - 1])) {
                    temp_name[k - 1] = '\0';
                } else {
                    break;
                }
            }

            if (dir.inode != 0 && strcmp(temp_name, filename) == 0) {
                // 找到了匹配的目录项,将其标记为空闲(例如将inode置为0)
                dir.inode = 0;
                memset(dir.name, ' ', NAME_LEN); // 清理文件名
                memcpy(&logical_block[j * sizeof(struct dir_entry)], &dir, sizeof(struct dir_entry));
                write_partition_logical_block(parent_inode_info.i_zone[i]); // 写回磁盘
                return 1; // 成功删除
            }
        }
    }

    return 0; // 没有找到对应的目录项
}

void free_inode_blocks(struct d_inode* inode) {
    for (int i = 0; i < 7; i++) { // 直接块
        if (inode->i_zone[i]) {
            mark_block_as_free(inode->i_zone[i]);
            inode->i_zone[i] = 0;
        }
    }

    if (inode->i_zone[7]) { // 单级间接块
        unsigned int indirect_block[256];
        get_partition_logical_block(inode->i_zone[7]);
        memcpy(indirect_block, logical_block, sizeof(indirect_block));
        for (int i = 0; i < 256; i++) {
            if (indirect_block[i]) {
                mark_block_as_free(indirect_block[i]);
                indirect_block[i] = 0;
            }
        }
        mark_block_as_free(inode->i_zone[7]);
        inode->i_zone[7] = 0;
    }
}

void mark_block_as_free(unsigned int block_id) {
    // 更新位图以标记该块为空闲
    zone_bitmap[block_id / 8] &= ~(1 << (block_id % 8));
}



void mark_inode_as_free(unsigned short inode_id) {
    // 更新 inode 位图以标记该 inode 为空闲
    inode_bitmap[inode_id / 8] &= ~(1 << (inode_id % 8));

    // 如果有必要,还可以清空 inode 的内容
    struct d_inode empty_inode = {0};
    write_inode(inode_id, &empty_inode);
}



void delete_file(const char* path) {
    unsigned short file_inode = find_inode_by_path(path);
    if (!file_inode) {
        printf("文件未找到: %s\n", path);
        return;
    }

    struct d_inode file_inodeInfo;
    get_inode(file_inode, &file_inodeInfo);

    // 分离出父目录路径和文件名
    char parent_dir[PATH_MAX];
    strcpy(parent_dir, path);
    char *filename = strrchr(parent_dir, '/');
    if (filename) {
        *(filename++) = '\0'; // 将路径拆分为父目录和文件名

        if(!remove_dirent_from_parent(parent_dir, filename)) {
            printf("无法从父目录中移除文件项: %s\n", filename);
            return;
        }
    } else {
        printf("路径格式不正确。\n");
        return;
    }

    // 释放该文件占用的所有数据块
    free_inode_blocks(&file_inodeInfo);

    // 将该 inode 标记为空闲
    mark_inode_as_free(file_inode);

    printf("have deleted: %s\n", path);
}

主函数调用:

    // 删除 "/usr/root/hello.c" 文件
    delete_file("/usr/root/hello.c");

 运行结果:

 任务七:

删除“/usr/root/shoe”文件夹。

void delete_directory(const char* dir_path) {
    unsigned short dir_inode = find_inode_by_path(dir_path);
    if (!dir_inode) {
        printf("Directory not found: %s\n", dir_path);
        return;
    }

    struct d_inode dir_inode_info;
    get_inode(dir_inode, &dir_inode_info);

    // 遍历目录中的所有条目
    for (int i = 0; i < 7; i++) { // 遍历每个直接区段
        if (dir_inode_info.i_zone[i] == 0)
            break;

        get_partition_logical_block(dir_inode_info.i_zone[i]);
        for (int j = 0; j < 64; j++) { // 每个逻辑块最多存储64个目录项
            struct dir_entry dir;
            memcpy(&dir, &logical_block[j * sizeof(struct dir_entry)], sizeof(struct dir_entry));

            if (dir.inode != 0) {
                char temp_name[NAME_LEN + 1];
                strncpy(temp_name, dir.name, NAME_LEN);
                temp_name[NAME_LEN] = '\0'; // 强制结尾

                // 如果是"." 或 "..",则跳过
                if (strcmp(temp_name, ".") == 0 || strcmp(temp_name, "..") == 0)
                    continue;

                char entry_path[PATH_MAX];
                snprintf(entry_path, PATH_MAX, "%s/%s", dir_path, temp_name);

                struct d_inode child_inode_info;
                get_inode(dir.inode, &child_inode_info);

                if (S_ISDIR(child_inode_info.i_mode)) {
                    // 递归删除子目录
                    delete_directory(entry_path);
                } else {
                    // 删除普通文件
                    delete_file(entry_path);
                }
            }
        }
    }

    // 只有在删除整个目录本身时才调用 remove_dirent_from_parent()
    const char* slash = strrchr(dir_path, '/');
    if (!slash || slash == dir_path) {
        printf("Invalid path format: %s\n", dir_path);
        return;
    }

    char parent_dir[PATH_MAX];
    size_t parent_len = slash - dir_path;
    strncpy(parent_dir, dir_path, parent_len);
    parent_dir[parent_len] = '\0';

    const char* dir_name = slash + 1;

    if (!remove_dirent_from_parent(parent_dir, dir_name)) {
        printf("Failed to remove directory entry from parent: %s/%s\n", parent_dir, dir_name);
    } else {
        printf("Successfully removed directory entry from parent: %s/%s\n", parent_dir, dir_name);
    }

    // 释放当前目录的数据块和 inode
    free_inode_blocks(&dir_inode_info);
    mark_inode_as_free(dir_inode);

    printf("The directory has been successfully deleted.: %s\n", dir_path);
}

主函数调用:

   // 删除 "/usr/root/shoe" 文件夹
    delete_directory("/usr/root/shoe");

 

这里由于在调试的时候已经删过了,所以报错了:

任务八:

新建“/usr/root/dir”文件夹。

int add_dirent_to_parent(const char* parent_dir, const char* new_dir_name, unsigned short new_dir_inode) {
    unsigned short parent_inode_num = find_inode_by_path(parent_dir);
    if (!parent_inode_num) {
        printf("Parent directory not found: %s\n", parent_dir);
        return -1;
    }

    struct d_inode parent_inode;
    get_inode(parent_inode_num, &parent_inode);

    for (int i = 0; i < 7; ++i) {
        int zone = parent_inode.i_zone[i];
        if (zone == 0)
            continue;

        get_partition_logical_block(zone);
        for (int j = 0; j < 64; ++j) {
            struct dir_entry* dir = (struct dir_entry*)(logical_block + j * sizeof(struct dir_entry));
            if (dir->inode == 0) {
                // 找到空目录项
                dir->inode = new_dir_inode;
                strncpy(dir->name, new_dir_name, NAME_LEN);
                write_partition_logical_block(zone);
                return 0;
            }
        }
    }

    printf("Failed to add new dirent to parent directory.\n");
    return -1;
}

unsigned short allocate_inode() {
    // inode bitmap 中每个字节代表 8 个 inode 的状态
    for (int byte_index = 0; byte_index < sblock.s_imap_blocks * LOGICAL_BLOCK_SIZE; ++byte_index) {
        unsigned char byte = inode_bitmap[byte_index];

        for (int bit_index = 0; bit_index < 8; ++bit_index) {
            if ((byte & (1 << bit_index)) == 0) {
                // 找到空闲 inode
                inode_bitmap[byte_index] |= (1 << bit_index); // 标记为已使用

                // 写回磁盘中的 inode bitmap
                for (int b = 0; b < sblock.s_imap_blocks; ++b) {
                    memcpy(logical_block, &inode_bitmap[b * LOGICAL_BLOCK_SIZE], LOGICAL_BLOCK_SIZE);
                    write_partition_logical_block(2 + b); // inode bitmap 存储在 block 2 起始位置
                }

                // inode 编号从 1 开始计算
                return byte_index * 8 + bit_index + 1;
            }
        }
    }

    printf("No free inode available.\n");
    return 0;
}

int create_directory(const char* dir_path) {
    // Step 1: Extract parent directory and the new directory name
    char parent_dir[PATH_MAX];
    const char* new_dir_name = strrchr(dir_path, '/');
    if (!new_dir_name || new_dir_name == dir_path) { // Handle root or invalid path
        printf("Invalid path format: %s\n", dir_path);
        return -1;
    }

    size_t parent_len = new_dir_name - dir_path;
    strncpy(parent_dir, dir_path, parent_len);
    parent_dir[parent_len] = '\0';
    new_dir_name++;

    // Step 2: Check if parent directory exists
    unsigned short parent_inode_num = find_inode_by_path(parent_dir);
    if (!parent_inode_num) {
        printf("Parent directory not found: %s\n", parent_dir);
        return -1;
    }

    // Step 3: Allocate a new inode for the new directory
    unsigned short new_dir_inode = allocate_inode();
    if (!new_dir_inode) {
        printf("Failed to allocate inode for new directory.\n");
        return -1;
    }

    // Step 4: Initialize the new directory's inode and contents
    struct d_inode new_dir_inode_info;
    memset(&new_dir_inode_info, 0, sizeof(new_dir_inode_info));
    new_dir_inode_info.i_mode = S_IFDIR; // Set directory flag

    // Add "." and ".." entries
    struct dir_entry dot_entries[2];
    dot_entries[0].inode = new_dir_inode;
    strncpy(dot_entries[0].name, ".", NAME_LEN);
    dot_entries[1].inode = parent_inode_num;
    strncpy(dot_entries[1].name, "..", NAME_LEN);

    // Write the new directory's first block with "." and ".."
    memcpy(logical_block, dot_entries, sizeof(dot_entries));
    write_partition_logical_block(new_dir_inode_info.i_zone[0]); // Assuming zone[0] is used for the first block

    // Step 5: Add an entry for the new directory in the parent directory
    add_dirent_to_parent(parent_dir, new_dir_name, new_dir_inode);

    printf("Directory created successfully: %s\n", dir_path);
    return 0;
}

主函数调用:

//创建新目录
    if (create_directory("/usr/root/dir") == 0) {
        printf("Directory '/usr/root/dir' created successfully.\n");
    } else {
        printf("Failed to create directory '/usr/root/dir'.\n");
    }

 结果如下:

任务九:

新建“/usr/root/file.txt”文件,并设置初始大小为 10KB。 

/**
 * 创建并初始化一个指定大小的新文件
 */
int create_file(const char* file_path, size_t initial_size) {
    // Step 1: Extract parent and name
    const char* slash = strrchr(file_path, '/');
    if (!slash || slash == file_path) {
        printf("Invalid path format: %s\n", file_path);
        return -1;
    }

    char parent_dir[PATH_MAX];
    size_t parent_len = slash - file_path;
    strncpy(parent_dir, file_path, parent_len);
    parent_dir[parent_len] = '\0';
    const char* file_name = slash + 1;

    // Step 2: Allocate inode for new file
    unsigned short new_inode = allocate_inode();
    if (!new_inode) {
        printf("Failed to allocate inode for new file.\n");
        return -1;
    }

    // Step 3: Initialize inode as regular file
    struct d_inode new_inode_info;
    memset(&new_inode_info, 0, sizeof(new_inode_info));
    new_inode_info.i_mode = S_IFREG; // 设置为普通文件类型
    new_inode_info.i_uid = 0;
    new_inode_info.i_gid = 0;
    new_inode_info.i_nlinks = 1; // 普通文件链接数通常为1
    new_inode_info.i_size = initial_size;
    new_inode_info.i_time = time(NULL);

    // Step 4: Calculate number of blocks needed
    int blocks_needed = (initial_size + LOGICAL_BLOCK_SIZE - 1) / LOGICAL_BLOCK_SIZE;

    // Allocate blocks for the file
    for (int i = 0; i < blocks_needed && i < 7; ++i) { // Direct blocks only
        for (int b = 0; b < sblock.s_nzones; ++b) {
            if (!is_zone_used(b)) { // Check if the zone is free
                new_inode_info.i_zone[i] = b;
                mark_zone_as_used(b); // Mark it as used in zone bitmap
                break;
            }
        }
    }

    // Write initial data if necessary
    for (int i = 0; i < blocks_needed && i < 7; ++i) {
        memset(logical_block, 0, LOGICAL_BLOCK_SIZE); // Fill with zeros or any other initial data
        write_partition_logical_block(new_inode_info.i_zone[i]);
    }

    // 写入 inode 到磁盘
    write_inode(new_inode, &new_inode_info);

    // Step 5: Add new file to parent directory
    if (add_dirent_to_parent(parent_dir, file_name, new_inode) != 0) {
        printf("Failed to add new file to parent.\n");
        return -1;
    }

    printf("File created successfully: %s\n", file_path);
    return 0;
}

主函数调用:

    // 创建新文件
    if (create_file("/usr/root/file.txt", 10 * 1024) == 0) {
        printf("File '/usr/root/file.txt' created successfully with 10KB size.\n");
    } else {
        printf("Failed to create file '/usr/root/file.txt'.\n");
    }

 

输出结果:

 

完整文件我放在资源里了~ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值