手写简易操作系统(二十五)--文件系统第三部分

前情提要

一、文件写入

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

但是为了方便大家理解,给大家简单叙述一下代码流程

  1. 初始化:初始化一些变量,如布尔变量 first_write_block,并将文件指针定位到文件末尾。
  2. 分配存储块:根据文件大小,动态分配存储块,并将已分配的块地址保存到 all_blocks 数组中。
  3. 循环写入数据:进入循环,不断将数据写入磁盘,直到写入完所有数据。在循环中,依次执行以下操作:
    • 计算当前所在的扇区号、扇区起始地址、当前扇区内偏移字节数和当前扇区剩余可写字节数。
    • 确定本次写入的数据大小。
    • 如果是第一次写入块,则从磁盘中读取该块数据到缓冲区中。
    • 将待写入数据拷贝到缓冲区中相应位置。
    • 将缓冲区中的数据写入到磁盘对应的扇区。
    • 更新文件指针、文件大小和已写入字节数。
  4. 同步文件信息:循环结束后,将更新后的文件的 inode 信息同步到磁盘。
  5. 释放内存:释放动态分配的内存空间。
  6. 返回结果:返回已写入的字节数。

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、仿真

image-20240407224231064

二、文件读取

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;
}
  1. 首先,函数接收一个文件结构体指针 file,一个指向缓冲区的指针 buf,以及要读取的字节数 count
  2. 函数先检查要读取的字节数是否超出了文件可读的剩余量,如果超出则将待读取的字节数限制为文件剩余量。
  3. 然后函数分配了两个缓冲区,io_buf 用于临时存储从磁盘读取的数据块,all_blocks 用于存储文件所有的块地址。
  4. 接着函数根据文件的起始位置和要读取的字节数,确定了需要读取的数据所在的块范围。
  5. 函数根据不同的情况构建了 all_blocks 数组,用于存储需要读取的块地址。
  6. 最后函数进入一个循环,不断地从磁盘读取数据块,直到读取完指定的字节数为止。在每次循环中,函数计算了当前要读取的数据块的位置和大小,并进行了相应的读取和拷贝操作,更新了文件指针和已读取的字节数。

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;
}
  1. 函数接收三个参数:文件描述符 fd,偏移量 offset,以及相对位置 whence

  2. 首先检查文件描述符是否有效,如果 fd 小于 0,则返回错误信息。

  3. 然后根据文件描述符获取文件结构体指针 pf

  4. 接着根据whence参数的不同取值,计算出新的偏移量new_pos

    • 如果 whenceSEEK_SET,则新的偏移量就是 offset
    • 如果 whenceSEEK_CUR,则新的偏移量是当前位置加上 offset
    • 如果 whenceSEEK_END,则新的偏移量是文件尺寸加上 offset
  5. 然后检查新的偏移量是否有效,即必须在文件大小范围内。

  6. 如果新的偏移量有效,则更新文件结构体中的 fd_pos 成员为新的偏移量,并返回新的偏移量。

  7. 如果出现错误,则返回 -1。

3.2、仿真

image-20240407225608891

四、文件删除

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本身的功能。逐步解释其主要逻辑:

  1. 首先通过 inode_open 函数获取要回收的inode的结构体指针 inode_to_del
  2. 然后初始化一些变量,包括 block_idxblock_cntblock_bitmap_idxall_blocks 数组。其中:
    • block_idx 用于循环遍历inode的直接块;
    • block_cnt 初始化为12,表示直接块的数量;
    • block_bitmap_idx 用于记录块在块位图中的索引;
    • all_blocks 数组用于存储inode所有的数据块地址。
  3. 将前12个直接块的地址存入 all_blocks 数组中。
  4. 如果一级间接块表存在(即第13个块),则将其128个间接块的地址读取到 all_blocks 数组中,并释放一级间接块表所占的扇区。释放一级间接块表的方法是:
    • 读取一级间接块表的数据块;
    • 根据数据块的地址计算出其在块位图中的索引;
    • 将该索引对应的位清零,表示该数据块空闲;
    • 将修改后的块位图写回磁盘。
  5. 逐个回收 all_blocks 数组中记录的数据块。回收的方法是:
    • 判断数据块的地址是否为0,如果不为0,则表示该数据块存在,需要回收;
    • 计算数据块在块位图中的索引;
    • 将该索引对应的位清零,表示该数据块空闲;
    • 将修改后的块位图写回磁盘。
  6. 回收完数据块后,将该inode所占用的inode位图中的相应位清零,表示该inode已经被释放。
  7. 最后,释放inode本身。这部分包括申请内存、调用 inode_delete 函数删除inode,然后释放内存。
  8. 最后关闭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;
}
  1. 首先,代码通过遍历所有的块来寻找目录项。它使用一个循环来遍历all_blocks数组,检查每个块是否为空(值为0),如果为空,则跳过该块。

  2. 对于每个非空块,代码进行以下操作:

    • 初始化一个计数器dir_entry_cnt,用于统计该扇区内的目录项数量。
    • 读取扇区内容到io_buf缓冲区中。
  3. 接下来,代码遍历该扇区内的所有目录项,统计目录项的个数,并判断是否有待删除的目录项。在遍历过程中,代码会检查目录项是否是特殊目录(“.“和”…”),并将目录的第一个块标记为true。

  4. 如果找到要删除的目录项,则清除该目录项,并判断是否回收整个扇区。如果目录项个数为1且不是目录的第一个块,则回收该扇区。具体操作包括:

    • 在块位图中回收该块,即将对应的位设置为0。
    • 将块地址从数组i_sectors或索引表中移除。对于直接索引表,将对应的项置为0;对于间接索引表,根据情况擦除间接索引表所在的块。
  5. 如果目录项个数大于1或者是目录的第一个块,则仅将要删除的目录项清空,然后将内容写回扇区。

  6. 最后,代码更新目录的大小信息,并将目录的i结点信息同步到硬盘。

  7. 如果成功删除目录项,则返回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;
}
  1. 首先,通过调用search_file函数检查待删除的文件是否存在,并获取其对应的i结点号(inode number)。如果文件不存在,则打印错误消息并返回-1。
  2. 然后,检查待删除的文件是否为目录。如果是目录,则打印错误消息并返回-1。因为不能使用unlink()函数直接删除目录,应该使用rmdir()函数来删除目录。
  3. 接着,检查待删除的文件是否正在被使用,即检查它是否在已打开文件列表(文件表)中。如果正在使用,则打印错误消息并返回-1。
  4. io_buf申请缓冲区,大小为SECTOR_SIZE + SECTOR_SIZEio_buf用于临时存储读取或写入磁盘的数据。
  5. 调用delete_dir_entry函数,通过传递参数cur_part(当前分区)、parent_dir(父目录)、inode_no(待删除文件的i结点号)、io_buf来删除目录项。该函数用于从父目录中删除指定的目录项,并相应地更新父目录的信息。
  6. 调用inode_release函数释放待删除文件所占用的inode节点。这一步是文件删除的关键,因为文件的数据块可以被覆盖重用,但是inode节点需要被显式释放,以便系统知道这个inode节点可以被重用。
  7. 释放之前申请的缓冲区io_buf
  8. 最后,关闭父目录,并返回0表示成功删除文件。

4.1、仿真

可以看到删除了 /file2

image-20240407193229361

结束语

本节实现了文件的读写以及删除文件的功能

老规矩,代码地址 https://github.com/lyajpunov/os.git

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

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

相关文章

leetcode2009--使数组连续的最少操作数

1. 题意 给定一个数组&#xff0c;求最少的操作数使得数组连续。 每次操作你可选择一个数&#xff0c;将它变为任意其他数。 leetcode2009 2. 题解 思路&#xff1a;反向考虑&#xff0c;最多能保留多少个数字使得当前数组连续。 就变成了 [ x , x s z − 1 ] [x,xsz-1]…

基于Java+SpringBoot+Vue民宿预约管理系统(源码+文档+部署+讲解)

一.系统概述 随着社会的不断进步与发展&#xff0c;人们经济水平也不断的提高&#xff0c;于是对各行各业需求也越来越高。利用计算机网络来处理各行业事务这一概念更深入人心&#xff0c;由于工作繁忙以及其他的原因&#xff0c;到实体店进行预约也是比较难实施的。如果开发一…

es6新增加的语法

let和const关键字&#xff1a;let和const允许你声明具有块级作用域的变量和常量。这有助于避免使用var时可能出现的变量提升和全局污染问题。模板字符串&#xff1a;使用反引号()可以创建多行字符串和嵌入表达式。 javascript let name world; let greeting Hello, ${name}!…

迪拜公司怎么注册 迪拜公司注册优势 迪拜公司注册条件

一、迪拜公司注册优势 1、税收优势&#xff1a;迪拜是一个没有个人所得税、企业所得税和增 值税的地区&#xff0c;这为注册公司在迪拜提供了巨大的税收优势。 2、地理位置优势&#xff1a;迪拜位于东西方和南北方的交汇点&#xff0c;拥有得天独厚的地理位置。这使得迪拜成为…

LLM Agents调研

LLM Agents调研 1、从 Copilot 到 Agent2、Agent概述3、agent框架2.1 框架介绍2.2框架对比 4、应用场景3.1single-agent应用3.2multi-agent 应用 5、agent功能选型参考&#xff1a; 1、从 Copilot 到 Agent 参考&#xff1a;https://mp.weixin.qq.com/s/vVUO-WRkp8FS3wKcfgu45…

【Vue3 + ElementUI】表单校验无效(写法:this.$refs[‘formName‘].validate((valid) =>{} ))

一. 表单校验 1.1 template模块 el-form 中 若校验&#xff0c;ref 和 rules 必须要有 <template><div style"padding:20px"><el-form ref"formName" :model"form" :rules"formRules" label-width"120px"…

C++进阶之路---何为智能指针?

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、为什么需要智能指针&#xff1f; 下面我们先分析一下下面这段程序有没有什么内存方面的问题&#xff1f;提示一下&am…

AI技术的创业机会

AI技术创业有哪些机会&#xff1f; 人工智能&#xff08;AI&#xff09;技术作为当今科技创新的前沿领域&#xff0c;为创业者提供了广阔的机会和挑战。随着AI技术的快速发展和应用领域的不断拓展&#xff0c;未来AI技术方面会有哪些创业机会呢&#xff1f; 方向一&#xff1a…

Spring 之 IoC基于XML管理Bean

1. 环境搭建 参考我之前的笔记&#xff1a; Spring6 基础入门-CSDN博客 2. 获取Bean的方式 2.1 根据Id 获取 参考之前的笔记&#xff1a; Spring6 基础入门-CSDN博客 Testpublic void testGetBeanById(){//加载spring配置文件ApplicationContext context new ClassPathXmlA…

医疗图像分割 | 基于Pyramid-Vision-Transformer算法实现医疗息肉分割

项目应用场景 面向医疗图像息肉分割场景&#xff0c;项目采用 Pytorch Pyramid-Vision-Transformer 深度学习算法来实现。 项目效果 项目细节 > 具体参见项目 README.md (1) 模型架构 (2) 项目依赖&#xff0c;包括 python 3.8、pytorch 1.7.1、torchvision 0.8.2(3) 下载…

粤嵌教育:继续坚持诚信为本,分享合理投诉小妙招

在如今的教育市场中&#xff0c;诚信已经成为一个不可忽视的关键词。粤嵌教育作为一家有着深厚教育背景和丰富教学经验的培训机构&#xff0c;始终坚持诚信为本&#xff0c;以优质的教学服务赢得了广大学员和社会的认可。然而&#xff0c;在教育行业中&#xff0c;难免会遇到一…

C++:Stmt预处理SQL与大文件存取(五)

1、预处理相关API mysql_stmt_init&#xff1a;初始化生成一个预编译处理的Stmt对象mysql_stmt_prepare&#xff1a;预处理SQL语句&#xff0c;值部分用&#xff1f;进行占位&#xff08;可以防止SQL注入&#xff09;mysql_stmt_bind_param&#xff1a;给预处理的SQL语句中的&…

数据仓库实践

什么是数据仓库&#xff1f; 数据仓库是一个用于存储大量数据并支持数据分析与报告的系统。它通常用于集成来自不同来源的数据&#xff0c;提供一个统一的视图&#xff0c;以便进行更深入的分析和决策。 数据仓库的主要优势&#xff1f; 决策支持&#xff1a;为企业决策提供可靠…

渗透知识贴

文章目录 基础知识同源策略 常见web漏洞SQL注入漏洞 web中间件 基础知识 同源策略 同源策略是目前所有浏览器都实行的一种安全政策。A网页设置的 Cookie&#xff0c;B网页不能打开&#xff0c;除非这两个网页同源。所谓同源&#xff0c;是指&#xff1a;协议、端口、域名相同…

Windows Server 2008添加Web服务器(IIS)、WebDAV服务、网络负载均衡

一、Windows Server 2008添加Web服务器&#xff08;IIS&#xff09; &#xff08;1&#xff09;添加角色&#xff0c;搭建web服务器&#xff08;IIS&#xff09; &#xff08;2&#xff09;添加网站&#xff0c;关闭默认网页&#xff0c;添加默认文档 在客户端浏览器输入服务器…

面试算法-165-随机链表的复制

题目 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节…

前端saas化部署

在项目中难免会遇到一些特殊的需求&#xff0c;例如同一套代码需要同时部署上两个不同的域名A和B。A和B的不同之处仅在于&#xff0c;例如一些背景图片&#xff0c;logo&#xff0c;展示模块的不同&#xff0c;其他业务逻辑是和展示模块是完全一样的。此时我们当然可以考虑单独…

Python网络爬虫(六):油管视频评论

本文的思路是利用googleapiclient.discovery连接Google API服务,获取油管视频的评论。Google API可以为开发人员提供很多有用的工具和数据,使用起来也非常简单方便。注意,连接Google API服务需要设置代理,这里需要用到httplib2库,利用该库设置代理信息,然后传入googleapi…

HTML入门基础操作(1)

如果没有下载专业的软件&#xff0c;可直接在txt文本中写出代码后对txt文件名后缀改为.html&#xff0c;即可使用浏览器打开&#xff0c;以作学习。 1、HTML 标题&#xff08;Heading&#xff09;是通过 <h1> - <h6> 等标签进行定义的&#xff0c;标题的大小从h1到…

倒计时1天 | 袋鼠云春季发布会完整议程出炉!快快预约直播

在日新月异的数字化经济时代&#xff0c;企业和组织不断寻求利用先进技术构建自身的核心竞争力。其中&#xff0c;大数据与AI的深度融合正在成为推动企业实现新质生产力的关键路径。 在此背景下&#xff0c;袋鼠云举办春季发布会&#xff0c;以“DataAI&#xff0c;构建新质生…