自制操作系统Antz day04——进入保护模式 (下) 实现内核并从硬盘载入

简介: 目前已经完成了MBR的雏形,并且直接操作显卡完成了屏幕的内容显示。接下来我们要改造之前的MBR,做一个大的改进,使MBR可以读取硬盘,因为我们的MBR受限制于512字节大小,在这么小的空间里没法为内核准备好环境,更不要说加载内核到内存中并运行了,所以我们需要在另一个程序中完成初始化环境与加载内核的任务,这个程序我们叫做loader。

  Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html

  Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html

  Github地址:https://github.com/CasterWx

  目前已经完成了MBR的雏形,并且直接操作显卡完成了屏幕的内容显示。接下来我们要改造之前的MBR,做一个大的改进,使MBR可以读取硬盘,因为我们的MBR受限制于512字节大小,在这么小的空间里没法为内核准备好环境,更不要说加载内核到内存中并运行了,所以我们需要在另一个程序中完成初始化环境与加载内核的任务,这个程序我们叫做loader。loader这个程序放在哪里呢?如何去执行呢?这就是这次MBR改进的任务了,我们需要从硬盘上去把loader加载到内存中,并把执行权的接力棒交给它。

  在第一天讲过了,MBR是在硬盘的第0扇区,第一扇区是空闲的,但是离的太近总是感觉不安全,所以我们将loader放到第二扇区。MBR从第二扇区中把它都出来,然后放到哪里呢?

  原则上空闲位置都是可以的。图中7E00~9FBFF和500~7BFF这两段可用区域都可以。随着功能的添加,内核必然会越来越大,所以我们尽量把loader放在低处,所以我选择为了0x500处。

  说完了本次的基本任务和流程,接下来就是关键的问题了,如何操作硬盘?

 


 

0. 关于硬盘

  关于硬盘的种类历史此处不做介绍。

    如有兴趣可参考:硬盘

  我们先来讲讲它的工作原理,如图是机械硬盘的示意图。

    

  主轴上面有两个盘片,其实不止两个,这里只是示意性画了两个。盘片固定在主轴上随主轴高速转动,每个盘片分为上下两面,每个面都存储有数据,每个盘面都各有一个磁头来读取数据,故一个盘片对应两个磁头(注意盘片和盘面,不要看混)。由于盘面和磁头是一一对应的关系,故用磁头号来标识盘面号,磁头0对应盘面0,磁头1对应盘面1,从0开始计数,盘面0就是第一个盘面。磁头不会自己在盘片上移动,它需要被固定在磁头臂上,在磁头臂的带动下,沿着盘片的边缘向圆心的方向来回摆动,注意摆动的轨迹是个弧,并不是绝对径向地直来直去。一方面是因为磁头臂是步进电机驱动的,磁头臂一段时步进电机主轴,另一端的磁头,电机每次都会转动一个角度,所以带动磁头臂在“画圆”,而磁头位于磁头臂的另一端,所以也跟着呈钟摆运动,轨迹时弧线,并不是直线。另一方面,磁头读取数据也不需要做直来直去的运动,能否找到数据,只跟它最终落点有关,和中间路径形状无关,所以一方面盘面自转,另一方面磁头摆动,使得磁头可以盘面任意位置的数据。

  说完了运动,在说存储逻辑,盘片表面时用于存储数据的磁性介质,为了更有效的管理磁盘,这些磁性介质也被“格式化”成易于管理的格局,即将盘面划分成了多个同心环,以同心环画扇形,扇形与每个同心环相交的弧状区域作为最基本的数据存储单元。这个同心环就称为磁道,而同心环上弧状的扇形部分就称为扇区,它作为我们硬盘存储数据的最基本单位,大小是512字节。我们写入数据最终是写入了扇形的扇区中。注意,磁道是一个环,不是线,线上可无法存储数据。磁头臂带动磁头在盘片上方移动,就是在找磁道的位置,盘片高速自转,就是在磁道内定位扇区。

   磁道的编号和磁头的编号也是从0开始,相同编号的磁道组成的管状区域就称为柱面。柱面有什么用呢? 机械硬盘大的寻道时间是整个硬盘的瓶颈,为了减少寻道时间,就尽量在存储上下功夫。寻道,简而言之就是在磁头在磁道间跳转,跳转所需要的时间就是寻道时间。柱面就可以减少寻道的时间。至于原理,可以这样理解,当我们要存储的数据少于一个磁道的存储量时,我们可以直接存储在一个磁道里面,而不需要跳转到其他磁道(不需要寻道)。当要存储量大于一个磁道时,需要多次寻道,而寻道会浪费大量时间,如果我们使用柱面,存满一个磁道后,将剩下的数据存储在其他盘面的相同磁道号处,就可以避免寻道了,反过来,读取数据也是这样,以此,盘面越多,硬盘越快

  扇区的编号与磁道磁头不同,它是从1开始编号的,而且一个扇区只对当前磁道有效,所以各个磁道间的扇区编号都相同,至于一个磁道中的扇区数量多少与厂商有关,一般都是63个扇区。磁头如何找到所需的扇区呢? 每个扇区其实都是有自己的头部的,头部之后才是512字节的存储区,头部包含了扇区自身的信息,哪些信息可以唯一定位一个扇区呢? 当然是磁头号,磁道号和扇区号了。

 

1. 控制硬盘之前

  之前在直接操作显存中说过,CPU不会直接与这些设备联系,而是与IO接口通信,再由IO接口向下传达信息,CPU与硬盘的联系就是通过硬盘控制器。

  硬盘控制器与硬盘的关系就好像显卡与显示器。关于硬盘的接口,你可能听说过PATA和SATA,ATA是一种全球化的标准,PATA是并行ATA,SATA是后来的串行ATA。以前的主机一般至支持4个并行PATA,在串行SATA出现之后,支持几块硬盘完全取决于主板能力。

  两种类型线缆完全不一样,PATA接口的线缆也称为IDE线,一个IDE线上可以挂两块硬盘,一个是主盘,一个是从盘。主盘从盘分工很明显,很多工作都要靠主盘来进行,比如系统就要装在主盘上。随着时代发展,兼容性的提升,主盘从盘已经没有了区别。之前说一个主板支持四块PATA硬盘,那么就是两个IDE线接口。这两个接口也是以0开始编号的,分别是IDE0,IDE1。不过按照ATA的说法,这两个插槽接口叫做通道,IDE0就是Primary通道,IDE1就是Secondary通道。SATA硬盘也是兼容PATA的编程接口。(这里不要把主盘从盘和通道弄混了)。

  硬盘是一个很复杂的结构,我们暂时只需要知道一部分端口就可以了。

  端口可以分为两组,Command Block registers和Control Block registers。 Command Block registers用于向硬盘启动器写入命令字或者从硬盘控制器获取硬盘状态,Control Block registers用于控制硬盘工作状态。

  端口是按照通道给出的,所以不要认为端口是直接针对某块硬盘的,一个通道的主从硬盘都是使用这些端口号的,要想操作某通道上的某块硬盘,需要单独指定。看上面的表格,有一个叫做Device的寄存器,这就是驱动器设备,也就是和硬盘相关的。不过此寄存器是八位的,一个通道上就两块硬盘,指定哪块硬盘只用一位就可以了,至于其他位当然也有用处,很多设置都会集中在此寄存器,其中的第四位便是指定通道上的主或从硬盘,0是主盘,1是从盘。端口用途在读写时是有区别的,比如Primary通道上的0x1F1端口来说,读操作时,如果读取失败,里面存储的是失败状态信息,所以称为error寄存器,并且此时会在0x1F2端口中存储未读的扇区数。写操作时就变成Features寄存器,此寄存器用于写命令参数。至于为什么要把一个寄存器分为两种状态,可能时在早期多加寄存器有很大代价吧。

  接下来介绍一下表中各个寄存器的功能。

  data寄存器顾名思义就是管理数据的,数据的读写当然是越快越好,所以data寄存器比其他寄存器宽一些,16位。在读硬盘时,硬盘准备好数据后,硬盘控制器将其放在内部的缓存区中,不断读此寄存器便是读出缓存器中的全部数据。在写硬盘时,我们要把数据不断写入此寄存器中,然后数据便会被送入缓存区,硬盘控制器发现这个缓存区中有数据了,便将此处数据写入相应扇区中。

  读硬盘时0x171或0x1F1的寄存器叫做Error寄存器,只在读取失败时才有用,里面有记录失败的信息,尚未读取的扇区数在Sector count寄存器中。在写硬盘时,该寄存器叫做Feature寄存器,里面是一些命令需要指定的额外参数。Error和Feature是同一个寄存器,只是在不同情况有不同的名称,它是八位寄存器。

  Sector count寄存器用来指定带读取或者带写入的扇区数。硬盘每完成一个扇区,此寄存器中的值就会减一,这是一个八位寄存器,最大值为255,若指定为0,则表示需要操作256个扇区。

  LBA寄存器有LBA low,LBA mid,LBA high三个,它们三个都是8位,LBA  low寄存器用来存储28位地址的第0~7位,LBA mid用来存储28位的第8~15位,LBA high寄存器用来存储28位的第16~23位。那么剩下的四位呢? 这就是device寄存器的任务了。

  device寄存器是个杂项,它的宽度是八位,第四位是存储LBA的第24位~27位。结合上面的三个LBA寄存器,第四位用来指定通道上的主盘或从盘,0代表从,1代表主。第六位用来存储是否启用LBA方式,1代表LBA模式,0代表CHS模式。另外两位第五位和第七位是固定为1的,称为MBS位,可以不用注意。

  读硬盘时,端口0x1F7或0x177的寄存器叫Status,它是8位宽度的寄存器,用来给出硬盘的状态信息。第0位是ERR位,如果此位为1,表示命令出错了,具体原因可见Error寄存器。第三位data request位,如果此位为1,表示数据已经准备好了。第6位为DRDY,表示硬盘就绪。第七位是BSY位,表示硬盘是否繁忙。

  写硬盘时,端口0x1F7或0x177的寄存器叫Command,它和Status是同一个,此寄存器用来存储让硬盘执行的命令,把命令写入此寄存器,只要把命令写入此寄存器,硬盘就开始工作了。主要是以下三个命令:

    1)identify : 0xEC    硬盘识别

    2)read sector : 0x20   即读扇区

    3)  write sector : 0x30   即写扇区

 

 

2. 控制硬盘步骤

  有的指令直接往command寄存器中写就可以了,有的还需要feature寄存器中写入参数。最主要的就是command寄存器一定要最后写,因为一旦command寄存器被写入后,硬盘就开始干活了。关于操作步骤如下:

  1)先选择通道,往该通道的sector count寄存器中写入带操作的扇区数。

  2)往该通道上的三个LBA寄存器写入扇区起始地址的低24位。

  3)往device寄存器中写入LBA地址的24~27位,并置第六位为1,使其为LBA模式,设置第4位,选择操作的硬盘(主从)。

  4)往该通道上的额command寄存器中写入操作命令。

  5)读取该通道上的status寄存器,判断硬盘工作是否完成

  6)如果以上步骤是读硬盘,进入下一个步骤。否则,完工

  7)将硬盘数据读出

3. 使用硬盘

  MBR即将改造成可以读取硬盘,那么我们的内核加载就有了方法。所以我们要学会从另一个程序中完成初始化环境并加载内核,这个程序叫做loader,loader放在第二个扇区,地址之前已经讲过了0x500~0x7BFF区域中。

  1 %include "boot.inc"
  2 SECTION MBR vstart=0x7c00
  3   mov ax,cs
  4   mov ds,ax
  5   mov es,ax
  6   mov ss,ax
  7   mov fs,ax
  8   mov sp,0x7c00
  9   mov ax,0xb800
 10   mov gs,ax
 11 
 12   mov ax,0x600
 13   mov bx,0x700
 14   mov cx,0
 15   mov dx,0x1010
 16   int 0x10
 17 
 18   mov byte [gs:0x00],'A'
 19   mov byte [gs:0x01],0xA4
 20 
 21   mov byte [gs:0x02],'n'
 22   mov byte [gs:0x03],0x13
 23 
 24   mov byte [gs:0x04],'t'
 25   mov byte [gs:0x05],0x52
 26 
 27   mov byte [gs:0x06],'z'
 28   mov byte [gs:0x07],0xB1
 29 
 30   mov byte [gs:0x08],' '
 31   mov byte [gs:0x09],0xCC
 32 
 33   mov byte [gs:0x0A],'U'
 34   mov byte [gs:0x0B],0x2B
 35 
 36   mov byte [gs:0x0C],'h'
 37   mov byte [gs:0x0D],0x6D
 38 
 39   mov byte [gs:0x0E],'l'
 40   mov byte [gs:0x0F],0x7E
 41 
 42   mov byte [gs:0x10],' '
 43   mov byte [gs:0x11],0x49
 44 
 45   mov byte [gs:0x12],'K'
 46   mov byte [gs:0x13],0xE5
 47 
 48   mov byte [gs:0x14],'o'
 49   mov byte [gs:0x15],0x8A
 50 
 51   mov byte [gs:0x16],'n'
 52   mov byte [gs:0x17],0x96
 53 
 54   mov byte [gs:0x18],'e'
 55   mov byte [gs:0x19],0x68
 56 
 57   mov eax,LOADER_START_SECTOR
 58   mov bx,LOADER_BASE_ADDR
 59   mov cx,1
 60   call rd_disk_m_16
 61 
 62   jmp LOADER_BASE_ADDR
 63 
 64 rd_disk_m_16:
 65 
 66   mov esi,eax
 67   mov di,cx
 68   mov dx,0x1f2
 69   mov al,cl
 70   out dx,al
 71 
 72   mov eax,esi
 73 
 74   mov dx,0x1f3
 75   out dx,al
 76 
 77   mov cl,8
 78   shr eax,cl
 79   mov dx,0x1f4
 80   out dx,al
 81 
 82   shr eax,cl
 83   mov dx,0x1f5
 84   out dx,al
 85 
 86   shr eax,cl
 87   and al,0x0f
 88   or al,0xe0
 89   mov dx,0x1f6
 90   out dx,al
 91 
 92   mov dx,0x1f7
 93   mov al,0x20
 94   out dx,al
 95 
 96 not_ready:
 97   nop
 98   in al,dx
 99   and al,0x88
100 
101   cmp al,0x08
102   jnz not_ready
103 
104   mov ax,di
105   mov dx,256
106   mul dx
107   mov cx,ax
108 
109   mov dx,0x1f0
110 
111 go_on_read:
112   in ax,dx
113   mov [bx],ax
114   add bx,2
115   loop go_on_read
116   ret
117 
118   times 510-($-$$) db 0
119   db 0x55,0xaa

  boot.inc文件内容如下:

1 ;-------------     loader和kernel   ----------
2 LOADER_BASE_ADDR equ 0x900 
3 LOADER_START_SECTOR equ 0x2

  

  LOADER_BASE_ADDR就是loader在内存中的位置,LOADER_START_SECTOR说明了loader放在了第二个扇区。

  内核加载器如下:

 1 %include "boot.inc"
 2 section loader vstart=LOADER_BASE_ADDR
 3 
 4 ; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
 5 mov byte [gs:0x00],'2'
 6 mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色
 7 
 8 mov byte [gs:0x02],' '
 9 mov byte [gs:0x03],0xA4
10 
11 mov byte [gs:0x04],'L'
12 mov byte [gs:0x05],0xA4
13 
14 mov byte [gs:0x06],'O'
15 mov byte [gs:0x07],0xA4
16 
17 mov byte [gs:0x08],'A'
18 mov byte [gs:0x09],0xA4
19 
20 mov byte [gs:0x0a],'D'
21 mov byte [gs:0x0b],0xA4
22 
23 mov byte [gs:0x0c],'E'
24 mov byte [gs:0x0d],0xA4
25 
26 mov byte [gs:0x0e],'R'
27 mov byte [gs:0x0f],0xA4
28 
29 jmp $               ; 通过死循环使程序悬停在此

  使用dd命令将之前生成的bin写入第0个扇区,loader生成的bin写入第2个扇区(个人爱好,也可以是第一个,但boot.inc也要改变)。

目录
相关文章
|
7天前
|
安全 Linux 开发者
探索操作系统的心脏:内核与用户空间的交互
在数字世界的每一次点击和命令背后,隐藏着一个复杂而精妙的操作系统世界。本文将带你走进这个世界的核心,揭示内核与用户空间的神秘交互。通过深入浅出的解释和直观的代码示例,我们将一起理解操作系统如何协调硬件资源,管理进程和内存,以及提供文件系统服务。无论你是编程新手还是资深开发者,这篇文章都将为你打开一扇通往操作系统深层原理的大门。让我们一起开始这段旅程,探索那些支撑我们日常数字生活的技术基石吧!
19 6
|
6天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
7天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
12天前
|
存储 Linux 开发者
探索操作系统的内核——从理论到实践
操作系统是计算机科学的核心,它像一位默默无闻的指挥官,协调着硬件和软件之间的复杂关系。本文将深入操作系统的心脏——内核,通过直观的解释和丰富的代码示例,揭示其神秘面纱。我们将一起学习进程管理、内存分配、文件系统等关键概念,并通过实际代码,体验内核编程的魅力。无论你是初学者还是有经验的开发者,这篇文章都将带给你新的视角和知识。
|
10天前
|
机器学习/深度学习 人工智能 物联网
操作系统的心脏——深入理解内核机制
在本文中,我们揭开操作系统内核的神秘面纱,探索其作为计算机系统核心的重要性。通过详细分析内核的基本功能、类型以及它如何管理硬件资源和软件进程,我们将了解内核是如何成为现代计算不可或缺的基础。此外,我们还会探讨内核设计的挑战和未来趋势,为读者提供一个全面的内核知识框架。
|
11天前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。
|
15天前
|
存储 调度 开发者
探索操作系统的心脏:内核与用户空间的交互之旅
在数字世界的无限广阔中,操作系统扮演着枢纽的角色,连接硬件与软件,支撑起整个计算生态。本篇文章将带领读者深入操作系统的核心——内核,揭示其与用户空间的神秘交互。我们将透过生动的例子和易于理解的比喻,深入浅出地探讨这一复杂主题,旨在为非专业读者揭开操作系统的神秘面纱,同时为有一定基础的读者提供更深层次的认识。从进程管理到内存分配,从文件系统到设备驱动,每一个环节都是精确而优雅的舞蹈,它们共同编织出稳定而高效的计算体验。让我们开始这场奇妙之旅,一探操作系统背后的科学与艺术。
22 5
|
13天前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
25 2
|
13天前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
39 1
|
8天前
|
C语言
探索操作系统的心脏:内核与用户空间的交互
本文将深入操作系统的核心,揭示其内部结构与运作原理。我们将通过浅显易懂的方式,探讨操作系统的两个主要组成部分:内核和用户空间。文章旨在帮助读者理解这两者之间的界限以及它们如何协同工作来管理计算机资源。我们还将介绍系统调用的概念,并展示一个简单的代码示例,以便读者更好地理解这一过程。