实验27.fwrite,fread,fseek

已完成实验

《操作系统真相还原》部分实验记录

简介

实验 27. fwrite,fread,fseek

总结

  • fwrite: 1.确保 inode 的数据块有那么多空间,如果没有就扩容。2.把数据写入文件的数据块。
  • fread: 读出 inode 的所有数据块偏移,然后在数据块中读出数据。
  • fseek: 修改 file 结构体的 fd_pos 即可。

主要代码

  • 实现 file_wirte, file_read (file.c)
  • 实现 sys_write, sys_read, sys_lfseek(fs.c)
  • 修改函数调表 SYS_WRITE 调用号的处理函数为 sys_write (syscall.c, syscall-init.c)
  • 修改 printf 的 write 参数 (stido.c)

file.c

/// @brief 写入文件
/// 把buf中的count个字节写入file,
/// @param file
/// @param buf
/// @param count
/// @return 成功则返回写入的字节数,失败则返回-1
int32_t file_write(struct file* file, const void* buf, uint32_t count) {
    // 0 如果新数据长度超过最大值,返回错误
    if ((file->fd_inode->i_size + count) > (BLOCK_SIZE * 140)) {  // 文件目前最大只支持512*140=71680字节
        printk("exceed max file_size 71680 bytes, write file failed\n");
        return -1;
    }


    // 1 如果这个文件一个块都没有,那么先申请一个直接块
    int32_t block_lba = -1;         // 块地址
    uint32_t block_bitmap_idx = 0;  // 块相对于块位图的索引
    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;
        ASSERT(block_bitmap_idx != 0);
        bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
    }


    // 2 扩容,保证文件的可用大小>新数据长度
    // 2.1 计算要额外申请的块数
    uint32_t file_has_used_blocks = file->fd_inode->i_size / BLOCK_SIZE + 1;  // 写入前该文件已经占用的块数
    uint32_t file_will_use_blocks = (file->fd_inode->i_size + count) / BLOCK_SIZE + 1;  // 写入后该文件将占用的块数
    ASSERT(file_will_use_blocks <= 140);
    uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;  // add_blocks就是需要额外申请的块数


    uint32_t* all_blocks = (uint32_t*)sys_malloc(140 * 4);
    if (all_blocks == NULL) {
        printk("file_write: sys_malloc for all_blocks failed\n");
        return -1;
    }
    int32_t indirect_block_table;  // 用来获取一级间接表地址
    uint32_t block_idx;            // 块索引

    // 2.2 把扩容之后的所有块地址都放在all_blocks里
    if (add_blocks == 0) {                         // 不需要额外申请块来放数据
        if (file_has_used_blocks <= 12) {          // 写之前文件的长度小于等于12个块
            block_idx = file_has_used_blocks - 1;  // 那么只需要同步最后一个块地址
            all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
        } else {  // 写之前文件的长度就已经超过了12个块
            ASSERT(file->fd_inode->i_sectors[12] != 0);
            indirect_block_table = file->fd_inode->i_sectors[12];  // 读取间接表
            ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    } else {
        // 如果要额外申请块
        // 情况1:旧数据长度<12个块,数据长度<12个块
        // 情况2:旧数据长度<=12个块,新数据长度>12个块
        // 情况3:旧数据长度>12个块,新数据长度>12个块
        if (file_will_use_blocks <= 12) {  // 情况1
            // 把最后一个块的地址保存到all_blocks
            block_idx = file_has_used_blocks - 1;
            ASSERT(file->fd_inode->i_sectors[block_idx] != 0);
            all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];

            // 再将未来要用的扇区分配好后写入all_blocks
            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 1 failed\n");
                    return -1;
                }

                // 把申请的块地址同步到内存的inode和all_blocks
                ASSERT(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++;  // 下一个分配的新扇区
            }
        } else if (file_has_used_blocks <= 12 && file_will_use_blocks > 12) {  // 情况2

            // 把最后一个块的地址保存到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;
            }
            // 自己新增,同步间接块位图到硬盘,原书没有
            block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

            // 分配一级间接块索引表
            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;
                }

                if (block_idx < 12) {  // 新创建的0~11块直接存入all_blocks数组
                    ASSERT(file->fd_inode->i_sectors[block_idx] == 0);  // 确保尚未分配扇区地址
                    file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
                } else {  // 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘
                    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) {                    // 情况3
            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);
        }
    }


    // 3 开始写数据,此时新数据长度 < 文件块
    const uint8_t* src = buf;                   // 用src指向buf中待写入的数据
    uint32_t size_left = count;                 // 用来记录未写入数据大小
    uint32_t bytes_written = 0;                 // 用来记录已写入数据大小
    uint32_t sec_idx;                           // 用来索引扇区
    uint32_t sec_lba;                           // 扇区地址
    uint32_t sec_off_bytes;                     // 扇区内字节偏移量
    uint32_t sec_left_bytes;                    // 扇区内剩余字节量
    uint32_t chunk_size;                        // 每次写入硬盘的数据块大小
    bool first_write_block = true;              // 含有剩余空间的扇区标识
    file->fd_pos = file->fd_inode->i_size - 1;  // 置fd_pos为文件大小-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;
    }

    // 直到写完所有数据
    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_sync(cur_part, file->fd_inode, io_buf);


    sys_free(all_blocks);
    sys_free(io_buf);
    return bytes_written;
}


/// @brief 文件读取
/// 从文件file中读取count个字节写入buf
/// @param file
/// @param buf
/// @param count
/// @return 返回读出的字节数,若到文件尾则返回-1
int32_t file_read(struct file* file, void* buf, uint32_t count) {
    // 1 读取字节太多,能读多少返回多少
    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) {  // 若到文件尾则返回-1
            return -1;
        }
    }


    // 2 把inode的所有数据块地址读取到all_blocks
    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;
    }

    // 2.1 计算要读读取多少个块 read_blocks
    uint32_t block_read_start_idx = file->fd_pos / BLOCK_SIZE;         // 数据所在块的起始地址
    uint32_t block_read_end_idx = (file->fd_pos + size) / BLOCK_SIZE;  // 数据所在块的终止地址
    uint32_t read_blocks = block_read_start_idx - block_read_end_idx;  // 如增量为0,表示数据在同一扇区
    ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);

    int32_t indirect_block_table;  // 用来获取一级间接表地址
    uint32_t block_idx;            // 获取待读的块地址

    // 2.2 读取所有块地址
    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) {
            /* 第二种情况: 待读入的数据跨越直接块和间接块两类*/
            /* 先将直接块地址写入all_blocks */
            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_blocks */
            indirect_block_table = file->fd_inode->i_sectors[12];
            ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12,
                     1);  // 将一级间接块表读进来写入到第13个块的位置之后
        } else {
            /* 第三种情况: 数据在间接块中*/
            ASSERT(file->fd_inode->i_sectors[12] != 0);            // 确保已经分配了一级间接块表
            indirect_block_table = file->fd_inode->i_sectors[12];  // 获取一级间接表地址
            ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12,
                     1);  // 将一级间接块表读进来写入到第13个块的位置之后
        }
    }

    // 3 开始读数据,用到的块地址已经收集到all_blocks中
    uint8_t* buf_dst = (uint8_t*)buf;
    uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
    if (io_buf == NULL) { printk("file_read: sys_malloc for io_buf failed\n"); }

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

fs.h

/// @brief 文件读写位置偏移量
enum whence { SEEK_SET = 1, SEEK_CUR, SEEK_END };

fs.c

/// @brief 写入
/// 将buf中连续count个字节写入文件描述符fd
/// @param fd
/// @param buf
/// @param count
/// @return 成功则返回写入的字节数,失败返回-1
int32_t sys_write(int32_t fd, const void* buf, uint32_t count) {
    if (fd < 0) {
        printk("sys_write: fd error\n");
        return -1;
    }

    // 1 标准输出
    if (fd == stdout_no) {
        char tmp_buf[1024] = {0};
        memcpy(tmp_buf, buf, count);
        console_put_str(tmp_buf);
        return count;
    }

    // 2 写入文件
    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;
    }
}

/// @brief 读取
/// @param fd
/// @param buf
/// @param count
/// @return
int32_t sys_read(int32_t fd, void* buf, uint32_t count) {
    if (fd < 0) {
        printk("sys_read: fd error\n");
        return -1;
    }
    ASSERT(buf != NULL);
    uint32_t _fd = fd_local2global(fd);
    return file_read(&file_table[_fd], buf, count);
}


/// @brief 文件偏移
/// 重置用于文件读写操作的偏移指针
/// @param fd
/// @param offset
/// @param whence
/// @return 成功时返回新的偏移量,出错时返回-1
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence) {
    if (fd < 0) {
        printk("sys_lseek: fd error\n");
        return -1;
    }
    ASSERT(whence > 0 && whence < 4);

    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:  // offse可正可负
            new_pos = (int32_t)pf->fd_pos + offset;
            break;

        /* SEEK_END 新的读写位置是相对于文件尺寸再增加offset个位移量 */
        case SEEK_END:  // 此情况下,offset应该为负值
            new_pos = file_size + offset;
    }
    if (new_pos < 0 || new_pos > (file_size - 1)) { return -1; }
    pf->fd_pos = new_pos;
    return pf->fd_pos;
}

syscall.c

uint32_t write(int32_t fd, const void* buf, uint32_t count) { return _syscall3(SYS_WRITE, fd, buf, count); }

syscall-init.c

/// @brief 初始化系统调用
/// @param
void syscall_init(void) {
    put_str("[syscall] syscall_init start\n");

    syscall_table[SYS_GETPID] = sys_getpid;
    syscall_table[SYS_WRITE] = sys_write;
    syscall_table[SYS_MALLOC] = sys_malloc;
    syscall_table[SYS_FREE] = sys_free;

    put_str("[syscall] syscall_init done\n");
}

stdio.c

 uint32_t printf(const char* format, ...) {
     va_list args;
     char buf[1024] = {0};  // 用于存储拼接后的字符串

     va_start(args, format);  // 使args指向format
     vsprintf(buf, format, args);
     va_end(args);

    return write(1, buf, strlen(buf));
 }

main.c

int main(void) {
    put_str("I am kernel\n");

    init_all();

    process_execute(u_prog_a, "user_prog_a");
    process_execute(u_prog_b, "user_prog_b");
    thread_start("k_thread_a", 31, k_thread_a, "argA ");
    thread_start("k_thread_b", 31, k_thread_b, "argB ");

    // 1 打开文件,写入 运行一次就可以了,这个写入时追加写入
    // uint32_t fd = sys_open("/file1", O_RDWR);
    // printf("fd:%d\n", fd);
    // sys_write(fd, "hello,world\n", 12);
    // sys_close(fd);
    // printf("write down fd:%d\n", fd);

    // 2 读取文件
    // fd = sys_open("/file1", O_RDWR);
    uint32_t fd = sys_open("/file1", O_RDWR);
    char buf[64] = {0};

    // 2.1 先读6字节
    memset(buf, 0, 64);
    int read_bytes = sys_read(fd, buf, 6);
    printf("2.1 read %d bytes: %s\n", read_bytes, buf);

    // 2.2 再读6字节
    memset(buf, 0, 64);
    read_bytes = sys_read(fd, buf, 6);
    printf("2.2 read %d bytes:%s\n", read_bytes, buf);
    sys_close(fd);
    printf("read down fd:%d\n", fd);

    // 3 偏移读取
    fd = sys_open("/file1", O_RDWR);
    sys_lseek(fd, 7, SEEK_SET);
    memset(buf, 0, 64);
    read_bytes = sys_read(fd, buf, 5);
    printf("3 fseek and read %d bytes: %s\n", read_bytes, buf);
    sys_close(fd);


    while (1);
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值