文件删除这一块在书中比较长,代码也比较多。不过其实际的原理还是比较简单的,文件在硬盘上就对应着一个inode结构以及为该inode结构所分配的数据块。所以,删除一个文件分以下两步操作进行即可:
(一)、在父目录中删除该文件的目录项
(二)、回收该文件在硬盘中占用的数据块(通过硬盘分区中的block_bitmap位图)以及inode结构(通过硬盘分区中的inode_bitmap位图)
第一步对应的是书中第14.10.2节,梳理一下delete_dir_entry函数的流程:
(1)、收集父目录对应的所有磁盘数据块
(2)、从头开始逐个遍历数据块,查找是否有inode_no对应的目录项存在
(3)、判断扇区中是否只有一个目录项,若只有一个目录项则需要对该数据块整体进行回收,否则将当前目录项清空后写入原数据块即可。
(3.1)、进一步判断该块是直接块(block_idx <12)还是间接块(block_idx>=12)
(3.1.1)、直接块清空pdir->inode->i_sectors对应下标即可,最后要将inode同步回硬盘
(3.2.2)、间接块需要进一步判断间接表是否只有当前一个间接块
(3.2.2.1)、若只有当前一个间接块还需要回收索引表
(3.2.2.2)、若还有其他间接块仅在索引表中擦除当前这个间接块地址即可
(4)、将父目录inode_size减去一个dir_entry大小,并将内存中inode信息同步回硬盘
下面上代码(fs/dir.c中)
/* 把分区part目录pdir中编号为inode_no的目录项删除 */
bool delete_dir_entry(struct partition* part, struct dir* pdir, uint32_t inode_no, void* io_buf) {
/* 从pdir中拿到inode */
struct inode* dir_inode = pdir->inode;
if(dir_inode == NULL) {
printk("delete_dir_entry: parent dir's inode is empty, delete fail\n");
return false;
}
/* 申请all_blocks */
uint32_t* all_blocks = sys_malloc(48 + SECTOR_SIZE);
if(all_blocks == NULL) {
printk("delete_dir_entry: sys_malloc for all_blocks fail\n");
}
/* 填充all_blocks */
uint32_t block_idx = 0;
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(part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
/* 逐个数据块读入数据,遍历每个dir_entry,对比inode_no */
struct dir_entry* dir_e = (struct dir_entry*) io_buf;
uint32_t dir_entry_size = part->sb->dir_entry_size;
uint32_t dir_entrys_per_sec = SECTOR_SIZE / dir_entry_size;
uint32_t data_start_lba = part->sb->data_start_lba;
block_idx = 0;
while(block_idx < 140) {
if(all_blocks[block_idx] != 0) {
/* 在某个数据块中是否找到inode */
memset(io_buf, 0, SECTOR_SIZE);
ide_read(part->my_disk, all_blocks[block_idx], io_buf, 1);
uint32_t dir_entry_idx = 0;
uint32_t dir_entry_count = 0;
uint32_t dir_found_idx = -1;
bool is_first_block = false;
bool found = false;
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_first_block = true;
} else if (strcmp(".", (dir_e + dir_entry_idx)->filename) && strcmp("..", (dir_e + dir_entry_idx)->filename)) {
dir_entry_count++;
if((dir_e + dir_entry_idx)->i_no == inode_no) {
found = true;
dir_found_idx = dir_entry_idx;
}
}
}
dir_entry_idx++;
}
if(!found) {
block_idx++;
continue;
}
/* 找到对应inode_no的目录项则需要判断当前数据块是否只有一个目录项 */
/* 只有一个目录需要删除整个数据块 */
if(!is_first_block && dir_entry_count == 1) {
bitmap_set(&part->block_bitmap, all_blocks[block_idx] - data_start_lba, 0);
bitmap_sync(part, all_blocks[block_idx] - data_start_lba, BLOCK_BITMAP);
all_blocks[block_idx] = 0;
if(block_idx < 12) {
/* 如果是直接块内只有一个目录项回收直接块即可 */
dir_inode->i_sectors[block_idx] = 0;
} else {
/* 如果是间接块内只有一个目录项回收间接块的同时,还要看间接块索引表中是否已经没有间接块,此时还需要回收间接块表 */
uint32_t indirect_block_count = 0;
int i;
for(i = 12; i < 140; i++) {
if(all_blocks[i] != 0) {
indirect_block_count++;
}
}
if(indirect_block_count == 0) {
bitmap_set(&part->block_bitmap, pdir->inode->i_sectors[12] - data_start_lba, 0);
bitmap_sync(part, pdir->inode->i_sectors[12] - data_start_lba, BLOCK_BITMAP);
pdir->inode->i_sectors[12] = 0;
} else {
ide_write(part->my_disk, pdir->inode->i_sectors[12], all_blocks + 12, 1);
}
}
} else {
/* 不止一个目录项存在,不用删除数据块,把目录项删除写回磁盘即可 */
memset(dir_e + dir_found_idx, 0, sizeof(struct dir_entry));
ide_write(part->my_disk, all_blocks[block_idx], io_buf, 1);
}
ASSERT(pdir->inode->i_size >= dir_entry_size);
pdir->inode->i_size -= dir_entry_size;
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(part, dir_inode, io_buf);
sys_free(all_blocks);
return true;
}
block_idx++;
}
sys_free(all_blocks);
return false;
}
复杂的说完了,下面说简单的,第二步回收inode数据块和inode本身,这一功能通过函数inode_release实现,梳理后的主要流程如下:
(1)、收集inode对应的所有数据块lba地址存入all_blocks中
(2)、将所有块回收,若块地址存在(!=0)则在block_bitmap中将对应位清0并写入硬盘即可
(3)、将inode回收,即将inode_bitmap中对应位清0后写入硬盘即可
(4)、将inode数组中对应的inode结构所有字节清0(这一步可有可无,因为将来的inode结构会覆盖旧数据,不清除没任何影响,这里清除通过inode_delete函数实现)
下面上代码(fs/inode.c中):
/* 将硬盘分区上的inode清空 */
void inode_delete(struct partition* part, uint32_t inode_no, void* io_buf) {
/* 定位inode */
struct inode_position inode_pos;
inode_locate(part, inode_no, &inode_pos);
/* 读入inode所在扇区到buf */
if(inode_pos.two_sec) {
ide_read(part->my_disk, inode_pos.sec_lba, io_buf, 2);
/* 把buf中的inode清空,写回inode数组到磁盘 */
memset(io_buf + inode_pos.off_size, 0, sizeof(struct inode));
ide_write(part->my_disk, inode_pos.sec_lba, io_buf, 2);
} else {
ide_read(part->my_disk, inode_pos.sec_lba, io_buf, 1);
memset(io_buf + inode_pos.off_size, 0, sizeof(struct inode));
ide_write(part->my_disk, inode_pos.sec_lba, io_buf, 1);
}
}
/* 回收inode的数据块和inode本身 */
void inode_release(struct partition* part, uint32_t inode_no) {
/* 打开inode */
struct inode* inode_to_del = inode_open(part, inode_no);
/* 分配all_blocks */
uint32_t* all_blocks = (uint32_t*) sys_malloc(48 + SECTOR_SIZE);
/* 读入所有数据块lba地址 */
uint32_t block_idx = 0;
while(block_idx < 12) {
all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
block_idx++;
}
if(inode_to_del->i_sectors[12] != 0) {
ide_read(part, inode_to_del->i_sectors[12], all_blocks + 12, 1);
}
/* 逐个数据块在分区的inode位图中收回 */
block_idx = 0;
uint32_t data_start_lba = part->sb->data_start_lba;
while (block_idx < 140) {
if(all_blocks[block_idx] != 0) {
bitmap_set(&part->block_bitmap, all_blocks[block_idx] - data_start_lba, 0);
bitmap_sync(part, all_blocks[block_idx] - data_start_lba, BLOCK_BITMAP);
}
block_idx++;
}
/* 在inode位图中收回编号为inode_no的inode */
bitmap_set(&part->inode_bitmap, inode_no, 0);
bitmap_sync(part, inode_no, INODE_BITMAP);
void* io_buf = sys_malloc(SECTOR_SIZE * 2);
inode_delete(part, inode_no, io_buf); //这里可有可无
sys_free(io_buf);
sys_free(all_blocks);
inode_close(inode_to_del);
}
最后在fs/fs.c中提供sys_unlink函数方便调用,该函数实际就是个包装函数,进行一些检查后调用delete_dir_entry以及inode_release来删除文件。
需要进行的前置检查包括两项:
(1)、检查待删除的文件是否存在
(2)、检查待删除的文件是否正在被使用(即在已打开文件列表中有file结构的inode等于待删除文件的inode)
下面上代码(fs/fs.c中)
/* 删除文件非目录,成功返回0,失败返回-1 */
int32_t sys_unlink(const char* pathname) {
ASSERT(strlen(pathname) < MAX_PATH_LEN);
/* 先检查待删除的文件是否存在 */
struct path_search_record search_record;
memset(&search_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(pathname, &search_record);
ASSERT(inode_no != 0);
if(inode_no == -1) {
printk("file %s not found!\n", pathname);
dir_close(search_record.parent_dir);
return -1;
}
if(search_record.file_type == FT_UNKNOWN) {
printk("can't delete a directory with unlink(), use rmdir() to instead\n");
dir_close(search_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(search_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(search_record.parent_dir);
printk("sys_unlink: malloc for io_buf failed\n");
return -1;
}
struct dir* parent_dir = search_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(search_record.parent_dir);
return 0;
}
实验结果
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall.h"
#include "syscall-init.h"
#include "stdio.h"
#include "fs.h"
int main(void) {
put_str("I am kernel\n");
init_all();
intr_enable();
printf("/file1 delete %s!\n", sys_unlink("/file1") == 0 ? "done" : "fail");
while (1);
return 0;
}
一切正常,跟书上运行结果一模一样,下一节,目录开始!