Hello World!

让我们编写一个简单的内核,可以在X86系统上加载GRUB引导程序。此内核将在屏幕上显示一条消息,然后挂起。

x86机器是如何启动的呢?

先看看机器如何启动并将控制转移到内核:

x86CPU 在物理地址[0xFFFFFFF0]处开始执行。实际上,它是32位地址空间的最后16个字节。该地址只含一条地址跳转指令,指向BIOS复制自身的内存中的地址。

因此,BIOS代码开始执行,BIOS首先按照配置过的引导设备顺序搜寻可引导设备,它会检查某个magic number去确定设备是否可引导。

一旦BIOS找到可引导设备,它就会从物理地址[0x7c00]处开始将设备第一个扇区的内容复制到RAM中。然后然后跳转到该地址并执行刚刚加载的代码——此代码称为引导加载程序。

接着,引导加载程序将内核加载到物理地址[0x100000],地址[0x100000]用作x86计算机上所有大内核的起始地址。

我们需要什么?

  • 一台x86的计算机
  • Linux
  • NASM汇编程序
  • gcc
  • ld (GNU Linker)
  • grub

源代码

在 **mkernel**中

使用汇编语言写程序入口

我们习惯用C语言编写所有的内容,但是我们无法避免一点儿汇编语言。我们将用x86汇编语言编写一个小文件,作为我们内核的起点。我们所有的汇编文件将会调用一个外部函数,我们将用C语言编写,然后暂停程序流。

那么我们如何确保此汇编代码作为内核的起点呢?

我们将使用链接脚本来链接目标文件以生成最终的内核可执行文件(后面会详细说明)。在此链接脚本文件中,我们将明确指定我们希望将二进制文件加载到地址[0x100000]。正如我们之前所说的那样,这个地址是内核预期的地方。因此,引导加载程序将负责触发内核的入口。

以下是汇编代码:

;;kernel.asmbits 32                ;nasm directive(指令) - 32位section .textglobel startextern kmain           ;kmain is defined in the c filestart:cli                ;block interruptscall kmainhlt                ;CPU暂停

第一个指令bits 32不是x86汇编指令,它是NASM汇编程序的第一个指令,它指定应该在32位处理器上运行代码。在我们的实例中这并非强制要求,但这里包含它确实是个好习惯。

第二行开始文本部分(aka代码部分),这是我们放置所有代码的部分。

global是另一个将源代码中的符号设置为全局变量的NASM指令,这样,链接器知道符号开始的位置,这恰好是我们的切入点。

kmain是我们将在kernel.c文件中定义的函数,extern声明该函数在其他地方声明。

然后,我们有start函数,它调用kmain函数并使用hlt指令暂停CPU,而中断可以从hlt指令中唤醒CPU,所以我们事先使用cli指令禁用中断,而cliclear-interrupts的缩写。

C语言部分的kernel

在文件kernel.asm中,我们调用了函数kmain(),所以我们的C代码将开始在kmain()处执行

/** kernel.c*/
void kmain(void)
{char *str="my first kernel";char *vidptr=(char*)0xb8000;  //video mem begins hereunsigned int i = 0;unsigned int j = 0;// clear allwhile(j<80 * 25 * 2){//blank charactervidptr[j] = ''; //attribute-byte:黑色屏幕上的浅灰色vidptr[j+1] = 0x07;j += 2;}j = 0;while(str[j] != '\0'){vidptr[i] = str[j];vidptr[i+1] = 0x07;++j;i += 2;}return;
}

我们所有的内核都会清除屏幕并写入字符串"my first kernel"。

首先,我们创建一个指向地址[0xb8000]的指针vidpri,这个地址是保护模式下video memory的开始。屏幕的文本内存只是我们地址空间中的一块内存,而屏幕的输入/输出从[0xb8000]开始,支持25行,每行包含80个ascii字符。

在该文本缓存中每个字符元素由16位(2字节)表示,而不是我们熟知的8位(1字节)。第一个字节应该有ASCII中的有的字符,第二个字节是属性字节。这描述了字符的格式,包括颜色等属性。

要在黑色背景上打印绿色字符s,我们将字符s存储在video memory地址的第一个字节,将值[0x02]存储在第二个字节,0表示黑色背景,2表示绿色背景

可以查看下表中的不同颜色:

0 - Black, 1 - Blue, 2 - Green, 3 - Cyan, 4 - Red, 5 - Magenta, 6 - Brown, 7 - Light Grey, 8 - Dark Grey, 9 - Light Blue, 10/a - Light Green, 11/b - Light Cyan, 12/c - Light Red, 13/d - Light Magenta, 14/e - Light Brown, 15/f – White.

在我们的内核中,我们将在黑色背景上使用浅灰色字符,所以我们的属性字节值必须为[0x07]

在第一个while循环中,程序在25行80列中写入[0x07]属性的空白字符,这样可以清除屏幕。

在第二个while循环中,空终止字符串"my first kernel"的字符被写入video memory块中,每个字符有[0x07]的属性字节。

一波操作之后,这将在屏幕中显示字符串。

链接部分

我们将使用NASMkernel.asm链接到目标文件中,然后使用GCC编译kernel.c成为另一个目标文件,现在,我们的工作是将这些对象链接到一个可执行引导内核。

因此,我们使用显示链接脚本器,它可以作为参数传递给ld(我们的链接器)

/** link.ld*/
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{. = 0X100000;.text : { *(.text)}.data : { *(.data)}.bss  : { *(.bss)}
}

首先,我们将输出的格式设置为32位可执行可链接格式(ELF),ELFx86架构上类Unix系统的标准二进制文件格式。

ENTRY有一个参数,他指定我们可执行文件入口的symbol name

SECTIONS使我们最终要的地方,在这里我们定义可执行文件的布局。我们可以指定如何合并不同的部分以及每个部分放置在哪里。

SECTIONS语句后面的大括号内,"句点’.’ '"表示位置计数器,位置计数器在SECTION语句的开头总是始终初始化为[0x0],可以通过给它分配新值以修改它。

记住,早些时候,我告诉过你,内核的代码应该在地址[0x100000]开始,因此,我们将位置计数器设置为[0x100000]

看下一行.text : { *(.text) },星号"*"是一个匹配任何文件名的通配符,因此,表达式*(.text)表示来自所有输入文件的所有.text输入部分。

因此,链接器 在位置计数器地址处 将目标文件的所有文本部分合并到可执行文件中。因此,我们可执行文件的代码部分从[0x100000]开始。

链接器放置文本输出部分后,位置计数器的值变为[0x100000]+文本输出部分的大小。

类似地,databss部分被合并到位置计数器的接下来的值所表示的地址处。

Grub与多重引导

现在,我们已经准备好构建内核的所有文件,但是,由于我们希望使用Grub引导加载程序引导内核,因此还有一步之遥。

已经有一个给不同使用引导加载程序的x86内核的标准,称为"多重引导规范(Multiboot specification)"。

如果我们的内核符合"多重引导规范",那么GRUB将仅加载我们的内核。

根据规范,内核必须在其8千字节内包含引导头(也就是Multiboot header)。

此外,Multiboot header必须包含三个(align 4)的字段:

  • a magic field:包含magic number [0x1BADB002],以识别引导头
  • a flags field:我们不关心此字段,只是简单地将其设置为0
  • a checksum field:当加上前两个字段时,该字段必须为0

所以我们的kernel.asm文件改为:

;;kernel.asm
;nasm directive - 32 bit
bits 32
section .text;multiboot specalign 4dd 0x1BADB002             ;magicdd 0x00                   ;flagsdd - (0x1BADB002 + 0x00)  ;checksum. m+f+c should be zero
global startextern kmain                      ;kmain is define in the c filestart:cli                           ;block interruptscall kmainhlt

dd指令定义了大小为4字节的双精度变量。

构建内核

我们现在将使用kernel.asmkernel.c创建目标文件,然后使用我们的链接脚本链接。

nasm -f elf32 kernel.asm -o kasm.o

这条指令将运行汇编程序以ELF-32格式创建目标文件kasm.o

gcc -m32 -c kernel.c -o kc.o

-c选项确保在编译之后,链接不会隐式发生

ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o

这条指令将使用我们的链接脚本运行链接器并生成名为kernel的可执行文件。

配置GRUB并运行内核

GRUB要求您的内核名称为kernel-<version>的格式,所以,我们得重命名内核,重命名为kernel-701

现在将它放置到/boot目录中,你需要root权限才可以执行此操作。

GRUB的配置文件grub.cfg中,添加一个条目:

title mykernelroot (hd0, 0)kernel /boot/kernel-701 ro

不要忘记删除指令hiddenmenu(如果存在的话),重启计算机,您将获得列出内核名称的列表选择。

选择你新建的内核,你讲看到:

这就是你的内核!!!

PS:

  • 建议您使用虚拟机来进行内核游戏
  • 要在grub2linux新发行版的默认引导程序)上运行,你的配置应该是这样的:
menuentry 'kernel 7001' {set root='hd0,msdosl'multiboot /boot/kernel-70001 ro
}
  • 此外,如果要在qemu仿真器上运行内核而不是使用GRUB启动,可以通过一下方式执行此操作:
qemu-system-i386 -kernel kernel

这是英文原版地址:https://download.csdn.net/download/haozihuang/11264520

写下第一个linux内核相关推荐

  1. 烧写linux内核的步骤,启动redboot后,向目标机烧写一个linux内核的全过程

    命令说明 对一个没有内核和根文件系统的板子是不可以运转的,当通过一种方式下载了RedBoot引导程序以后,我们可以通过下面的方式下载内核和根文件系统. 在本系统中,我们对内核和根文件系统的下载是通过t ...

  2. 如何成为一个Linux内核开发者

    你想成知道如何成为一个Linux内核开发者么?或者你的老板告诉你,"去为这个设备写一个Linux驱动."这篇文档的目的,就是通过描述你需要经历的过程和提示你如何和社区一起工作,来教 ...

  3. 【技术分享篇】Linux内核——手把手带你实现一个Linux内核文件系统丨Linux内核源码分析

    手把手带你实现一个Linux内核文件系统 1. 内核文件系统架构分析 2. 行行珠玑,代码实现 [技术分享篇]Linux内核--手把手带你实现一个Linux内核文件系统丨Linux内核源码分析 更多L ...

  4. 记一个linux内核内存提权问题

    前些天,linux内核曝出了一个内存提权漏洞.通过骇客的精心构造,suid程序将print的输出信息写到了自己的/proc/$pid/mem文件里面,从而修改了自己的可执行代码,为普通用户开启了一个带 ...

  5. 如何替换一个Linux内核函数的实现-热补丁原理

    昨晚发过誓了.不会再接着写二进制hook的手艺了,今天有网友咨询技术细节,终于又忍不住了- 为了不违背即便是胡乱说出口誓言,今天不写二进制hook,今天用C语言写,二进制只是沾点边儿! 看题目, 替换 ...

  6. 如何来实现一个Linux内核的系统调用(基于tiny4412开发板)

    关于系统调用,相信学习过操作系统的同学应该都不陌生. 那么,什么是系统调用? 百度的权威解释如下: 点击打开链接 由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Applicat ...

  7. “土法”排查与修复一个 Linux 内核 Bug

    最近有幸捡了个漏,修了个有13 年历史的 Linux 内核 bug,相关修复已经合并到 Linux 主线版本 5.14-rc3.发现新的 Linux 内核 bug 的机会不总是有,在客户现场进行调试和 ...

  8. ubuntu下wget下载Linux内核源码、make生成.config文件

    根据资料,如果要调试Linux内核源码,需要自己编译内核源码:原因是,一般下载的内核并不是为调试而编译的,要在编译时开启 "Compile the kernel with debug inf ...

  9. debian下,下载linux内核

    apt serch linux-source 查看适合自己当前debian linux内核的linux源码 apt source linux-source-4.19 我这里下载的是4.19版本 sud ...

最新文章

  1. Blender 3.0基础入门学习教程 Introduction to Blender 3.0
  2. 银行卡突然收到500万银行会监控吗?
  3. java spring 单例模式,spring中的单例模式
  4. P4351-[CERC2015]Frightful Formula【组合数学,MTT】
  5. mysql 启动_mysql安装、启动
  6. uniapp+typeScript+vue3.0+vite
  7. 第三十一章 线程------GIL、线/近程池、异/同步、异步回调
  8. linux大一实验报告,linux实验报告
  9. fastjson对Date的处理
  10. 关于数据库设计是否需要加入(建立)外键
  11. 李阳疯狂英语900句 121-330
  12. Mysql 数据库操作系统 官网 安装教程
  13. 概率论————思维导图(上岸必备)(随机事件与概率)
  14. CAD2008中常用快捷命令
  15. 极限的性质(汤家凤B站的学习笔记)
  16. tiny6410烧录
  17. python爬虫语句_Python爬虫练手之爬句子迷
  18. 记录:前端框架Bootstrap学习使用之组件——Collapse(折叠)
  19. android自定义View 中秋节放个烟花吧~
  20. 云平台是什么、什么是云、云平台的分类、主流公有云平台有哪些、云的三种服务、PaaS、SaaS、IaaS

热门文章

  1. Android WebView中打开相机拍照和选择相册
  2. 萌新必看—10种前端存储哪家强,一文读尽!
  3. SAP License:利润中心
  4. linux 修改系统时间和日期,Linux 修改系统日期和时间
  5. 2022“杭电杯”中国大学生算法设计超级联赛(7)
  6. TCP/IP 之 大明王朝邮差
  7. 域名解析配置 以及 修改DNS服务器(腾讯云域名注册商、阿里云DNS服务器)
  8. 第28届计算机命题搜索赛试题,第28届计算机表演赛命题搜索赛.docx
  9. 【Java余元面向对象】(23)抽象方法/抽象类
  10. Hive 10、Hive的UDF、UDAF、UDTF