用 C/C++ 编写 16 位虚拟内核






4.98/5 (79投票s)
理解 FAT 文件系统和 C/C++ 内核编程
引言
在我之前的文章中,我只简要介绍了如何编写引导加载程序。那很有趣也很挑战。我非常享受。但在学习了如何编写引导加载程序后,我想编写更强大的东西,比如在其中嵌入更多功能。但是,加载程序的尺寸不断增加,超过了 512 字节,每次用启动盘重启系统时,我都会看到“这不是可引导磁盘”的错误。
本文的范围是什么?
在本文中,我将尝试简要介绍文件系统对我们的引导加载程序的重要性,并尝试编写一个虚拟内核,它什么也不做,只是显示一个提示符,让用户输入文本。为什么我要将引导加载程序嵌入到 FAT 格式的软盘中,以及这对我有什么好处。由于一篇文章不足以提及文件系统,我将尽我所能将其写得简短易懂。
背景
- 如果您有任何语言的编程经验,本文将对您非常有帮助。虽然本文看起来相当入门,但为引导编写汇编和 C 程序可能是一项艰巨的任务。如果您是计算机编程新手,我建议您先阅读一些关于编程入门和计算机基础知识的教程,然后再来看这篇文章。
- 在整篇文章中,我将以问答的形式向您介绍各种与计算机相关的术语。坦白说,我将以介绍给自己为出发点来写这篇文章。有很多问答式的对话,以确保我能理解它在我日常生活中的重要性和目的。例如:你说的计算机是什么意思?或者为什么我需要它们,因为我比它们聪明多了?
您还可以查看我之前的文章,以对引导加载程序及其编写方法(汇编和 C)有一个基本了解。
以下是链接。
https://codeproject.org.cn/Articles/664165/Writing-a-boot-loader-in-Assembly-and-C-Parthttps://codeproject.org.cn/Articles/668422/Writing-a-boot-loader-in-Assembly-and-C-Part
内容是如何组织的?
这是本文主题的细分。
- 引导加载程序的局限性
- 从引导加载程序调用磁盘上的其他文件
- FAT 文件系统
- FAT 工作流程
- 开发环境
- 编写 FAT 引导加载程序
- 开发环境
- 迷你项目 - 编写 16 位内核
- 测试内核
引导加载程序的局限性
在之前的文章中,我试图编写引导加载程序,在屏幕上打印彩色矩形后,我想在其中嵌入更多功能。但是,512 字节的大小对我来说是一个很大的限制,无法更新引导加载程序的代码以做更多的事情……
挑战如下
- 在引导加载程序中嵌入更多功能代码
- 将引导加载程序的大小限制在 512 字节。
我将如何处理以上问题?
让我简要介绍如下。
第一部分
- 我将用 C 语言编写一个名为 kernel.c 的程序,确保我期望的所有额外功能都正确地写在里面。
- 编译并保存可执行文件为 kernel.bin
- 现在,将 kernel.bin 文件复制到可引导驱动器的第二个扇区。
第二部分:
在我们的引导加载程序中,我们能做的就是将可引导驱动器的第二个扇区(kernel.bin)加载到地址为 0x1000 的 RAM 内存中,然后从 0x7c00 跳转到 0x1000 地址开始执行 kernel.bin 文件。
下面是一张图片,您可以参考以获得想法。
从引导加载程序调用磁盘上的其他文件
之前,我们了解到我们实际上可以将控制权从引导加载程序(0x7c00)转移到内存中存储 kernel.bin 等磁盘文件所在的其他位置,然后继续进行。但我心里有几个疑问。
您知道 kernel.bin 文件将在磁盘上占用多少个扇区吗?
我认为这很容易。我们所要做的就是以下步骤
1 个扇区 = 512 字节
所以,如果 kernel.bin 的大小是 512 字节,它将占用 1 个扇区;如果大小是 1024 字节,则占用 2 个扇区,依此类推……
现在,关键在于根据 kernel.bin 文件的大小,您必须在引导加载程序中硬编码要读取的 kernel.bin 文件的扇区数量。
这意味着将来,例如,您想通过频繁更新内核来升级内核,您还必须记住在引导加载程序中记录 kernel.bin 文件占用的扇区数量,否则内核将崩溃。
如果您想在可引导驱动器上除了 kernel.bin 之外,还添加 office.bin、entertainment.bin、drivers.bin 等文件,您认为会怎样?
在某些时候,是的,您所要做的就是将文件一个接一个地添加到您的软盘中,在此过程中,您必须不断更新引导加载程序,告知每个文件在启动盘上的确切位置以及每个文件占用的扇区数量,等等。但我想引起您注意的是,系统正在变得越来越复杂,不知何故我并不喜欢。
您如何知道您一个接一个添加到引导扇区后的文件是您想要的吗?
我们所做的就是将相应的扇区从引导加载程序加载到内存中,然后开始执行它。但这并不完美,似乎缺少了什么。缺少什么?
我认为引导加载程序只是盲目地加载每个文件的扇区,然后开始执行文件。但即使在引导加载程序尝试将文件加载到内存之前,也应该有一种方法来检查文件是否存在于启动盘上。如果我不小心将错误的文件复制到启动盘的第二个扇区,然后更新引导加载程序并运行,会发生什么?
我的系统会直接崩溃,用户会因为我的系统不符合他的要求而丢弃它。因此,在这种情况下,我需要一个固定在启动盘上的位置,其中所有文件名都像书中的索引一样写着。
我的引导加载程序将查询软盘的索引,查找文件名,如果文件名在索引中列出,则继续将文件加载到内存中。
哇!!!这太好了,我喜欢它,因为它节省了很多操作。
这消除了几个问题
以前的引导加载程序盲目地加载硬编码在其中的扇区。
如果你不知道正在加载的文件是否正确,为什么还要加载它?
解决方案是什么?
我们所要做的就是组织我们上面列出的磁盘上的信息,然后开始组织数据,然后重新编程我们的引导加载程序,以便它可以高效地加载文件。
这种大规模组织数据的方式称为文件系统。市面上有许多商业和免费的文件系统。我将在下面列出其中一些。
- FAT
- FAT16
- FAT32
- NTFS
- EXT
- EXT2
- EXT3
- EXT4
FAT 文件系统
在向您介绍文件系统之前,有几个术语您需要了解。
在 FAT 文件系统中,一个簇占用 1 个扇区,而一个扇区在存储介质上占用 512 字节。因此,在 FAT 格式的磁盘驱动器上,1 个簇等同于 1 个扇区。
簇和扇区是 FAT 文件系统中的最小单位。
为了方便使用,FAT 文件系统分为四个主要部分,它们列在下面。
- 引导扇区
- FAT
- 根目录
- 数据区
我已尽力以图片形式展示给您,以便更好地理解。
现在我来简要介绍每个部分。
引导扇区
FAT 格式磁盘上的引导扇区嵌入了一些与 FAT 相关的信息,以便每次将磁盘插入系统时,操作系统都能自动识别其文件系统。
操作系统读取 FAT 格式磁盘的引导扇区,然后解析所需信息,识别文件系统类型,然后相应地开始读取内容。
嵌入在引导扇区中的 FAT 文件系统信息称为引导参数块。
引导参数块
我将根据引导扇区的值向您展示引导参数块中的值。
文件分配表
此表充当链接列表,其中包含文件的下一个簇值。
从 FAT 中为特定文件获得的簇值有两种用途。
- 确定文件结束
- 如果簇值在 0x0ff8 和 0x0fff 之间,则该文件在其他扇区没有数据(已达到文件结束)。
- 确定文件数据所在的位置扇区
注意
我在图中标出了 FAT 表 1 和 2。您只需要记住,一个表是另一个表的副本。如果其中一个数据丢失或损坏,另一个表的数据可以作为备份。这是引入两个表而不是一个表的真正意图。
根目录
根目录充当磁盘上所有文件名列表的索引。因此,引导加载程序应该在根目录中搜索文件名,如果找到,则可以在根目录中找到第一个簇,然后相应地加载数据。
在根目录中找到第一个簇后,引导加载程序应该使用 FAT 表查找下一个簇,以检查文件是否结束。
数据区
这是实际包含文件数据(或多个文件数据)的区域。
一旦程序确定了文件的正确扇区,就可以从数据区域提取文件数据。
FAT 工作流程
假设,我们的引导加载程序应该将 kernel.bin 文件加载到内存中,然后执行它。现在,在这种情况下,我们所要做的就是将以下功能编写到我们的引导加载程序中。
将根目录表中偏移量为 0 的数据的前 11 个字节与“kernel.bin”进行比较。
如果字符串匹配,则在根目录表中偏移量为 26 的位置提取“kernel.bin”文件的第一个簇。
现在您有了“kernel.bin”文件的起始簇。
您所要做的就是将簇转换为相应的扇区,然后将数据加载到内存中。
现在,在找到“kernel.bin”文件的第一个扇区后,将其加载到内存中,然后在文件分配表中查找文件的下一个簇,以检查文件是否还有数据或是否已达到文件结束。
下面是供您参考的图。
开发环境
要成功实现此任务,我们需要了解以下内容。请参考我之前的文章以获取更多信息。
- 操作系统 (GNU Linux)
- 汇编器 (GNU Assembler)
- 指令集(x86 系列)
- 使用 GNU Assembler 为 x86 微处理器编写 x86 指令。
- 编译器(C 编程语言 - GNU C 编译器 GCC)
- 链接器 (GNU 链接器 ld)
- 像 bochs 这样的 x86 模拟器,用于我们的测试目的。
编写 FAT 引导加载程序
下面是用于在 FAT 格式磁盘上执行 kernel.bin 文件的代码片段。
这是引导加载程序
文件名:stage0.S
/*********************************************************************************
* *
* *
* Name : stage0.S *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* Description: The main logic involves scanning for kernel.bin file on a *
* fat12 formatted floppy disk and then pass the control to it *
* for its execution *
* Usage : Please read the readme.txt for more information *
* *
* *
*********************************************************************************/
.code16
.text
.globl _start;
_start:
jmp _boot
nop
/*bios parameter block description of each entity */
/*-------------------- -------------------------- */
.byte 0x6b,0x69,0x72,0x55,0x58,0x30,0x2e,0x31 /* oem label */
.byte 0x00,0x02 /* total bytes per sector */
.byte 0x01 /* total sectors per cluster */
.byte 0x01,0x00 /* total reserved sectors */
.byte 0x02 /* total fat tables */
.byte 0xe0,0x00 /* total directory entries */
.byte 0x40,0x0b /* total sectors */
.byte 0xf0 /* media description */
.byte 0x09,0x00 /* size in of each fat table */
.byte 0x02,0x01 /* total sectors per track */
.byte 0x02,0x00 /* total heads per cylinder */
.byte 0x00,0x00, 0x00, 0x00 /* total hidden sectors */
.byte 0x00,0x00, 0x00, 0x00 /* total big sectors */
.byte 0x00 /* boot drive identifier */
.byte 0x00 /* total unused sectors */
.byte 0x29 /* external boot signature */
.byte 0x22,0x62,0x79,0x20 /* serial number */
.byte 0x41,0x53,0x48,0x41,0x4b,0x49 /* volume label 6 bytes of 11 */
.byte 0x52,0x41,0x4e,0x20,0x42 /* volume label 5 bytes of 11 */
.byte 0x48,0x41,0x54,0x54,0x45,0x52,0x22 /* file system type */
/* include macro functions */
#include "macros.S"
/* begining of main code */
_boot:
/* initialize the environment */
initEnvironment
/* load stage2 */
loadFile $fileStage2
/* infinite loop */
_freeze:
jmp _freeze
/* abnormal termination of program */
_abort:
writeString $msgAbort
jmp _freeze
/* include functions */
#include "routines.S"
/* user-defined variables */
bootDrive : .byte 0x0000
msgAbort : .asciz "* * * F A T A L E R R O R * * *"
#fileStage2: .ascii "STAGE2 BIN"
fileStage2: .ascii "KERNEL BIN"
clusterID : .word 0x0000
/* traverse 510 bytes from beginning */
. = _start + 0x01fe
/* append boot signature */
.word BOOT_SIGNATURE
这是主加载程序文件,它执行以下操作。
- 通过调用 initEnvironment 宏来初始化所有寄存器并设置堆栈。
- 调用 loadFile 宏将 kernel.bin 文件加载到地址为 0x1000:0000 的内存中,然后将其控制权传递给它以进行进一步执行。
文件名:macros.S
这是一个包含所有预定义宏和宏函数的*.S文件。
/********************************************************************************* * *
* *
* Name : macros.S *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* *
*********************************************************************************/
/* predefined macros: boot loader */
#define BOOT_LOADER_CODE_AREA_ADDRESS 0x7c00
#define BOOT_LOADER_CODE_AREA_ADDRESS_OFFSET 0x0000
/* predefined macros: stack segment */
#define BOOT_LOADER_STACK_SEGMENT 0x7c00
#define BOOT_LOADER_ROOT_OFFSET 0x0200
#define BOOT_LOADER_FAT_OFFSET 0x0200
#define BOOT_LOADER_STAGE2_ADDRESS 0x1000
#define BOOT_LOADER_STAGE2_OFFSET 0x0000
/* predefined macros: floppy disk layout */
#define BOOT_DISK_SECTORS_PER_TRACK 0x0012
#define BOOT_DISK_HEADS_PER_CYLINDER 0x0002
#define BOOT_DISK_BYTES_PER_SECTOR 0x0200
#define BOOT_DISK_SECTORS_PER_CLUSTER 0x0001
/* predefined macros: file system layout */
#define FAT12_FAT_POSITION 0x0001
#define FAT12_FAT_SIZE 0x0009
#define FAT12_ROOT_POSITION 0x0013
#define FAT12_ROOT_SIZE 0x000e
#define FAT12_ROOT_ENTRIES 0x00e0
#define FAT12_END_OF_FILE 0x0ff8
/* predefined macros: boot loader */
#define BOOT_SIGNATURE 0xaa55
/* user-defined macro functions */
/* this macro is used to set the environment */
.macro initEnvironment
call _initEnvironment
.endm
/* this macro is used to display a string */
/* onto the screen */
/* it calls the function _writeString to */
/* perform the operation */
/* parameter(s): input string */
.macro writeString message
pushw \message
call _writeString
.endm
/* this macro is used to read a sector into */
/* the target memory */
/* It calls the _readSector function with */
/* the following parameters */
/* parameter(s): sector Number */
/* address to load */
/* offset of the address */
/* Number of sectors to read */
.macro readSector sectorno, address, offset, totalsectors
pushw \sectorno
pushw \address
pushw \offset
pushw \totalsectors
call _readSector
addw $0x0008, %sp
.endm
/* this macro is used to find a file in the */
/* FAT formatted drive */
/* it calls readSector macro to perform this */
/* activity */
/* parameter(s): root directory position */
/* target address */
/* target offset */
/* root directory size */
.macro findFile file
/* read fat table into memory */
readSector $FAT12_ROOT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_ROOT_OFFSET, $FAT12_ROOT_SIZE
pushw \file
call _findFile
addw $0x0002, %sp
.endm
/* this macro is used to convert the given */
/* cluster into a sector number */
/* it calls _clusterToLinearBlockAddress to */
/* perform this activity */
/* parameter(s): cluster number */
.macro clusterToLinearBlockAddress cluster
pushw \cluster
call _clusterToLinearBlockAddress
addw $0x0002, %sp
.endm
/* this macro is used to load a target file */
/* into the memory */
/* It calls findFile and then loads the data */
/* of the respective file into the memory at */
/* address 0x1000:0x0000 */
/* parameter(s): target file name */
.macro loadFile file
/* check for file existence */
findFile \file
pushw %ax
/* read fat table into memory */
readSector $FAT12_FAT_POSITION, $BOOT_LOADER_CODE_AREA_ADDRESS, $BOOT_LOADER_FAT_OFFSET, $FAT12_FAT_SIZE
popw %ax
movw $BOOT_LOADER_STAGE2_OFFSET, %bx
_loadCluster:
pushw %bx
pushw %ax
clusterToLinearBlockAddress %ax
readSector %ax, $BOOT_LOADER_STAGE2_ADDRESS, %bx, $BOOT_DISK_SECTORS_PER_CLUSTER
popw %ax
xorw %dx, %dx
movw $0x0003, %bx
mulw %bx
movw $0x0002, %bx
divw %bx
movw $BOOT_LOADER_FAT_OFFSET, %bx
addw %ax, %bx
movw $BOOT_LOADER_CODE_AREA_ADDRESS, %ax
movw %ax, %es
movw %es:(%bx), %ax
orw %dx, %dx
jz _even_cluster
_odd_cluster:
shrw $0x0004, %ax
jmp _done
_even_cluster:
and $0x0fff, %ax
_done:
popw %bx
addw $BOOT_DISK_BYTES_PER_SECTOR, %bx
cmpw $FAT12_END_OF_FILE, %ax
jl _loadCluster
/* execute kernel */
initKernel
.endm
/* parameter(s): target file name */
/* this macro is used to pass the control of */
/* execution to the loaded file in memory at */
/* address 0x1000:0x0000 */
/* parameters(s): none */
.macro initKernel
/* initialize the kernel */
movw $(BOOT_LOADER_STAGE2_ADDRESS), %ax
movw $(BOOT_LOADER_STAGE2_OFFSET) , %bx
movw %ax, %es
movw %ax, %ds
jmp $(BOOT_LOADER_STAGE2_ADDRESS), $(BOOT_LOADER_STAGE2_OFFSET)
.endm
initEnvironment
- 此宏用于按需设置段寄存器。
- 所需参数数量为零
用法:initEnvironment
writeString
- 此宏用于在屏幕上显示一个以 null 结尾的字符串。
- 传递给它的参数是一个以 null 结尾的字符串变量。
用法:writeString <字符串变量>
readSector
- 此宏用于从磁盘读取给定扇区,然后将其加载到目标地址
- 所需参数数量为 4。
用法:readSector <扇区号>, <目标地址>, <目标地址偏移量>, <要读取的总扇区数>
findFile
- 此宏用于检查文件是否存在。
- 所需参数数量为 1
用法:findFile <目标文件名>
clusterToLinearBlockAddress
- 此宏用于将给定的簇 ID 转换为扇区号。
- 所需参数数量为 1
用法:clusterToLinearBlockAddress <簇 ID>
loadFile
- 此宏用于将目标文件加载到内存中,然后将其执行控制权传递给它。
- 所需参数数量为 1
用法:loadFile <目标文件名>
initKernel
- 此宏用于将执行控制权传递给 RAM 上的特定地址位置
- 所需参数数量为零。
用法:initKernel
文件名:routines.S
/*********************************************************************************
* *
* *
* Name : routines.S *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : assembly language *
* Author : Ashakiran Bhatter *
* *
* *
*********************************************************************************/
/* user-defined routines */
/* this function is used to set-up the */
/* registers and stack as required */
/* parameter(s): none */
_initEnvironment:
pushw %bp
movw %sp, %bp
_initEnvironmentIn:
cli
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $BOOT_LOADER_STACK_SEGMENT, %sp
sti
_initEnvironmentOut:
movw %bp, %sp
popw %bp
ret
/* this function is used to display a string */
/* onto the screen */
/* parameter(s): input string */
_writeString:
pushw %bp
movw %sp , %bp
movw 4(%bp) , %si
jmp _writeStringCheckByte
_writeStringIn:
movb $0x000e, %ah
movb $0x0000, %bh
int $0x0010
incw %si
_writeStringCheckByte:
movb (%si) , %al
orb %al , %al
jnz _writeStringIn
_writeStringOut:
movw %bp , %sp
popw %bp
ret
/* this function is used to read a sector */
/* into the target memory */
/* parameter(s): sector Number */
/* address to load */
/* offset of the address */
/* Number of sectors to read */
_readSector:
pushw %bp
movw %sp , %bp
movw 10(%bp), %ax
movw $BOOT_DISK_SECTORS_PER_TRACK, %bx
xorw %dx , %dx
divw %bx
incw %dx
movb %dl , %cl
movw $BOOT_DISK_HEADS_PER_CYLINDER, %bx
xorw %dx , %dx
divw %bx
movb %al , %ch
xchg %dl , %dh
movb $0x02 , %ah
movb 4(%bp) , %al
movb bootDrive, %dl
movw 8(%bp) , %bx
movw %bx , %es
movw 6(%bp) , %bx
int $0x13
jc _abort
cmpb 4(%bp) , %al
jc _abort
movw %bp , %sp
popw %bp
ret
/* this function is used to find a file in */
/* the FAT formatted drive */
/* parameter(s): root directory position */
/* target address */
/* target offset */
/* root directory size */
_findFile:
pushw %bp
movw %sp , %bp
movw $BOOT_LOADER_CODE_AREA_ADDRESS, %ax
movw %ax , %es
movw $BOOT_LOADER_ROOT_OFFSET, %bx
movw $FAT12_ROOT_ENTRIES, %dx
jmp _findFileInitValues
_findFileIn:
movw $0x000b , %cx
movw 4(%bp) , %si
leaw (%bx) , %di
repe cmpsb
je _findFileOut
_findFileDecrementCount:
decw %dx
addw $0x0020, %bx
_findFileInitValues:
cmpw $0x0000, %dx
jne _findFileIn
je _abort
_findFileOut:
addw $0x001a , %bx
movw %es:(%bx), %ax
movw %bp, %sp
popw %bp
ret
/* this function is used to convert the given*/
/* cluster into a sector number */
/* parameter(s): cluster number */
_clusterToLinearBlockAddress:
pushw %bp
movw %sp , %bp
movw 4(%bp) , %ax
_clusterToLinearBlockAddressIn:
subw $0x0002, %ax
movw $BOOT_DISK_SECTORS_PER_CLUSTER, %cx
mulw %cx
addw $FAT12_ROOT_POSITION, %ax
addw $FAT12_ROOT_SIZE, %ax
_clusterToLinearBlockAddressOut:
movw %bp , %sp
popw %bp
ret
_initEnvironment
- 此函数用于按需设置段寄存器。
- 所需参数数量为零
用法:call _initEnvironment
_writeString
- 此函数用于在屏幕上显示一个以 null 结尾的字符串。
- 传递给它的参数是一个以 null 结尾的字符串变量。
用法
- pushw <字符串变量>
- call _writeString
- addw $0x02, %sp
readSector
- 此宏用于从磁盘读取给定扇区,然后将其加载到目标地址
- 所需参数数量为 4。
用法
- pushw <扇区号>
- pushw <地址>
- pushw <偏移量>
- pushw <总扇区数>
- call _readSector
- addw $0x0008, %sp
findFile
- 此函数用于检查文件是否存在。
- 所需参数数量为 1
用法
- pushw <目标文件变量>
- call _findFile
- addw $0x02, %sp
clusterToLinearBlockAddress
- 此宏用于将给定的簇 ID 转换为扇区号。
- 所需参数数量为 1
用法
- pushw <簇 ID>
- call _clusterToLinearBlockAddress
- addw $0x02, %sp
loadFile
- 此宏用于将目标文件加载到内存中,然后将其执行控制权传递给它。
- 所需参数数量为 1
用法
- pushw <目标文件>
- call _loadFile
- addw $0x02, %sp
文件名:stage0.ld
此文件用于在链接时链接 stage0.object 文件。
/********************************************************************************* * * * * * Name : stage0.ld * * Date : 23-Feb-2014 * * Version : 0.0.1 * * Source : assembly language * * Author : Ashakiran Bhatter * * * * * *********************************************************************************/ SECTIONS { . = 0x7c00; .text : { _ftext = .; } = 0 }
文件名:bochsrc.txt
这是运行 bochs 模拟器所需的配置文件,该模拟器用于测试。
megs: 32
floppya: 1_44=../iso/stage0.img, status=inserted
boot: a
log: ../log/bochsout.txt
mouse: enabled=0
迷你项目 - 编写 16 位内核
以下文件是作为测试过程一部分引入的虚拟内核的源代码。我们所要做的就是利用 Makefile 编译源代码,看看它是否被引导加载程序加载。
屏幕上会显示带有龙图像的启动画面,然后是欢迎屏幕,最后是命令提示符,供用户输入任何内容。
里面没有编写任何命令或实用程序来执行,但这个内核只是为了我们的测试目的而引入的,目前它没有什么价值。
文件名:kernel.c
/*********************************************************************************
* *
* *
* Name : kernel.c *
* Date : 23-Feb-2014 *
* Version : 0.0.1 *
* Source : C *
* Author : Ashakiran Bhatter *
* *
* Description: This is the file that the stage0.bin loads and passes the *
* control of execution to it. The main functionality of this *
* program is to display a very simple splash screen and a *
* command prompt so that the user can type commands *
* Caution : It does not recognize any commands as they are not programmed *
* *
*********************************************************************************/
/* generate 16 bit code */
__asm__(".code16\n");
/* jump to main function or program code */
__asm__("jmpl $0x1000, $main\n");
#define TRUE 0x01
#define FALSE 0x00
char str[] = "$> ";
/* this function is used to set-up the */
/* registers and stack as required */
/* parameter(s): none */
void initEnvironment() {
__asm__ __volatile__(
"cli;"
"movw $0x0000, %ax;"
"movw %ax, %ss;"
"movw $0xffff, %sp;"
"cld;"
);
__asm__ __volatile__(
"movw $0x1000, %ax;"
"movw %ax, %ds;"
"movw %ax, %es;"
"movw %ax, %fs;"
"movw %ax, %gs;"
);
}
/* vga functions */
/* this function is used to set the */
/* the VGA mode to 80*24 */
void setResolution() {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0003)
);
}
/* this function is used to clear the */
/* screen buffer by splitting spaces */
void clearScreen() {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0200), "b"(0x0000), "d"(0x0000)
);
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0920), "b"(0x0007), "c"(0x2000)
);
}
/* this function is used to set the */
/* cursor position at a given column */
/* and row */
void setCursor(short col, short row) {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0200), "d"((row <<= 8) | col)
);
}
/* this function is used enable and */
/* disable the cursor */
void showCursor(short choice) {
if(choice == FALSE) {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0100), "c"(0x3200)
);
} else {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0100), "c"(0x0007)
);
}
}
/* this function is used to initialize*/
/* the VGA to 80 * 25 mode and then */
/* clear the screen and set the cursor*/
/* position to (0,0) */
void initVGA() {
setResolution();
clearScreen();
setCursor(0, 0);
}
/* io functions */
/* this function is used to get a chara*/
/* cter from keyboard with no echo */
void getch() {
__asm__ __volatile__ (
"xorw %ax, %ax\n"
"int $0x16\n"
);
}
/* this function is same as getch() */
/* but it returns the scan code and */
/* ascii value of the key hit on the */
/* keyboard */
short getchar() {
short word;
__asm__ __volatile__(
"int $0x16" : : "a"(0x1000)
);
__asm__ __volatile__(
"movw %%ax, %0" : "=r"(word)
);
return word;
}
/* this function is used to display the*/
/* key on the screen */
void putchar(short ch) {
__asm__ __volatile__(
"int $0x10" : : "a"(0x0e00 | (char)ch)
);
}
/* this function is used to print the */
/* null terminated string on the screen*/
void printString(const char* pStr) {
while(*pStr) {
__asm__ __volatile__ (
"int $0x10" : : "a"(0x0e00 | *pStr), "b"(0x0002)
);
++pStr;
}
}
/* this function is used to sleep for */
/* a given number of seconds */
void delay(int seconds) {
__asm__ __volatile__(
"int $0x15" : : "a"(0x8600), "c"(0x000f * seconds), "d"(0x4240 * seconds)
);
}
/* string functions */
/* this function isused to calculate */
/* length of the string and then return*/
/* it */
int strlength(const char* pStr) {
int i = 0;
while(*pStr) {
++i;
}
return i;
}
/* UI functions */
/* this function is used to display the */
/* logo */
void splashScreen(const char* pStr) {
showCursor(FALSE);
clearScreen();
setCursor(0, 9);
printString(pStr);
delay(10);
}
/* shell */
/* this function is used to display a */
/* dummy command prompt onto the screen */
/* and it automatically scrolls down if */
/* the user hits return key */
void shell() {
clearScreen();
showCursor(TRUE);
while(TRUE) {
printString(str);
short byte;
while((byte = getchar())) {
if((byte >> 8) == 0x1c) {
putchar(10);
putchar(13);
break;
} else {
putchar(byte);
}
}
}
}
/* this is the main entry for the kernel*/
void main() {
const char msgPicture[] =
" .. \n\r"
" ++` \n\r"
" :ho. `.-/++/. \n\r"
" `/hh+. ``:sds: \n\r"
" `-odds/-` .MNd/` \n\r"
" `.+ydmdyo/:--/yMMMMd/ \n\r"
" `:+hMMMNNNMMMddNMMh:` \n\r"
" `-:/+++/:-:ohmNMMMMMMMMMMMm+-+mMNd` \n\r"
" `-+oo+osdMMMNMMMMMMMMMMMMMMMMMMNmNMMM/` \n\r"
" ``` .+mMMMMMMMMMMMMMMMMMMMMMMMMMMMMNmho:.` \n\r"
" `omMMMMMMMMMMMMMMMMMMNMdydMMdNMMMMMMMMdo+- \n\r"
" .:oymMMMMMMMMMMMMMNdo/hMMd+ds-:h/-yMdydMNdNdNN+ \n\r"
" -oosdMMMMMMMMMMMMMMd:` `yMM+.+h+.- /y `/m.:mmmN \n\r"
" -:` dMMMMMMMMMMMMMd. `mMNo..+y/` . . -/.s \n\r"
" ` -MMMMMMMMMMMMMM- -mMMmo-./s/.` ` \n\r"
" `+MMMMMMMMMMMMMM- .smMy:.``-+oo+//:-.` \n\r"
" .yNMMMMMMMMMMMMMMd. .+dmh+:. `-::/+:. \n\r"
" y+-mMMMMMMMMMMMMMMm/` ./o+-` . \n\r"
" :- :MMMMMMMMMMMMMMMMmy/.` \n\r"
" ` `hMMMMMMMMMMMMMMMMMMNds/.` \n\r"
" sNhNMMMMMMMMMMMMMMMMMMMMNh+. \n\r"
" -d. :mMMMMMMMMMMMMMMMMMMMMMMNh:` \n\r"
" /. .hMMMMMMMMMMMMMMMMMMMMMMMMh. \n\r"
" . `sMMMMMMMMMMMMMMMMMMMMMMMMN. \n\r"
" hMMMMMMMMMMMMMMMMMMMMMMMMy \n\r"
" +MMMMMMMMMMMMMMMMMMMMMMMMh ";
const char msgWelcome[] =
" *******************************************************\n\r"
" * *\n\r"
" * Welcome to kirUX Operating System *\n\r"
" * *\n\r"
" *******************************************************\n\r"
" * *\n\r"
" * *\n\r"
" * Author : Ashakiran Bhatter *\n\r"
" * Version: 0.0.1 *\n\r"
" * Date : 01-Mar-2014 *\n\r"
" * *\n\r"
" ******************************************************";
initEnvironment();
initVGA();
splashScreen(msgPicture);
splashScreen(msgWelcome);
shell();
while(1);
}
我来简要介绍一下这些函数
initEnvironment()
- 这是用于设置段寄存器然后设置堆栈的函数。
- 所需参数为零。
- 用法:initEnvironment();
setResolution():
- 此函数用于将视频模式设置为 80*25。
- 所需参数为零。
- 用法:setResolution();
clearScreen()
- 此函数用于用空格填充屏幕缓冲区。
- 所需参数为零
- 用法:clearScreen();
setCursor()
- 此函数用于将光标设置在屏幕上的给定位置。
- 所需参数为 2。
- 用法:setCursor(列, 行);
showCursor()
- 此函数用于根据用户的选择启用或禁用光标。
- 所需参数为 1
- 用法:showCursor(1);
initVGA()
- 此函数用于将视频分辨率设置为 80*25,然后清除屏幕,最后将光标设置在屏幕的 (0,0) 位置。
- 所需参数为零
- 用法:initVGA();
getch()
- 此函数用于获取用户按键,不回显。
- 所需参数为零
- 用法:getch();
getchar()
- 此函数用于返回按键扫描码和相应的 ASCII 码
- 所需参数为零。
- 用法:putchar();
putchar()
printString()
delay()
strlength()
splashScreen()
shell()
以下是引导加载程序加载的内核的屏幕截图。
测试内核
使用源代码
附加文件 sourcecode.tar.gz,其中包含所需的源文件以及生成二进制文件的必要目录。
因此,请确保您是系统的超级用户,然后开始将文件解压缩到目录或文件夹中。
请确保您已安装 bochs-x64 模拟器和 GNU bin-utils,以便继续进行编译和测试源代码。
下面是您在从 zip 文件中提取文件后将看到的目录结构。
应该有 5 个目录
- bin
- iso
- 内核
- log
- src
环境准备就绪后,请确保打开终端并运行以下命令
- cd $(DIRECTORY)/src
- make -f Makefile test
- bochs
供您参考的屏幕截图
屏幕 1
这是内核执行时显示的第一个屏幕。
屏幕:2
这是内核的欢迎屏幕
屏幕:3
这是我试图在屏幕上显示的命令提示符,以便用户可以输入文本。
屏幕:4
这是用户输入命令的屏幕截图,当用户按下回车键时,屏幕会按要求滚动。
如果您遇到任何问题,也请告知我。我很乐意为您提供帮助。
结论
我希望这篇文章能让您了解文件系统的使用及其在操作系统中的重要性。此外,希望这篇文章能帮助您编写引导加载程序来解析文件系统以及如何用 C/C++ 编写 16 位内核。如果您喜欢代码,可以尝试编辑代码并嵌入更多功能。
这应该很有趣。再见 :)