《操作系统真象还原》第十四章(2)——文件描述符、文件操作基础函数

news2025/7/18 10:15:40

文章目录

    • 前言
    • 文件描述符简介
      • 文件描述符原理
      • 文件描述符实现
        • 修改thread.h
        • 修改thread.c
    • 文件操作相关的基础函数
      • inode操作相关函数
      • 文件相关函数
        • 编写file.h
        • 编写file.c
      • 目录相关函数
        • 完善fs/dir.h
        • 编写fs/dir.c
      • 路径解析相关函数
      • 实现文件检索功能
        • 修改fs.h
        • 继续完善fs.c
      • makefile
    • 结语

前言

本章是14章第二篇博客,计划完成14.3、14.4两个小节内容。14.3是文件描述符简介,比较短,我们摘一些要点即可,14.4是一个大章,我们要完成文件操作的很多基础函数,代码量很大。


文件描述符简介

文件描述符原理

首先引入文件结构这个概念。文件结构用于描述文件被打开后,文件读写偏移量等信息。每次打开一个文件,就会创建一个文件结构,多次打开会创建多个文件结构。

Linux 把所有的“文件结构”组织到一起形成数组统一管理, 该数组称为文件表,我们要多次引用此概念。

然后引入文件描述符。在Linux中,我们读写函数文件时都是通过操作文件描述符来完成的。例如open,返回一个数字,而该数字就是我们所说的文件描述符,文件描述符是个整数,准确地说,它是PCB中文件描述符数组元素的下标,只不过此数字并不用来表示“数量”,而是用 来表示“位置”,它是位于进程PCB中的文件描述符数组的元素的下标,而文件描述符数组元素中的信息又指向文件表中的某个文件结构。

为什么文件描述符是数字,而不是像其他描述符那样,是个具有多个成员属性的复合数据结构?(1)所有进程可打开的文件数是一致的,每个进程都要有一套独立完整的文件描述符数组。(2)文件结构中包含进程执行文件操作的偏移量,它属于与各个任务单独绑定的资源,因此最好放在 PCB 中管理。

综合以上两点,我们不会把完整庞大的文件表塞进pcb,只要在 PCB中建立个文件描述符数组就可以了,该数组成员不需要是真正的文件结构,出于简单处理,咱们用int整型就足够了,用它存储文件表中文件结构的下标。

如何通过一个数字,也就是文件描述符,来找到文件数据块的?见示意图

梳理寻址过程:某个进程调用类似于open这样的函数,把文件描述符(int类型)作为参数提交给文件系统,文件系统通过进程pcb中的文件描述符数组,索引到一个文件表的文件结构表项,从这个文件结构获取inode指针,通过inode指针找到数据块。

文件描述符数组在pcb中创建,inode队列在创建文件系统时就已经实现,所以创建文件描述符的过程就是在pcb数组、文件表和inode队列中寻找空位填充的过程。

文件描述符实现

这部分其实是在pcb中完善文件描述符数组属性。完整的通过文件描述符操作文件我们会在14.4中实现

修改thread.h

只给出修改部分吧,开头新增了一个宏,pcb结构体新增了一个文件描述符属性。

...
#define MAX_FILES_OPEN_PER_PROC 8 // 每个进程最大能同时打开的文件数 
...
/* 线程或进程的pcb程序控制块 */
struct task_struct
{
    uint32_t *self_kstack;     // 线程自己的栈的栈顶指针
    pid_t pid;                 // 线程的pid,系统调用部分对它进行操作
    enum thread_status status; // 线程的状态
    uint8_t priority;          // 线程的优先级
    uint8_t ticks;             // 线程的时间片,在处理器上运行的时间滴答数
    uint32_t elapsed_ticks;    // 线程的运行时间,也就是这个线程已经执行了多久
    char name[16];             // 线程的名字

    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;                              // 如果是进程,这是进程的页表结构中页目录表的虚拟地址,线程则置为NULL
    struct virtual_addr userprog_vaddr;           // 用户进程的虚拟地址,后续转化为物理地址后存入cr3寄存器
    struct mem_block_desc u_block_desc[DESC_CNT]; // 进程内存块描述符数组,用于用户进程的堆内存管理
    uint32_t stack_magic;                         // 线程栈的魔数,边界标记,用来检测栈溢出
};
...
修改thread.c

初始化刚刚新增的文件描述符数组

...
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio)
{
    memset(pthread, 0, sizeof(*pthread)); // 清空线程pcb
    pthread->pid = allocate_pid();        // 获取唯一的pid
    strcpy(pthread->name, name);          // 线程名字
    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->fd_table[0] = 0;
    pthread->fd_table[1] = 1;
    pthread->fd_table[2] = 2;
    // 剩下的设置为-1
    uint8_t fd_idx = 3;
    while (fd_idx < MAX_FILES_OPEN_PER_PROC)
    {
        pthread->fd_table[fd_idx] = -1;
        fd_idx++;
    }

    pthread->priority = prio;          // 线程优先级
    pthread->ticks = prio;             // 线程时间片
    pthread->elapsed_ticks = 0;        // 线程运行时间
    pthread->pgdir = NULL;             // 线程页表
    pthread->stack_magic = 0x20250325; // 线程栈的魔数,边界标记,用来检测栈溢出
}
...

有关线程的部分就修改这些,剩下的就涉及到其他文件模块了,我们下一小节实现。


文件操作相关的基础函数

摘一下书上的原文:在本节我们要想实现文件及目录的创建、打开、读、写操作,必须建设好基础设施,现在咱们要一步 一个脚印,慢慢走向目的地。 为了帮助大伙儿理清楚函数间的依赖关系,本文按照它们的调用关系来介绍相关的文件,同时为了减 少学习的复杂性,根据实际情况有可能只会列出文件中的部分代码,并不是一股脑地把不相关的内容也搬 出来,待需要的时候依然会在相同的文件中添加新功能,因此有可能同一件文件会在不同功能的讲解中反 复更新,请您知晓。

inode操作相关函数

回顾:inode在inode数组里,inode数组实际位置在根目录前,inode位图后,inode数组的起始地址和大小也被记录在超级块里。这部分可以参考14.1文件系统布局。

在我们的文件系统里,引导扇区占据第0扇区、超级块占用第1扇区,位图类结构必须对齐到扇区,数据类结构(包括数组,根目录,空闲块)大小不定,可以跨扇区存在。

来看代码,在fs路径下创建inode.c文件,总共是写160行左右的代码,实现5个函数


#include "inode.h"
#include "../device/ide.h"
#include "../kernel/debug.h"
#include "../kernel/interrupt.h"
#include "../thread/thread.h"
#include "../lib/string.h"
#include "../lib/kernel/stdint.h"

/*用来存储inode位置的结构体*/
struct inode_position
{
    bool two_sec;      // 此inode是否跨区
    uint32_t sec_lba;  // 此inode起始扇区号
    uint32_t off_size; // 此inode在扇区内的字节偏移量
};

/*获取inode所在的扇区和扇区内的偏移量*/
static void inode_locate(struct partition *part, uint32_t inode_no, struct inode_position *inode_pos)
{
    ASSERT(inode_no < 4096);

    uint32_t inode_table_lba = part->sb->inode_table_lba;
    uint32_t inode_size = sizeof(struct inode);
    uint32_t off_size = inode_no * inode_size; // 第no号inode据inode数组起始位置的字节偏移量
    uint32_t off_sec = off_size / 512;         // 字节偏移量对应的扇区偏移量
    uint32_t off_size_in_sec = off_size % 512; // 待查找的inode在此扇区的起始地址

    // 判断此inode是否跨区
    uint32_t left_in_sec = 512 - off_size_in_sec; // 本扇区剩余存储空间
    // 剩余空间不够一个inode
    if (left_in_sec < inode_size)
    {
        inode_pos->two_sec = true;
    }
    else
    {
        inode_pos->two_sec = false;
    }
    inode_pos->sec_lba = inode_table_lba + off_sec;
    inode_pos->off_size = off_size_in_sec;
}

/*将内存中的inode写入到硬盘分区part*/
void inode_sync(struct partition *part, struct inode *inode, void *io_buf)
{
    uint8_t inode_no = inode->i_no;
    struct inode_position inode_pos;
    // 调用上面的函数获取inode所在的扇区和偏移量,保存到inode_pos
    inode_locate(part, inode_no, &inode_pos);
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    struct inode pure_inode;
    memcpy(&pure_inode, inode, sizeof(struct inode));

    // 以下三个成员只在内存中有意义,写入硬盘时清理掉即可
    pure_inode.i_open_cnts = 0;
    pure_inode.write_deny = false; // 可读可写
    pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;

    // 以下,先把原有inode读取出来,更新后再写入
    char *inode_buf = (char *)io_buf;
    if (inode_pos.two_sec)
    {
        // 如果跨扇区了,需要读两个扇区
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
        /*inode_buf + inode_pos.off_sizes是在缓冲区内的偏移地址
         *用于在512/1024字节的缓冲区内定位到实际indoe位置*/
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    }
    else
    {
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

/*根据i节点号返回i节点指针*/
struct inode *inode_open(struct partition *part, uint32_t inode_no)
{
    // 先在每个分区中存在的,打开的i节点链表中寻找i节点,此链表是为了提速创建的缓冲区
    struct list_elem *elem = part->open_inodes.head.next;
    struct inode *inode_found;
    while (elem != &part->open_inodes.tail)
    {
        inode_found = elem2entry(struct inode, inode_tag, elem);
        if (inode_found->i_no == inode_no) // 如果成功找到,inode打开次数+1,返回indoe地址
        {
            inode_found->i_open_cnts++;
            return inode_found;
        }
        elem = elem->next;
    }

    /*目前在链表中没有找到,于是从硬盘中读入inode并加入链表*/
    struct inode_position inode_pos;
    // 调用locate函数,获知no对应的inode的信息
    inode_locate(part, inode_no, &inode_pos);

    /*为了让进程新创建的inode被共享,需要将inode放在内核区
     *需要临时将pcb的pgdir设置为NULL*/
    struct task_struct *cur = running_thread();
    uint32_t *cur_pagedir_bak = cur->pgdir; // 临时记录
    cur->pgdir = NULL;
    inode_found = (struct inode *)sys_malloc(sizeof(struct inode));
    cur->pgdir = cur_pagedir_bak;

    char *inode_buf;
    if (inode_pos.two_sec == true)
    {
        // 跨扇区读两个扇区
        inode_buf = (char *)sys_malloc(1024);
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    }
    else
    {
        inode_buf = (char *)sys_malloc(512);
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
    memcpy(inode_found, inode_buf + inode_pos.off_size, sizeof(struct inode));
    // 加入队列方便后续使用
    list_push(&part->open_inodes, &inode_found->inode_tag);
    // 队列里没有,说明这是第一次被打开,打开次数设置为1
    inode_found->i_open_cnts = 1;
    sys_free(inode_buf);
    return inode_found;
}

/*关闭indoe或减少inode打开数*/
void inode_close(struct inode *inode)
{
    enum intr_status old_status = intr_disable(); // 关inode应为原子操作
    if (--inode->i_open_cnts == 0)
    {
        list_remove(&inode->inode_tag);
        // 内存中新的inode开辟在内核空间,移除时也需要确保回收内核空间
        struct task_struct *cur = running_thread();
        uint32_t *cur_pagedir_bak = cur->pgdir; // 临时记录
        cur->pgdir = NULL;
        sys_free(inode);
        cur->pgdir = cur_pagedir_bak;
    }
    intr_set_status(old_status);
}

/*初始化new_inode*/
void inode_init(uint32_t inode_no, struct inode *new_inode)
{
    new_inode->i_no = inode_no;
    new_inode->i_size = 0;
    new_inode->i_open_cnts = 0;
    new_inode->write_deny = false;
    // 初始化块索引数组i_sector
    uint8_t sec_idx = 0;
    while (sec_idx < 13)
    {
        new_inode->i_sectors[sec_idx] = 0;
        sec_idx++;
    }
}

这个文件已经编译过一次,排除了编译错误。

现在是2025年5月11日21点11分,今天就写到这里吧。

文件相关函数

这部分涉及两个新文件,file.c和file.h,都在fs目录下。

编写file.h
#ifndef __FS_FILE_H
#define __FS_FILE_H

#include "../lib/kernel/stdint.h"
struct inode;     // 前向声明,代替inode
struct partition; // 代替ide.h

/*文件结构,每次打开文件就会创建一个此结构
 *记录偏移量,打开情况,inode指针等信息*/
struct file
{
    uint32_t fd_pos;        // 记录文件操作的偏移地址,最小是0,最大是size-1
    uint32_t fd_flag;       // 记录是否打开
    struct inode *fd_inode; // 记录此文件的inode指针
};

/*标准输入输出描述符*/
enum std_fd
{
    stdin_no,  // 0,标准输入
    stdout_no, // 1,标准输出
    stderr_no  // 2,标准错误
};

/*位图类型*/
enum bitmap_type
{
    INODE_BITMAP, // inode位图
    BLOCK_BITMAP  // 块位图
};

#define MAX_FILE_OPEN 32 // 系统可打开的最大文件数

/*从文件表file_table中获取一个空闲位,成功返回下标,失败返回-1*/
int32_t get_free_slot_in_global(void);
/*将全局描述符下标安装到进程或线程自己的文件描述符数组fd_table中
 *成功返回下标,失败返回-1*/
int32_t pcb_fd_install(int32_t global_fd_idx);
/*分配一个i节点,返回i结点号*/
int32_t inode_bitmap_alloc(struct partition *part);
/*分配1个扇区,返回扇区地址*/
int32_t block_bitmap_alloc(struct partition *part);
/*将内存中bitmap第bit_idx位所在的512个字节同步到硬盘*/
void bitmap_sync(struct partition *part, uint32_t bit_idx, uint8_t btmp);
#endif
编写file.c
#include "file.h"
#include "inode.h"            //对应前向声明
#include "../device/ide.h"    //对应前向声明
#include "../lib/stdio.h"     //printk
#include "../thread/thread.h" //pcb

/*文件表,前三个成员预留给标准输入、标准输出、标准错误*/
struct file file_table[MAX_FILE_OPEN];

/*从文件表file_table中获取一个空闲位,成功返回下标,失败返回-1*/
int32_t get_free_slot_in_global(void)
{
    uint32_t fd_idx = 3; // 跳过0、1、2
    while (fd_idx < MAX_FILE_OPEN)
    {
        if (file_table[fd_idx].fd_inode == NULL)
        {
            break; // i结点==NULL说明这个文件结构没被使用,存在空闲
        }
        fd_idx++;
    }
    if (fd_idx == MAX_FILE_OPEN)
    {
        // 超出最大打开文件数限制
        printk("exceed max open files\n");
        return -1;
    }
    return fd_idx;
}

/*将全局描述符下标安装到进程或线程自己的文件描述符数组fd_table中
 *成功返回下标,失败返回-1*/
int32_t pcb_fd_install(int32_t global_fd_idx)
{
    struct task_struct *cur = running_thread();
    uint8_t local_fd_idx = 3;
    while (local_fd_idx < MAX_FILES_OPEN_PER_PROC)
    {
        // 我们在线程初始化的时候,用-1表示线程文件表空位,所以-1对应可以使用
        if (cur->fd_table[local_fd_idx] == -1)
        {
            cur->fd_table[local_fd_idx] = global_fd_idx;
            break;
        }
        local_fd_idx++;
    }
    if (local_fd_idx == MAX_FILES_OPEN_PER_PROC)
    {
        // 超出单个进程最大打开文件数限制
        printk("exceed max open files_per_proc\n");
        return -1;
    }
    return local_fd_idx;
}

/*分配一个i节点,返回i结点号*/
int32_t inode_bitmap_alloc(struct partition *part)
{
    int32_t bit_idx = bitmap_scan(&part->inode_bitmap, 1);
    if (bit_idx == -1)
    {
        return -1; // 申请失败
    }
    // 申请成功,设置位图并返回编号
    bitmap_set(&part->inode_bitmap, bit_idx, 1);
    return bit_idx;
}

/*分配1个扇区,返回扇区lba地址*/
int32_t block_bitmap_alloc(struct partition *part)
{
    int32_t bit_idx = bitmap_scan(&part->block_bitmap, 1);
    if (bit_idx == -1)
    {
        return -1; // 申请失败
    }
    bitmap_set(&part->block_bitmap, bit_idx, 1);
    // 返回扇区lba号
    return (part->sb->block_bitmap_lba + bit_idx);
}

/*内存中某个bitmap第bit_idx位修改后,将对应的修改同步到硬盘*/
void bitmap_sync(struct partition *part, uint32_t bit_idx, uint8_t btmp)
{
    // 一个位图的一个位进行了修改,说明512字节的一个扇区被分配或者回收,需要更新硬盘位图
    // 位图结构总是整扇区大小的,一次至少更新512字节
    uint32_t off_sec = bit_idx / 4096; // 找到位图对应的硬盘上的扇区偏移量
    uint32_t off_size = off_sec * 512; // 对应的字节偏移量
    uint32_t sec_lba;
    uint8_t *bitmap_off;
    if (btmp == INODE_BITMAP)
    {
        sec_lba = part->sb->inode_bitmap_lba + off_sec;
        bitmap_off = part->inode_bitmap.btmp_bits + off_size;
    }
    else if (btmp == BLOCK_BITMAP)
    {
        sec_lba = part->sb->block_bitmap_lba + off_sec;
        bitmap_off = part->block_bitmap.btmp_bits + off_size;
    }
    ide_write(part->my_disk, sec_lba, bitmap_off, 1);
}

目前file.c是100行左右代码,后续还会新增。

目录相关函数

先回顾目录和目录项:目录本身是一种特殊文件,内部的数据块保存的是目录项的列表。目录项是目录文件的一条记录,用于记录该目录下的一个文件或目录信息。这两种结构被抽象在dir.h中,大小都不需要是整扇区。

完善fs/dir.h

新增了函数声明

#ifndef __FS_DIR_H
#define __FS_DIR_H

#include "../lib/kernel/stdint.h"
#include "fs.h" //提供enum file_types
struct inode;   // inode前向声明

#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;                    // 对应的i结点编号
    enum file_types f_type;           // 文件类型
};

/*打开根目录*/
void open_root_dir(struct partition *part);
/*在part分区打开i结点编号inode_no的目录,并返回目录指针*/
struct dir *dir_open(struct partition *part, uint32_t inode_no);
/*在part分区内的pdir目录内寻找名为name的文件或目录
 *找到后返回true,并把目录项存在dir_e,否则返回false*/
bool search_dir_entry(struct partition *part, struct dir *pdir, const char *name, struct dir_entry *dir_e);
/*关闭目录*/
void dir_close(struct dir *dir);
/*在内存中初始化目录项p_de*/
void create_dir_entry(char *filename, uint32_t inode_no, uint8_t file_type, struct dir_entry *p_de);
/*将目录项p_de写入父目录parent_dir中,io_buf由主调函数提供*/
bool sync_dir_entry(struct dir *parent_dir, struct dir_entry *p_de, void *io_buf);
#endif
编写fs/dir.c

回顾一下inode结构。一块一扇区512字节。对于我们的inode,只有13个指针,前12个是直接块指针,最后一个指针指向一级间接块索引表。索引表页也是一个块,512字节大小,内部包含128个4字节指针,再次指向128个块。一个inode结构可以控制140个块。

这是本节新增文件,写完是230+行。


#include "dir.h"
#include "inode.h"            // 前向声明
#include "file.h"             //block_bitmap_alloc函数
#include "../device/ide.h"    // partition结构体
#include "../kernel/memory.h" // sys_malloc函数
#include "../kernel/debug.h"  //ASSERT哨兵
#include "../lib/stdio.h"     //printk函数
#include "../lib/string.h"    //strcmp函数

struct dir root_dir; // 根目录

/*打开根目录,即初始化*/
void open_root_dir(struct partition *part)
{
    root_dir.inode = inode_open(part, part->sb->root_inode_no);
    root_dir.dir_pos = 0; // 偏移地址为0
}

/*在part分区打开i结点编号inode_no的目录,并返回目录指针*/
struct dir *dir_open(struct partition *part, uint32_t inode_no)
{
    struct dir *pdir = (struct dir *)sys_malloc(sizeof(struct dir));
    pdir->inode = inode_open(part, inode_no);
    pdir->dir_pos = 0;
    return pdir;
}

/*在part分区内的pdir目录内寻找名为name的文件或目录
 *找到后返回true,并把目录项存在dir_e,否则返回false*/
bool search_dir_entry(struct partition *part, struct dir *pdir, const char *name, struct dir_entry *dir_e)
{
    /*对于我们的inode,只有13个指针,前12个是直接块指针,最后一个指针指向一级间接块索引表
     *索引表页也是一个块,512字节大小,内部包含128个4字节指针,再次指向128个块
     *当硬盘被虚拟抽象到内存后,LBA号也就抽象成了指针*/
    uint32_t block_cnt = 12 + 128; // 12个直接块+128个一级间接块指针
    // all_block保存此inode所有块指针,32位系统中,一个指针大小为4字节
    uint32_t *all_block = (uint32_t *)sys_malloc((12 + 128) * 4);
    if (all_block == NULL)
    {
        printk("search_dir_entry: sys_malloc for all_block failed");
        return false;
    }
    uint32_t block_idx = 0;

    // 先处理12个直接块
    while (block_idx < 12)
    {
        all_block[block_idx] = pdir->inode->i_sectors[block_idx];
        block_idx++;
    }
    block_idx = 0;
    // 如果使用了一级间接块表,处理一级间接块
    if (pdir->inode->i_sectors[12] != 0)
    {
        // 因为all_block的类型是uint32_t *,所以后面的+12其实是+12*4,目的是跳过12个直接块
        ide_read(part->my_disk, pdir->inode->i_sectors[12], all_block + 12, 1);
    }

    uint8_t *buf = (uint8_t *)sys_malloc(SECTOR_SIZE);
    struct dir_entry *p_de = (struct dir_entry *)buf; // 指向目录项的指针

    uint32_t dir_entry_size = part->sb->dir_entry_size;
    uint32_t dir_entry_cnt = SECTOR_SIZE / dir_entry_size; // 计算一个扇区包含多少目录项

    while (block_idx < block_cnt) // 遍历这个目录拥有的所有的块
    {
        // 地址为0说明块内无数据,寻找下一个块
        if (all_block[block_idx] == 0)
        {
            block_idx++;
            continue;
        }
        ide_read(part->my_disk, all_block[block_idx], buf, 1);

        // 现在我们读取了一个块的数据到buf中(也就是pd_e中),接下来遍历这个扇区内所有的目录项
        uint32_t dir_entry_idx = 0;
        while (dir_entry_idx < dir_entry_cnt)
        {
            // 取反的原因涉及到strcmp函数的返回值
            if (!strcmp(p_de->filename, name))
            {
                // 找到了相关文件或目录,复制到指定内存dir_e
                memcpy(dir_e, p_de, dir_entry_size);
                sys_free(buf);
                sys_free(all_block);
                return true;
            }
            dir_entry_idx++; // 进入下一个目录项
            p_de++;
        }
        block_idx++;                    // 此扇区已遍历完,进入下一个扇区
        p_de = (struct dir_entry *)buf; // 指向新扇区的buf
        memset(buf, 0, SECTOR_SIZE);    // 将buf清零
    }
    sys_free(buf);
    sys_free(all_block);
    return false;
}

/*关闭目录*/
void dir_close(struct dir *dir)
{
    /******************** 根目录不能被关闭 ********************
     *1 根目录自打开后就不应该关闭,否则还需要再次open_root_dir();
     *2 root_dir 所在的内存是低端1MB之内,并非在堆中,free会出问题 */
    if (dir == &root_dir)
    {
        return;
    }
    // 对于一般目录,关闭目录就是关闭目录文件inode,然后释放dir所占内存
    inode_close(dir->inode);
    sys_free(dir);
}

/*在内存中初始化目录项p_de*/
void create_dir_entry(char *filename, uint32_t inode_no, uint8_t file_type, struct dir_entry *p_de)
{
    ASSERT(strlen(filename) <= MAX_FILE_NAME_LEN);
    memcpy(p_de->filename, filename, strlen(filename));
    p_de->i_no = inode_no;
    p_de->f_type = file_type;
}

/*将目录项p_de写入父目录parent_dir中,io_buf由主调函数提供*/
bool sync_dir_entry(struct dir *parent_dir, struct dir_entry *p_de, void *io_buf)
{
    struct inode *dir_inode = parent_dir->inode;
    uint32_t dir_size = dir_inode->i_size;
    uint32_t dir_entry_size = cur_part->sb->dir_entry_size;

    ASSERT(dir_size % dir_entry_size == 0); // 目录大小是目录项大小的整数倍

    uint32_t dir_entry_per_sec = (512 / dir_size); // 每扇区目录项数
    int32_t block_lba = -1;                        // 数据块lba

    uint8_t block_idx = 0;
    uint32_t all_block[140] = {0};
    // 处理直接块
    while (block_idx < 12)
    {
        all_block[block_idx] = dir_inode->i_sectors[block_idx];
        block_idx++;
    }

    struct dir_entry *dir_e = (struct dir_entry *)io_buf; // dir_e用来在io_buf中遍历目录项
    int32_t block_bitmap_idx = -1;                        // 数据块位图索引

    /*开始遍历扇区寻找空目录项,如果已有扇区没有空目录项,在文件大小范围内,申请新扇区*/
    while (block_idx < 140)
    {
        block_bitmap_idx = -1;
        if (all_block[block_idx] == 0) // 此块未使用
        {
            // 申请此块,获得lba
            block_lba = block_bitmap_alloc(cur_part);
            if (block_lba == -1) // 失败
            {
                printk("alloc block bitmap for sync_dir_entry failed\n");
                return false;
            }
            // 数据块位图索引=当前lba-数据块起始lba
            block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
            ASSERT(block_bitmap_idx != -1);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
            block_bitmap_idx = -1;

            // 先定位到空目录项所在扇区
            if (block_idx < 12) // 直接块
            {
                dir_inode->i_sectors[block_idx] = all_block[block_idx] = block_lba;
            }
            else if (block_idx == 12) // 还未分配一级间接块表地址
            {
                dir_inode->i_sectors[12] = block_lba; // 将上面获取的lba作为一级间接块表地址
                block_lba = -1;
                block_lba = block_bitmap_alloc(cur_part); // 分配第0个间接块地址
                if (block_lba == -1)                      // 分配失败
                {
                    block_bitmap_idx = dir_inode->i_sectors[12] - cur_part->sb->data_start_lba;
                    bitmap_set(&cur_part->block_bitmap, block_bitmap_idx, 0); // 将相应块位图设置为未使用
                    dir_inode->i_sectors[12] = 0;
                    printk("alloc block bitmap for sync_dir_entry failed\n");
                    return false;
                }
                // 同步block_bitmap
                block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
                ASSERT(block_bitmap_idx != -1);
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                all_block[12] = block_lba;
                // 写入硬盘
                ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_block + 12, 1);
            }
            else // 还有未分配的间接块
            {
                all_block[block_idx] = block_lba;
                // 我们更新硬盘中的一级间接表,让间接表多一个指向新扇区的指针
                ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_block + 12, 1);
            }

            // 将新目录项p_de写入新分配的间接块
            memset(io_buf, 0, 512);
            memcpy(io_buf, p_de, dir_entry_size);
            // 区别dir_inode->i_sectors[12]和all_block
            // 前者是inode内一级块表,通过它索引到all_block后128项
            ide_write(cur_part->my_disk, all_block[block_idx], io_buf, 1);
            dir_inode->i_size += dir_entry_size;
            return true;
        }

        /*对应此块未使用的情况,如果此块已被使用,将块读入内存
         *然后寻找块内有没有空目录项*/
        ide_read(cur_part->my_disk, all_block[block_idx], io_buf, 1);
        uint8_t dir_entry_idx = 0; // 用于按目录项遍历块
        while (dir_entry_idx < dir_entry_per_sec)
        {
            if ((dir_e + dir_entry_idx)->f_type == FT_UNKNOWN)
            {
                // FT_UNKNOWN代表未使用或已删除,总之就是空白
                memcpy(dir_e + dir_entry_idx, p_de, dir_entry_size);
                ide_write(cur_part->my_disk, all_block[block_idx], io_buf, 1);
                dir_inode->i_size += dir_entry_size;
                return true;
            }
            dir_entry_idx++;
        }
        block_idx++;
    }
    printk("directory is full!\n");
    return false;
}

从今天下午4点开始写,到现在是2025年5月12日20点45分,进度推进到这里。

路径解析相关函数

路径解析,就是根据路径把文件名分层,逐层在磁盘查找,确认文件名是否存在。这部分继续编写fs.c,总共是实现两个函数。

/*将最上层路径名解析出来,在name_store保存当前路径名,然后返回解析后的子路径*/
static char *path_paser(char *pathname, char *name_store)
{
    if (pathname[0] == '/') // 跳过前面所有的/
    {
        while (*pathname == '/')
        {
            pathname++;
        }
    }
    // 开始一般的路径解析,提取最上层路径名,即从字符开始到第一个/停止
    while (*pathname != '/' && *pathname != 0) // 0是空字符ascii码
    {
        // 我不喜欢写自增,因为自增容易带来阅读障碍
        *name_store = *pathname;
        name_store++;
        pathname++;
    }
    if (pathname[0] == 0)
    {
        return NULL;
    }
    return pathname;
}

/*返回路径深度*/
int32_t path_depth_cnt(char *pathname)
{
    ASSERT(pathname != NULL);
    char *p = pathname;           // 用于保存每次path_paser返回的子路径
    char name[MAX_FILE_NAME_LEN]; // 用于保存每次path_paser返回的原文件名
    uint32_t depth = 0;
    p = path_paser(p, name);
    while (name[0] != 0) // 只要存在当前文件
    {
        depth++;
        memset(name, 0, MAX_FILE_NAME_LEN);
        if (p != 0) // 只要存在子路径
        {
            p = path_paser(p, name);
        }
    }
    return depth;
}

实现文件检索功能

文件检索就是确定文件是否存在于此路径

修改fs.h

新增了宏,枚举,结构体,还有上一节的函数的声明。

#ifndef __FS_FS_H
#define __FS_FS_H

struct partition; // 前向声明,代替ide.h
struct dir;       // 代替dir.h

#define MAX_FILES_PER_PART 4096 // 每个扇区最大支持文件数
#define BITS_PER_SECTOR 4096    // 每扇区的位数
#define SECTOR_SIZE 512         // 每扇区的字节数
#define BLOCK_SIZE SECTOR_SIZE  // 块字节大小 我们设置为1个块==1个扇区
#define MAX_PATH_LEN 512        // 路径最大长度

/*文件类型枚举*/
enum file_types
{
    FT_UNKNOWN,  // 0,未知文件类型
    FT_REGULAR,  // 1,普通文件类型
    FT_DIRECTORY // 2,目录文件类型
};

/*打开文件的选项枚举*/
enum oflags
{
    O_RDONLY,   // 只读
    O_WRONLY,   // 只写
    O_RDWR,     // 读写
    O_CREAT = 4 // 创建
};

/*记录查找过程中的上级路径*/
struct path_search_record
{
    char searched_path[MAX_PATH_LEN]; // 父路径
    struct dir *parent_dir;           // 直接父目录
    enum file_types file_type;        // 找到的文件的类型
};

void filesys_init(void);                /*在磁盘上搜索文件系统,若没有则格式化分区创建文件系统*/
int32_t path_depth_cnt(char *pathname); /*返回路径深度*/

extern struct partition *cur_part;
#endif
继续完善fs.c
#include "fs.h"
#include "inode.h"
#include "dir.h"
#include "super_block.h"
#include "../lib/kernel/stdint.h"
#include "../lib/kernel/list.h"
#include "../lib/string.h"
#include "../lib/stdio.h"
#include "../device/ide.h" //partition
#include "../kernel/debug.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;
        // 创建用来保存超级块的缓冲区
        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);
        /*把缓冲区超级块数据复制到cur_part的sb中*/
        memcpy(cur_part->sb, sb_buf, sizeof(struct super_block));
        /*为什么要先读入缓冲区,再把缓冲区数据复制到相应的变量中?
         *缓冲区大小就是1扇区512字节,和硬盘读取标准对齐,而实际超级块结构体小于512字节
         *如果直接读入实际结构体,会导致硬盘读写很慢。*/

        /*将分区的块位图写入内存*/
        // 开辟内存空间给位图指针
        cur_part->block_bitmap.btmp_bits = (uint8_t *)sys_malloc(sb_buf->block_bitmap_sects * SECTOR_SIZE);
        if (cur_part->block_bitmap.btmp_bits == NULL)
        {
            PANIC("alloc memory failed!");
        }
        // 设置位图长度
        cur_part->block_bitmap.btmp_bytes_len = sb_buf->block_bitmap_sects * SECTOR_SIZE;
        // sb_buf->block_bitmap_sects等价于cur_part->sb->block_bitmap_sects
        // 给位图指针赋值
        ide_read(hd, sb_buf->block_bitmap_lba, cur_part->block_bitmap.btmp_bits, sb_buf->block_bitmap_sects);

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

        list_init(&cur_part->open_inodes);
        printk("mount %s done!\n", part->name);
        /*返回true是为了配合定义在list.c的list_traversal函数,和本函数功能无关
         *返回true时list_traversal停止对链表的遍历*/
        return true;
    }
    return false;
}

static void partition_format(struct disk *hd, struct partition *part)
{
    uint32_t boot_sector_sects = 1; // 根目录扇区
    uint32_t super_block_sects = 1; // 超级块扇区
    // inode位图所占扇区
    uint32_t inode_bitmap_sects = DIV_ROUND_UP(MAX_FILES_PER_PART, BITS_PER_SECTOR);
    // inode表所占扇区
    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 = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
    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 = 0x20250325;
    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;
    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"
           "  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_sects: 0x%x\n"
           "  inode_bitmap_lba:   0x%x\n"
           "  inode_bitmap_sects: 0x%x\n"
           "  inode_table_lba:    0x%x\n"
           "  inode_table_sects:  0x%x\n"
           "  data_start_lba:     0x%x\n",
           part->name,
           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);

    // 1.将超级块写入本分区1扇区
    ide_write(hd, part->start_lba + 1, &sb, 1);
    printk("  super_blcok_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;
    buf_size *= SECTOR_SIZE;
    uint8_t *buf = (uint8_t *)sys_malloc(buf_size);

    // 2.将块位图初始化并写入sb.block_bitmap_lba
    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;
    // last_size是保存位图的最后一个扇区中,多余出的位
    uint32_t last_size = (SECTOR_SIZE - block_bitmap_last_byte % SECTOR_SIZE);
    // 先将超出实际块数的部分设置为已占用1
    memset(&buf[block_bitmap_last_byte], 0xff, last_size);
    // 在将有效位重新设置为未占用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;
    /*inode_table中有4096个inode,正好一个扇区,
     *inode_bitmap扇区没有多余无效位,不需要进一步处理*/
    ide_write(hd, sb.inode_bitmap_lba, buf, sb.inode_bitmap_sects);

    // 4 将inode数组初始化并写入sb.inode_table_lba
    // 初始化了第一个indoe
    memset(buf, 0, buf_size);
    struct inode *i = (struct inode *)buf;
    i->i_size = sb.dir_entry_size * 2; // 留出..和.目录
    i->i_no = 0;
    i->i_sectors[0] = sb.data_start_lba;
    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;

    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;
    // 开辟超级块缓冲区
    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) // channel_cnt声明在ide.h,实现在ide.c
    {
        dev_no = 0;
        while (dev_no < 2) // 一个通道可以挂载2个设备
        {
            if (dev_no == 0)
            {
                dev_no++;
                continue;
            }
            struct disk *hd = &channels[channel_no].devices[dev_no];
            struct partition *part = hd->prim_parts; // 初始指向4个主分区

            while (part_idx < 12) // 4主分区+8逻辑分区
            {
                if (part_idx == 4)
                {
                    part = hd->logic_parts; // 开始处理逻辑分区
                }
                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 == 0x20250325)
                    {
                        printk("    %s has file system\n", part->name);
                    }
                    // 不匹配,认为不存在文件系统,于是创建我的操作系统
                    else
                    {
                        // 提示正在进行初始化
                        printk("formatting %s's partition %s......\n", hd->name, part->name);
                        // 调用函数创建每个分区的文件系统
                        partition_format(hd, part);
                    }
                }
                part_idx++;
                part++; // 进入下一分区
            }
            dev_no++; // 进入下一磁盘
        }
        channel_no++; // 进入下一通道
    }
    sys_free(sb_buf);

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

/*将最上层路径名解析出来,在name_store保存当前路径名,然后返回解析后的子路径*/
static char *path_paser(char *pathname, char *name_store)
{
    if (pathname[0] == '/') // 跳过前面所有的/
    {
        while (*pathname == '/')
        {
            pathname++;
        }
    }
    // 开始一般的路径解析,提取最上层路径名,即从字符开始到第一个/停止
    while (*pathname != '/' && *pathname != 0) // 0是空字符ascii码
    {
        // 我不喜欢写自增,因为自增容易带来阅读障碍
        *name_store = *pathname;
        name_store++;
        pathname++;
    }
    if (pathname[0] == 0)
    {
        return NULL;
    }
    return pathname;
}

/*返回路径深度*/
int32_t path_depth_cnt(char *pathname)
{
    ASSERT(pathname != NULL);
    char *p = pathname;           // 用于保存每次path_paser返回的子路径
    char name[MAX_FILE_NAME_LEN]; // 用于保存每次path_paser返回的原文件名
    uint32_t depth = 0;
    p = path_paser(p, name);
    while (name[0] != 0) // 只要存在当前文件
    {
        depth++;
        memset(name, 0, MAX_FILE_NAME_LEN);
        if (p != 0) // 只要存在子路径
        {
            p = path_paser(p, name);
        }
    }
    return depth;
}

/*搜索文件pathname,若找到返回inode号,否则返回-1*/
static int search_file(const char *pathname, struct path_search_record *search_record)
{
    /*如果查找的是根目录,直接返回根目录信息*/
    if (!strcmp(pathname, "/") || !strcmp(pathname, "/.") || !strcmp(pathname, "/.."))
    {
        search_record->file_type = FT_DIRECTORY;
        search_record->parent_dir = &root_dir;
        search_record->searched_path[0] = 0;
        return 0;
    }

    uint32_t path_len = strlen(pathname);
    ASSERT(path_len < MAX_PATH_LEN);
    char *sub_path = (char *)pathname;
    struct dir *parent_dir = &root_dir;
    struct dir_entry dir_e; // 保存查到的目录项

    /*逐级解析路径*/
    char name[MAX_FILE_NAME_LEN] = {0};
    search_record->parent_dir = parent_dir;
    search_record->file_type = FT_UNKNOWN;
    uint32_t parent_inode_no = 0; // 父目录的inode号
    sub_path = path_paser(sub_path, name);
    while (name[0])
    {
        ASSERT(strlen(search_record->searched_path) < MAX_PATH_LEN);

        /*记录已经存在的路径*/
        strcat(search_record->searched_path, "/");
        strcat(search_record->searched_path, name);

        // 如果成功找到了当前本级文件名的目录项
        if (search_dir_entry(cur_part, parent_dir, name, &dir_e))
        {
            memset(name, 0, MAX_FILE_NAME_LEN);
            if (sub_path) // 如果还有子目录项
            {
                sub_path = path_paser(sub_path, name);
            }

            if (dir_e.f_type == FT_DIRECTORY) // 目录文件,需要进一步查找
            {
                parent_inode_no = parent_dir->inode->i_no;
                dir_close(parent_dir);
                parent_dir = dir_open(cur_part, dir_e.i_no);
                search_record->parent_dir = parent_dir;
                continue;
            }
            else if (dir_e.f_type == FT_REGULAR) // 普通文件,查找到了终点
            {
                search_record->file_type = FT_REGULAR;
                return dir_e.i_no;
            }
        }
        else // 没有找到当前文件名的目录项
        {
            return -1;
        }
    }
    /*至此,已经遍历完完整目录,并且最后一个文件是目录文件*/
    dir_close(search_record->parent_dir);
    // 更新查找记录
    search_record->parent_dir = dir_open(cur_part, parent_inode_no);
    search_record->file_type = FT_DIRECTORY;
    return dir_e.i_no;
}

这是目前能通过编译的fs.c,365行代码。

makefile

最后放一下这章结束时的makefile吧,这部分没有开bochs检验,因为还都是基础函数,所以没有什么运行截图。目前的makefile也是为了确保代码能编译,不保证代码逻辑的完全正确。

BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS =  -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
      $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
      $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
	  $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
	  $(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
	  $(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
	  $(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \
	  $(BUILD_DIR)/stdio.o $(BUILD_DIR)/ide.o $(BUILD_DIR)/fs.o \
	  $(BUILD_DIR)/inode.o $(BUILD_DIR)/file.o $(BUILD_DIR)/dir.o 

################	c代码编译   ##################
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
        lib/kernel/stdint.h kernel/init.h kernel/debug.h \
		kernel/memory.h thread/thread.h kernel/interrupt.h \
		device/console.h userprog/process.h lib/user/syscall.h \
		userprog/syscall-init.h lib/stdio.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
        lib/kernel/stdint.h kernel/interrupt.h device/timer.h \
		kernel/memory.h thread/thread.h device/console.h \
		device/keyboard.h userprog/tss.h userprog/syscall-init.h \
		device/ide.h fs/fs.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
        lib/kernel/stdint.h kernel/global.h kernel/io.h \
		lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h \
        kernel/io.h lib/kernel/print.h kernel/interrupt.h \
		thread/thread.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
        lib/kernel/print.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
		kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
		lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h \
		lib/string.h thread/sync.h thread/thread.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
		lib/string.h kernel/interrupt.h lib/kernel/print.h \
		kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
		lib/kernel/stdint.h lib/kernel/list.h lib/string.h \
		kernel/memory.h kernel/interrupt.h kernel/debug.h \
		lib/kernel/print.h userprog/process.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
		lib/kernel/stdint.h kernel/interrupt.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
		lib/kernel/stdint.h thread/thread.h kernel/debug.h \
		kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/console.o: device/console.c device/console.h \
		lib/kernel/print.h thread/sync.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
		lib/kernel/print.h kernel/interrupt.h kernel/io.h \
		lib/kernel/stdint.h device/ioqueue.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
		lib/kernel/stdint.h thread/thread.h thread/sync.h \
		kernel/interrupt.h kernel/debug.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
		lib/kernel/stdint.h thread/thread.h kernel/global.h \
		lib/kernel/print.h lib/string.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
		kernel/global.h lib/kernel/stdint.h thread/thread.h \
		kernel/debug.h userprog/tss.h device/console.h \
		lib/string.h kernel/interrupt.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
		lib/kernel/stdint.h lib/user/syscall.h thread/thread.h \
		lib/kernel/print.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h \
		lib/kernel/stdint.h lib/string.h kernel/debug.h \
		lib/user/syscall.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/ide.o: device/ide.c device/ide.h \
		lib/stdio.h kernel/debug.h kernel/global.h \
		thread/sync.h kernel/io.h device/timer.h \
		kernel/interrupt.h lib/string.h fs/super_block.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h \
		fs/inode.h fs/super_block.h fs/dir.h \
		lib/stdio.h lib/string.h kernel/debug.h \
		device/ide.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/inode.o: fs/inode.c fs/inode.h \
		device/ide.h kernel/debug.h kernel/interrupt.h \
		thread/thread.h lib/string.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/file.o: fs/file.c fs/file.h \
		fs/inode.h device/ide.h thread/thread.h \
		lib/stdio.h lib/kernel/stdint.h
	$(CC) $(CFLAGS) $< -o $@

$(BUILD_DIR)/dir.o: fs/dir.c fs/dir.h \
		fs/inode.h fs/file.h device/ide.h \
		kernel/memory.h kernel/debug.h lib/stdio.h \
		lib/string.h
	$(CC) $(CFLAGS) $< -o $@

##############    汇编代码编译    ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/print.o: lib/kernel/print.S
	$(AS) $(ASFLAGS) $< -o $@

$(BUILD_DIR)/switch.o: thread/switch.S
	$(AS) $(ASFLAGS) $< -o $@

##############    连接所有目标文件    #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
	$(LD) $(LDFLAGS) $^ -o $@

.PHONY : mk_dir hd clean all

mk_dir:
	if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi

hd:
	dd if=$(BUILD_DIR)/kernel.bin \
           of=/home/hongbai/bochs/bin/os_hd_60M.img \
           bs=512 count=200 seek=10 conv=notrunc

clean:
	cd $(BUILD_DIR) && rm -f ./*

build: $(BUILD_DIR)/kernel.bin

all: mk_dir build hd


结语

现在是5月13日晚上8点,从前天晚上开始到现在,算是写完了14.3和14.4这部分。后面就是一个个功能的实现了,还是非常期待的。

这周能自由支配的时间比较短,所以一点点写吧,能写多少算多少。

感觉文件这部分想要学好,必须理解虚拟在内存的硬盘和内存之间的通信关系,只能说目前我只能是看懂代码copy一下,还需要进一步学习理解啊。

后续文件操作6章两篇博客,目录操作5章两篇博客,再有4篇博客我们就能完成文件系统这一部分了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EMQX v5.0通过连接器和规则同步数据

1 概述 EMQX数据集成功能&#xff0c;帮助用户将所有的业务数据无需额外编写代码即可快速完成处理与分发。 数据集成能力由连接器和规则两部分组成&#xff0c;用户可以使用数据桥接或 MQTT 主题来接入数据&#xff0c;使用规则处理数据后&#xff0c;再通过数据桥接将数据发…

2. 盒模型/布局模块 - 响应式产品展示页_案例:电商产品网格布局

2. 盒模型/布局模块 - 响应式产品展示页 案例&#xff1a;电商产品网格布局 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style type"text/css">:root {--primary-color…

LVGL的三层屏幕结构

文章目录 &#x1f31f; LVGL 的三层屏幕架构1. **Top Layer&#xff08;顶层&#xff09;**2. **System Layer&#xff08;系统层&#xff09;**3. **Active Screen&#xff08;当前屏幕层&#xff09;** &#x1f9e0; 总结对比&#x1f50d; 整体作用✅ 普通屏幕层对象&…

【PDF】使用Adobe Acrobat dc添加水印和加密

【PDF】使用Adobe Acrobat dc添加水印和加密 文章目录 [TOC](文章目录) 前言一、添加保护加密口令二、添加水印三、实验四、参考文章总结 实验工具&#xff1a; 1.Adobe Acrobat dc 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、添加保护加…

Windows下安装mysql8.0

一、下载安装离线安装包 &#xff08;下载过了&#xff0c;可以跳过&#xff09; 下载网站&#xff1a;MySQL :: Download MySQL Installerhttps://dev.mysql.com/downloads/installer/ 二、安装mysql 三、安装完成验证

水滴Android面经及参考答案

static 关键字有什么作用&#xff0c;它修饰的方法可以使用非静态的成员变量吗&#xff1f; static关键字在 Java 中有多种作用。首先&#xff0c;它可以用来修饰变量&#xff0c;被static修饰的变量称为静态变量。静态变量属于类&#xff0c;而不属于类的某个具体实例&#xf…

工程师必读! 3 个最常被忽略的 TDR 测试关键细节与原理

TDR真的是一个用来看阻抗跟Delay的好工具&#xff0c;通过一个Port的测试就可以看到通道各个位置的阻抗变化。 可是使用上其实没这么单纯&#xff0c;有很多细节需要非常地小心&#xff0c;才可以真正地看到您想看的信息&#xff01; 就让我们整理3个极为重要的TDR使用小细节&…

C++中的各式类型转换

隐式转换&#xff1a; 基本类型的隐式转换&#xff1a; 当函数参数类型非精确匹配&#xff0c;但是可以转换的时候发生 如&#xff1a; void func1(double x){cout << x << endl; }void func2(char c){cout << c << endl; }int main(){func1(2);//…

Nacos源码—9.Nacos升级gRPC分析七

大纲 10.gRPC客户端初始化分析 11.gRPC客户端的心跳机制(健康检查) 12.gRPC服务端如何处理客户端的建立连接请求 13.gRPC服务端如何映射各种请求与对应的Handler处理类 14.gRPC简单介绍 10.gRPC客户端初始化分析 (1)gRPC客户端代理初始化的源码 (2)gRPC客户端启动的源码…

【计算机视觉】基于深度学习的实时情绪检测系统:emotion-detection项目深度解析

基于深度学习的实时情绪检测系统&#xff1a;emotion-detection项目深度解析 1. 项目概述2. 技术原理与模型架构2.1 核心算法1) 数据预处理流程2) 改进型MobileNetV2 2.2 系统架构 3. 实战部署指南3.1 环境配置3.2 数据集准备3.3 模型训练3.4 实时推理 4. 常见问题与解决方案4.…

【图像处理基石】什么是油画感?

在图像处理中&#xff0c;“油画感”通常指图像呈现出类似油画的块状纹理、笔触痕迹或色彩过渡不自然的现象&#xff0c;表现为细节模糊、边缘不锐利、颜色断层或人工纹理明显。这种问题常见于照片处理、视频帧截图或压缩后的图像&#xff0c;本质是画质受损的一种表现。以下是…

AD PCB布线的常用命令

PCB布线顺序&#xff1a;先信号&#xff0c;再电源&#xff0c;再GNG 1.多根走线的应用 将IC上的引脚分类 更改一类引脚以及引线的颜色&#xff0c;画出走线&#xff08;将脚引出&#xff09; 选中这些走线&#xff0c;点击‘交互式总线布线’&#xff0c;便可以多根拉线 shi…

【3-2】HDLC

前言 前面我们提到了 PSTN&#xff08;Public Switched Telephone Network&#xff09; &#xff0c;今天介绍一种很少见的数据链路层的协议&#xff0c;HDLC&#xff01; 文章目录 前言1. 定义2. 帧边界3. 零比特填充4. 控制字段4.1. 信息帧&#xff08;I帧&#xff09;4.2. …

MySQL 学习(八)如何打开binlog日志

目录 一、默认状态二、如何检查 binlog 状态三、如何开启 binlog3.1 临时开启&#xff08;重启后失效&#xff09;3.2 永久开启&#xff08;需修改配置文件&#xff09;3.3 验证是否开启成功3.4 查看 binlog 内容 四、高级配置建议五、注意事项六、开启后的日常维护 知识回顾&a…

OpenCV进阶操作:光流估计

文章目录 前言一、光流估计1、光流估计是什么&#xff1f;2、光流估计的前提&#xff1f;1&#xff09;亮度恒定2&#xff09;小运动3&#xff09;空间一致 3、OpenCV中的经典光流算法1&#xff09;Lucas-Kanade方法&#xff08;稀疏光流&#xff09;2&#xff09; Farneback方…

4. 文字效果/2D-3D转换 - 3D翻转卡片

4. 文字效果/2D-3D转换 - 3D翻转卡片 案例&#xff1a;3D产品展示卡片 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style type"text/css">.scene {width: 300px;height…

【AI News | 20250513】每日AI进展

AI Repos 1、iap-diffusion-labs 从零开始带我们构建完整的扩散模型。通过三个精心设计的实验练习&#xff0c;循序渐进地引导我们实现流匹配和扩散模型&#xff0c;从基础 SDE 到条件图像生成&#xff0c;每一步都有详尽指导和完整代码&#xff0c;让复杂理论简单易懂。主要内…

mybatisplus 集成逻辑删除

一开始&#xff0c;没去查资料&#xff0c;后面要被AI气死了&#xff0c;先看它的的话 一开始&#xff0c;看ai的描述&#xff0c;我还以为&#xff0c;不需要改数据库&#xff0c;mybatis-puls自动拦截集成就可以实现逻辑删除&#xff0c;c&#xff0c;最后还是要给数据库加一…

SimScape物理建模实例2--带控制的单质量弹簧阻尼系统

模型下载&#xff1a; 基于simscape&#xff0c;单质量系统带位置控制资源-CSDN文库 在实例1中&#xff0c;我们搭建了不带控制的单质量弹簧阻尼系统&#xff0c;该系统没有外界力量介入&#xff0c;只有弹簧的初始弹力&#xff0c;带着弹簧使劲弹来弹去。 SimScape物理建模实…

PyGame游戏开发(含源码+演示视频+开结题报告+设计文档)

前言&#xff1a; 大二小学期python课上基于pygame做的一个游戏小demo&#xff0c;当时老师花了一天讲解了下python基础语法后&#xff08;也是整个大学四年唯一学习python的时间&#xff09;&#xff0c;便让我们自学网课一周然后交项目&#xff0c;所以做的非常仓促&#xff…