《oranges一个操作系统的实现》阅读笔记三

第三章工具的使用

1.如何调试超过一个扇区的程序?

在这里总结以下从第一章到第四章,书上所用各种工具的意义。
1) 第一章直接用软盘,用bochs加载软盘,程序写到软盘的第一个扇区,bochs相当于一个实体机器,从软盘第一个扇区读取程序到0x7c00h,并跳转到此处执行。(具体开机流程可以参考第一篇笔记)
2)第二章用软盘的镜像文件充当虚拟的软盘,并没有突破引导扇区一个扇区容量的限制
3) 第三章由于引导扇区512字节的限制,限制了实验程序的大小,而又暂时不想进入引导扇区加载其他程序进内存比如Loader的步骤,所以就暂时利用freedos这个操作系统,bochs通过利用freedos.img的第一个扇区当做引导扇区,先把CPU控制权交给freedos,freedos格式化我们实验程序写入的b.img为dos文件系统格式,我们将自己的实验程序拷贝到b.img(就是向文件系统中拷贝个文件)中,然后在freedos通过指令加载执行我们的实验程序,利用了dos的文件系统
4).第四章我们自己写引导扇区,加载自己的Loader,要突破第一扇区,就得靠自己加载了,因为bios程序只负责将引导扇区的程序读到0x7c00h中,然后就将控制权交给了引导扇区的程序。但如果我们自己去读取程序的话,只能靠硬件软盘,硬盘的物理划分来读取,一个扇区一个扇区的加载到程序,这限制了我们程序边界的灵活性,要读多个文件的话也很复杂,这个时候就需要文件系统的帮忙了,但鉴于我们还没有自己的文件系统,我们可以先用一个成熟的文件系统FAT12,这样我们就可以方便的将自己的Loader写到软盘的文件系统FAT12里,并在引导扇区程序里方便灵活的找到它。

我本来是按照书上所说使用freedos来执行编译的程序,但是想调试32位汇编程序,并没有找到好工具,根据网上所说我找到了TDdebug.exe,但是执行起来有问题(费了老大的劲又是重装bochs又是找各种版本),结果最后还是不行,后来用了个Debug32.exe但是不知道如何现实X86架构下的各个寄存器,所以最后放弃了。(其实想想很有可能是freedos根本不支持X86下的东西,或者版本不对,反正搞得很不爽,所以就停了)。最后就用bochs调试了,但是毕竟由于扇区限制和又还没写自己的Loader那。我的解决方案就是删程序。在这里我也建议大家如果没有更好的调试方法可以通过删程序将程序精简到512字节内直接用bochs调试。第一比较直观没有其他系统或者文件系统的干扰,第二在前几章还没有很复杂的程序出现,只需要保留自己想实验的代码,把没用的删除了,第三这样更能考察对于书中代码的理解,如果只是把书上的源代码原封不动的拿来调试,会忽视很多问题,只有出现问题,才能解决问题,如果没有出现任何问题也就证明没有解决任何问题。

2. 为什么用COM,ELF文件?

COM, ELF文件都只是中文件格式,就是除了文件内容有一些标识文件类型的信息,用来被解释者读取使用。COM是dos下可执行文件格式,ELF是Linux下可执行文件格式。前三章一直用com格式是因为一直在用dos加载执行程序,而后续我们要自己加载执行ELF文件。可执行文件除了代码外还会有其他的执行相关信息,比如代码,数据的分段,将代码加载到内存什么位置,这些信息在编译链接的时候写进可执行文件,加载的时候被加载程序读取用来做相应处理。所以说加载程序和可执行文件是配对的。采用ELF文件是因为我们会在Linux开发自己的操作系统,所以生成的执行文件就是ELF格式的,我们自己同样要开发加载程序去读取ELF文件中的信息。

3.什么时候使用汇编语言什么时候用C语言?

这个问题应该从技术发展的历史角度看。刚开始程序员用汇编写操作系统,但后来发现开发效率非常低下,所以发明了高级语言C语言,高级语言是为了开发通用软件时的效率。但是高级语言受限于编译器,主流的编译器只能将C语言转化为有限的汇编也就是汇编的一个子集,所以必然有一部分指令是没办法通过C语言实现的,这个时候就需要汇编了。

4.boot, loader, kernel都实现了什么?

Boot是引导扇区代码,受限于512字节大小限制,负责根据FAT12文件系统找到Loader所在软盘扇区位置,并将它加载到内存,将CPU控制权交给Loader。实现的是MBR程序的功能。但是使用了FAT12文件系统这个现有的功能并未自己实现。
Loader是负责加载kernel进内存,并且进入保护模式,然后将kernel根据ELF program信息将kernel载入不同的段,并且实现了分页,最后将CPU控制权交给Kernel。所以Loader实现了程序加载器的功能。
Kernel就是我们的内核,操作系统的核心,进程管理,内存管理,磁盘管理等的实现处。

5.既然需要用到FAT12文件系统还需要自己定义引导盘的盘头信息吗?

这个问题的意思是既然我们需要将自己的软盘格式化为FAT12格式,我们还需要定义下图的FAT盘头信息吗?

其实不需要了,因为反正我们都要将整个软盘格式化成FAT12,因为我们没有自己的文件系统,格式化的时候,引导扇区就已經被写入了上图定义的那些数据了。

可以看到前面62个字节就是所谓的软盘BP头信息,所以boot.asm其实没有必须定义这些数据,所以将
BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节
改成Off_BS_OEMNAME   equ 3,其他信息一样的处理,这样变量定义变成了常量,可以节省62个字节的内存,这对于引导扇区512字节可是很珍贵的,相应的使用语句
add bx, [BPB_BytsPerSec]需要变成add bx, [Off_BPB_BytsPerSec+7c00h],因为编译器不会再帮我们计算偏移地址了,经测试程序可以正常运行。

6. 编译,链接,加载,执行流程?

编译器主要负责程序的翻译,以及内存布局等指导信息和一些链接需要的符号表信息。我们现在主要通过nasm编译汇编程序,用gcc编译C程序
链接主要是将多个独立编译的由编译器生成的目标文件和一些已经预编译好的公共库文件,根据各自提供的符号表信息合成最终的可执行文件,我们现在链接过程用ld 
加载主要是内存中负责将外部存储设备上的可执行文件挪到内存中,并根据可执行文件中的信息,将不同的数据搬到不同的内存段中,我们现在加载靠自己写的Loader属于操作系统的一部分
操作系统加载完程序后,跳转执行可执行文件的第一条指令,CPU控制权交给可执行文件
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,这里是一个简单的仓库管理系统的Java代码示例: ``` import java.util.*; class Product { String name; int quantity; public Product(String name, int quantity) { this.name = name; this.quantity = quantity; } } public class Warehouse { Map<String, Product> inventory; public Warehouse() { inventory = new HashMap<>(); } public void addProduct(String name, int quantity) { if (inventory.containsKey(name)) { Product product = inventory.get(name); product.quantity += quantity; } else { Product product = new Product(name, quantity); inventory.put(name, product); } } public void removeProduct(String name, int quantity) { if (inventory.containsKey(name)) { Product product = inventory.get(name); if (product.quantity >= quantity) { product.quantity -= quantity; } else { System.out.println("Error: Not enough " + name + " in stock."); } } else { System.out.println("Error: " + name + " not found in inventory."); } } public void printInventory() { System.out.println("Current Inventory:"); for (String name : inventory.keySet()) { Product product = inventory.get(name); System.out.println(name + ": " + product.quantity); } } public static void main(String[] args) { Warehouse warehouse = new Warehouse(); warehouse.addProduct("Apples", 10); warehouse.addProduct("Oranges", 5); warehouse.printInventory(); warehouse.removeProduct("Apples", 5); warehouse.printInventory(); warehouse.removeProduct("Bananas", 3); warehouse.printInventory(); } } ``` 这个示例代码实现一个简单的仓库管理系统,其中`Product`类代表产品,包含产品名称和数量。`Warehouse`类代表仓库,包含了一个`inventory`哈希表来存储产品和其数量。主要的方法包括`addProduct()`来添加产品到仓库,`removeProduct()`来从仓库中移除产品,和`printInventory()`来打印当前库存清单。在`main()`方法中,我们创建了一个新的仓库对象,添加了一些产品,并且打印了当前的库存清单。然后我们移除了一些产品,再次打印了库存清单来确认移除操作的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值