前情提要
一、文件写入
1.1、file的写入
文件写入比较复杂,函数行数相当多
/*** @description: 把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1 * @param {file*} file 文件* @param {void*} buf 缓存* @param {uint32_t} count 写入的字节数* @return {*} 写入的字节数,失败返回-1*/
int32_t file_write(struct file* file, const void* buf, uint32_t count) {// 文件目前最大只支持512*140=71680字节if ((file->fd_inode->i_size + count) > (BLOCK_SIZE * 140)) {printk("file_write: exceed max file_size 71680 bytes\n");return -1;}// 一个扇区的缓存uint8_t* io_buf = sys_malloc(BLOCK_SIZE);if (io_buf == NULL) {printk("file_write: sys_malloc for io_buf failed\n");return -1;}// 记录所有的块地址的缓存uint32_t* all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE + 48);if (all_blocks == NULL) {printk("file_write: sys_malloc for all_blocks failed\n");return -1;}const uint8_t* src = buf; // 用src指向buf中待写入的数据 uint32_t bytes_written = 0; // 用来记录已写入数据大小uint32_t size_left = count; // 用来记录未写入数据大小int32_t block_lba = -1; // 块地址uint32_t block_bitmap_idx = 0; // 用来记录block对应于block_bitmap中的索引,做为参数传给bitmap_syncuint32_t sec_idx; // 用来索引扇区uint32_t sec_lba; // 扇区地址uint32_t sec_off_bytes; // 扇区内字节偏移量uint32_t sec_left_bytes; // 扇区内剩余字节量uint32_t chunk_size; // 每次写入硬盘的数据块大小int32_t indirect_block_table; // 用来获取一级间接表地址uint32_t block_idx; // 块索引// 判断文件是否是第一次写,如果是,先为其分配一个块if (file->fd_inode->i_sectors[0] == 0) {block_lba = block_bitmap_alloc(cur_part);if (block_lba == -1) {printk("file_write: block_bitmap_alloc failed\n");return -1;}file->fd_inode->i_sectors[0] = block_lba;// 每分配一个块就将位图同步到硬盘block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);}// 写入count个字节前,该文件已经占用的块数 uint32_t file_has_used_blocks = file->fd_inode->i_size / BLOCK_SIZE + 1;// 存储count字节后该文件将占用的块数uint32_t file_will_use_blocks = (file->fd_inode->i_size + count) / BLOCK_SIZE + 1;if (file_will_use_blocks > 140) {printk("file_write: buf is more than 140 blocks\n");return -1;}// 通过此增量判断是否需要分配扇区,如增量为0,表示原扇区够用uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;// 若不需要增加扇区if (add_blocks == 0) {// 文件数据量将在12块之内if (file_has_used_blocks <= 12) {block_idx = file_has_used_blocks - 1;all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];}// 文件数据量将在12块之外else {indirect_block_table = file->fd_inode->i_sectors[12];ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);}}// 若需要增加扇区则分为三种情况else {// 12个直接块够用if (file_will_use_blocks <= 12) {// 先将有剩余空间的可继续用的扇区地址写入all_blocksblock_idx = file_has_used_blocks - 1;all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];// 再将未来要用的扇区分配好后写入all_blocksblock_idx = file_has_used_blocks;while (block_idx < file_will_use_blocks) {block_lba = block_bitmap_alloc(cur_part);if (block_lba == -1) {printk("file_write: block_bitmap_alloc for situation 1 failed\n");return -1;}// 写文件时,不应该存在块未使用但已经分配扇区的情况,当文件删除时,就会把块地址清0ASSERT(file->fd_inode->i_sectors[block_idx] == 0); file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;// 每分配一个块就将位图同步到硬盘block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);// 下一个分配的新扇区block_idx++; }}// 第二种情况: 旧数据在12个直接块内,新数据将使用间接块else if (file_has_used_blocks <= 12 && file_will_use_blocks > 12) {// 先将有剩余空间的可继续用的扇区地址收集到all_blocks block_idx = file_has_used_blocks - 1;all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];// 创建一级间接块表block_lba = block_bitmap_alloc(cur_part);if (block_lba == -1) {printk("file_write: block_bitmap_alloc for situation 2 failed\n");return -1;}// 确保一级间接块表未分配ASSERT(file->fd_inode->i_sectors[12] == 0); // 分配一级间接块索引表indirect_block_table = file->fd_inode->i_sectors[12] = block_lba;// 第一个未使用的块,即本文件最后一个已经使用的直接块的下一块block_idx = file_has_used_blocks; while (block_idx < file_will_use_blocks) {block_lba = block_bitmap_alloc(cur_part);if (block_lba == -1) {printk("file_write: block_bitmap_alloc for situation 2 failed\n");return -1;}// 新创建的0~11块直接存入all_blocks数组if (block_idx < 12) {// 确保尚未分配扇区地址ASSERT(file->fd_inode->i_sectors[block_idx] == 0);file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;}// 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘else {all_blocks[block_idx] = block_lba;}// 每分配一个块就将位图同步到硬盘block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);// 下一个新扇区block_idx++; }// 同步一级间接块表到硬盘ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);}// 第三种情况:新数据占据间接块else if (file_has_used_blocks > 12) {// 已经具备了一级间接块表ASSERT(file->fd_inode->i_sectors[12] != 0);// 获取一级间接表地址indirect_block_table = file->fd_inode->i_sectors[12];// 已使用的间接块也将被读入all_blocks,无须单独收录ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);// 第一个未使用的间接块,即已经使用的间接块的下一块block_idx = file_has_used_blocks; while (block_idx < file_will_use_blocks) {block_lba = block_bitmap_alloc(cur_part);if (block_lba == -1) {printk("file_write: block_bitmap_alloc for situation 3 failed\n");return -1;}all_blocks[block_idx++] = block_lba;// 每分配一个块就将位图同步到硬盘block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);}// 同步一级间接块表到硬盘ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);}}// 含有剩余空间的扇区标识bool first_write_block = true;// 块地址已经收集到all_blocks中,下面开始写数据 file->fd_pos = file->fd_inode->i_size - 1;// 直到写完所有数据while (bytes_written < count) {memset(io_buf, 0, BLOCK_SIZE);sec_idx = file->fd_inode->i_size / BLOCK_SIZE;sec_lba = all_blocks[sec_idx];sec_off_bytes = file->fd_inode->i_size % BLOCK_SIZE;sec_left_bytes = BLOCK_SIZE - sec_off_bytes;// 判断此次写入硬盘的数据大小chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;if (first_write_block) {ide_read(cur_part->my_disk, sec_lba, io_buf, 1);first_write_block = false;}memcpy(io_buf + sec_off_bytes, src, chunk_size);ide_write(cur_part->my_disk, sec_lba, io_buf, 1);//调试,完成后去掉printk("file write at lba 0x%x\n", sec_lba);// 将指针推移到下个新数据src += chunk_size;// 更新文件大小file->fd_inode->i_size += chunk_size;// 更新文件指针file->fd_pos += chunk_size;// 更新已写入数据bytes_written += chunk_size;// 更新未写入数据size_left -= chunk_size;}// 更新文件的inode信息inode_sync(cur_part, file->fd_inode);// 释放内存sys_free(all_blocks);sys_free(io_buf);return bytes_written;
}
但是为了方便大家理解,给大家简单叙述一下代码流程
- 初始化:初始化一些变量,如布尔变量
first_write_block
,并将文件指针定位到文件末尾。 - 分配存储块:根据文件大小,动态分配存储块,并将已分配的块地址保存到
all_blocks
数组中。 - 循环写入数据:进入循环,不断将数据写入磁盘,直到写入完所有数据。在循环中,依次执行以下操作:
- 计算当前所在的扇区号、扇区起始地址、当前扇区内偏移字节数和当前扇区剩余可写字节数。
- 确定本次写入的数据大小。
- 如果是第一次写入块,则从磁盘中读取该块数据到缓冲区中。
- 将待写入数据拷贝到缓冲区中相应位置。
- 将缓冲区中的数据写入到磁盘对应的扇区。
- 更新文件指针、文件大小和已写入字节数。
- 同步文件信息:循环结束后,将更新后的文件的 inode 信息同步到磁盘。
- 释放内存:释放动态分配的内存空间。
- 返回结果:返回已写入的字节数。
1.2、sys_write
int32_t sys_write(int32_t fd, const void* buf, uint32_t count) {if (fd < 0) {console_put_str("sys_write: fd error\n");return -1;}if (fd == stdout_no) {char tmp_buf[1024] = { 0 };memcpy(tmp_buf, buf, count);console_put_str(tmp_buf);return count;}uint32_t _fd = fd_local2global(fd);struct file* wr_file = &file_table[_fd];if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR) {uint32_t bytes_written = file_write(wr_file, buf, count);return bytes_written;}else {console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");return -1;}
}
这段代码就是将file_write
包装了一下,如果是标准输出的话,就直接输出到终端。
1.3、仿真
二、文件读取
2.1、file的读取
int32_t file_read(struct file* file, void* buf, uint32_t count) {uint8_t* buf_dst = (uint8_t*)buf;uint32_t size = count, size_left = size;// 若要读取的字节数超过了文件可读的剩余量, 就用剩余量做为待读取的字节数if ((file->fd_pos + count) > file->fd_inode->i_size) {size = file->fd_inode->i_size - file->fd_pos;size_left = size;if (size == 0) {// 若到文件尾则返回-1return -1;}}uint8_t* io_buf = sys_malloc(BLOCK_SIZE);if (io_buf == NULL) {printk("file_read: sys_malloc for io_buf failed\n");}// 用来记录文件所有的块地址uint32_t* all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE + 48);if (all_blocks == NULL) {printk("file_read: sys_malloc for all_blocks failed\n");return -1;}// 数据所在块的起始地址uint32_t block_read_start_idx = file->fd_pos / BLOCK_SIZE;// 数据所在块的终止地址uint32_t block_read_end_idx = (file->fd_pos + size) / BLOCK_SIZE;// 如增量为0,表示数据在同一扇区uint32_t read_blocks = block_read_start_idx - block_read_end_idx;// 确保在最大数据范围内ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);// 用来获取一级间接表地址int32_t indirect_block_table;// 获取待读的块地址 uint32_t block_idx;// 以下开始构建all_blocks块地址数组,专门存储用到的块地址(本程序中块大小同扇区大小)if (read_blocks == 0) {// 在同一扇区内读数据,不涉及到跨扇区读取ASSERT(block_read_end_idx == block_read_start_idx);if (block_read_end_idx < 12) {// 待读的数据在12个直接块之内block_idx = block_read_end_idx;all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];}else {// 若用到了一级间接块表,需要将表中间接块读进来indirect_block_table = file->fd_inode->i_sectors[12];ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);}}// 若要读多个块else {// 第一种情况: 起始块和终止块属于直接块if (block_read_end_idx < 12) {// 数据结束所在的块属于直接块block_idx = block_read_start_idx;while (block_idx <= block_read_end_idx) {all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];block_idx++;}}// 第二种情况: 待读入的数据跨越直接块和间接块两类else if (block_read_start_idx < 12 && block_read_end_idx >= 12) { block_idx = block_read_start_idx;while (block_idx < 12) {all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];block_idx++;}// 确保已经分配了一级间接块表ASSERT(file->fd_inode->i_sectors[12] != 0);// 再将间接块地址写入all_blocksindirect_block_table = file->fd_inode->i_sectors[12];// 将一级间接块表读进来写入到第13个块的位置之后ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);}// 第三种情况: 数据在间接块中else {// 确保已经分配了一级间接块表ASSERT(file->fd_inode->i_sectors[12] != 0);// 获取一级间接表地址indirect_block_table = file->fd_inode->i_sectors[12];// 将一级间接块表读进来写入到第13个块的位置之后ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); }}// 用到的块地址已经收集到all_blocks中,下面开始读数据uint32_t sec_idx, sec_lba, sec_off_bytes, sec_left_bytes, chunk_size;uint32_t bytes_read = 0;// 直到读完为止while (bytes_read < size) {sec_idx = file->fd_pos / BLOCK_SIZE;sec_lba = all_blocks[sec_idx];sec_off_bytes = file->fd_pos % BLOCK_SIZE;sec_left_bytes = BLOCK_SIZE - sec_off_bytes;// 待读入的数据大小chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;memset(io_buf, 0, BLOCK_SIZE);ide_read(cur_part->my_disk, sec_lba, io_buf, 1);memcpy(buf_dst, io_buf + sec_off_bytes, chunk_size);buf_dst += chunk_size;file->fd_pos += chunk_size;bytes_read += chunk_size;size_left -= chunk_size;}sys_free(all_blocks);sys_free(io_buf);return bytes_read;
}
- 首先,函数接收一个文件结构体指针
file
,一个指向缓冲区的指针buf
,以及要读取的字节数count
。 - 函数先检查要读取的字节数是否超出了文件可读的剩余量,如果超出则将待读取的字节数限制为文件剩余量。
- 然后函数分配了两个缓冲区,
io_buf
用于临时存储从磁盘读取的数据块,all_blocks
用于存储文件所有的块地址。 - 接着函数根据文件的起始位置和要读取的字节数,确定了需要读取的数据所在的块范围。
- 函数根据不同的情况构建了
all_blocks
数组,用于存储需要读取的块地址。 - 最后函数进入一个循环,不断地从磁盘读取数据块,直到读取完指定的字节数为止。在每次循环中,函数计算了当前要读取的数据块的位置和大小,并进行了相应的读取和拷贝操作,更新了文件指针和已读取的字节数。
2.2、sys_read
其中标准输入是从键盘中读取数据,其他读取是从文件中读取数据
int32_t sys_read(int32_t fd, void* buf, uint32_t count) {int32_t ret = -1;if (fd < 0 || fd == stdout_no || fd == stderr_no) {printk("sys_read: fd error\n");return -1;}else if (fd == stdin_no) {char* buffer = buf;uint32_t bytes_read = 0;while (bytes_read < count) {*buffer = ioq_getchar(&kbd_buf);bytes_read++;buffer++; }ret = (bytes_read == 0 ? -1 : (int32_t)bytes_read); }else {uint32_t _fd = fd_local2global(fd);ret = file_read(&file_table[_fd], buf, count); }return ret;
}
2.3、仿真
三、读写指针的定位
3.1、代码
首先新引入一个结构体,表示从文件的那个位置更新读写指针
/* 文件读写位置偏移量 */
enum whence {SEEK_SET = 1, // 新的读写位置是相对于文件开头再增加offset个位移量,offset正值SEEK_CUR, // 新的读写位置是相对于当前的位置增加offset个位移量,offset可正可负SEEK_END // 新的读写位置是相对于文件末尾再增加offset个位移量,offset负值
};
再实现更新指针的功能
/*** @description: 重置用于文件读写操作的偏移指针,成功时返回新的偏移量,出错时返回-1* @param {int32_t} fd 文件描述符* @param {int32_t} offset 偏移量* @param {uint8_t} whence 相对于哪个位置的偏移量* @return {*}*/
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence) {if (fd < 0) {printk("sys_lseek: fd error\n");return -1;}// 获取文件uint32_t _fd = fd_local2global(fd);struct file* pf = &file_table[_fd];//新的偏移量必须位于文件大小之内int32_t new_pos = 0;int32_t file_size = (int32_t)pf->fd_inode->i_size;switch (whence) {// SEEK_SET 新的读写位置是相对于文件开头再增加offset个位移量case SEEK_SET:new_pos = offset;break;// SEEK_CUR 新的读写位置是相对于当前的位置增加offset个位移量case SEEK_CUR:new_pos = (int32_t)pf->fd_pos + offset;break;// SEEK_END 新的读写位置是相对于文件尺寸再增加offset个位移量case SEEK_END:new_pos = file_size + offset;}if (new_pos < 0 || new_pos >(file_size - 1)) {printk("sys_lseek: offset error\n");return -1;}pf->fd_pos = new_pos;return pf->fd_pos;
}
-
函数接收三个参数:文件描述符
fd
,偏移量offset
,以及相对位置whence
。 -
首先检查文件描述符是否有效,如果
fd
小于 0,则返回错误信息。 -
然后根据文件描述符获取文件结构体指针
pf
。 -
接着根据
whence
参数的不同取值,计算出新的偏移量new_pos
- 如果
whence
为SEEK_SET
,则新的偏移量就是offset
。 - 如果
whence
为SEEK_CUR
,则新的偏移量是当前位置加上offset
。 - 如果
whence
为SEEK_END
,则新的偏移量是文件尺寸加上offset
。
- 如果
-
然后检查新的偏移量是否有效,即必须在文件大小范围内。
-
如果新的偏移量有效,则更新文件结构体中的
fd_pos
成员为新的偏移量,并返回新的偏移量。 -
如果出现错误,则返回 -1。
3.2、仿真
四、文件删除
4.1、删除inode
/*** @description: 回收inode的数据块和inode本身* @param {partition*} part 扇区* @param {uint32_t} inode_no inode编号* @return {*}*/
void inode_release(struct partition* part, uint32_t inode_no) {// 获取inodestruct inode* inode_to_del = inode_open(part, inode_no);// 1 回收inode占用的所有块uint8_t block_idx = 0, block_cnt = 12;uint32_t block_bitmap_idx;uint32_t all_blocks[140] = {0};// 1.1 先将前12个直接块存入all_blockswhile (block_idx < 12) {all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];block_idx++;}// 1.2 如果一级间接块表存在,将其128个间接块读到all_blocks[12~], 并释放一级间接块表所占的扇区if (inode_to_del->i_sectors[12] != 0) {ide_read(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;// 修改bitmapbitmap_set(&part->block_bitmap, block_bitmap_idx, 0);// 每次修改bitmap都要先写入硬盘bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);}// 1.3 inode所有的块地址已经收集到all_blocks中,下面逐个回收for (block_idx = 0; block_idx < block_cnt; block_idx++) {if (all_blocks[block_idx] != 0) {block_bitmap_idx = 0;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);}}// 2 回收该inode所占用的inodebitmap_set(&part->inode_bitmap, inode_no, 0); bitmap_sync(cur_part, inode_no, INODE_BITMAP);// 3 删除inode,这部分本不需要,inode是否存在收到inode bitmap控制,其中的inode不需要清0void* io_buf = sys_malloc(1024);inode_delete(part, inode_no, io_buf);sys_free(io_buf);// 关闭inodeinode_close(inode_to_del);
}
这段代码实现了回收指定inode所占用的数据块和inode本身的功能。逐步解释其主要逻辑:
- 首先通过
inode_open
函数获取要回收的inode的结构体指针inode_to_del
。 - 然后初始化一些变量,包括
block_idx
、block_cnt
、block_bitmap_idx
和all_blocks
数组。其中:block_idx
用于循环遍历inode的直接块;block_cnt
初始化为12,表示直接块的数量;block_bitmap_idx
用于记录块在块位图中的索引;all_blocks
数组用于存储inode所有的数据块地址。
- 将前12个直接块的地址存入
all_blocks
数组中。 - 如果一级间接块表存在(即第13个块),则将其128个间接块的地址读取到
all_blocks
数组中,并释放一级间接块表所占的扇区。释放一级间接块表的方法是:- 读取一级间接块表的数据块;
- 根据数据块的地址计算出其在块位图中的索引;
- 将该索引对应的位清零,表示该数据块空闲;
- 将修改后的块位图写回磁盘。
- 逐个回收
all_blocks
数组中记录的数据块。回收的方法是:- 判断数据块的地址是否为0,如果不为0,则表示该数据块存在,需要回收;
- 计算数据块在块位图中的索引;
- 将该索引对应的位清零,表示该数据块空闲;
- 将修改后的块位图写回磁盘。
- 回收完数据块后,将该inode所占用的inode位图中的相应位清零,表示该inode已经被释放。
- 最后,释放inode本身。这部分包括申请内存、调用
inode_delete
函数删除inode,然后释放内存。 - 最后关闭inode,释放相关资源。
4.2、删除目录项
/*** @description: 把分区part目录pdir中编号为inode_no的目录项删除* @param {partition*} part 分区* @param {dir*} pdir 路径* @param {uint32_t} inode_no inode编号* @param {void*} io_buf io缓存* @return {*}*/
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;uint32_t all_blocks[140] = { 0 };// 收集目录全部块地址while (block_idx < 12) {all_blocks[block_idx] = dir_inode->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;// 找到的inode_no目录项struct dir_entry* dir_entry_found = NULL;// 是否是目录的第1个块 bool is_dir_first_block = false;// 1、遍历所有块,寻找目录项for (block_idx = 0; block_idx < 140; block_idx++) {is_dir_first_block = false;if (all_blocks[block_idx] == 0) continue;// 初始化uint8_t dir_entry_cnt = 0;memset(io_buf, 0, SECTOR_SIZE);// 1.1、读取扇区,获得目录项ide_read(part->my_disk, all_blocks[block_idx], io_buf, 1);// 1.2、遍历所有的目录项,统计该扇区的目录项数量及是否有待删除的目录项for (uint8_t dir_entry_idx = 0; dir_entry_idx < dir_entrys_per_sec; dir_entry_idx++) {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++;// 如果找到此i结点,就将其记录在dir_entry_foundif ((dir_e + dir_entry_idx)->i_no == inode_no) {// 确保目录中只有一个编号为inode_no的inode,找到一次后dir_entry_found就不再是NULLASSERT(dir_entry_found == NULL);// 找到后也继续遍历,统计总共的目录项数dir_entry_found = dir_e + dir_entry_idx;}}}}// 1.3、若此扇区未找到该目录项,继续在下个扇区中找if (dir_entry_found == NULL) continue;// 1.4、在此扇区中找到目录项后,清除该目录项并判断是否回收扇区,随后退出循环直接返回ASSERT(dir_entry_cnt >= 1);// 1.5、若除目录第1个扇区外,若该扇区上只有该目录项自己,则将整个扇区回收if (dir_entry_cnt == 1 && !is_dir_first_block) {// 1.5.1、在块位图中回收该块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);// 1.5.2、将块地址从数组i_sectors或索引表中去掉if (block_idx < 12) {dir_inode->i_sectors[block_idx] = 0;}else {uint32_t indirect_blocks = 0;for (uint32_t i = 12; i < 140; i++) {if (all_blocks[i]) indirect_blocks++;}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);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;inode_sync(part, dir_inode);return true;}// 所有块中未找到则返回false,若出现这种情况应该是serarch_file出错了return false;
}
-
首先,代码通过遍历所有的块来寻找目录项。它使用一个循环来遍历
all_blocks
数组,检查每个块是否为空(值为0),如果为空,则跳过该块。 -
对于每个非空块,代码进行以下操作:
- 初始化一个计数器
dir_entry_cnt
,用于统计该扇区内的目录项数量。 - 读取扇区内容到
io_buf
缓冲区中。
- 初始化一个计数器
-
接下来,代码遍历该扇区内的所有目录项,统计目录项的个数,并判断是否有待删除的目录项。在遍历过程中,代码会检查目录项是否是特殊目录(“.“和”…”),并将目录的第一个块标记为true。
-
如果找到要删除的目录项,则清除该目录项,并判断是否回收整个扇区。如果目录项个数为1且不是目录的第一个块,则回收该扇区。具体操作包括:
- 在块位图中回收该块,即将对应的位设置为0。
- 将块地址从数组
i_sectors
或索引表中移除。对于直接索引表,将对应的项置为0;对于间接索引表,根据情况擦除间接索引表所在的块。
-
如果目录项个数大于1或者是目录的第一个块,则仅将要删除的目录项清空,然后将内容写回扇区。
-
最后,代码更新目录的大小信息,并将目录的i结点信息同步到硬盘。
-
如果成功删除目录项,则返回true;如果在所有块中未找到目录项,则返回false。
4.3、删除文件
/*** @description: 删除文件(非目录),成功返回0,失败返回-1* @param {char*} pathname 地址* @return {*}*/
int32_t sys_unlink(const char* pathname) {ASSERT(strlen(pathname) < MAX_PATH_LEN);// 1、先检查待删除的文件是否存在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("sys_unlink: file %s not found!\n", pathname);dir_close(searched_record.parent_dir);return -1;}if (searched_record.file_type == FT_DIRECTORY) {printk("sys_unlink: can`t delete a direcotry with unlink(), use rmdir() to instead\n");dir_close(searched_record.parent_dir);return -1;}// 2、检查是否在已打开文件列表(文件表)中,不允许删除正在使用的文件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("sys_unlink: file %s is in use, not allow to delete!\n", pathname);return -1;}// 3、为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;}// 3、删除目录struct dir* parent_dir = searched_record.parent_dir;delete_dir_entry(cur_part, parent_dir, inode_no, io_buf);// 4、删除inode节点inode_release(cur_part, inode_no);// 5、释放缓存sys_free(io_buf);// 6、关闭父目录dir_close(searched_record.parent_dir);// 7、成功删除文件 return 0;
}
- 首先,通过调用
search_file
函数检查待删除的文件是否存在,并获取其对应的i结点号(inode number)。如果文件不存在,则打印错误消息并返回-1。 - 然后,检查待删除的文件是否为目录。如果是目录,则打印错误消息并返回-1。因为不能使用
unlink()
函数直接删除目录,应该使用rmdir()
函数来删除目录。 - 接着,检查待删除的文件是否正在被使用,即检查它是否在已打开文件列表(文件表)中。如果正在使用,则打印错误消息并返回-1。
- 为
io_buf
申请缓冲区,大小为SECTOR_SIZE + SECTOR_SIZE
。io_buf
用于临时存储读取或写入磁盘的数据。 - 调用
delete_dir_entry
函数,通过传递参数cur_part
(当前分区)、parent_dir
(父目录)、inode_no
(待删除文件的i结点号)、io_buf
来删除目录项。该函数用于从父目录中删除指定的目录项,并相应地更新父目录的信息。 - 调用
inode_release
函数释放待删除文件所占用的inode节点。这一步是文件删除的关键,因为文件的数据块可以被覆盖重用,但是inode节点需要被显式释放,以便系统知道这个inode节点可以被重用。 - 释放之前申请的缓冲区
io_buf
。 - 最后,关闭父目录,并返回0表示成功删除文件。
4.1、仿真
可以看到删除了 /file2
结束语
本节实现了文件的读写以及删除文件的功能
老规矩,代码地址 https://github.com/lyajpunov/os.git