写下第一个linux内核
Hello World!
让我们编写一个简单的内核,可以在X86
系统上加载GRUB
引导程序。此内核将在屏幕上显示一条消息,然后挂起。
x86
机器是如何启动的呢?
先看看机器如何启动并将控制转移到内核:
x86
CPU 在物理地址[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
指令禁用中断,而cli
是clear-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]
的属性字节。
一波操作之后,这将在屏幕中显示字符串。
链接部分
我们将使用NASM
将kernel.asm
链接到目标文件中,然后使用GCC
编译kernel.c
成为另一个目标文件,现在,我们的工作是将这些对象链接到一个可执行引导内核。
因此,我们使用显示链接脚本器,它可以作为参数传递给ld
(我们的链接器)
/** link.ld*/
OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{. = 0X100000;.text : { *(.text)}.data : { *(.data)}.bss : { *(.bss)}
}
首先,我们将输出的格式设置为32位可执行可链接格式(ELF),ELF
是x86
架构上类Unix
系统的标准二进制文件格式。
ENTRY
有一个参数,他指定我们可执行文件入口的symbol name
SECTIONS
使我们最终要的地方,在这里我们定义可执行文件的布局。我们可以指定如何合并不同的部分以及每个部分放置在哪里。
在SECTIONS
语句后面的大括号内,"句点’.’ '"表示位置计数器,位置计数器在SECTION
语句的开头总是始终初始化为[0x0]
,可以通过给它分配新值以修改它。
记住,早些时候,我告诉过你,内核的代码应该在地址[0x100000]
开始,因此,我们将位置计数器设置为[0x100000]
看下一行.text : { *(.text) }
,星号"*"是一个匹配任何文件名的通配符,因此,表达式*(.text)
表示来自所有输入文件的所有.text
输入部分。
因此,链接器 在位置计数器地址处 将目标文件的所有文本部分合并到可执行文件中。因此,我们可执行文件的代码部分从[0x100000]
开始。
链接器放置文本输出部分后,位置计数器的值变为[0x100000]
+文本输出部分的大小。
类似地,data
和bss
部分被合并到位置计数器的接下来的值所表示的地址处。
Grub与多重引导
现在,我们已经准备好构建内核的所有文件,但是,由于我们希望使用Grub
引导加载程序引导内核,因此还有一步之遥。
已经有一个给不同使用引导加载程序的x86
内核的标准,称为"多重引导规范(Multiboot specification
)"。
如果我们的内核符合"多重引导规范",那么GRUB
将仅加载我们的内核。
根据规范,内核必须在其8千字节内包含引导头(也就是Multiboot header
)。
此外,Multiboot header
必须包含三个(align 4)
的字段:
a magic field
:包含magic number [0x1BADB002]
,以识别引导头a flags field
:我们不关心此字段,只是简单地将其设置为0a 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.asm
和kernel.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:
- 建议您使用虚拟机来进行内核游戏
- 要在
grub2
(linux
新发行版的默认引导程序)上运行,你的配置应该是这样的:
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内核相关推荐
- 烧写linux内核的步骤,启动redboot后,向目标机烧写一个linux内核的全过程
命令说明 对一个没有内核和根文件系统的板子是不可以运转的,当通过一种方式下载了RedBoot引导程序以后,我们可以通过下面的方式下载内核和根文件系统. 在本系统中,我们对内核和根文件系统的下载是通过t ...
- 如何成为一个Linux内核开发者
你想成知道如何成为一个Linux内核开发者么?或者你的老板告诉你,"去为这个设备写一个Linux驱动."这篇文档的目的,就是通过描述你需要经历的过程和提示你如何和社区一起工作,来教 ...
- 【技术分享篇】Linux内核——手把手带你实现一个Linux内核文件系统丨Linux内核源码分析
手把手带你实现一个Linux内核文件系统 1. 内核文件系统架构分析 2. 行行珠玑,代码实现 [技术分享篇]Linux内核--手把手带你实现一个Linux内核文件系统丨Linux内核源码分析 更多L ...
- 记一个linux内核内存提权问题
前些天,linux内核曝出了一个内存提权漏洞.通过骇客的精心构造,suid程序将print的输出信息写到了自己的/proc/$pid/mem文件里面,从而修改了自己的可执行代码,为普通用户开启了一个带 ...
- 如何替换一个Linux内核函数的实现-热补丁原理
昨晚发过誓了.不会再接着写二进制hook的手艺了,今天有网友咨询技术细节,终于又忍不住了- 为了不违背即便是胡乱说出口誓言,今天不写二进制hook,今天用C语言写,二进制只是沾点边儿! 看题目, 替换 ...
- 如何来实现一个Linux内核的系统调用(基于tiny4412开发板)
关于系统调用,相信学习过操作系统的同学应该都不陌生. 那么,什么是系统调用? 百度的权威解释如下: 点击打开链接 由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Applicat ...
- “土法”排查与修复一个 Linux 内核 Bug
最近有幸捡了个漏,修了个有13 年历史的 Linux 内核 bug,相关修复已经合并到 Linux 主线版本 5.14-rc3.发现新的 Linux 内核 bug 的机会不总是有,在客户现场进行调试和 ...
- ubuntu下wget下载Linux内核源码、make生成.config文件
根据资料,如果要调试Linux内核源码,需要自己编译内核源码:原因是,一般下载的内核并不是为调试而编译的,要在编译时开启 "Compile the kernel with debug inf ...
- debian下,下载linux内核
apt serch linux-source 查看适合自己当前debian linux内核的linux源码 apt source linux-source-4.19 我这里下载的是4.19版本 sud ...
最新文章
- Blender 3.0基础入门学习教程 Introduction to Blender 3.0
- 银行卡突然收到500万银行会监控吗?
- java spring 单例模式,spring中的单例模式
- P4351-[CERC2015]Frightful Formula【组合数学,MTT】
- mysql 启动_mysql安装、启动
- uniapp+typeScript+vue3.0+vite
- 第三十一章 线程------GIL、线/近程池、异/同步、异步回调
- linux大一实验报告,linux实验报告
- fastjson对Date的处理
- 关于数据库设计是否需要加入(建立)外键
- 李阳疯狂英语900句 121-330
- Mysql 数据库操作系统 官网 安装教程
- 概率论————思维导图(上岸必备)(随机事件与概率)
- CAD2008中常用快捷命令
- 极限的性质(汤家凤B站的学习笔记)
- tiny6410烧录
- python爬虫语句_Python爬虫练手之爬句子迷
- 记录:前端框架Bootstrap学习使用之组件——Collapse(折叠)
- android自定义View 中秋节放个烟花吧~
- 云平台是什么、什么是云、云平台的分类、主流公有云平台有哪些、云的三种服务、PaaS、SaaS、IaaS
热门文章
- Android WebView中打开相机拍照和选择相册
- 萌新必看—10种前端存储哪家强,一文读尽!
- SAP License:利润中心
- linux 修改系统时间和日期,Linux 修改系统日期和时间
- 2022“杭电杯”中国大学生算法设计超级联赛(7)
- TCP/IP 之 大明王朝邮差
- 域名解析配置 以及 修改DNS服务器(腾讯云域名注册商、阿里云DNS服务器)
- 第28届计算机命题搜索赛试题,第28届计算机表演赛命题搜索赛.docx
- 【Java余元面向对象】(23)抽象方法/抽象类
- Hive 10、Hive的UDF、UDAF、UDTF