当前位置: 首页 > news >正文

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

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_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->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指向formatchar buf[1024] = {0};   // 用于存储拼接后的字符串vsprintf(buf, format, args);va_end(args);return write(1, buf, strlen(buf));//其实就是更改这里
}

相关文章:

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

14.7 实现文件写入 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 本节要实现的 sys_write 是系统调用 write 的内核实现&#xff0c;咱们之前的 write 是个简易版&#xff0c;它是为了临时完成输出打印的功能&#xff0c;不支持文件描述符。如今要让…...

FastAPI 学习之路(四十九)WebSockets(五)修复接口测试中的问题

其实代码没有问题&#xff0c;但是我们忽略了一个问题&#xff0c;就是在正常的开发中&#xff0c;肯定是遇到过这样的情况&#xff0c;我们频繁的有客户端链接&#xff0c;断开连接&#xff0c;需要统一的管理这些链接&#xff0c;那么应该如何管理呢。其实可以声明一个类去管…...

STM32智能楼宇照明系统教程

目录 引言环境准备智能楼宇照明系统基础代码实现&#xff1a;实现智能楼宇照明系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;楼宇照明管理与优化问题解决方案与优化收尾与总结 1. 引言 智能楼宇照明系…...

【C语言】原码、反码、补码详解 -《码上有道 ! 》

目录 原码、反码、补码详解及其在C语言中的应用一、原码&#xff08;Sign-Magnitude&#xff09;1.1 定义与表示1.2 历史来源与作用1.3 示例1.4 C语言示例1.5 代码运行结果 二、反码&#xff08;Ones Complement&#xff09;2.1 定义与表示2.2 历史来源与作用2.3 示例2.4 C语言…...

C++找到错误的具体信息

fprintf(stderr, "Errno: %d, Error message: %s\n", errno, strerror(errno));为什么不用printf来打印输出&#xff1f; 使用 fprintf(stderr, …)&#xff0c;将错误消息输出到标准错误流 stderr。这种做法是为了将错误信息输出到一个专门用于记录错误的流中&…...

Windows 安装Zookeeper

安装 下载地址&#xff1a;Apache ZooKeeper 我下载的版本&#xff1a;zookeeper-3.4.12 下载后&#xff0c;解压 配置 1、 在D:\zookeeper-3.4.12文件夹中创建一个“data”文件夹和“log”文件夹 2、 复制zoo_sample.cfg&#xff0c;改名&#xff1a;zoo.cfg 修改zoo.c…...

从人工巡检到智能预警:视频AI智能监控技术在水库/河湖/水利防汛抗洪中的应用

一、背景需求分析 近日&#xff0c;我国多省市遭遇连日暴雨&#xff0c;导致水库、湖泊、河道等水域水位暴涨&#xff0c;城市内涝频发。随着夏季汛期的到来&#xff0c;降雨天气频繁&#xff0c;水利安全管理面临严峻挑战。为保障水库安全、预防和减少洪涝灾害&#xff0c;采…...

【轻松拿捏 】Java-static关键字(面试)

Java-static关键字 1. 定义和基本概念 回答要点&#xff1a; 示例回答&#xff1a; 2. static 变量 回答要点&#xff1a; 示例回答&#xff1a; 代码示例&#xff1a; 3. static方法 回答要点&#xff1a; 示例回答&#xff1a; 代码示例&#xff1a; 4. static 代…...

【阶乘】个人练习-Leetcode-LCP 22. 黑白方格画

题目链接&#xff1a;https://leetcode.cn/problems/ccw6C7/description/ 题目大意&#xff1a;给出一块白方格面积为n*n&#xff0c;给出一个数字k&#xff0c;每一次操作可以把方格的某一整行或者某一整列涂黑&#xff0c;求使得黑色格子数字为k的【最终图案】的个数。 思路…...

十七、【文本编辑器(三)】图像坐标变换

目录 一、缩放功能 二、旋转功能 三、镜像功能 四、QMatrix简单介绍 一、缩放功能 &#xff08;1&#xff09;在头文件中添加 “protected slots:" 变量&#xff1a; void ShowZoomln( ); &#xff08;2&#xff09;在 createActionso函数的最后添力口事件关联&…...

生活中生智慧

【 圣人多过 小人无过 】 觉得自己做得不够才能做得更好&#xff0c;互相成全&#xff1b;反求诸己是致良知的第一步&#xff1b;有苦难才能超越自己&#xff0c;开胸怀和智慧&#xff1b;不浪费任何一次困苦&#xff0c;危机中寻找智慧&#xff0c;成长自己。 把困苦当作当下…...

2024第18届中国西部(成都)教育装备展12月14日举办

2025中国国际工业气体设备技术及应用展览会 2025 China International Industrial Gas Equipment Technology and Application Exhibition 时间&#xff1a;2025年3月18-20日 地点&#xff1a;北京全国农业展览馆&#xff08;新馆&#xff09; 展览前言 随着需求市场的持续…...

Webpack看这篇就够了

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…...

基于京东电商蓝牙耳机产品评论数据的情感分析与文本分析

摘要 随着电子商务的迅速发展&#xff0c;了解用户对产品的意见和情感倾向对企业至关重要。本研究旨在利用Python大数据技术对电商产品评论进行情感分析和主题建模&#xff0c;为企业提供有价值的市场洞察。 通过爬虫获取电商评论数据&#xff0c;使用pandas清洗和预处理数据&a…...

【Linux网络】poll{初识poll / poll接口 / poll vs select / poll开发多客户端echo服务器}

文章目录 1.初识pollpoll与select的主要联系与区别poll的原理poll的优点poll的缺点poll vs select 2.poll开发多客户端echo服务器封装套接字接口Makefile主函数日志服务聊天服务器 1.初识poll poll是Linux系统中的一个系统调用&#xff0c;它用于监控多个文件描述符&#xff08…...

数据库db文件损坏修复方法(sqlite3:database disk image is malformed)

参考博客&#xff1a; https://www.cnblogs.com/liuyangQAQ/p/18037546 sqlite3数据库提示database disk image is malformed 解决办法-CSDN博客 【SQL】sqlite数据库损坏报错&#xff1a;database disk image is malformed&#xff08;已解决&#xff09;-CSDN博客 一、第…...

Prometheus 云原生 - 微服务监控报警系统 (Promethus、Grafana、Node_Exporter)部署、简单使用

目录 开始 Prometheus 介绍 基本原理 组件介绍 下文部署组件的工作方式 Prometheus 生态安装&#xff08;Mac&#xff09; 安装 prometheus 安装 grafana 安装 node_exporter Prometheus 生态安装&#xff08;Docker&#xff09; 安装 prometheus 安装 Grafana 安装…...

Spring源码注解篇三:深入理解@Component注解

Component及其派生注解的源码 Spring框架作为Java开发中不可或缺的一部分&#xff0c;其依赖注入机制的核心是通过注解来实现的。本文将深入探讨Spring中Component及其派生注解的源码实现&#xff0c;分析Spring如何通过类路径扫描&#xff08;Classpath Scanning&#xff09;和…...

SpringBoot中常用的注解及其用法

1. 常用类注解 RestController和Controller是Spring中用于定义控制器的两个类注解. 1.1 RestController RestController是一个组合类注解,是Controller和ResponseBody两个注解的组合,在使 用 RestController 注解标记的类中&#xff0c;每个方法的返回值都会以 JSON 或 XML…...

【大语言模型】私有化搭建-企业知识库-知识问答系统

下面是我关于大语言模型学习的一点记录 目录 人工智能学习路线 MaxKB 系统(基于大语言模型的知识问答系统) 部署开源大语言模型LLM 1.CPU模式(没有好的GPU&#xff0c;算力和效果较差) 2.GPU模式&#xff08;需要有NVIDIA显卡支持&#xff09; Ollama网络配置 Ollama前…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...