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

前情提要

一、文件写入

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,一经查实,立即删除!

相关文章

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

一.系统概述 随着社会的不断进步与发展&#xff0c;人们经济水平也不断的提高&#xff0c;于是对各行各业需求也越来越高。利用计算机网络来处理各行业事务这一概念更深入人心&#xff0c;由于工作繁忙以及其他的原因&#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…

医疗图像分割 | 基于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) 下载…

数据仓库实践

什么是数据仓库&#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;其中每个新节点的值都设为其对应的原节点的值。新节…

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

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

2024 年广西职业院校技能大赛高职组 《云计算应用》赛项赛题第①套

2024 年广西职业院校技能大赛高职组 《云计算应用》赛项赛题第①套 模块一 私有云&#xff08;30 分&#xff09;任务 1 私有云服务搭建&#xff08;5 分&#xff09;任务 2 私有云服务运维&#xff08;15 分&#xff09;任务 3 私有云运维开发&#xff08;10 分&#xff09; 模…

【Nature Electronics】二维钙钛矿氧化物SNO作为high-κ栅介质的应用

【Li, S., Liu, X., Yang, H. et al. Two-dimensional perovskite oxide as a photoactive high-κ gate dielectric. Nat Electron 7, 216–224 (2024). https://doi.org/10.1038/s41928-024-01129-9】 概括总结&#xff1a; 本研究探讨了二维钙钛矿氧化物Sr2Nb3O10&#xf…

商家跑路城市拥堵大数据论文代码开源

原始数据&#xff1a; 日期名称类型所属区拥挤指数速度客流指数20240405世界之花假日广场购物;购物中心大兴区2.46621.369.4920240405华润五彩城购物;购物中心海淀区2.01329.7111.1720240405北京市百货大楼购物;购物中心东城区1.85615.938.2320240405apm购物;购物中心东城区1.…

常用12个自动化测试工具

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

基于springboot实现校园资料分享平台系统项目【项目源码+论文说明】

基于springboot实现校园资料分享平台系统演示 摘要 随着信息互联网购物的飞速发展&#xff0c;国内放开了自媒体的政策&#xff0c;一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求&#xff…

kali基础渗透学习,永恒之蓝,木马实战

简介 kali的学习本质是在linux上对一些攻击软件的使用&#xff0c;只是学习的初期 先在终端切换到root用户&#xff0c;以便于有些工具对权限的要求 下载链接 镜像源kali 攻击流程 公网信息搜集 寻找漏洞&#xff0c;突破口&#xff0c;以进入内网 进入内网&#xff0c…

(学习日记)2024.04.10:UCOSIII第三十八节:事件实验

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

Golang——方法

一. 方法定义 Golang方法总是绑定对象的实例&#xff0c;并隐式将实例作为第一实参。 只能为当前包内命名类型定义方法参数receiver可以任意命名。如方法中未曾使用&#xff0c;可省略参数名参数receiver类型可以是T或*T。基类型T不能是接口或指针类型(即多级指针)不支持方法重…

2024.4.5-day10-CSS 布局模型(层模型)

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 作业 2024.4.5-学习笔记1 CSS定位1.1 相对定位 relative1.2 绝对定位 absolut…

多乐空气处理设备有限公司现已加入2024第13届生物发酵展

参展企业介绍 为满足日益发展的中国大陆市场对环境的要求&#xff0c;更接近最终用户&#xff0c;多乐集团于2001年在上海松江设立了第一家生产基地。经过十数年来的高速发展&#xff0c;多乐以其精湛的加工工艺、一流的制造技术方面的优势&#xff0c;在对温度湿度有严格要求…