首页 > 其他分享 >操作系统真象还原:实现文件写入

操作系统真象还原:实现文件写入

时间:2024-07-16 23:30:35浏览次数:20  
标签:blocks 操作系统 idx 写入 write fd 真象 file block

14.7 实现文件写入

这是一个网站有所有小节的代码实现,同时也包含了Bochs等文件
本节要实现的 sys_write 是系统调用 write 的内核实现,咱们之前的 write 是个简易版,它是为了临时完成输出打印的功能,不支持文件描述符。如今要让 write 支持文件描述符的话,还要修改下周边与此系统调用相关的内容。

14.7.1 实现file_write
/*把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1*/
int32_t file_write(struct file* file, const void* buf, uint32_t count){
    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;
    }
    uint8_t* io_buf = sys_malloc(512);
    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);    //用来记录文件所有的块地址,一个地址4字节,一共有140个块所以要560字节内存
    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_sync

    uint32_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->block_bitmap_lba;
        ASSERT(block_bitmap_idx!=0);
        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;
    ASSERT(file_will_use_blocks <= 140);

    /*通过此增量判断是否要分配扇区,如果增量为0,表示原扇区够用*/
    uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;
    
    /*将写文件所用到的块地址收集到all_blocks,系统中快大小等于扇区大小,后面都统一在all_blocks中获取写入扇区地址*/
    if(add_blocks==0){
        /*在同一扇区内写入数据,不涉及到分配新扇区*/
        if(file_will_use_blocks<=12){
            //文件数据将在12块之内
            block_idx = file_has_used_blocks - 1;   //指向最后一个已有数据扇区
            all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
        }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);
        }
    }else{
        /*若是有增量,变涉及到分配新扇区及是否分配一级间接块表,下面要分三种情况处理*/
        /*第一种情况:12个块直接够用*/
        if(file_will_use_blocks <= 12){
            /*先将剩余空间的可继续用的扇区地址写入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;
                }
                /*写文件时,不应该存在块未使用但已经分配扇区的情况,当文件删除时,就会把块地址清0*/
                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->block_bitmap_lba;
                ASSERT(block_bitmap_idx!=0);
                bitmap_sync(cur_part,block_bitmap_idx,BLOCK_BITMAP);
                block_idx++;    //分配下一个扇区
            }
        }else if(file_has_used_blocks <= 12 && file_will_use_blocks > 12){
            /*第二种情况:旧数据在12个直接块中,数据将使用间接块*/
            /*先将剩余空间的可继续用的扇区地址写入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];

            /*创建一级间接块表*/
            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;
                }

                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_blocks数组中,待全部分配完成后一次同步到硬盘
                    all_blocks[block_idx] = block_lba;
                }
                /*每分配一个块就将文图同步到硬盘*/
                block_bitmap_idx = block_lba - cur_part->sb->block_bitmap_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->block_bitmap_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);//同步一级间接块表到硬盘
        }
    }

    /*用到的块地址已经收集到了all_blocks中,下面开是写数据*/
    bool first_write_block = true;  //含有剩余空间的块标识
    file->fd_pos = file->fd_inode->i_size - 1;  //置fd_pos为文件大小-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;
}

file_write:功能是把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1。核心的原理是:传进函数的文件结构struct file指针中有个指向操作文件inode的指针,通过这个inode中的i_sizei_sectors[ ],我们可以顺利知道文件大小与存储位置信息。先将文件已有数据的最后一块数据读出来并与将要写入的数据在缓冲区中共同拼凑成一个完整的块,然后写入磁盘。剩下的数据以块为单位继续写入磁盘即可。

流程:1.先判断写入的buf会不会超出文件的大小;2.申请缓冲区;3.写入count字节前,先计算该文件已经占用的块数;4.计算存储count字节后该文件将占用的块数;5.将写文件所用到的块地址收集到all_blocks,共分为三种情况讨论;6.用到的块地址已经收集到了all_blocks中,开始写数据。

14.7.2 改进sys_write及write

因为之前我们实现的sys_write,由于没有实现file_write,所以它就是调用了console_put_str打印字符串,现在我们改进sys_write

如果传入的fd表示标准输出,直接调用console_put_str打印即可。否则就调用fd_local2global获取文件描述符fd对应于文件表中的下标_fd,然后获得待写入文件的文件结构指针wr_file,再判断他的flag,最后做出判断。

//fs.c
/*将buf中连续count个字节写入文件描述符fd,成功则返回写入的字节数,失败返回-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;
    }

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

同时修改write

/* 把buf中count个字符写入文件描述符fd */
uint32_t write(int32_t fd, const void *buf, uint32_t count)
{
    return _syscall3(SYS_WRITE, fd, buf, count);//其实就是更改这里
}

最后再把printf修改一下:

/* 格式化输出字符串format */
uint32_t printf(const char *format, ...)
{
    va_list args;
    va_start(args, format); // 使args指向format
    char buf[1024] = {0};   // 用于存储拼接后的字符串
    vsprintf(buf, format, args);
    va_end(args);
    return write(1, buf, strlen(buf));//其实就是更改这里
}

标签:blocks,操作系统,idx,写入,write,fd,真象,file,block
From: https://blog.csdn.net/weixin_44269804/article/details/140473516

相关文章

  • 第一章 操作系统的概述
    操作系统的概论操作系统的概念操作系统是计算机系统中的一个系统软件,有效的组织和管理计算机系统当中的硬件和软件资源,合理组织计算机的工作流程,控制程序的执行,并给用户提供各种服务,是计算机系统可以高效的运行(资源管理和控制程序的执行)有效:考虑用户的需求,提高系统的资源利用率......
  • 【操作系统】
    第一章一、操作系统的定义操作系统是一组计算机程序的集合,用于控制和管理硬件资源和软件资源,合理地组织计算机的工作流程,为用户提供方便、快捷、友好的应用程序使用接口。是系统最基本最核心的软件,属于系统软件控制和管理整个计算机的硬件和软件资源合理的组织、调度计算机......
  • [操作系统]进程
    进程进程、线程、协程的概念进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。线程:是进程的一个执行单元,是进程内的调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。协程:是一种比线程更加轻量级的......
  • qt 单独线程实现日志写入功能
    https://blog.csdn.net/u012329294/article/details/88286961<divid="content_views"class="htmledit_views"><p>在qt开发中,应用程序运行中常常会因为写日志的原因,造成系统性能低下,</p>那么这个时候就应该考虑采用单独的线程来实现日志写入......
  • 信创学习笔记(三),信创之操作系统OS思维导图
    创作不易只因热爱!!热衷分享,一起成长!“你的鼓励就是我努力付出的动力”title!!#f1c232点击上方蓝色小字即可一键关注!!!!#f1c232创作不易只因热爱!!:::primary!18热衷分享,一起成长!:::^**你好呀,我是卫码士。一个医信行业工程师,喜欢学习,喜欢搞机,喜欢......
  • 全网最最实用--基于Mac ARM 芯片实现操作系统MIT 6.S081-lab3
    文章目录实验三页表一、代码理解1.对于内存布局定义的理解2.对虚拟内存的理解3.对分配和释放物理内存的理解--删除或者分配物理内存为啥不需更改相应的页表?二、Printapagetable1.题目描述2.题目思考3.提交实验三、Akernelpagetableperprocess1.题目描述2.题目......
  • Windows 注册表编辑器(regedit)的演变和发展主要是由 Microsoft Windows 操作系统的设计
    Windows注册表编辑器(regedit)的演变和发展主要是由MicrosoftWindows操作系统的设计和需求驱动的。下面是大致的演化过程:需求和设计:在早期的Windows系统中,配置信息分散存储在各种配置文件和INI文件中,管理起来不够方便。为了统一管理系统配置信息,并提高系统的灵活性和可维......
  • 上榜!天翼分布式云操作系统入选“科创中国”先导技术榜单!
    在近日召开的第二十六届中国科协年会上,中国科协正式发布2023年“科创中国”系列榜单,榜单包括先导技术榜、新锐企业榜、融通创新组织榜、技术经理人先锋榜等。天翼云自主研发的天翼分布式云操作系统入选先导技术榜,充分展现了在科技创新方面的硬实力。“科创中国”系列榜单由中国......
  • sqlalchemy pandas转化字典转为orm写入到sqlite数据库报错类型错误的解决办法
    使用pandas读取csv数据,然后将其转化为字典,再写入到数据库的时候,数据库总是报错类型错误,于是转为orm之前,统一转化一下类型fromsqlalchemyimportDECIMAL,Index,String,Date,Integer,Text,CHAR,SmallInteger,Float,Time,case,and_,extract,TypeDecoratorfrom......
  • [操作系统]线程
    线程线程的状态转换参考文章线程在一定条件下,状态会发生变化。线程一共有以下几种状态:新建状态(New):新创建了一个线程对象。就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即......