操作系统真象还原:实现文件删除功能

14.10 实现文件删除功能

在Linux 下用于文件删除的函数是 unlink,咱们本节就要实现它。删除文件是创建文件的逆过程,会涉及到 inodeinode 位图、目录 inode 中的i_size、目录项、数据块及数据块位图的回收操作,因此还是先构建“底层建筑”。

14.10.1 回收inode

inode 是文件系统的灵魂,删除文件最重要的就是回收文件对应的 inode 。与inode有关的资源有:

  1. inode位图
  2. inode_table
  3. inodei_sectors[0-11]中的直接块和一级间接索引块表i_sectors[12]中的间接块
  4. 一级间接索引块表本身的扇区地址
/*将硬盘分区part上的inode清空,功能是把 inode 从 inode_table
中擦除,也就是在 inode_table 中把该 inode 对应的空间清0。*/
void inode_delete(struct partition* part, uint32_t inode_no, void* io_buf){
    ASSERT(inode_no<4096);
    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));
    
    char* inode_buf = (char*)io_buf;
    if(inode_pos.two_swc){  //inode跨扇区,读入两个扇区
        /*将原硬盘上的内容先读出来*/
        ide_read(part->my_disk,inode_pos.sec_lba,inode_buf,2);
        /*将inode_buf清0*/
        memset((inode_buf+inode_pos.off_size),0,sizeof(struct inode));
        /*用清0的内存数据覆盖磁盘*/
        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);
        /*将inode_buf清0*/
        memset((inode_buf+inode_pos.off_size),0,sizeof(struct inode));
        /*用清0的内存数据覆盖磁盘*/
        ide_write(part->my_disk,inode_pos.sec_lba,inode_buf,1);
    }
}

inode_delete:接受 3 个参数,分区 partinode 编号 inode_no 及缓冲区 io_buf,功能是把 inodeinode_table中擦除,也就是在 inode_table 中把该 inode 对应的空间清 0 。

inode_delete:传入要删除的inodeinode数组中的索引,然后删除磁盘中的这个inode。这个函数可有可无,因为inode分配是依靠inode位图来完成的,我们回收一个inode,只需要回收一个inode位图中的位即可。因为下次分配这个inode位之后,新的inode数据会覆盖旧有inode数据,这样还能避免不必要的磁盘读写。

函数原理:调用inode_locate可以将inode数组索引转换为这个inode在磁盘中的起始扇区和字节偏移。我们将这个inode所在整体扇区读出内存缓冲区中,然后将这个内存缓冲区中的inode清除,再将内存缓冲区中的数据写回磁盘中即可。

/*回收inode的数据块和inode本身,功能是回收inode,这
包括inode中的数据块和inode本身在inode位图中的bit
核心操作是调用bitmap_set将内存中位图的block_bitmap_idx位置为0,
再调用bitmap_sync将内存中的位图同步到硬盘。*/
void inode_release(struct partition* part, uint32_t inode_no){
    struct inode* inode_to_del = inode_open(part,inode_no);
    ASSERT(inode_to_del->i_no == inode_no);

    /*1 回收inode所占用的所有块*/
    uint32_t block_idx = 0, block_cnt = 12;
    uint32_t block_bitmap_idx;
    uint32_t all_blocks[140] = {0}; //12个直接块+128个间接块

    /*a 先将前12个直接块存入all_blocks*/
    while(block_idx<12){
        all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
        block_idx++;
    }

    /*b 如果一级间接块表存在,将在其128个间接块读到all_blocks[12~],并释放一级间接块表所占的扇区*/
    if(inode_to_del->i_sectors[12]!=0){
        inode_release(part->my_disk,inode_to_del->i_sectors[12],all_blocks+12,1);
        block_cnt = 140;

        /*回收一级间接块表所占用的扇区*/
        block_bitmap_idx = inode_to_del->i_sectors[12] - part->sb->data_start_lba;
        ASSERT(block_bitmap_idx > 0);
        bitmap_set(&part->block_bitmap,block_bitmap_idx,0);
        bitmap_sync(cur_part,block_bitmap_idx,BLOCK_BITMAP);
    }

    /*c inode所有的块地址已经收集到all_blocks中,下面逐个回收*/
    block_idx  = 0;
    while(block_idx<block_cnt){
        if(all_blocks[block_idx!=0]){
            block_bitmap_idx = 0;
            block_bitmap_idx = all_blocks[block_idx]-part->sb->data_start_lba;
            ASSERT(block_bitmap_idx > 0);
            bitmap_set(&part->block_bitmap,block_bitmap_idx,0);
            bitmap_sync(cur_part,block_bitmap_idx,BLOCK_BITMAP);
        }
        block_idx++;
    }

    /*2 回收inode所占用的inode*/
    bitmap_set(&part->inode_bitmap,inode_no,0);
    bitmap_sync(cur_part,inode_no,INODE_BITMAP);

    /***************************************************
     * 以下inode_table是调试用的
     * 此函数会在inode_table中将此inode清0
     * 但实际上不需要的,inode分配是由inode位图控制的
     * 硬盘上的数据不需要清0,可以直接覆盖
    */
   void* io_buf = sys_malloc(1024);//这里分配1024是因为inode节点可能会夸扇区
   inode_delete(part,inode_no,io_buf);
   sys_free(io_buf);
   /*****************************************************/
   inode_close(inode_to_del);
}

inode_release:它接受 2 个参数,分区 partinode 编号 inode_no,功能是回收 inode,这包括 inode 中的数据块和 inode 本身在 inode 位图中的 bit 。回收inode的数据块和inode本身,功能是回收inode,这包括inode中的数据块和inode本身在inode位图中的bit核心操作是调用bitmap_set将内存中位图的block_bitmap_idx位置为0,再调用bitmap_sync将内存中的位图同步到硬盘。

这个过程包含:1、回收文件所占用的所有块,通过inode中的i_sectors[ ]即可知道占用了哪些块,然后去清除对应的块位图中的位即可,也就是并没有真正删除文件数据;2、回收inode,首先清除该inodeinode位图中对应的位,然后调用inode_delete删除在磁盘中的这个inode

14.10.2 删除目录项

文件名是以目录项的形式存在的,删除文件必须在目录中将其目录项擦除:

  1. 在文件所在的目录中擦除该文件的目录项,使其为 0 。
  2. 根目录是必须存在的,它是文件读写的根基,不应该被清空,它至少要保留1个块。如果目录项独占 1个块,并且该块不是根目录最后一个块的话,将其回收。
  3. 目录 inodei_size 是目录项大小的总和,因此还要将i_size减去一个目录项的单位大小
  4. 目录 inode 改变后,要同步到硬盘。
/*把分区part目录pdir中编号为inode_no的目录项删除*/
bool delete_dir_entry(struct partition* part, struct dir* pdir, uint32_t inode_no, void* io_buf){
    struct inode* dir_inode = pdir->inode;
    uint32_t block_idx = 0, all_blocks[140] = {0};
    /*收集目录全部地址块*/
    while(block_idx<12){
        all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
        block_idx++;
    }
    if(dir_inode->i_sectors[12]){
        ide_read(part->my_disk,dir_inode->i_sectors[12],all_blocks+12,1);
    }

    /*目录项在存储时保证不会跨越扇区*/
    uint32_t dir_entry_size = part->sb->dir_entry_size;
    uint32_t dir_entrys_per_sec = (SECTOR_SIZE / dir_entry_size);   //每个扇区最大的目录项数
    struct dir_entry* dir_e = (struct dir_entry*)io_buf;
    struct dir_entry* dir_entry_found = NULL;
    uint8_t dir_entry_idx, dir_entry_cnt;
    bool is_dir_first_block = false;    //目录的第1个块

    /*遍历所有块,寻找目录项*/
    block_idx = 0;
    while (block_idx < 140)
    {
        is_dir_first_block = false;
        if (all_blocks[block_idx] == 0)
        {
            block_idx++;
            continue;
        }
        dir_entry_idx = dir_entry_cnt = 0;
        memset(io_buf, 0, SECTOR_SIZE);
        /* 读取扇区,获得目录项 */
        ide_read(part->my_disk, all_blocks[block_idx], io_buf, 1);

        /* 遍历所有的目录项,统计该扇区的目录项数量及是否有待删除的目录项 */
        while (dir_entry_idx < dir_entrys_per_sec)
        {
            if ((dir_e + dir_entry_idx)->f_type != FT_UNKNOWN)
            {
                if (!strcmp((dir_e + dir_entry_idx)->filename, "."))
                {
                    is_dir_first_block = true;
                }
                else if (strcmp((dir_e + dir_entry_idx)->filename, ".") &&
                         strcmp((dir_e + dir_entry_idx)->filename, ".."))
                {
                    dir_entry_cnt++; // 统计此扇区内的目录项个数,用来判断删除目录项后是否回收该扇区
                    if ((dir_e + dir_entry_idx)->i_no == inode_no)
                    {                                    // 如果找到此i结点,就将其记录在dir_entry_found
                        ASSERT(dir_entry_found == NULL); // 确保目录中只有一个编号为inode_no的inode,找到一次后dir_entry_found就不再是NULL
                        dir_entry_found = dir_e + dir_entry_idx;
                        /* 找到后也继续遍历,统计总共的目录项数 */
                    }
                }
            }
            dir_entry_idx++;
        }

        /* 若此扇区未找到该目录项,继续在下个扇区中找 */
        if (dir_entry_found == NULL)
        {
            block_idx++;
            continue;
        }

        /* 在此扇区中找到目录项后,清除该目录项并判断是否回收扇区,随后退出循环直接返回 */
        ASSERT(dir_entry_cnt >= 1);
        /* 除目录第1个扇区外,若该扇区上只有该目录项自己,则将整个扇区回收 */
        if (dir_entry_cnt == 1 && !is_dir_first_block)
        {
            /* a 在块位图中回收该块 */
            uint32_t block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
            bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

            /* b 将块地址从数组i_sectors或索引表中去掉 */
            if (block_idx < 12)
            {
                dir_inode->i_sectors[block_idx] = 0;
            }
            else
            { // 在一级间接索引表中擦除该间接块地址
                /*先判断一级间接索引表中间接块的数量,如果仅有这1个间接块,连同间接索引表所在的块一同回收 */
                uint32_t indirect_blocks = 0;
                uint32_t indirect_block_idx = 12;
                while (indirect_block_idx < 140)
                {
                    if (all_blocks[indirect_block_idx] != 0)
                    {
                        indirect_blocks++;
                    }
                }
                ASSERT(indirect_blocks >= 1); // 包括当前间接块

                if (indirect_blocks > 1)
                { // 间接索引表中还包括其它间接块,仅在索引表中擦除当前这个间接块地址
                    all_blocks[block_idx] = 0;
                    ide_write(part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
                }
                else
                { // 间接索引表中就当前这1个间接块,直接把间接索引表所在的块回收,然后擦除间接索引表块地址
                    /* 回收间接索引表所在的块 */
                    block_bitmap_idx = dir_inode->i_sectors[12] - part->sb->data_start_lba;
                    bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
                    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                    /* 将间接索引表地址清0 */
                    dir_inode->i_sectors[12] = 0;
                }
            }
        }
        else
        { // 仅将该目录项清空
            memset(dir_entry_found, 0, dir_entry_size);
            ide_write(part->my_disk, all_blocks[block_idx], io_buf, 1);
        }

        /* 更新i结点信息并同步到硬盘 */
        ASSERT(dir_inode->i_size >= dir_entry_size);
        dir_inode->i_size -= dir_entry_size;
        memset(io_buf, 0, SECTOR_SIZE * 2);
        inode_sync(part, dir_inode, io_buf);

        return true;
    }
    /*所有块中未找到则返回 false ,出现这种情况应该是 serarch_file 出错了*/
    return false;
}

delete_dir_entry:接受 4 个参数,分区 part、目录 pdirinode 编号 inode_no、缓冲区 io_buf,功能是把分区 part 目录 pdir 中编号为 inode_no 的目录项删除。

核心原理:传入的要删除文件的父目录结构体struct dir指针内有inode成员,这个inode内有i_sectors[ ]记录着这个目录文件的存储位置,我们自然可以以块为单位从磁盘中把父目录文件读取到缓冲区中,然后遍历找到要删除的目录项,删除缓冲区内对应的目录项,然后写回缓冲区数据。

14.10.3 实现sys_unlink与功能验证

Linux 下删除文件是用的 unlink 系统调用,其原型是int unlink(const char *pathname),成功删除文件返回 0,否则返回-1。

/* 删除文件(非目录),成功返回0,失败返回-1 */
int32_t sys_unlink(const char* pathname) {
   ASSERT(strlen(pathname) < MAX_PATH_LEN);

   /* 先检查待删除的文件是否存在 */
   struct path_search_record searched_record;
   memset(&searched_record, 0, sizeof(struct path_search_record));
   int inode_no = search_file(pathname, &searched_record);
   ASSERT(inode_no != 0);
   if (inode_no == -1) {
      printk("file %s not found!\n", pathname);
      dir_close(searched_record.parent_dir);
      return -1;
   }
   if (searched_record.file_type == FT_DIRECTORY) {
      printk("can`t delete a direcotry with unlink(), use rmdir() to instead\n");
      dir_close(searched_record.parent_dir);
      return -1;
   }

   /* 检查是否在已打开文件列表(文件表)中 */
   uint32_t file_idx = 0;
   while (file_idx < MAX_FILE_OPEN) {
      if (file_table[file_idx].fd_inode != NULL && (uint32_t)inode_no == file_table[file_idx].fd_inode->i_no) {
	 break;
      }
      file_idx++;
   }
   if (file_idx < MAX_FILE_OPEN) {
      dir_close(searched_record.parent_dir);
      printk("file %s is in use, not allow to delete!\n", pathname);
      return -1;
   }
   ASSERT(file_idx == MAX_FILE_OPEN);
   
   /* 为delete_dir_entry申请缓冲区 */
   void* io_buf = sys_malloc(SECTOR_SIZE + SECTOR_SIZE);
   if (io_buf == NULL) {
      dir_close(searched_record.parent_dir);
      printk("sys_unlink: malloc for io_buf failed\n");
      return -1;
   }

   struct dir* parent_dir = searched_record.parent_dir;  
   delete_dir_entry(cur_part, parent_dir, inode_no, io_buf);
   inode_release(cur_part, inode_no);
   sys_free(io_buf);
   dir_close(searched_record.parent_dir);
   return 0;   // 成功删除文件 
}

sys_unlink:删除文件(非目录),成功返回0,失败返回-1。原理:首先调用 search_file搜索路径以返回文件的inode,判断该inode是否对应某个打开全局文件结构,如果是,则说明此文件正在被使用,那么就不应该被删除。如果不是,调用 delete_dir_entry删除这个文件在磁盘中的目录项,调用inode_release删除inode对应的文件,这就完成了删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值