学习操作系统原理最好的方法是自己写一个简单的操作系统。


本讲代码文件为boot.asm,要读取的文件为data.txt。

一、在FAT16系统中读取文件的流程

在GrapeOS中用到的文件少且小,所有文件都放在了根目录下,数量不会超过16个,占用的簇不会超过254个。所以读取目录项只需要读取根目录的第1个扇区即可,读取FAT表项也只需读取FAT1表的第1个扇区即可。

以下是读取文件的流程图:

二、代码及讲解

boot.asm中的代码如下:

;--------------------定义常量--------------------
;FAT16目录项中各成员的偏移量:
;名称 偏移 长度 描述
DIR_Name equ 0 ;11 文件名8B,扩展名3B
DIR_Attr equ 11 ;1 目录项属性
;保留位 12 10 保留位
DIR_WrtTime equ 22 ;2 最后一次写入时间
DIR_WrtDate equ 24 ;2 最后一次写入日期
DIR_FstClus equ 26 ;2 起始簇号
DIR_FileSize equ 28 ;4 文件大小 BOOT_ADDRESS equ 0x7c00 ;boot程序加载到内存的地址。
FILE_ADDRESS equ 0x1000 ;文件读到内存中的地址。
DISK_BUFFER equ 0x7e00 ;读磁盘临时存放数据用的缓存区,放到boot程序之后。
DISK_SIZE_M equ 4 ;磁盘容量,单位M。
FAT1_SECTORS equ 32 ;FAT1占用扇区数
ROOT_DIR_SECTORS equ 32 ;根目录占用扇区数
SECTOR_NUM_OF_FAT1_START equ 1 ;FAT1表起始扇区号
SECTOR_NUM_OF_ROOT_DIR_START equ 33 ;根目录区起始扇区号
SECTOR_NUM_OF_DATA_START equ 65 ;数据区起始扇区号,对应簇号为2。
SECTOR_CLUSTER_BALANCE equ 63 ;簇号加上该值正好对应扇区号。
FILE_NAME_LENGTH equ 11 ;文件名8字节加扩展名3字节共11字节。
DIR_ENTRY_SIZE equ 32 ;目录项为32字节。
DIR_ENTRY_PER_SECTOR equ 16 ;每个扇区能存放目录项的数目。 ;--------------------MBR开始--------------------
org BOOT_ADDRESS
jmp boot_start
nop ;FAT16参数区:
BS_OEMName db 'GrapeOS ' ;厂商名称(8字节,含空格)
BPB_BytesPerSec dw 0x0200 ;每扇区字节数
BPB_SecPerClus db 0x01 ;每簇扇区数
BPB_RsvdSecCnt dw 0x0001 ;保留扇区数(引导扇区的扇区数)
BPB_NumFATs db 0x01 ;FAT表的份数
BPB_RootEntCnt dw 0x0200 ;根目录可容纳的目录项数
BPB_TotSec16 dw 0x2000 ;扇区总数(4MB)
BPB_Media db 0xf8 ;介质描述符
BPB_FATSz16 dw 0x0020 ;每个FAT表扇区数
BPB_SecPerTrk dw 0x0020 ;每磁道扇区数
BPB_NumHeads dw 0x0040 ;磁头数
BPB_hiddSec dd 0x00000000 ;隐藏扇区数
BPB_TotSec32 dd 0x00000000 ;如果BPB_TotSec16是0,由这个值记录扇区数。
BS_DrvNum db 0x80 ;int 13h的驱动器号
BS_Reserved1 db 0x00 ;未使用
BS_BootSig db 0x29 ;扩展引导标记
BS_VolID dd 0x00000000 ;卷序列号
BS_VolLab db 'Grape OS ';卷标(11字节,含空格)
BS_FileSysType db 'FAT16 ' ;文件系统类型(8字节,含空格) ;通过以上参数可知硬盘容量为4MB,共8K个扇区。扇区具体分布如下:
;区域名 扇区数 扇区号 字节偏移 说明
;引导扇区 1个扇区 扇区0 0x0000~0x01ff
;FAT1表 32个扇区 扇区1~32 0x0200~0x41ff 可记录8K-2个簇
;FAT2表 无 无 无 无
;根目录区 32个扇区 扇区33~64 0x4200~0x81ff 可容纳512个目录项
;数据区 8127个扇区 扇区65~0x1fff 0x8200~0x3fffff ;--------------------程序开始--------------------
boot_start:
;初始化寄存器
mov ax,cs
mov ds,ax
mov es,ax ;cmpsb会用到ds:si和es:di ;读取文件开始
;读取根目录的第1个扇区(1个扇区可以存放16个目录项,我们用到的文件少,不会超过16个。)
mov esi,SECTOR_NUM_OF_ROOT_DIR_START
mov di,DISK_BUFFER
call func_read_one_sector ;在16个目录项中通过文件名查找文件
cld ;cld将标志位DF置0,在串处理指令中控制每次操作后让si和di自动递增。std相反。下面repe cmpsb会用到。
mov bx,0 ;用bx记录遍历第几个目录项。
next_dir_entry:
mov si,bx
shl si,5 ;乘以32(目录项的大小)
add si,DISK_BUFFER ;源地址指向目录项中的文件名。
mov di,read_file_name_string ;目标地址指向文件在硬盘中的正确文件名。
mov cx,FILE_NAME_LENGTH ;字符比较次数为FAT16文件名长度,每比较一个字符,cx会自动减一。
repe cmpsb ;逐字节比较ds:si和es:di指向的两个字符串。
jcxz file_found ;当cx为0时跳转,cx为0表示上面比较的两个字符串相同。找到了文件。
inc bx
cmp bx,DIR_ENTRY_PER_SECTOR
jl next_dir_entry ;检查下一个目录项。
jmp file_not_found ;没有找到文件。 file_found: ;找到了文件
;从目录项中获取文件的起始簇号
shl bx,5 ;乘以32
add bx,DISK_BUFFER
mov bx,[bx+DIR_FstClus] ;文件的起始簇号 ;读取FAT1表的第1个扇区(我们用到的文件少且小,只用到了该扇区中的簇号。)
mov esi,SECTOR_NUM_OF_FAT1_START
mov di,DISK_BUFFER ;放到boot程序之后
call func_read_one_sector mov bp,FILE_ADDRESS ;文件内容读取到内存中的起始地址 ;按簇号读文件内容
read_file:
xor esi,esi ;esi清零
mov si,bx ;簇号
add esi,SECTOR_CLUSTER_BALANCE
mov di,bp
call func_read_one_sector
add bp,512 ;下一个目标地址 ;获取下一个簇号(每个FAT表项为2字节)
shl bx,1 ;乘2,每个FAT表项占2个字节
mov bx,[bx+DISK_BUFFER] ;判断下一个簇号
cmp bx,0xfff8 ;大于等于0xfff8表示文件的最后一个簇
jb read_file ;jb无符号小于则跳转,jl有符号小于则跳转。 read_file_finish: ;读取文件结束
jmp stop file_not_found: ;没有找到文件 stop:
hlt
jmp stop ;读取硬盘1个扇区(主硬盘控制器主盘)
;输入参数:esi,ds:di。
;esi LBA扇区号
;ds:di 将数据写入到的内存起始地址
;输出参数:无。
func_read_one_sector:
;第1步:检查硬盘控制器状态
mov dx,0x1f7
.not_ready1:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0xc0 ;第7位为1表示硬盘忙,第6位为1表示硬盘控制器已准备好,正在等待指令。
cmp al,0x40 ;当第7位为0,且第6位为1,则进入下一个步。
jne .not_ready1 ;若未准备好,则继续判断。
;第2步:设置要读取的扇区数
mov dx,0x1f2
mov al,1
out dx,al ;读取1个扇区
;第3步:将LBA地址存入0x1f3~0x1f6
mov eax,esi
;LBA地址7~0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入端口写入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;第4步:设置device端口
shr eax,8
and al,0x0f ;LBA第24~27位
or al,0xe0 ;设置7~4位为1110,表示LBA模式,主盘
mov dx,0x1f6
out dx,al
;第5步:向0x1f7端口写入读命令0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第6步:检测硬盘状态
.not_ready2:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0x88 ;第7位为1表示硬盘忙,第3位为1表示硬盘控制器已准备好数据传输。
cmp al,0x08 ;当第7位为0,且第3位为1,进入下一步。
jne .not_ready2 ;若未准备好,则继续判断。
;第7步:从0x1f0端口读数据
mov cx,256 ;每次读取2字节,一个扇区需要读256次。
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [di],ax
add di,2
loop .go_on_read
ret read_file_name_string:db "DATA TXT",0 ;要读取的文件在硬盘中存储的文件名,共11个字节,含空格。 times 510-($-$$) db 0
db 0x55,0xaa

关于代码的讲解基本都写在注释中了,结合之前讲的内容,大家应该能看懂。

三、通过Linux将文件复制到虚拟硬盘中

本讲要读取的文件是data.txt,如何将该文件复制到虚拟硬盘的FAT16文件系统中呢?我们这里采用的方法是将该虚拟硬盘挂载到Linux系统上,然后就可以将data.txt复制到虚拟硬盘中了。前提是需要先将虚拟硬盘格式化,格式化的方法就是将boot程序写入到虚拟硬盘的第一个扇区。因为boot程序中含有FAT16的结构化数据,Linux系统就知道如何读写该文件系统了。

1.将boot程序写入到虚拟硬盘的第一个扇区

dd if=/dev/zero of=/media/VMShare/GrapeOS.img bs=1M count=4
nasm boot.asm -o boot.bin
dd if=boot.bin of=/media/VMShare/GrapeOS.img conv=notrunc

截图如下:

2.将虚拟硬盘挂载到Linux系统上并将data.txt复制到虚拟硬盘中

mount /media/VMShare/GrapeOS.img /mnt/ -t msdos -o loop
ll /mnt/
cp data.txt /mnt/
sync #数据同步,立马把数据写入硬盘。
ll /mnt/
umount /mnt/

截图如下:

上图中在复制完data.txt后,通过ll /mnt/查看虚拟硬盘根目录,此时虽然看到的文件名是小写“data.txt”,但实际上在虚拟硬盘里存储的文件名已经是全部大写的了,在下面的分析中可以看到。

3.虚拟硬盘数据分析

通过hexdump查看虚拟硬盘数据:

hexdump /media/VMShare/GrapeOS.img -C

截图如下:

截图中的部分数据如下:

000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200 00 00 00 00 00 00 04 00 05 00 ff ff 00 00 00 00 |................|
00000210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00004200 44 41 54 41 20 20 20 20 54 58 54 20 00 00 00 00 |DATA TXT ....|
00004210 00 00 00 00 00 00 fa 4e 78 56 03 00 58 04 00 00 |.......NxV..X...|
00004220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

前面我们计算过,FAT1表的起始扇区是扇区1,字节偏移是0x200,根目录区的起始扇区是扇区33,字节偏移是0x4200。

从上面的截图和数据可以看到:

  1. 在根目录区的第一个目录项就是我们要读的文件,文件名已是全大写。
  2. 从目录项中可以看到文件的起始簇号是0x0003。
  3. 在FAT表中第3个FAT表项的值是0x0004,表示该文件的第二个簇号是0x0004。
  4. 在FAT表中第4个FAT表项的值是0x0005,表示该文件的第三个簇号是0x0005。
  5. 在FAT表中第5个FAT表项的值是0xffff,表示该文件没有下一个簇了,到此结束。
  6. 这个文件的内容共占用3个簇的空间,依次是簇3、簇4、簇5,读取该文件就是依次将这3个簇中的数据读取出来。

四、程序演示

在cmd命令行中启动QEMU的调试模式:

C:\Users\CYJ>qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img -S -s

在Linux命令行中启动GDB:

[root@CentOS7 Lesson23]# gdb
(gdb) target remote 你的Windows的IP地址:1234
(gdb) b *0x7c00
(gdb) c
(gdb) x /32xb 0x1000 #在读文件前查看此时0x1000处的内存数据,可以看到都是0。
0x1000: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1008: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x1018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(gdb) c
运行几秒,然后Ctrl键+C键暂停运行,此时读取文件的程序已运行完毕。
(gdb) x /32xb 0x1000 #在读完文件后查看此时0x1000处的内存数据,可以看到已经不是0了。
0x1000: 0x78 0x38 0x36 0x20 0x28 0x61 0x6c 0x73
0x1008: 0x6f 0x20 0x6b 0x6e 0x6f 0x77 0x6e 0x20
0x1010: 0x61 0x73 0x20 0x38 0x30 0x78 0x38 0x36
0x1018: 0x20 0x6f 0x72 0x20 0x74 0x68 0x65 0x20
(gdb) x /32c 0x1000 #为了方便观察可以以字符形式展示数据。通过对比,下面的32个字符的确和data.txt中前32个字符相同。
0x1000: 120 'x' 56 '8' 54 '6' 32 ' ' 40 '(' 97 'a' 108 'l' 115 's'
0x1008: 111 'o' 32 ' ' 107 'k' 110 'n' 111 'o' 119 'w' 110 'n' 32 ' '
0x1010: 97 'a' 115 's' 32 ' ' 56 '8' 48 '0' 120 'x' 56 '8' 54 '6'
0x1018: 32 ' ' 111 'o' 114 'r' 32 ' ' 116 't' 104 'h' 101 'e' 32 ' '
(gdb) x /32c 0x1440 #查看文件的最后二十多个字符。通过对比可以看到和data.txt中的相同。
0x1440: 105 'i' 116 't' 115 's' 32 ' ' 90 'Z' 105 'i' 108 'l' 111 'o'
0x1448: 103 'g' 32 ' ' 90 'Z' 45 '-' 56 '8' 48 '0' 32 ' ' 118 'v'
0x1450: 97 'a' 114 'r' 105 'i' 97 'a' 110 'n' 116 't' 41 ')' 46 '.'
0x1458: 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'

通过上述演示说明读取文件成功。


视频版地址:https://www.bilibili.com/video/BV1xN411K7Lc/

配套的代码与资料在:https://gitee.com/jackchengyujia/grapeos-course

GrapeOS操作系统交流QQ群:643474045

自己动手从零写桌面操作系统GrapeOS系列教程——23.从硬盘读取文件的更多相关文章

  1. 别人写的一个Bootstrap系列教程

    http://www.cnblogs.com/lansy/category/659061.html

  2. 一个人写的操作系统 - Sparrow OS

    一个人写的操作系统 - Sparrow OS 自己写一个操作系统,这是在过去的几年里我一直为之努力的目标,现在终于完成了. 缘起 自己动手写操作系统的动机最初来自于学习Linux遇到的困难. 我是一个 ...

  3. 自制 os 极简教程1:写一个操作系统有多难

    为什么叫极简教程呢?听我慢慢说 不知道正在阅读本文的你,是否是因为想自己动手写一个操作系统.我觉得可能每个程序员都有个操作系统梦,或许是想亲自动手写出来一个,或许是想彻底吃透操作系统的知识.不论是为了 ...

  4. 【操作系统】关于Linux桌面操作系统

    以前是Win+Ubuntu+黑苹果,周末想体验一下deepin,于是简单安装了一下,安装过程很简单,这里不再描述.安装之后,第一次打开系统,确实很惊艳,赏心悦目的操作系统. 之前用Ubuntu时候,C ...

  5. 盘点|2021年最受欢迎Linux桌面操作系统前十名

    镜像下载.域名解析.时间同步请点击 阿里云开源镜像站 阿里云开源镜像站利用云服务上的优势,提供快速.稳定的镜像分发服务.和免费的CDN加速服务.更新频率高,基本上一天一更新,对于Centos/Ubun ...

  6. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  7. 《一步一步写嵌入式操作系统》读书笔记1—Skyeye介绍、安装和HelloWorld

    2013-11-14 最近在看<一步一步写嵌入式操作系统>,感觉此书甚好,许多地方讲得很清楚.可操作性强,计划边读边实践边写笔记,希望能够逐步熟悉嵌入式操作系统底层的东西,最终剪裁出一套实 ...

  8. Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(异常处理和引用计数)

    我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等.强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Pyt ...

  9. Zedboard学习(一):移植Ubuntu桌面操作系统 标签: ubuntu移植zedboardFPGA 2017-07-04 21:53 26人阅读

    环境准备: 首先,需要的肯定是Ubuntu操作系统.可以在自己的电脑上安装物理机,也可以是虚拟机下运行的.我的是在Vmware下运行的Ubuntu14.04 32位操作系统. 由于zedboard上的 ...

  10. 27、从零写UVC驱动之分析数据传输(设置ubuntu通过串口打印,指定打印到文件,ubuntu切换root用户)

    A. 设置ubuntu让它从串口0输出printk信息a. 设置vmware添加serial port, 使用文件作为串口(在vmware中设置,文件是保存在windows中)b. 启动ubuntu, ...

随机推荐

  1. 1.3 C语言--指针与结构体

    指针 指针概念的引入 关于内存 程序有数据和指令组成,数据和指令在执行过程中存放在内存中.变量是程序数据中的一种,因此变量也存储在内存中:内存中的每个字节都有一个唯一的编码,即内存地址.32位机的内存 ...

  2. Windows如何生成公钥和私钥

    Windows如何生成公钥和私钥 方法一)使用git命令 一. 首先安装git二. 桌面上右键 Git Bash Here三. 命令ssh-keygen -t rsa然后 一直enter 四. 将公钥 ...

  3. @Scheduled不执行

    今天发现@Scheduled不执行,注释掉netty的初始化事件就能正常执行了 原因是@PostConstruct是在主线程执行,@PostConstruct不能堵塞,堵塞会导致整个应用挂起不可用

  4. 【peewee】Python使用peewee时where中不同类型比较的问题

    问题 以学生表为例,TableStudents表中age字段是TextField类型,想要筛选出18岁以上的学生 TableStudents.select().where(TableStudents. ...

  5. js-var,let ,const 的区别

    变量提升: 在js预编译阶段,函数和变量的声明会被提前检索编译,打乱了编写时的声明顺序. 函数字面量表达式不会被提升 var a = function(){}; ex: console.log(a) ...

  6. springboot pom文件引入本地jar包

    记录maven引用本地jar包 配置 及打包的其中一个方法,作为个人笔记,供参考: <dependency> <groupId>gdin</groupId> < ...

  7. Lombok和MapStruct冲突

    Lombok和MapStruct冲突导致无法生成正确的class文件. lombok自动生成getset等冗余代码. MapStruct对象copy.传统的BeanUtils.copy等利用的反射原理 ...

  8. 百度自定义底图(瓦片图)升级 HTTPS

    本文地址:https://www.cnblogs.com/veinyin/p/14338414.html 记录一下开发中遇到的问题与解决方案 使用 Leaflet 开发,设计为了美观采用百度自定义底图 ...

  9. The Ultimate Guide to Dynamics 365 Pricing and Licensing

        Microsoft Dynamics 365 integrates powerful ERP and CRM capabilities in the cloud to provide busi ...

  10. C/C++ 数据结构优先级队列的实现(使用二级指针)

    #include <iostream> #include <Windows.h> #include <iomanip> //优先级队列的实现 using names ...