真象还原 --文件系统/系统交互 study(5)

真象还原 --文件系统/系统交互 study(5)

H_Haozi Lv3

A 前情提要

在此之前,第一部分我们完成了基础的环境配置,bochs配置,以及MBR,loader的的基础编写,成功的进入了保护模式并且开启了内存分页功能

第二部分完成了对内联汇编中断初始化定时器初始化,也实现了基础打印函数*

第三部分完成了对内存管理线程同步,也实现了基础生产者与消费者*

第四部分完成了对用户进程/完善内核(系统调用,完善内存)/硬盘驱动相关内容,也实现了printf

这一部分准备学习文件系统系统交互这两个部分,也是这本书的最后部分!!!!!

ps:如果参考本系列文章来实操,需要结合《操作系统真象还原》一起观看,否则会缺失很多细节

B 文件系统

B.1 创建文件系统

我们先在创建一个文件夹mouse/fs,以后有关文件系统的内容就放在这里了,然后是先做一些准备工作,当然还要略微修改一下Makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# =================================================================================
# Makefile 内核构建文件 /home/mouse/OS_mouse/tool/bochs/mouse/kernel/Makefile
# =================================================================================

# .PHONY 标明伪目标,它代表了一个需要被执行的动作或任务,而非一个需要被生成的文件
# 可以在本文件所在目录下 使用make all 、 make clean 等命令执行一系列任务
.PHONY: all kernel user clean img dirs

# ================================ 基础路径 =======================================
# 其中的 ":=" 表示立即展开使用,即右边的变量值会被立刻赋值,"$" 可以引用之前创建的变量
# =================================================================================
MOUSE_DIR := ..
BUILD_DIR := $(MOUSE_DIR)/build
BUILD_KERNEL_DIR := $(BUILD_DIR)/kernel
BUILD_USER_DIR := $(BUILD_DIR)/user
IMG_PATH := /home/mouse/OS_mouse/tool/bochs/hd60M.img

# ================================ 编译工具 =======================================
CC := gcc
ASM := nasm
LD := ld

# C编译器标志,这里指出输出32位,编译器不识别/使用内建函数,禁用栈保护机制,指定当前为独立式环境(标准库不存在),
# -I 分别包含需要的头文件路径(共用库,内核库,用户库,内核,设备,线程,用户进程)
CFLAGS := -m32 -fno-builtin -fno-stack-protector -ffreestanding \
-I$(MOUSE_DIR)/lib -I$(MOUSE_DIR)/lib/kernel -I$(MOUSE_DIR)/lib/user \
-I$(MOUSE_DIR)/kernel -I$(MOUSE_DIR)/device -I$(MOUSE_DIR)/thread -I$(MOUSE_DIR)/fs -I$(MOUSE_DIR)/userprog -c
ASMFLAGS := -f elf
LDFLAGS := -m elf_i386 -Ttext 0xc0001500 -e main

# ===================== 包含子Makefile,导入子模块源文件 ===============================
include $(MOUSE_DIR)/lib/kernel/Makefile
include $(MOUSE_DIR)/lib/user/Makefile
include $(MOUSE_DIR)/device/Makefile
include $(MOUSE_DIR)/lib/Makefile
include $(MOUSE_DIR)/thread/Makefile
include $(MOUSE_DIR)/userprog/Makefile
include $(MOUSE_DIR)/fs/Makefile

# ============================== 本目录源文件 ===========================================
KERNEL_SRCS := main.c init.c interrupt.c debug.c memory.c
KERNEL_ASMS := kernel.S

# ========================== 为各模块添加前缀路径 =======================================
# $(addprefix <prefix>,<names>)是一个内置函数,能将路径prefix添加到names前
# ======================================================================================
KERNEL_SRCS := $(addprefix $(MOUSE_DIR)/kernel/,$(KERNEL_SRCS))
KERNEL_ASMS := $(addprefix $(MOUSE_DIR)/kernel/,$(KERNEL_ASMS))

LIB_KERNEL_SRCS := $(addprefix $(MOUSE_DIR)/lib/kernel/,$(LIB_KERNEL_SRCS))
LIB_KERNEL_ASMS := $(addprefix $(MOUSE_DIR)/lib/kernel/,$(LIB_KERNEL_ASMS))

LIB_USER_SRCS := $(addprefix $(MOUSE_DIR)/lib/user/,$(LIB_USER_SRCS))
LIB_USER_ASMS := $(addprefix $(MOUSE_DIR)/lib/user/,$(LIB_USER_ASMS))

DEVICE_SRCS := $(addprefix $(MOUSE_DIR)/device/,$(DEVICE_SRCS))
DEVICE_ASMS := $(addprefix $(MOUSE_DIR)/device/,$(DEVICE_ASMS))

LIB_SRCS := $(addprefix $(MOUSE_DIR)/lib/,$(LIB_SRCS))
LIB_ASMS := $(addprefix $(MOUSE_DIR)/lib/,$(LIB_ASMS))

THREAD_SRCS := $(addprefix $(MOUSE_DIR)/thread/,$(THREAD_SRCS))
THREAD_ASMS := $(addprefix $(MOUSE_DIR)/thread/,$(THREAD_ASMS))

USERPROG_SRCS := $(addprefix $(MOUSE_DIR)/userprog/,$(USERPROG_SRCS))
USERPROG_ASMS := $(addprefix $(MOUSE_DIR)/userprog/,$(USERPROG_ASMS))

FS_SRCS := $(addprefix $(MOUSE_DIR)/fs/,$(FS_SRCS))
FS_ASMS := $(addprefix $(MOUSE_DIR)/fs/,$(FS_ASMS))


# ========================== 汇总全部源文件 ================================================
ALL_C_SRCS := $(KERNEL_SRCS) $(LIB_KERNEL_SRCS) $(LIB_USER_SRCS) $(DEVICE_SRCS) $(LIB_SRCS) $(THREAD_SRCS) $(USERPROG_SRCS) $(FS_SRCS)
ALL_ASMS := $(KERNEL_ASMS) $(LIB_KERNEL_ASMS) $(LIB_USER_ASMS) $(DEVICE_ASMS) $(LIB_ASMS) $(THREAD_ASMS) $(USERPROG_ASMS) $(FS_ASMS)

# ========================== 生成对应的 .o 文件路径 ========================================
# $(filter <pattern...>,<text>):这个函数用于从 <text>中筛选出符合模式 <pattern>的单词
# $(patsubst <pattern>,<replacement>,<text>):将 <text>中所有匹配 <pattern>的单词替换为
# <replacement>的形式,<pattern>中可以使用通配符 %
# 通过这个函数,将生成的.o文件分别生成到build路径下的不同路径(这里除了用户,都生成到/build/kernel)
# =========================================================================================
OBJS_KERNEL := \
$(patsubst $(MOUSE_DIR)/%.c,$(BUILD_KERNEL_DIR)/%.o,$(filter $(MOUSE_DIR)/%.c,$(ALL_C_SRCS))) \
$(patsubst $(MOUSE_DIR)/%.S,$(BUILD_KERNEL_DIR)/%.o,$(filter $(MOUSE_DIR)/%.S,$(ALL_ASMS)))

# OBJS_KERNEL := \
# $(patsubst $(MOUSE_DIR)/%.c,$(BUILD_KERNEL_DIR)/%.o,$(filter $(MOUSE_DIR)/%.c,$(filter-out $(MOUSE_DIR)/lib/user/%,$(ALL_C_SRCS)))) \
# $(patsubst $(MOUSE_DIR)/%.S,$(BUILD_KERNEL_DIR)/%.o,$(filter $(MOUSE_DIR)/%.S,$(filter-out $(MOUSE_DIR)/lib/user/%,$(ALL_ASMS))))

# OBJS_USER := \
# $(patsubst $(MOUSE_DIR)/lib/user/%.c,$(BUILD_USER_DIR)/%.o,$(filter $(MOUSE_DIR)/lib/user/%.c,$(ALL_C_SRCS))) \
# $(patsubst $(MOUSE_DIR)/lib/user/%.S,$(BUILD_USER_DIR)/%.o,$(filter $(MOUSE_DIR)/lib/user/%.S,$(ALL_ASMS)))

KERNEL_BIN := $(BUILD_KERNEL_DIR)/kernel.bin

# ==================================== 主要规则 ==========================================
all: dirs kernel user

# 以@开头的指令,终端只会显示命令的输出,不显示命令本身--这里创建build文件夹
dirs:
@echo "Creating build directories..."
@mkdir -p $(BUILD_KERNEL_DIR) $(BUILD_USER_DIR)

kernel: $(KERNEL_BIN)
user: $(OBJS_USER)

$(KERNEL_BIN): $(OBJS_KERNEL) #$(OBJS_USER)
@echo "Linking kernel object files to generate kernel.bin..."
$(LD) $(LDFLAGS) -o $@ $(filter $(BUILD_KERNEL_DIR)/%.o,$^) $(filter $(BUILD_USER_DIR)/%.o,$^)
@echo "Kernel linking completed!"

# ================================= 编译规则 ============================================
$(BUILD_KERNEL_DIR)/%.o: $(MOUSE_DIR)/%.c | dirs
@echo "Compiling C: $<"
@mkdir -p $(@D)
$(CC) $(CFLAGS) -o $@ $<

$(BUILD_KERNEL_DIR)/%.o: $(MOUSE_DIR)/%.S | dirs
@echo "Assembling: $<"
@mkdir -p $(@D)
$(ASM) $(ASMFLAGS) -o $@ $<

$(BUILD_USER_DIR)/%.o: $(MOUSE_DIR)/lib/user/%.c | dirs
@echo "Compiling user C: $<"
@mkdir -p $(@D)
$(CC) $(CFLAGS) -o $@ $<

$(BUILD_USER_DIR)/%.o: $(MOUSE_DIR)/lib/user/%.S | dirs
@echo "Assembling user S: $<"
@mkdir -p $(@D)
$(ASM) $(ASMFLAGS) -o $@ $<

$(BUILD_USER_DIR)/%.o: $(MOUSE_DIR)/userprog/%.c | dirs
@echo "Compiling userprog C: $<"
@mkdir -p $(@D)
$(CC) $(CFLAGS) -o $@ $<

$(BUILD_USER_DIR)/%.o: $(MOUSE_DIR)/userprog/%.S | dirs
@echo "Assembling userprog S: $<"
@mkdir -p $(@D)
$(ASM) $(ASMFLAGS) -o $@ $<

# ================================== 镜像生成 ==========================================
img: $(KERNEL_BIN)
@echo "Writing kernel image to disk..."
dd if=$(KERNEL_BIN) of=$(IMG_PATH) bs=512 count=200 seek=9 conv=notrunc
@echo "Kernel image writing completed!"

# ================================== 清理规则 ==========================================
clean:
@echo "Cleaning build files..."
-rm -f $(BUILD_KERNEL_DIR)/*.o $(BUILD_KERNEL_DIR)/kernel.bin
-rm -f $(BUILD_USER_DIR)/*.o
-rm -rf $(BUILD_DIR)
@echo "Cleanup completed!"

mouse: clean all img
@echo "Mouse build process (clean -> all -> img) completed!"
1
2
3
4
5
6
7
8
# =================================================================================
# Makefile 子文件 /home/mouse/OS_mouse/tool/bochs/mouse/fs/Makefile
# =================================================================================

FS_SRCS := fs.c # 例如:thread.c, scheduler.c
FS_ASMS := # 例如:thread_switch.S

# 如果没有汇编文件,THREAD_ASMS 应为空

这个图片就是我们的简易文件系统的布局(来自书中)


B1.1 创建超级块、i 结点、目录项

话不多说,概念还是不说了,直接搓代码,这里创建了四个头文件,都使用用来描述文件系统一些结构体定义等…..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// /home/mouse/OS_mouse/tool/bochs/mouse/fs/super_blcok.h

#ifndef __FS_SUPER_BLOCK_H
#define __FS_SUPER_BLOCK_H、
#include "stdint.h"
#include "global.h"

//超级块
struct super_block{
uint32_t magic; // 用来标识文件系统类型,适用于多文件系统用来判断使用
uint32_t sec_cnt; // 本分区总共的扇区数
uint32_t inode_cnt; // 本分区中 inode 数量
uint32_t part_lba_base; // 本分区的起始 lba 地址

uint32_t block_bitmap_lba; // 块位图本身起始扇区地址
uint32_t block_bitmap_sects; // 扇区位图本身占用的扇区数量

uint32_t inode_bitmap_lba; // i 结点位图起始扇区 lba 地址
uint32_t inode_bitmap_sects; // i 结点位图占用的扇区数量

uint32_t inode_table_lba; // i 结点表起始扇区 lba 地址
uint32_t inode_table_sects; // i 结点表占用的扇区数量

uint32_t data_start_lba; // 数据区开始的第一个扇区号
uint32_t root_inode_no; // 根目录所在的 I 结点号
uint32_t dir_entry_size; // 目录项大小

uint8_t pad[460]; // 加上 460 字节,凑够 512 字节 1 扇区大小
} __attribute__ ((packed));

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// /home/mouse/OS_mouse/tool/bochs/mouse/fs/inode.h

#ifndef __FS_INODE_H
#define __FS_INODE_H
#include "stdint.h"
#include "global.h"
#include "list.h"

// inode 结构
struct inode {
uint32_t i_no; // inode 编号
/* 当此 inode 是文件时,i_size 是指文件大小,
若此 inode 是目录,i_size 是指该目录下所有目录项大小之和*/
uint32_t i_size;
uint32_t i_open_cnts; // 记录此文件被打开的次数
bool write_deny; // 写文件不能并行,进程写文件前检查此标识
/* i_sectors[0-11]是直接块,i_sectors[12]用来存储一级间接块指针 */
uint32_t i_sectors[13];
struct list_elem inode_tag; //记录是否是已经打开的inode
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// /home/mouse/OS_mouse/tool/bochs/mouse/fs/dir.h

#ifndef __FS_DIR_H
#define __FS_DIR_H
#include "stdint.h"
#include "global.h"
#include "fs.h"



#define MAX_FILE_NAME_LEN 16 // 最大文件名长度

// 目录结构
// 并不在磁盘上存在,只用于与目录相关的操作时,在内存中创建的结构,用过之后就释放了,不会回写到磁盘中
struct dir {
struct inode* inode;
uint32_t dir_pos; // 记录在目录内的偏移
uint8_t dir_buf[512]; // 目录的数据缓存
};

// 目录项结构
struct dir_entry {
char filename[MAX_FILE_NAME_LEN]; // 普通文件或目录名称
uint32_t i_no; // 普通文件或目录对应的 inode 编号
enum file_types f_type; // 文件类型
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// /home/mouse/OS_mouse/tool/bochs/mouse/fs/fs.h

#ifndef __FS_FS_H
#define __FS_FS_H
#include "stdint.h"
#include "global.h"
#include "list.h"

#define MAX_FILES_PER_PART 4096

// 每个分区所支持最大创建的文件数
#define BITS_PER_SECTOR 4096 // 每扇区的位数
#define SECTOR_SIZE 512 // 扇区字节大小
#define BLOCK_SIZE SECTOR_SIZE // 块字节大小

// 文件类型
enum file_types {
FT_UNKNOWN, // 不支持的文件类型
FT_REGULAR, // 普通文件
FT_DIRECTORY // 目录
};
#endif

B1.2 创建文件系统

本节开始创建文件系统,也就是平时咱们所说的高级格式化分区,相关的代码在fs/fs.c中,下面直接给出完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// /home/mouse/OS_mouse/tool/bochs/mouse/fs/fs.c
#include "fs.h"
#include "super_block.h"
#include "inode.h"
#include "dir.h"
#include "stdint.h"
#include "stdio-kernel.h"
#include "list.h"
#include "string.h"
#include "ide.h"
#include "global.h"
#include "debug.h"
#include "memory.h"

/* 格式化分区,也就是初始化分区的元信息,创建文件系统 */
static void partition_format(struct partition* part) {
/* 为方便实现,一个块大小是一扇区 */
uint32_t boot_sector_sects = 1;
uint32_t super_block_sects = 1;
uint32_t inode_bitmap_sects = DIV_ROUND_UP(MAX_FILES_PER_PART, BITS_PER_SECTOR); // I结点位图占用的扇区数.最多支持4096个文件
uint32_t inode_table_sects = DIV_ROUND_UP(((sizeof(struct inode) * MAX_FILES_PER_PART)), SECTOR_SIZE);
uint32_t used_sects = boot_sector_sects + super_block_sects + inode_bitmap_sects + inode_table_sects;
uint32_t free_sects = part->sec_cnt - used_sects;

/************** 简单处理块位图占据的扇区数 ***************/
uint32_t block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
/* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects;
block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);
/*********************************************************/

/* 超级块初始化 */
struct super_block sb;
sb.magic = 0x19590318;
sb.sec_cnt = part->sec_cnt;
sb.inode_cnt = MAX_FILES_PER_PART;
sb.part_lba_base = part->start_lba;

sb.block_bitmap_lba = sb.part_lba_base + 2; // 第0块是引导块,第1块是超级块
sb.block_bitmap_sects = block_bitmap_sects;

sb.inode_bitmap_lba = sb.block_bitmap_lba + sb.block_bitmap_sects;
sb.inode_bitmap_sects = inode_bitmap_sects;

sb.inode_table_lba = sb.inode_bitmap_lba + sb.inode_bitmap_sects;
sb.inode_table_sects = inode_table_sects;

sb.data_start_lba = sb.inode_table_lba + sb.inode_table_sects;
sb.root_inode_no = 0;
sb.dir_entry_size = sizeof(struct dir_entry);

printk("%s info:\n", part->name);
printk(" magic:0x%x\n part_lba_base:0x%x\n all_sectors:0x%x\n inode_cnt:0x%x\n block_bitmap_lba:0x%x\n block_bitmap_sectors:0x%x\n inode_bitmap_lba:0x%x\n inode_bitmap_sectors:0x%x\n inode_table_lba:0x%x\n inode_table_sectors:0x%x\n data_start_lba:0x%x\n", sb.magic, sb.part_lba_base, sb.sec_cnt, sb.inode_cnt, sb.block_bitmap_lba, sb.block_bitmap_sects, sb.inode_bitmap_lba, sb.inode_bitmap_sects, sb.inode_table_lba, sb.inode_table_sects, sb.data_start_lba);

struct disk* hd = part->my_disk;
/*******************************
* 1 将超级块写入本分区的1扇区 *
******************************/
ide_write(hd, part->start_lba + 1, &sb, 1);
printk(" super_block_lba:0x%x\n", part->start_lba + 1);

/* 找出数据量最大的元信息,用其尺寸做存储缓冲区*/
uint32_t buf_size = (sb.block_bitmap_sects >= sb.inode_bitmap_sects ? sb.block_bitmap_sects : sb.inode_bitmap_sects);
buf_size = (buf_size >= sb.inode_table_sects ? buf_size : sb.inode_table_sects) * SECTOR_SIZE;
uint8_t* buf = (uint8_t*)sys_malloc(buf_size); // 申请的内存由内存管理系统清0后返回

/**************************************
* 2 将块位图初始化并写入sb.block_bitmap_lba *
*************************************/
/* 初始化块位图block_bitmap */
buf[0] |= 0x01; // 第0个块预留给根目录,位图中先占位
uint32_t block_bitmap_last_byte = block_bitmap_bit_len / 8;
uint8_t block_bitmap_last_bit = block_bitmap_bit_len % 8;
uint32_t last_size = SECTOR_SIZE - (block_bitmap_last_byte % SECTOR_SIZE); // last_size是位图所在最后一个扇区中,不足一扇区的其余部分

/* 1 先将位图最后一字节到其所在的扇区的结束全置为1,即超出实际块数的部分直接置为已占用*/
memset(&buf[block_bitmap_last_byte], 0xff, last_size);

/* 2 再将上一步中覆盖的最后一字节内的有效位重新置0 */
uint8_t bit_idx = 0;
while (bit_idx <= block_bitmap_last_bit) {
buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
}
ide_write(hd, sb.block_bitmap_lba, buf, sb.block_bitmap_sects);

/***************************************
* 3 将inode位图初始化并写入sb.inode_bitmap_lba *
***************************************/
/* 先清空缓冲区*/
memset(buf, 0, buf_size);
buf[0] |= 0x1; // 第0个inode分给了根目录
/* 由于inode_table中共4096个inode,位图inode_bitmap正好占用1扇区,
* 即inode_bitmap_sects等于1, 所以位图中的位全都代表inode_table中的inode,
* 无须再像block_bitmap那样单独处理最后一扇区的剩余部分,
* inode_bitmap所在的扇区中没有多余的无效位 */
ide_write(hd, sb.inode_bitmap_lba, buf, sb.inode_bitmap_sects);

/***************************************
* 4 将inode数组初始化并写入sb.inode_table_lba *
***************************************/
/* 准备写inode_table中的第0项,即根目录所在的inode */
memset(buf, 0, buf_size); // 先清空缓冲区buf
struct inode* i = (struct inode*)buf;
i->i_size = sb.dir_entry_size * 2; // .和..
i->i_no = 0; // 根目录占inode数组中第0个inode
i->i_sectors[0] = sb.data_start_lba; // 由于上面的memset,i_sectors数组的其它元素都初始化为0
ide_write(hd, sb.inode_table_lba, buf, sb.inode_table_sects);

/***************************************
* 5 将根目录初始化并写入sb.data_start_lba
***************************************/
/* 写入根目录的两个目录项.和.. */
memset(buf, 0, buf_size);
struct dir_entry* p_de = (struct dir_entry*)buf;

/* 初始化当前目录"." */
memcpy(p_de->filename, ".", 1);
p_de->i_no = 0;
p_de->f_type = FT_DIRECTORY;
p_de++;

/* 初始化当前目录父目录".." */
memcpy(p_de->filename, "..", 2);
p_de->i_no = 0; // 根目录的父目录依然是根目录自己
p_de->f_type = FT_DIRECTORY;

/* sb.data_start_lba已经分配给了根目录,里面是根目录的目录项 */
ide_write(hd, sb.data_start_lba, buf, 1);

printk(" root_dir_lba:0x%x\n", sb.data_start_lba);
printk("%s format done\n", part->name);
sys_free(buf);
}

/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {
uint8_t channel_no = 0, dev_no, part_idx = 0;

/* sb_buf用来存储从硬盘上读入的超级块 */
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);

if (sb_buf == NULL) {
PANIC("alloc memory failed!");
}
printk("searching filesystem......\n");
while (channel_no < channel_cnt) {
dev_no = 0;
while(dev_no < 2) {
if (dev_no == 0) { // 跨过裸盘hd60M.img
dev_no++;
continue;
}
struct disk* hd = &channels[channel_no].devices[dev_no];
struct partition* part = hd->prim_parts;
while(part_idx < 12) { // 4个主分区+8个逻辑
if (part_idx == 4) { // 开始处理逻辑分区
part = hd->logic_parts;
}

/* channels数组是全局变量,默认值为0,disk属于其嵌套结构,
* partition又为disk的嵌套结构,因此partition中的成员默认也为0.
* 若partition未初始化,则partition中的成员仍为0.
* 下面处理存在的分区. */
if (part->sec_cnt != 0) { // 如果分区存在
memset(sb_buf, 0, SECTOR_SIZE);

/* 读出分区的超级块,根据魔数是否正确来判断是否存在文件系统 */
ide_read(hd, part->start_lba + 1, sb_buf, 1);

/* 只支持自己的文件系统.若磁盘上已经有文件系统就不再格式化了 */
if (sb_buf->magic == 0x19590318) {
printk("%s has filesystem\n", part->name);
} else { // 其它文件系统不支持,一律按无文件系统处理
printk("formatting %s`s partition %s......\n", hd->name, part->name);
partition_format(part);
}
}
part_idx++;
part++; // 下一分区
}
dev_no++; // 下一磁盘
}
channel_no++; // 下一通道
}
sys_free(sb_buf);
}

注意要在初始化总文件里面添加filesys_init()函数

这里可以运行测试,第一次运行会显示自动创建很多了新的文件系统(格式化),因为原本磁盘中是没有的;第二次运行会发现显示的是已经存在了文件系统,不会再进行格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#第一次
sdb8 format done
formatting sdb's partition sdb9..
sdb9 info:
magic:0x19590318
part_lba_base:Ox1D8BF
all_sectors:OxA521
inode_cnt:0x1000
block_bitmap_lba:Ox1D8C1
block_bitmap_sectors:OxB
inode_bitmap_lba:Ox1D8CC
inode_bitmap_sectors:0x1
inode_table_Iba:Ox1D8CD
inode_table_sectors:0x260
data_start_lba:Ox1DB2D
super_block_lba:Ox1D8C0
root_dir_lba:Ox1DB2D
sdb9 format done
init_all done
1
2
3
4
5
6
7
8
9
10
#第二次
ide_init done
searching filesystem.....
sdb1 has filesystem
sdb5 has filesystem
sdb6 has filesystem
sdb7 has filesystem
sdb8 has filesystem
sdb9 has filesystem
init_all done

B1.3 挂载分区

Windows 系统的分区盘符简单明了,C、D、E 盘直接摆在那,用不用都看得到。而 Linux 中却大不相同,分区使用的时候可以单独“拿”出来,不用的时候还可以“收”起来

这里来实现挂载sdb1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// /home/mouse/OS_mouse/tool/bochs/mouse/fs/fs.h

//略.........

struct partition* cur_part; // 默认情况下操作的是哪个分区

/* 在分区链表中找到名为part_name的分区,并将其指针赋值给cur_part */
static bool mount_partition(struct list_elem* pelem, int arg) {
char* part_name = (char*)arg;
struct partition* part = elem2entry(struct partition, part_tag, pelem);
if (!strcmp(part->name, part_name)) {
cur_part = part;
struct disk* hd = cur_part->my_disk;

/* sb_buf用来存储从硬盘上读入的超级块 */
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);

/* 在内存中创建分区cur_part的超级块 */
cur_part->sb = (struct super_block*)sys_malloc(sizeof(struct super_block));
if (cur_part->sb == NULL) {
PANIC("alloc memory failed!");
}

/* 读入超级块 */
memset(sb_buf, 0, SECTOR_SIZE);
ide_read(hd, cur_part->start_lba + 1, sb_buf, 1);

/* 把sb_buf中超级块的信息复制到分区的超级块sb中。*/
memcpy(cur_part->sb, sb_buf, sizeof(struct super_block));

/********** 将硬盘上的块位图读入到内存 ****************/
cur_part->block_bitmap.bits = (uint8_t*)sys_malloc(sb_buf->block_bitmap_sects * SECTOR_SIZE);
if (cur_part->block_bitmap.bits == NULL) {
PANIC("alloc memory failed!");
}
cur_part->block_bitmap.btmp_bytes_len = sb_buf->block_bitmap_sects * SECTOR_SIZE;
/* 从硬盘上读入块位图到分区的block_bitmap.bits */
ide_read(hd, sb_buf->block_bitmap_lba, cur_part->block_bitmap.bits, sb_buf->block_bitmap_sects);
/*************************************************************/

/********** 将硬盘上的inode位图读入到内存 ************/
cur_part->inode_bitmap.bits = (uint8_t*)sys_malloc(sb_buf->inode_bitmap_sects * SECTOR_SIZE);
if (cur_part->inode_bitmap.bits == NULL) {
PANIC("alloc memory failed!");
}
cur_part->inode_bitmap.btmp_bytes_len = sb_buf->inode_bitmap_sects * SECTOR_SIZE;
/* 从硬盘上读入inode位图到分区的inode_bitmap.bits */
ide_read(hd, sb_buf->inode_bitmap_lba, cur_part->inode_bitmap.bits, sb_buf->inode_bitmap_sects);
/*************************************************************/

list_init(&cur_part->open_inodes);
printk("mount %s done!\n", part->name);

/* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
return true;
}
return false; // 使list_traversal继续遍历
}

//略.........

/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {

//略.........

sys_free(sb_buf);

/* 确定默认操作的分区 */
char default_part[8] = "sdb1";
/* 挂载分区 */
list_traversal(&partition_list, mount_partition, (int)default_part);
}

然后重新运行可以在结尾看到一句话mount sdb1 done!


B.2 文件描述符

这里跳过了文件描述符的简介……

笼统地说,创建文件描述符的过程就是逐层在这三个数据结构中找空位,在该空位填充好数据后返回该位置的地址,比如:
(1)在全局的 inode 队列中新建一 inode(这肯定是在空位置处新建),然后返回该 inode 地址。
(2)在全局的文件表中的找一空位,在该位置填充文件结构,使其 fd_inode 指向上一步中返回的 inode
地址,然后返回本文件结构在文件表中的下标值。
(3)在 PCB 中的文件描述符数组中找一空位,使该位置的值指向上一步中返回的文件结构下标,并返回本文件描述符在文件描述符数组中的下标值

B2.1 文件描述符的实现

首先,修改pcb的结构体,创建文件描述符数组,并在init_thread中初始化它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// /home/mouse/OS_mouse/tool/bochs/mouse/thread/thread.h

//略.....

#define MAX_FILES_OPEN_PER_PROC 8 //每个任务可以打开的文件数

//略.....

/* 进程或线程的 pcb,程序控制块 */
struct task_struct {
uint32_t* self_kstack; // 各内核线程都用自己的内核栈,注意为首位
pid_t pid; // 任务的pid
enum task_status status; // 任务状态
uint8_t priority; // 线程优先级
char name[16]; // 名称
uint8_t ticks; // 每次在处理器上执行的时间滴答数,每次时钟中断都会对其减1,到0则换下处理器
uint32_t elapsed_ticks; // 从cpu运行至今占用了多少cpu滴答数
int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; //文件描述符数组
struct list_elem general_tag; //用于线程在一般队列的结点
struct list_elem all_list_tag; //用于线程队列thread_all_list的结点
uint32_t* pgdir; //进程自己的虚拟地址,为空则说明为内核线程

struct virtual_addr userprog_vaddr; //用户进程的虚拟地址
struct mem_block_desc u_block_desc[DESC_CNT]; //用户进程内存块描述符

uint32_t stack_magic; // 栈的边界标记,用于检测栈的溢出,注意为尾部
};

//略.....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// /home/mouse/OS_mouse/tool/bochs/mouse/thread/thread.c

//略.....
//初始化线程的基本信息
void init_thread(struct task_struct* pthread,char*name,int prio)
{
memset(pthread,0,sizeof(*pthread)); //先清零所有内容
pthread->pid = allocate_pid(); //获取pid
strcpy(pthread->name,name); //名称

//由于把main函数也封装成一个线程,并且也是一直运行的,所以直接设置成TASK_RUNNING,其余设置成就绪态
if(pthread == main_thread)
{
pthread->status = TASK_RUNNING;
}
else
{
pthread->status = TASK_READY;
}

//self_kstack是线程自己内核态下使用的栈顶地址
pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
pthread->priority = prio; //优先级
pthread->ticks = prio; //时间片
pthread->elapsed_ticks = 0; //0表示还没有开始运行
pthread->pgdir = NULL;

//预留标准输入输出(文件描述符数组)--0 是标准输入,1 是标准输出,2 是标准错误
pthread->fd_table[0] = 0;
pthread->fd_table[1] = 1;
pthread->fd_table[2] = 2;
uint8_t fd_idx = 3;
while (fd_idx < MAX_FILES_OPEN_PER_PROC)//其余的全置为-1
{
pthread->fd_table[fd_idx] = -1;
fd_idx++;
}

pthread->stack_magic = 0x22332233; //自定义的魔数,同时它也是栈的边界
}

//略.....

B.3 有点晕了咳咳(创建文件,打开/关闭文件,写入文件…….)

这里后面我将fs文件夹的文件都贴出来吧,因为确实看晕了,并且内容比较碎片,都是一点一点添加的内容,不好总结,大家可以自行看看书中内容理解

消化一段时间再继续学习吧,最近可能要做点别的东西………….

C 系统交互

D 完结撒花

参考文献

  1. [操作系统真象还原 (郑纲) (Z-Library)] — 大家可以自己在网上查找相关资源

留言

有问题请指出,你可以选择以下方式:

  1. 在下方评论区留言
  2. 邮箱留言
  • Title: 真象还原 --文件系统/系统交互 study(5)
  • Author: H_Haozi
  • Created at : 2025-11-10 15:01:00
  • Updated at : 2025-11-27 14:15:17
  • Link: https://redefine.ohevan.com/2025/11/10/os_elephant_five/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments