操作系统真象还原:文件操作相关的基础函数

14.4文件操作相关的基础函数

这是一个网站有所有小节的代码实现,同时也包含了Bochs等文件
任何有关文件及目录的操作都了不开对inode的操作,因为我们需要通过inode知道文件的存储位置,所以操作文件,总是意味着要找到该文件的inode。接下来我们实现一堆对于inode的处理函数。涉及:找到一个inode在磁盘中的位置,初始化一个inode,加载该inode到内存中,修改内存中的inode之后同步到磁盘中,从内存中删除一个inode。

14.4.1 inode操作有关的函数

既然文件、目录本质上都是 inode,因此任何有关文件及目录的操作都离不开 inode 的处理,咱们有必要从 inode 开始介绍。

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-05-08 09:08:57
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-05-08 10:14:17
 * @FilePath: /OS/chapter14/14.4/fs/inode.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "inode.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "ide.h"
#include "list.h"
#include "thread.h"
#include "memory.h"


/*用来存储inode位置*/
struct inode_position{
    bool two_swc;   //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){
    /*inode_table在硬盘上是连续的*/
    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;//第inode_no号i结点相对于inode_table_lba的字节偏移量
    uint32_t off_sec = off_size / 512; //第inode_no号i结点相对于inode_table_lba的扇区偏移量
    uint32_t off_size_in_sec = off_size % 512; //待查找的inode所在扇区中的起始地址
    
    /*判断i结点是否跨域2个扇区*/
    uint32_t left_in_sec = 512 - off_size_in_sec;
    if(left_in_sec < inode_size){
        //若扇区内剩下的空间不足以容纳一个inode ,必然是i结点跨越了2个扇区
        inode_pos->two_swc = true;
    }else{  //否则,所查找的 inode 未跨扇区
        inode_pos->two_swc = 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){
    //io_buf是用于硬盘IO的缓冲区
    uint8_t inode_no = inode->i_no;
    struct inode_position inode_pos;
    inode_locate(part,inode_no,&inode_pos); //inode位置信息存入inode_pos
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    /*硬盘中的inode中的成员inode_tag和i_open_cnts是不需要的,他们只在内存中记录链表位置和被多少进程共享*/
    struct inode pure_inode;
    memcpy(&pure_inode,inode,sizeof(struct inode));
    
    /*以下inode的三个成员只存在于内存中,现在将inode同步到硬盘,清理掉这三项中就好了*/
    pure_inode.i_open_cnts = 0;
    pure_inode.write_deny = false;  //置为false,以保证在硬盘中读出时为可写
    pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;

    char* inode_buf = (char*)io_buf;
    if(inode_pos.two_swc){
        ///若是跨了两个扇区,就要读出两个扇区再写入两个扇区

        /*读写硬盘是以扇区为单位,若写入的数据小于一扇区,要将原硬盘上的内容先读出来离和新数据拼成一扇区后再写入*/
        ide_read(part->my_disk,inode_pos.sec_lba,inode_buf,2);  //inode在format中写入硬盘时是连续写入的,所以读入2块扇区
        
        /*开始将待写入的 inode 拼入到这 2 个扇区中的相应位置**/
        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 ionde));
        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){
    /*先在已经打开的inode链表中找inode,次链表是为提速创建的缓冲区*/
    //在mount_partition中初始化的这个队列,一开始里面谁都没有
    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_found->i_open_cnts++;
            return inode_found;
        }
        elem = elem->next;
    }

    /*由于open_inodes链表找不到,所以我们就要从硬盘上读入此inode并加入到此链表*/
    struct inode_position inode_pos;
    
    /*inode位置信息会存入inode_pos,包括 inode 所在扇区地址和扇区内的字节偏移量*/
    inode_locate(part,inode_no,&inode_pos);
    /** 
     * 为使通过 sys_malloc 创建的新 inode 被所有任务共享,
     * 需要将 inode 置于内核空间,故需要临时
     * 将 cur_pbc->pgdir 置为 NULL
     * 因为sys_malloc是通过判断这个cur_pbc->pgdir来观察他是否是内核线程,因为只有用户进程才有这用户进程自己的虚拟页表
    */
    struct task_struct* cur = running_thread();
    uint32_t* cur_pagedit_bak = cur->pgdir;
    cur->pgdir = NULL;
    /*以上三行代码完成后下面分配的内存将位于内核空间*/
    inode_found = (struct inode*)sys_malloc(sizeof(struct inode));
    /*恢复pgdir*/
    cur->pgdir = cur_pagedit_bak;

    char* inode_buf;
    if(inode_pos.two_swc){
        //考虑跨扇区的情况
        inode_buf = (char*)sys_malloc(1024);
        /*i结点表是被partition_format函数连续写入扇区的所以下面可以被连续读出来*/
        ide_read(part->my_disk,inode_pos.sec_lba,inode_buf,2);
    }else{
        //否则所查拢的 inode 未跨扇区,一个扇区大小的缓冲区足够
        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));
    
    /*因为以后可能要用到此inode,故将其插入到队首便于提前检索到*/
    list_push(&part->open_inodes,&inode_found->inode_tag);
    inode_found->i_open_cnts = 1;
    sys_free(inode_buf);
    return inode_found;
}


/*关闭inode或减少inode的打开数*/
void inode_close(struct inode* inode){
    /*若没有进程再打开此文件,将此inode去掉并释放空间*/
    enum intr_status old_status = intr_disable();
    if(--inode->i_open_cnts == 0){
        list_remove(&inode->inode_tag); //将i结点从part->open_inodes中去掉
        /**
         * inode_open时为实现 inode 被所有进程共享,
         * 已经在 sys_malloc 为 inode 分配了内核空间,
         * 释放工 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){
        /*i_sectors[12]为一级间接块地址*/
        new_inode->i_sectors[sec_idx] = 0;
        sec_idx++;
    }
}

inode_locate:用于通过传入指定inodeinode数组中的索引,来获取inode所在扇区和扇区内的偏移。原理:由于传入了inodeinode数组中的索引,且超级块(挂载文件系统后,超级块在内存中)中已经记录了inode数组起始扇区。所以我们能通过索引 * 每个inode大小,计算出这个inode所在扇区和扇区内的偏移。

inode_sync:用于将一个inode写入磁盘中inode数组对应的位置处。原理:调用inode_locate解析出这个inode在磁盘中的位置,然后将这个inode所在的块整个读出到内存缓冲中,再将这个inode写入缓冲中对应的位置处,再将整个块写回磁盘。

inode_open:用于打开一个inode,也就是根据传入的inode数组索引找到该inode并加载到内存中。由于我们在管理分区的结构体struct partition中维护了一个本分区打开文件的inode链表,所以我们优先去这个链表中查找(这个链表在内存中),找不到再去磁盘中找,也就是调用inode_locate解析他在磁盘中的位置,然后读到内存中。由于打开的Inode链表需要对所有进程共享,所有我们存放inode的内存需要去内核堆区申请(因为所有的进程均可访问内核)

inode_close用于关闭一个inode,也就是从内存中移除一个inode。由于一个inode可以被多次打开,我们需要判断此时是否还有进程/线程打开这个inode,再决定是否真正移除。移除的inode所占用的内存空间是内核的堆空间,注意要把进程中PCB的页表变量变为NULL置为内核去,实现内核的内存回收,最后在变回来。

inode_init通过传入inode编号初始化一个inode

14.4.2 文件相关的函数
/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-05-08 10:26:42
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-05-08 11:04:25
 * @FilePath: /OS/chapter14/14.4/fs/file.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "file.h"
#include "stdint.h"
#include "stdio-kernel.h"
#include "stdio.h"
#include "thread.h"
#include "bitmap.h"
#include "ide.h"
#include "fs.h"
/*文件表*/
struct file file_table[MAX_FILE_OPEN];

/*从文件表file_table中获取一个空闲位,成功返回下标,失败返回-1*/
int32_t get_free_slot_in_global(void){
    uint32_t fd_idx = 3;
    while(fd_idx< MAX_FILE_OPEN){
        if(file_table[fd_idx].fd_inode == NULL){
            break;
        }
        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 globa_fd_idx){
    struct task_struct* cur = running_thread();
    uint8_t local_fd_idx = 3;   //跨过stdin,stout,stderr
    while (local_fd_idx<MAX_FILES_OPEN_PER_PROC)
    {
        if(cur->fd_table[local_fd_idx] == -1){
            //-1表示free_slot,可以用
            cur->fd_table[local_fd_idx] = globa_fd_idx;
            break;
        }
        local_fd_idx++;
    }
    if(local_fd_idx==MAX_FILES_OPEN_PER_PROC){
        printk("exceed max open file_per_proc\n");
        return -1;
    }
    return local_fd_idx;
}

/*分配一个i结点,返回i结点号*/
int32_t inode_bitmap_alloc(stuct 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个扇区,返回其扇区地址*/
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->inode_bitmap,bit_idx,1);
    /*和inode_bitmap_alloc不同,此处返回的不是位图索引,而是具体可用的扇区地址*/
    return (part->sb->data_start_lba+bit_idx);
}

/*将内存中bitmap第bit_idx位所在的512字节同步到硬盘*/
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp){
    uint32_t off_sec = bit_idx / 4096;  //本i结点缩影相对于位图的扇区偏移量,4096 是因为一个扇区的大小通常为 512 字节 * 8 = 4096 位。因此,bit_idx 除以 4096 得到的结果是该位所在的扇区索引
    uint32_t off_size = off_sec * BLOCK_SIZE;   //本i结点索引相对于位图的字节偏移量,乘以BLOCK_SIZE是因为位图中的每一位都表示一个块,而每个块的大小为 512 字节。所以,通过将 off_sec 乘以 512,可以得到位图中目标位所在的字节偏移量。

    uint32_t sec_lba;
    uint8_t* bitmap_off;

    /*需要被同步到硬盘的位图只有inode_bitmap和block_bitmap*/
    switch(btmp){
        case INODE_BITMAP:
            sec_lba = part->sb->inode_bitmap_lba + off_sec;
            bitmap_off = part->inode_bitmap.bits + off_size;
            break;
        
        case BLOCK_BITMAP:
            sec_lba = part->sb->block_bitmap_lba + off_sec;
            bitmap_off = part->block_bitmap.bits + off_size;
            break;
    }
    ide_write(part->my_disk,sec_lba,bitmap_off,1);
}

get_free_slot_in_global:从文件表file_table中获取一个空闲位,成功则返回空闲位下标,失败则返回-1。实现原理是遍历 file_table,找出inode为 null 的数组元素,该元素表示为空,将其下标返回即可。

pcb_fd_install:接受一个参数,全局描述符下标 globa_fd_idx,也就是数组file_table 的下标。功能是将globa_fd_idx安装到进程或者线程自己的文件描述符fd_table中。

inode_bitmap_alloc:分配一个i结点,返回i结点号

block_bitmap_alloc:分配1个扇区,返回其扇区地址

bitmap_sync:将内存中bitmapbit_idx位所在的512字节同步到硬盘

14.4.3 目录相关的函数

接下来实现一堆与目录相关的函数,涉及目录打开、关闭,在一个目录文件中寻找指定目录项,初始化一个目录项,将目录项写入父目录中。

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-05-08 11:05:56
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-05-09 11:27:46
 * @FilePath: /OS/chapter14/14.4/fs/dir.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "dir.h"
#include "ide.h"
#include "fs.h"
#include "global.h"
#include "memory.h"
#include "inode.h"
#include "stdio-kernel.h"
#include "stdint.h"
#include "string.h"
#include "file.h"
#include "debug.h"

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;
}

open_root_dir用于根据传入的分区,调用inode_open将根目录文件的inode调入内存,并将全局变量struct root_dir中的inode指针,指向根目录文件的inode

/*在分区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;
}

dir_open:功能是在分区part上打开i结点为inode_no的目录并返回目录指针。原理是申请一块内存区域存放目录,调用inode_open找到这个目录对应的inode并载入内存,然后让目录中的inode指针指向这个inode

所以我们可以得到一个结论是:打开目录,就是建立目录(struct dir结构体)与inode之间的关系。因为目录是一个内存中的概念,但它本身是磁盘中的一个文件!为了建立从内存到磁盘的关系,所以我们需要建立struct dirinode之间的关系。所以我们打开目录,自然意味着这个struct dir结构体要在内存中。同时一个目录中存储的是文件或者文件夹,在Linux下它又是一个文件,是文件就有inode,也就是这个目录中存储了很多inode,我们要找到这些文件,也就要先找到我们这个目录所在的位置,而这个位置被inode记录着,这个inode中记录了一堆文件也就是inode通过这个可以找到目录下的其他文件,所以我们肯定要将它的inode调入内存中,建立目录(struct dir结构体)与inode之间的关系。

/*在part分区内的pdir目录内寻址名为name的文件或目录,找到后返回ture并将其目录项存入dir_e,否则返回false,inode中指向的地方也是存储的dir结构*/
bool search_dir_entry(struct partition* part, struct dir* pdir, const char* name, struct dir_entry* dir_e){
    uint32_t block_cnt = 140;   //12个直接块+128个一级间接块=140

    /*12个直接块大小+128个间接块,供560字节*/
    uint32_t* all_blocks = (uint32_t*)sys_malloc(48+512);
    if(all_blocks == NULL){
        printk("search_dir_entry:sys_malloc for all_blocks failed");
        return false;
    }

    uint32_t block_idx = 0;
    while(block_idx < 12){
        all_blocks[block_idx] = pdir->inode->i_sectors[block_idx];
        block_idx++;
    }
    block_idx=0;

    if(pdir->inode->i_sectors[12]!=0){  //含有一级间接块表
        ide_read(part->my_disk,pdir->inode->i_sectors[12],all_blocks+12,1);
    }
    /*至此,all_blocks存储的是目录pdir的所有扇区地址*/

    /*写目录项的时候已经保证目录项不夸扇区,这样读目录项容易处理,值申请容纳一个扇区的内存*/
    uint8_t* buf = (uint8_t*)sys_malloc(SECTOR_SIZE);
    struct dir_entry* p_de = (struct dir_entry*)buf;
    //p_de为指向目录项的指针,值为buf起始地址
    uint32_t dir_entry_size = part->sb->dir_entry_size;
    uint32_t dir_entry_cnt = SECTOR_SIZE / dir_entry_size;  //1扇区可以容纳的目录项个数

    /*开始在所有块中查找目录项*/
    while(block_idx < block_cnt){
        /*块地址为0时表示该块中无数据,继续在其他块中找*/
        if(all_blocks[block_idx]==0){
            block_idx++;
            continue;
        }
        ide_read(part->my_disk,all_blocks[block_idx],buf,1);
        uint32_t dir_entry_idx = 0;
        /*遍历扇区中所有目录项*/
        while(dir_entry_idx < dir_entry_cnt){
            /*若找到了, 就直接复制整个目录项*/
            if(!strcmp(p_de->filename,name)){
                memcpy(dir_e,p_de,dir_entry_size);
                sys_free(buf);
                sys_free(all_blocks);
                return true;
            }
            dir_entry_idx++;
            p_de++;
        }
        p_de = (struct dir_entry*)buf;  //此时p_de已经指向扇区内最后一个完整目录项,需要恢复p_de指向为buf
        memset(buf,0,SECTOR_SIZE);
    }
    sys_free(buf);
    sys_free(all_blocks);
    return false;
}

search_dir_entry:功能是在part分区内的pdir目录内寻址名为name的文件或目录,找到后返回ture并将其目录项存入dir_e,否则返回falseinode中指向的地方也是存储的dir结构。核心原理就是目录结构体struct dir 有一个指向自己inode的成员,该成员记录了该目录在磁盘中存储的位置(inodei_sectors[ ])一共有140个块,我们可以根据这个找到目录文件,然后遍历其中的目录项即可。

/*关闭目录*/
void dir_close(struct dir* dir){
    /***************** 根目录不能关闭 ******************************/
    /*1 根目录自打开后就不应该关闭,否则还需要再次open_root_dir()*/
    /*2 root_dir锁在的内存是低端1MB之内,并非在堆中,free会出现问题*/
    if(dir == &root_dir){
        /*不做任何处理直接返回*/
        return;
    }
    inode_close(dir->inode);
    sys_free(dir);
}

dir_close:用于关闭目录,实质就是调用inode_close从内存中释放该目录的inode占用的内存,并且释放目录占用的内存,也就是解绑struct_dirinode之间的关系。需要注意的是:根目录不能被释放,因为一定会反复被用到.

/*在内存中初始化目录项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;
}

create_dir_entry用于初始化一个目录项,也就是给目录项指向的文件一个名字与inode索引。

/*将目录项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);//dir_size应该是dir_entry_size的整数倍

    uint32_t dir_entrys_per_sec = (512 / dir_entry_size); //每个扇区最大目录项数目
    int32_t block_lba = -1;

    /*将该目录的所有扇区地址(12个直接块+128个间接块)存入all_blocks*/
    uint8_t block_idx = 0;
    uint32_t all_blocks[140] = {0}; //all_blocks保存目录所有的块

    /*将12个直接块存入all_blocks*/
    while(block_idx < 12){
        all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
        block_idx++;
    }
    
    if (dir_inode->i_sectors[12] != 0) {	// 若含有一级间接块表
      	ide_read(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
   	}

    struct dir_entry* dir_e = (struct dir_entry*)io_buf;    //dir_e用来在io_buf中遍历目录项
    
    int32_t block_bitmap_idx = -1;

    /*开始遍历所有块以寻找目录项空位,若已有扇区中没有空闲位,在不超过文件大小的情况下申请新扇区来存储新目录项*/
    block_idx = 0;
    while(block_idx<140){//文件最大支持12个直接块+128个间接块=140
        block_bitmap_idx = -1;
        if(all_blocks[block_idx]==0){   //在三种情况下分配块
            block_lba = block_bitmap_alloc(cur_part);
            if(block_lba==-1){
                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);

            block_bitmap_idx = -1;
            if(block_idx<12){   //若是直接块
                dir_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
            }else if(block_idx == 12){  //若是尚未分配一级间接块表(block_idx 等于 12 表示第 0个间接块地址为 0)
                dir_inode->i_sectors[12] = block_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("all 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_blocks[12] = block_lba;
                /*把新分配的第0个间接块地址写入一级间接块表*/
                ide_write(cur_part->my_disk,dir_inode->i_sectors[12],all_blocks+12,1);
            }else{  //若是间接块未分配
                all_blocks[block_idx] = block_lba;
                /*把新分配的第(block_idx-12)个间接块写入一级间接块*/
                ide_write(cur_part->my_disk,dir_inode->i_sectors[12],all_blocks+12,1); 
            }

            /*在将新目录p_de写入新分配的间接块*/
            memset(io_buf,0,512);
            memcpy(io_buf,p_de,dir_entry_size);
            ide_write(cur_part->my_disk,all_blocks[block_idx],io_buf,1);//io_buf中的内容写入到新分配到all_blocks[block_idx]指向的新扇区地址中,也就写入到了父目录的目录项中
            dir_inode->i_size+=dir_entry_size;
            return true;
        }

        /*若block_idx块已经存在,将其读进内存,然后再该块总查找空目录项*/
        ide_read(cur_part->my_disk,all_blocks[block_idx],io_buf,1); //写入到了io_buf其实也是相当于写入到了dir_e中,
        /*在扇区中查找空目录项*/
        uint8_t dir_entry_idx = 0;
        while(dir_entry_idx < dir_entrys_per_sec){
            if((dir_e+dir_entry_idx)->f_type == FT_UNKNOWN){//FT_UNKNOWN为0,无论是初始化,或是删除文件后,都会将f_type置位FT_UNKNOWN
                memcpy(dir_e+dir_entry_idx,p_de,dir_entry_size);
                ide_write(cur_part->my_disk,all_blocks[block_idx],io_buf,1);
                dir_inode->i_size+=dir_entry_size;
                return true;
            }
            dir_entry_idx++;
        }
        block_idx++;
    }
    printk("dorectory is full!\n");
    return false;
}

sync_dir_entry:功能是将目录项p_de写入父目录parent_dir中,io_buf由主调函数提供。核心原理就是:父目录结构体struct dir有一个指向自己inode的成员,该成员记录了该父目录文件在磁盘中存储的位置(inodei_sectors[ ])我们可以根据这个找到目录文件,然后遍历目录文件找到空位,然后将目录项插入到这个空位即可。有些麻烦的是,需要判断inodei_sectors[ ]这些元素是否为空,如果为空,那么我们还需要申请块用于承载目录文件。

14.4.4 路径解析相关的函数

接下来我们要实现路径解析功能。

/*将最上层路径名称解析出来*/
static char* pathr_pase(char* pathname, char* name_store){
    if(pathname[0]=='/'){   //根目录不需要单独解析
        /*路径中出现1个或多个连续的字符'/',将这些‘/'跳过,如“///a/b”*/
        while(*(++pathname) == '/');
    }

    /*开始一般的路径解析*/
    while(*pathname!='/'&&*pathname!=0){
        *name_store++ = *pathname++;
    }

    if(pathname[0]==0){//若路径字符串为空,则返回NULL
        return NULL;
    }
    return pathname;
}

pathr_pase:函数功能是将最上层路径名称解析出来并存储到name_store中,调用结束后返回除顶层路径之外的子路径字符串的地址。比如此函数解析/home/kanshan/Desktop/test.c,第一次调用返回/kanshan/Desktop/test.cname_store中存储home。再次调用返回/Desktop/test.cname_store中存储kanshan

/*返回路径深度,比如/a/b/c,深度为3*/
int32_t pathr_depth_cnt(char* pathname){
    ASSERT(pathname!=NULL);
    char* p = pathname;
    char name[MAX_FILE_NAME_LEN];   //用于path_parse的参数做路径解析
    uint32_t depth = 0;

    /*解析路径,从中拆分出各级名称*/
    p = path_parse(p,name);
    while(name[0]){
        depth++;
        memset(name,0,MAX_FILE_NAME_LEN);
        if(p){  //如果p不等于NULL,继续分析路径
            p = path_parse(p,name);
        }
    }
    return depth;
}

pathr_depth_cnt:函数功能是返回路径深度,比如/a/b/c,深度为3。原理就是循环调用path_parse,看看返回值是不是空,决定是否继续调用path_parse

14.4.5 实现文件检索功能

在打开文件之前,咱们要确认文件是否在磁盘上存在,毕竟只有己存在的文件才谈得上“打开”。在打开文件之前,咱们要确认文件是否在磁盘上存在,毕竟只有己存在的文件才谈得上“打开”。这实际上就是文件搜索的功能。

修改增加用于记录搜索路径的结构体:

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-05-07 11:06:32
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-05-10 14:17:14
 * @FilePath: /OS/chapter14/14.2/fs/fs.h
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#ifndef _FS_FS_H
#define _FS_FS_H
#include "stdint.h"
#include "dir.h"

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

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

/*打开文件的选项,这里是按“位”来定义各标识的*/
enum oflags{
    O_RDONLY,   //只读000
    O_WRONLY,   //只写001
    O_RDWR, //读写010
    O_CREAT = 4 //创建100
};

/*用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中”走过的地方··*/
struct path_search_record{
    char searched_path[MAX_PATH_LEN];   //查找过程中的父目录
    struct dir* parent_dir; //文件或目录所在的直接父目录
    enum file_types file_type;  ///找到的是普通文件,还是目录,找不到将为未知类型( FT_UNKNOWN)
};

extern struct partition* cur_part;
int32_t pathr_depth_cnt(char* pathname);
int32_t sys_open(const char* pathname, uint8_t flags);
void filesys_init(void);
#endif // !_FS_FS_H

主要是添加了path_search_record:用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中走过的地方

/*搜索文件pathname,若找到则返回其inode号,否则就返回-1*/
static int search_file(const char* pathname,struct path_search_record* searched_record){
    /*如果待查找的是根目录,为避免下面无用的查找,直接返回已知根目录信息*/
    if(!strcmp(pathname,"/")||!strcmp(pathname,"/.")||!strcmp(pathname,"/..")){
        searched_record->parent_dir = &root_dir;
        searched_record->file_type = FT_DIRECTORY;
        searched_record->searched_path[0] = 0;  //搜索路径为空
        return 0;
    }

    uint32_t path_len = strlen(pathname);
    /*保证pathname至少是这样的路径/x,且小于最大长度*/
    ASSERT(pathname[0] == '/' && path_len>1 && path_len<MAX_PATH_LEN);
    char* sub_path = (char*)pathname;
    struct dir* parent_dir = &root_dir;
    struct dir_entry dir_e;

    /*记录路径解析出来的各级名称,如路径"/a/b/c",数组name每次的值分别是"a","b","c"*/
    char name[MAX_FILE_NAME_LEN] = {0};
    
    searched_record->parent_dir = parent_dir;
    searched_record->file_type = FT_UNKNOWN;
    uint32_t parent_inode_no = 0;//父目录的inode号

    sub_path = path_parse(sub_path,name);
    while(name[0]){//若第一个字符就是结束符,结束循环
        /*记录查找过的路径,但不能超过searched_path的长度512*/
        ASSERT(strlen(searched_record->searched_path)<512);

        /*记录已经存在的父目录*/
        strcat(searched_record->searched_path,"/");
        strcat(searched_record->searched_path,name);

        /*在所给的目录中查找文件*/
        if(search_dir_entry(cur_part,parent_dir,name,&dir_e)){
            memset(name,0,MAX_FILE_NAME_LEN);
            /*若sub_path不等于NULL,也就是未结束时继续拆分路径*/
            if(sub_path){
                sub_path = pathr_pase(sub_path,name);
            }

            if(FT_DIRECTORY == dir_e.f_type){//如果被打开的是目录
                parent_inode_no = parent_dir->inode->i_no;
                dir_close(parent_dir);
                parent_dir = dir_open(cur_part,dir_e.i_no);//更新父目录
                searched_record->parent_dir = parent_dir;
                continue;
            }else if(FT_REGULAR == dir_e.f_type){   //若是普通文件
                searched_record->file_type = FT_REGULAR;
                return dir_e.i_no;
            }
        }else{  //若找不到,则返回-1
            //找不到目录项时 ,要留着 parent_dir 不要关闭,若是创建新文件的话需要在 parent_dir 中创建
            return -1;
        }
    }
    /*执行到此,必然是遍历了完整路径,并且查找的文件或目录只有同名目录存在 */
    dir_close(searched_record->parent_dir);
    /*保存被查找到目录的直接父目录*/
    //parent_inode_no的作用:
    /**
     * “/a/b/c ”, c 是目录,不是普通文件,
     * 此时searched_record->parent_dir是路径pathname中的最后一级目录c,并不是倒数第二级的父目录b,
     * 我们在任何时候都应该使searched_record->parent_dir中记录的都应该是目录b
     * 因此我们需要把searched_record->parent_dir重新更新为父目录b, parent_inode_no就记录了父目录
    */
    searched_record->parent_dir = dir_open(cur_part,parent_inode_no);
    searched_record->file_type = FT_DIRECTORY;
    return dir_e.i_no;
}

search_file:搜索文件pathname,若找到则返回其inode号,否则就返回-1,用于提供文件路径,然后返回inode索引。核心原理就是不断循环:1,调用path_parse进行地址解析得到除根目录外最上层目录的名字;2,然后调用search_dir_entry从搜索目录中(第一次调用这个函数,其搜索目录自然是根目录)查找 1 得到的名字,去验证路径是否存在;3,然后更新下一次的搜索目录(就是 2 查找出来的目录项);继续解析,然后继续查找,继续更新搜索目录…

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值