本章将实现一个很基础的文件系统,并实现一些简单的内核文件操作,最终,我们将用从文件系统读入 ELF 文件的方式来创建进程。
8.1 SimpleFS
1、本节将对 Unix File System 的一个简单的实现——SimpleFileSystem 进行魔改,但是依旧沿用这个名字,因为这个文件系统足够 simple。
2、超级块
typedef struct {
uint32 magic; // 魔数
uint32 blocks; // 总磁盘块数
uint32 unusedBlocks; // 未使用的磁盘块数
uint32 freemapBlocks; // freemap 块数
uint8 info[32]; // 其他信息
} SuperBlock;
- 第一个字段就是 magic number。这个字段恒为
0x4D534653
,即 ASCII 码 “M”、“S”、“F”、“S”。在操作系统初始化文件系统时,会首先检查魔数以确定文件系统类型 - blocks 字段表示这个文件系统一共占用多少个磁盘块
- unusedBlocks 字段表示这个文件系统目前剩余的空闲磁盘块个数
- freemapBlocks 表示 Freemap 块的总个数,这个字段的更重要用处在于推断出 Root Inode 所在的磁盘块号。
- 最后就是 info 字段,这是一个字符串,记录了一些其他信息。
- 超级块的实际内容很小,只有 48 字节,超级块的剩余空间由 0 填充。
3、Inode ,一个 Inode 块代表了一个文件或文件夹,结构如下:
typedef struct
{
uint32 size; // 文件大小,type 为文件夹时该字段为 0
uint32 type; // 文件类型
uint8 filename[32]; // 文件名称
uint32 blocks; // 占据磁盘块个数
uint32 direct[12]; // 直接磁盘块
uint32 indirect; // 间接磁盘块
} Inode;
- type 字段表示这个 Inode 所代表的文件的类型,可用取值有
TYPE_FILE
(普通文件) 或TYPE_DIR
(文件夹)。当 type 取TYPE_DIR
时,size 字段为 0。并且直接磁盘块和间接磁盘块都是指向存储在该文件夹下的文件的 Inode。当 type 取TYPE_FILE
时,磁盘块指向实际的数据块。 - 注意,当 Inode 类型为文件夹时,
direct[0]
和direct[1]
分别存储指向当前和指向上一级文件夹的 Inode。 - 关于直接磁盘块和间接磁盘块,示意图如下:
- 每个 Inode 块中有一个长度为 12 的 direct 数据,如果 blocks 字段小于等于 12,可以直接使用该数组存储磁盘块号,否则,由 indirect 字段指向一个 Indirect Block,在该磁盘块中可以存储更多的磁盘块号。
- Inode 块的剩余空间也由 0 填充。
4、相关数据
- 一个 Freemap 块可以表示 32K 个磁盘块的空闲状况(一个磁盘块为
4K
大小,共32K
个bit位) - 一个文件夹下,除去
“.”
和“..”
以外,最多可以存储1034
个文件或文件夹(12+1024) - 单个普通文件大小最大为 4.04 MB(
1034*4K=4.04M
)
8.2 打包镜像
1、多用户程序编译
- 我们将 user 文件夹下的文件分成两类,一类是依赖,一类是用户编写的应用程序
UPROSBASE = \
$U/entry.o \
$U/malloc.o \
$U/io.o
UPROS = \
hello \
hello2
- 这里定义了两个应用程序,hello2 仅仅是把 hello 复制了一遍,并修改了输出。
- 接着我们就需要遍历
UPROS
,来将每一个用户程序都编译成一个可执行文件,而不是把它们全都链接成一个文件。
User: $(subst .c,.o,$(wildcard $U/*.c))
mkdir -p rootfs/bin
for file in $(UPROS); do \
$(LD) $(LDFLAGS) -o rootfs/bin/$$file $(UPROSBASE) $U/$$file.o; \
done
User
任务首先会创建文件夹rootfs/bin
,所有编译出的可执行文件都会被放置在这个文件夹下。之后就是将每个应用程序都和其依赖链接起来,链接成一个可执行文件。
2、经过以上过程后,所有的用户程序都会被编译成可执行文件放在 rootfs/bin
文件夹下。现在我们要以 rootfs
作为文件系统的根目录,将其打包成一个 SimpleFS 格式的镜像。
- 首先要定义文件系统的一些结构:
// mkfs/simplefs.h
#ifndef _SIMPLEFS_H
#define _SIMPLEFS_H
#include "types.h"
#define BLOCK_SIZE 4096
#define MAGIC_NUM 0x4D534653U // MSFS
typedef struct {
uint32 magic; // 魔数
uint32 blocks; // 总磁盘块数
uint32 unusedBlocks; // 未使用的磁盘块数
uint32 freemapBlocks; // freemap 块数
uint8 info[32]; // 其他信息
} SuperBlock;
#define TYPE_FILE 0
#define TYPE_DIR 1
typedef struct
{
uint32 size; // 文件大小,type 为文件夹时该字段为0
uint32 type; // 文件类型
uint8 filename[32]; // 文件名称
uint32 blocks; // 占据磁盘块个数
uint32 direct[12]; // 直接磁盘块
uint32 indirect; // 间接磁盘块
} Inode;
#endif
- 接着就是按部就班地填充各个块了。我们定义的一个
Image
字节数组,它的大小和文件系统一致,所有的块都首先写入Image
中,最后再将Image
保存成文件。
// mkfs/mksfs.c
/*
* mksfs.c 用于将一个文件夹作为根目录打包成一个 SimpleFS 镜像文件
*
* SimpleFS 镜像组成如下:
* +-------+------+ +------+------+------+ +------+
* | Super | Free | ... | Free | Root |Other | ... |Other |
* | Block | Map | | Map |Inode |Inode | |Inode |
* +-------+------+ +------+------+------+ +------+
*/
#include "types.h"
#include "simplefs.h"
// 标准库
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
// 总块数 256 块,大小为 1M
#define BLOCK_NUM 256
// Freemap 块的个数
#define FREEMAP_NUM 1
// 定义 Image 字节数组,它的大小和文件系统一致
// 所有的块都首先写入 Image 中,最后再将 Image 保存成文件
// 最终的镜像数据
char Image[BLOCK_SIZE * BLOCK_NUM];
// 临时的 freemap,最后需要写入 Image,此时一个 char 代表一块
char freemap[BLOCK_NUM];
uint32 freenum = BLOCK_NUM;
// 被打包的文件夹名称
char *rootdir = "rootfs";
void walk(char *dirName, Inode *nowInode, uint32 nowInodeNum);
uint64 getBlockAddr(int blockNum);
int getFreeBlock();
void copyInodeToBlock(int blockNum, Inode *in);
// 初始化各个块,并写入 Image 中
void
main()
{
// 最开始的几块分别是 超级块,freemap 块 和 root 文件夹所在的 inode
freemap[0] = 1;
int i;
// 设置 freemap 块为已占用
for(i = 0; i < FREEMAP_NUM; i ++) freemap[1+i] = 1;
freemap[FREEMAP_NUM + 1] = 1; // 设置根目录块为已占用
freenum -= (FREEMAP_NUM + 2); // 更新空闲块数量
// 填充 superblock 信息
SuperBlock spBlock;
spBlock.magic = MAGIC_NUM; // 魔数
spBlock.blocks = BLOCK_NUM; // 文件系统总块数
spBlock.freemapBlocks = FREEMAP_NUM; // 空闲块数
char *info = "SimpleFS By JokerDebug"; // 文件系统信息
for(i = 0; i < strlen(info); i ++) {
spBlock.info[i] = info[i];
}
spBlock.info[i] = '\0';
// 设置根 inode
Inode rootInode;
rootInode.size = 0; // 目录文件大小为0
rootInode.type = TYPE_DIR; // 文件类型为目录
rootInode.filename[0] = '/'; rootInode.filename[1] = '\0'; // 根目录文件名
// 递归遍历根文件夹,并设置和填充数据
// 递归遍历文件夹,为每个文件和文件夹创建 Inode 并填充信息
walk(rootdir, &rootInode, FREEMAP_NUM+1);
spBlock.unusedBlocks = freenum; // 设置超级块中未使用的块数
// 将超级块写入 Image
char *ptr = (char *)getBlockAddr(0), *src = (char *)&spBlock;
for(i = 0; i < sizeof(spBlock); i ++) {
ptr[i] = src[i];
}
// 将 freemap 写入 Image
ptr = (char *)getBlockAddr(1);
for(i = 0; i < BLOCK_NUM/8; i ++) {
char c = 0;
int j;
for(j = 0; j < 8; j ++) {
if(freemap[i*8+j]) {
c |= (1 << j);
}
}
*ptr = c;
ptr ++;
}
// 将 rootInode 写入 Image
copyInodeToBlock(FREEMAP_NUM+1, &rootInode);
// 将 Image 写到磁盘上
FILE *img = fopen("fs.img", "w+b");
fwrite(Image, sizeof(Image), 1, img);
fflush(img); fclose(img);
}
walk()
函数用于递归遍历文件夹,为每个文件和文件夹创建Inode
并填充信息
// mkfs/mksfs.c
/* 根据块号获取 Image 中的块的起始地址 */
uint64
getBlockAddr(int blockNum) {
void *addr = (void *)Image; // Image起始地址
addr += (blockNum * BLOCK_SIZE); // 加上偏移
return (uint64)addr;
}
// 递归遍历文件夹,为每个文件和文件夹创建 Inode 并填充信息,将文件的 inode 写入磁盘块
// dirName 当前文件夹名,nowInode 为当前文件夹的 Inode,nowInodeNum 为其 Inode 号
void
walk(char *dirName, Inode *nowInode, uint32 nowInodeNum)
{
// 打开当前文件夹
DIR *dp = opendir(dirName);
struct dirent *dirp;
// 文件夹下第一个文件为其自己
nowInode->direct[0] = nowInodeNum;
if(!strcmp(dirName, rootdir)) { // 判断是否为根目录
// 若在根目录,则无上一级,上一级文件夹也为其自己
nowInode->direct[1] = nowInodeNum;
}
// 下一个文件的序号
int emptyIndex = 2; // 初始化空闲位置索引,从第 2 个位置开始
// 遍历当前文件夹下所有文件,创建 Inode 并填充信息
while((dirp = readdir(dp))) {
if(!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, "..")) {
// 跳过 . 和 ..
continue;
}
int blockNum;
if(dirp->d_type == DT_DIR) {
// 文件夹处理,递归遍历
Inode dinode;
dinode.size = 0; // 设置文件夹 inode 大小为 0
dinode.type = TYPE_DIR; // 设置文件夹类型
int i;
for(i = 0; i < strlen(dirp->d_name); i ++) {
dinode.filename[i] = dirp->d_name[i]; // 将文件夹名称复制到 inode 的 filename
}
dinode.filename[i] = '\0';
blockNum = getFreeBlock(); // 获取一个空闲的块来存储文件夹
// 文件夹的前两个文件分别为 . 和 ..
dinode.direct[0] = blockNum; // 当前文件夹
dinode.direct[1] = nowInodeNum; // 父文件夹
char *tmp = (char *)malloc(strlen(dirName) + strlen(dirp->d_name) + 1);
sprintf(tmp, "%s/%s", dirName, dirp->d_name); // 拼接文件夹的完整路径
walk(tmp, &dinode, blockNum); // 递归处理子文件夹
copyInodeToBlock(blockNum, &dinode); // 将文件夹的 inode 写入磁盘块
} else if(dirp->d_type == DT_REG) {
// 普通文件处理
Inode finode;
finode.type = TYPE_FILE;
int i;
for(i = 0; i < strlen(dirp->d_name); i ++) {
finode.filename[i] = dirp->d_name[i]; // 将文件名称复制到 inode 的 filename
}
finode.filename[i] = '\0';
char *tmp = (char *)malloc(strlen(dirName) + strlen(dirp->d_name) + 1);
sprintf(tmp, "%s/%s", dirName, dirp->d_name); // 拼接文件的完整路径
// 获取文件信息
struct stat buf;
stat(tmp, &buf); // 获取文件的状态信息
finode.size = buf.st_size; // 设置文件大小
finode.blocks = (finode.size - 1) / BLOCK_SIZE + 1; // 计算文件所占的块数
blockNum = getFreeBlock(); // 获取一个空闲的块来存储文件数据
// 将文件数据复制到对应的块
uint32 l = finode.size; // 剩余未拷贝的大小
int blockIndex = 0;
FILE *fp = fopen(tmp, "rb"); // 以二进制读取文件
while(l) {
int ffb = getFreeBlock(); // 获取空闲块
char *buffer = (char *)getBlockAddr(ffb); // 获取块的地址
size_t size;
if(l > BLOCK_SIZE) size = BLOCK_SIZE; // 如果剩余数据大于块大小,则取块大小
else size = l; // 否则剩余的大小为实际大小
fread(buffer, size, 1, fp); // 将文件内容读取到 buffer
l -= size; // 减少剩余大小
if(blockIndex < 12) {
finode.direct[blockIndex] = ffb; // 前 12 块直接存储数据
} else {
// 12 个间接块均已使用
if(finode.indirect == 0) {
finode.indirect = getFreeBlock(); // 如果间接块未分配,分配一个空闲块
}
uint32 *inaddr = (uint32 *)getBlockAddr(finode.indirect); // 获取间接块的地址
inaddr[blockIndex - 12] = ffb; // 将块号保存到间接块中(共可存储1024个块,减去12的直接块偏移)
}
blockIndex ++; // 增加块索引
}
fclose(fp); // 关闭文件
copyInodeToBlock(blockNum, &finode); // 将文件的 inode 写入磁盘块
} else {
continue; // 跳过其他类型的文件
}
// 更新当前文件夹 nowInode 的信息
if(emptyIndex < 12) {
// 如果直接块未满,直接将块号存储到 nowInode 的 direct 数组中
nowInode->direct[emptyIndex] = blockNum;
} else {
if(nowInode->indirect == 0) {
nowInode->indirect = getFreeBlock(); // 如果间接块未分配,分配一个空闲块
}
uint32 *inaddr = (uint32 *)getBlockAddr(nowInode->indirect); // 获取间接块的地址
inaddr[emptyIndex - 12] = blockNum; // 将块号存储到间接块中
}
emptyIndex ++; // 增加空闲位置索引
}
closedir(dp); // 关闭当前文件夹
nowInode->blocks = emptyIndex; // 更新当前文件夹所占的块数
}
getFreeBlock()
函数用于从freemap
中找到一个空闲的块,将其标记为占用并返回其块号
// mkfs/mksfs.c
// 从 freemap 中找到一个空闲的块,将其标记为占用并返回其块号
int getFreeBlock() {
int i;
// 遍历freemap标志位
for(i = 0; i < BLOCK_NUM; i ++) {
if(!freemap[i]) {
freemap[i] = 1; // 标记为占用
freenum --; // 更新空闲块数
return i;
}
}
printf("get free block failed!\n");
exit(1);
}
3、在 Makefile 中新建一个 target,用来编译这个打包工具。此时就不需要使用 RISCV 工具链了(riscv64-linux-gnu-objdump),因为我们需要在当前机器上完成打包过程,所以直接使用 gcc 即可。编译完成后,在 User 运行的最后执行这个打包工具。
mksfs:
gcc mkfs/mksfs.c -o mksfs
User: mksfs $(subst .c,.o,$(wildcard $U/*.c))
mkdir -p rootfs/bin
for file in $(UPROS); do \
$(LD) $(LDFLAGS) -o rootfs/bin/$$file $(UPROSBASE) $U/$$file.o; \
done
./mksfs
- 打包后的结果为
fs.img
文件,大小为 1M,和我们在代码中定义的大小相同。
4、合并内核
- 这里为了简化过程,依旧是将文件系统的内容直接链接到 .data 段,这样做可以更加注重文件系统相关的操作,而不是去适配 QEMU 的驱动。
- 只需要修改之前用来链接 ELF 文件的汇编即可。
// kernel/linkFS.asm
# 将文件系统链接到 .data 段
.section .data
.global _fs_img_start
.global _fs_img_end
_fs_img_start:
.incbin "fs.img"
_fs_img_end:
8.3 内核文件驱动
1、目前的需求是,可以根据一个路径,从文件系统中找到这个文件的 Inode,并且读取它的所有数据到一个字节数组中。基于这个需求,我们先定义一些函数,创建文件等功能可以后续再支持。
- 文件系统初始化:找到 root inode 即可。
// kernel/fs.c
Inode *ROOT_INODE; // 声明一个指针 ROOT_INODE,指向根目录的 Inode 结构
char *FREEMAP; // 声明一个指针 FREEMAP,指向文件系统的 freemap
// 初始化文件系统(只需要找到 root inode 即可)
void
initFs()
{
// 获取 freemap 块地址
FREEMAP = (char *)getBlockAddr(1);
// 获取超级块的起始地址,_fs_img_start 是文件系统镜像的起始位置(在 linkUser 中定义)
SuperBlock* spBlock = (SuperBlock *)_fs_img_start;
// 根目录的 Inode 紧接在 freemap 块之后
ROOT_INODE = (Inode *)getBlockAddr(spBlock->freemapBlocks + 1);
}
- 因为线程初始化要从文件系统中读取 ELF 文件,所以文件系统的初始化需要在线程之前
// kernel/main.c
void main()
{
extern void initInterrupt(); initInterrupt(); // 设置中断处理程序入口 和 模式
extern void initTimer(); initTimer(); // 时钟中断初始化
extern void initMemory(); initMemory(); // 初始化 页分配 和 动态内存分配
extern void mapKernel(); mapKernel(); // 内核重映射,三级页表机制
extern void initFs(); initFs(); // 初始化文件系统
extern void initThread(); initThread(); // 初始化线程管理
extern void runCPU(); runCPU(); // 切换到 idle 调度线程,表示正式由 CPU 进行线程管理和调度
while(1) {}
}
lookup()
函数将传入的 Inode 作为当前目录,从当前目录下根据路径查找文件。例如,若当前目录为 /usr,传入./hello
和hello
甚至../usr/hello
都可以找到可执行文件 hello。如果传入的路径以/
开头,函数会忽略当前路径而从根目录下开始查找。如果传入的 Inode 为 null,函数也会从根目录开始查找。- 函数根据路径分割出需要在当前目录下查找的文件或文件夹名称,并递归调用 lookup 函数进一步搜索。
// kernel/fs.c
/*
* 传入的 Inode 作为当前目录,从当前目录下根据路径查找文件
* 若当前目录为 /usr,传入 ./hello 和 hello 甚至 ../usr/hello 都可以找到可执行文件 hello
* 如果传入的路径以 / 开头,函数会忽略当前路径而从根目录下开始查找
* 如果传入的 Inode 为 null,函数也会从根目录开始查找
*/
Inode *
lookup(Inode *node, char *filename)
{
// 如果文件名以 '/' 开头,表示从根目录开始查找
if(filename[0] == '/') {
node = ROOT_INODE; // 将当前节点指向根目录的 Inode
filename ++; // 跳过 '/' 字符
}
// 如果当前节点为 NULL,则设为根目录 Inode
if(node == 0) node = ROOT_INODE;
// 如果文件名为空(表示已找到目标 Inode),返回当前节点
if(*filename == '\0') return node;
// 如果当前节点不是目录类型,则返回 NULL
if(node->type != TYPE_DIR) return 0;
// 创建一个字符串变量用来存储目标文件名
char cTarget[strlen(filename) + 1];
int i = 0;
// 从文件名中提取出一个部分(直到遇到 '/' 或字符串结束)
while (*filename != '/' && *filename != '\0') {
cTarget[i] = *filename;
filename ++;
i ++;
}
cTarget[i] = '\0';
// 如果文件名后面还有 '/',则跳过它
if(*filename == '/') filename ++;
// 如果目标文件名是 ".",表示当前目录,递归调用 lookup 查找下一个部分
if(!strcmp(".", cTarget)) {
return lookup(node, filename);
}
// 如果目标文件名是 "..",表示父目录,递归查找父目录
if(!strcmp("..", cTarget)) {
Inode *upLevel = (Inode *)getBlockAddr(node->direct[1]); // 获取父目录的 Inode
return lookup(upLevel, filename); // 递归查找父目录
}
// 当前节点的块数
int blockNum = node->blocks;
// 如果块数小于等于 12,则直接在 direct 数组中查找文件
if(blockNum <= 12) {
for(i = 2; i < blockNum; i ++) {
Inode *candidate = (Inode *)getBlockAddr(node->direct[i]); // 获取当前块的 Inode
// 如果文件名匹配,则递归查找该文件
if(!strcmp((char *)candidate->filename, cTarget)) {
return lookup(candidate, filename);
}
}
return 0; // 如果没有找到匹配的文件,则返回 NULL
} else {
// 如果块数大于 12,先在 direct 数组中查找
for(i = 2; i < 12; i ++) {
Inode *candidate = (Inode *)getBlockAddr(node->direct[i]); // 获取当前块的 Inode
// 如果文件名匹配,则递归查找该文件
if(!strcmp((char *)candidate->filename, cTarget)) {
return lookup(candidate, filename);
}
}
// 如果文件仍然没有找到,则在间接块(indirect)中查找
uint32 *indirect = (uint32 *)getBlockAddr(node->indirect); // 获取间接块的地址
for(i = 12; i < blockNum; i ++) {
Inode *candidate = (Inode *)getBlockAddr(indirect[i-12]); // 获取间接块中的 Inode
// 如果文件名匹配,则递归查找该文件
if(!strcmp((char *)candidate->filename, cTarget)) {
return lookup(candidate, filename);
}
}
return 0; // 如果没有找到匹配的文件,则返回 NULL
}
}
readall()
函数传入一个 Inode 和一个字节数组,Inode 应当为文件类型的 Inode。这个函数将该 Inode 所代表的文件数据全部拷贝到字节数组中
/* 读取一个表示文件的 Inode 的所有字节到 buf 中 */
void
readall(Inode *node, char *buf) {
// 检查 Inode 类型是否为文件,如果不是文件则触发 panic
if(node->type != TYPE_FILE) {
panic("Cannot read a directory!\n");
}
// 获取文件的大小和文件占用的块数
int l = node->size, b = node->blocks;
// 如果文件占用的块数小于或等于 12,则直接在 direct 数组中读取数据
if(b <= 12) {
int i;
for(i = 0; i < b; i ++) {
// 获取当前块的地址
char *src = (char *)getBlockAddr(node->direct[i]);
// 拷贝大小判断,大于一页取4096
int copySize = l >= 4096 ? 4096 : l;
// 将数据从块中复制到 buf 中
copyByteToBuf(src, buf, copySize);
// 更新 buf 指针,指向下一个写入的位置
buf += copySize;
// 更新剩余大小
l -= copySize;
}
} else {
// 如果文件占用的块数大于 12,先处理前 12 块
int i;
// 同上
for(i = 0; i < 12; i ++) {
char *src = (char *)getBlockAddr(node->direct[i]);
int copySize = l >= 4096 ? 4096 : l;
copyByteToBuf(src, buf, copySize);
buf += copySize;
l -= copySize;
}
// 获取间接块地址
uint32 *indirect = (uint32 *)getBlockAddr(node->indirect);
// 处理所有间接块数据
for(i = 0; i < b-12; i ++) {
char *src = (char *)getBlockAddr(indirect[i]);
int copySize = l >= 4096 ? 4096 : l;
copyByteToBuf(src, buf, copySize);
buf += copySize;
l -= copySize;
}
}
}
8.4 文件系统测试
1、我们完成了文件系统的简单驱动,现在就可以从文件系统中读取 ELF 文件了!
// kernel/thread.c
// 初始化线程
void initThread()
{
// 1.创建调度函数实现
Scheduler s = {
schedulerInit,
schedulerPush,
schedulerPop,
schedulerTick,
schedulerExit};
s.init(); // 初始化调度器
// 2.创建线程池
ThreadPool pool = newThreadPool(s);
// 3.构建idle调度线程
Thread idle = newKernelThread((usize)idleMain);
// 4.初始化CPU调度器
initCPU(idle, pool);
// 5.构造线程并添加到CPU中
usize i;
for (i = 0; i < 5; i++)
{
Thread t = newKernelThread((usize)helloThread); // 构造新内核线程
usize args[8];
args[0] = i;
appendArguments(&t, args); // 为线程传入初始化参数
// 6.启动
addToCPU(t); // 将线程添加到调度队列中
}
// 从文件系统中读取 elf 文件
Inode *helloInode = lookup(0, "/bin/hello"); // 查找文件inode
char *buf = kalloc(helloInode->size); // 分配内存
readall(helloInode, buf); // 读取文件所有数据到buf
Thread t = newUserThread(buf);
kfree(buf);
// 创建一个用户线程并添加到 CPU
// extern void _user_img_start(); // linkUser.asm中定义用户程序位置
// Thread t = newUserThread((char *)_user_img_start);
addToCPU(t);
printf("***** init thread *****\n");
}
- 首先获取到 ELF 文件的 Inode,根据其大小分配一个 buffer,再将文件的内容全部读取到 buffer 里。再次运行,运行结果和上一章一致。
友情提示,在
kernel
文件夹下记得新增如下文件并引用:
// kernel/fs.h
#ifndef _SIMPLEFS_H
#define _SIMPLEFS_H
#include "types.h"
#define BLOCK_SIZE 4096
#define MAGIC_NUM 0x4D534653U // MSFS
typedef struct {
uint32 magic; // 魔数
uint32 blocks; // 总磁盘块数
uint32 unusedBlocks; // 未使用的磁盘块数
uint32 freemapBlocks; // freemap 块数
uint8 info[32]; // 其他信息
} SuperBlock;
#define TYPE_FILE 0
#define TYPE_DIR 1
typedef struct
{
uint32 size; // 文件大小,type 为文件夹时该字段为0
uint32 type; // 文件类型
uint8 filename[32]; // 文件名称
uint32 blocks; // 占据磁盘块个数
uint32 direct[12]; // 直接磁盘块
uint32 indirect; // 间接磁盘块
} Inode;
Inode *lookup(Inode *node, char *filename);
void readall(Inode *node, char *buf);
// void ls(Inode *node);
// char *getInodePath(Inode *inode, char path[256]);
#endif
- 同时也记得新增
string.c
的字符串操作
// kernel/string.c
// 字符串长度
int
strlen(char *str)
{
int num = 0;
while (*str != '\0') {
num ++;
str ++;
}
return num;
}
// 字符串对比
int
strcmp(char *str1, char *str2)
{
if(strlen(str1) != strlen(str2)) return 1;
int i, len = strlen(str1);
for(i = 0; i < len; i ++) {
if(str1[i] != str2[i]) return 1;
}
return 0;
}