编写和运行简单的"Hello World"操作系统内核
通常编写一个操作系统内核是一项浩大的工程。但我今天的目标是制作一个简单的内核,用比较方便的方法在虚拟机上验证它能够被grub装载和运行,并且可通过gdb进行调试,为接下去的工作创造一个基础环境。
首先,为了方便运行和调试我们需要一个虚拟机。虚拟机有很多选择,这里用最简单的qemu。
先用dd创建一个文件作为虚拟盘,100MB就可以了:
$ dd if=/dev/zero of=disk.img count= bs=
然后对这个虚拟磁盘进行分区:
$ fdisk -c=dos disk.img
用命令n创建一个分区就可以了。通常情况下分区的起始扇区是2048(不带选项-c=dos),如果用老式的msdos格式分区表(命令c),就可以选择从63扇区开始。以前文章里提到63个扇区足够塞下grub的核心映像,所以为了测试下grub就选择了msdos模式。用命令w把变动写入虚拟盘。
把这个分区虚拟成设备文件:
$ sudo losetup -o /dev/loop1 disk.img
这里指定了起始扇区的偏移量。63个扇区,每个扇区512个字节,总共是32256字节。如果你的分区起始扇区是2048,那么偏移量应该是2048 * 512字节。
格式化:
$ sudo mkfs.ext4 /dev/loop1
挂载起来,这样就可以方便地往里面放kernel和grub需要的配置文件和模块什么的:
$ sudo mount /dev/loop1 /mnt
安装grub:
$ sudo grub-install --boot-directory=/mnt --modules="part_msdos" disk.img
使用qemu来启动虚拟机(我用的是64位系统):
$ qemu-system-x86_64 -hda disk.img -m -s &
这时候应该能够看到grub的提示符了。当然现在还没有grub菜单也没有kernel,我们暂时先关掉虚拟机。
接下来可以为grub建立个multiboot启动菜单:
$ sudo vi /mnt/grub/grub.cfg
制作菜单命令:
menuentry "Hello" {
multiboot (hd0,msdos1)/kernel
boot
}
确保数据写回了虚拟盘:
$ sync
这时候如果你再打开虚拟机,应该就可以看到启动菜单了,当然因为还没有kernel,选择菜单项后无法继续,会提示kernel找不到。
下一步,我们用c语言从头编写个最简单kernel程序。这个kernel没有实现操作系统的基本功能。但是可以被grub装载和运行。
kernel.c:
/* 在文件里嵌入一个签名。Grub在multiboot时会寻找这个签名 */
#define GRUB_MAGIC 0x1BADB002
#define GRUB_FLAGS 0x0
#define GRUB_CHECKSUM (-1 * (GRUB_MAGIC + GRUB_FLAGS)) struct grub_signature {
unsigned int magic;
unsigned int flags;
unsigned int checksum;
}; struct grub_signature gs __attribute__ ((section (".grub_sig"))) = {
GRUB_MAGIC,
GRUB_FLAGS,
GRUB_CHECKSUM
}; /* 显示字符的函数。因为我们什么库都不能用,只能直接写屏了。0xB8000是VGA彩色字符模式的数据缓存。每个字符用两个字节表示。前一个是Ascii码,后一个代表颜色。 */
void puts( const char *s, int color ){
volatile char *buffer = (volatile char*)0xB8000; while( *s != ) {
*buffer++ = *s++;
*buffer++ = color;
}
} /* 主函数,程序入口。最后用个死循环,把代码指针困在那里。*/
void main (void) {
puts("Hello World!", 0x7);
while () {}
}
有必要再写个链接模板,确保编译好的kernel装载在内存地址0x100000,这里是grub代码最后跳转到的区域,从这里我们的kernel接过了接力棒。另外,虽然我用的虚拟机是64位的,但是我需要生成一个32位的kernel,因为做64位的kernel还需要做额外的设置工作,比如从32位保护模式打开long mode,比较麻烦。所以先暂时用32位的kernel。
kernel.ld:
OUTPUT_FORMAT("elf32-i386")
ENTRY(main)
SECTIONS
{
.grub_sig 0x100000 : AT(0x100000)
{
*(.grub_sig)
}
.text :
{
*(.text)
}
.data :
{
*(.data)
}
.bss :
{
*(.bss)
}
/DISCARD/ :
{
*(.comment)
*(.eh_frame)
}
}
还有一个Makefile,主要是设置一些编译选项。
Makefile:
CC = gcc
LD = ld
CFLAGS = -std=c99 -pedantic -Wall -nostdlib -ffreestanding -m32
LDFLAGS = -T kernel.ld -nostdlib -n -melf_i386
OBJS = kernel.o
.PHONY: all
all: kernel
%.o: %.c
${CC} -c ${CFLAGS} $<
kernel: $(OBJS) kernel.ld
${LD} ${LDFLAGS} -o kernel ${OBJS}
clean:
rm -f ${OBJS} kernel
编译生成kernel并放入我们的虚拟盘里:
$ sudo cp kernel /mnt/
$ sync
再次启动虚拟机,在启动菜单里选择multiboot我们的kernel,应该就能看到Hello World!的字符显示在虚拟机屏幕上了。
如果想要调试,可以运行gdb。因为我们在启动qemu的时候使用了-s选项,所以qemu默认会打开tcp端口1234作为gdb调试端口。在gdb中可以使用target remote tcp::1234命令来连接。试试看连接,会发现cpu一直在执行0x100066处的指令。用objdump -D kernel看下kernel的汇编代码:
0010004c <main>:
10004c:55 push %ebp
10004d:89 e5 mov %esp,%ebp
10004f:83 ec 08 sub $0x8,%esp
:c7 44 24 04 07 00 00 movl $0x7,0x4(%esp)
:0005a:c7 04 24 68 00 10 00 movl $0x100068,(%esp)
:e8 a6 ff ff ff call 10000c <puts>
:eb fe jmp <main+0x1a>
0x100066处的指令正好是死循环的那条jmp指令。
编写和运行简单的"Hello World"操作系统内核的更多相关文章
- 使用KEIL C51实现的简单合作式多任务操作系统内核(单片机实现版本)
基于网上网友的代码,自己在单片机上实现, 特此记录分享之. 基于https://blog.csdn.net/yyx112358/article/details/78877523 //使用KEIL C5 ...
- 红帽企业版RHEL7.1在研域工控板上,开机没有登陆窗口 -- 编写xorg.conf 简单三行解决Ubuntu分辩率不可调的问题
红帽企业版RHEL7.1在研域工控板上,开机没有登陆窗口 没有登陆窗口 的原因分析: 没有登陆窗口的原因是因为有多个屏幕在工作,其中一个就是build-in 屏幕(内置的虚拟屏幕)和外接的显示器,并且 ...
- Java语言编写计算器(简单的计算器)
Java编写的一个简单计算器,本人还比较菜,只能这样了,有点代码冗余,不能连续计算. import javax.swing.*; import java.awt.*; import java.awt. ...
- 为Lua5.3编写C模块简单示例
为Lua5.3编写C模块简单示例 一.编译安装Lua5.3 MSVC 命令行安装脚本: @echo off md bin md lib md include cd src cl /c /nologo ...
- 【JavaScript】使用setInterval()函数作简单的轮询操作
轮询(Polling)是一种CPU决策怎样提供周边设备服务的方式,又称"程控输出入"(Programmed I/O). 轮询法的概念是.由CPU定时发出询问.依序询问每个周边设备是 ...
- Java入门——编写并运行第一个程序
Java入门——编写并运行第一个程序 摘要:本文主要介绍如何使用Java语言编写并通过DOS运行简单的程序. 编写简单的程序 在D盘新建一个文本文档,输入如下代码: class Hello { pub ...
- 基于mykernel 2.0编写一个操作系统内核
一.配置mykernel 2.0,熟悉Linux内核的编译 1.实验环境:VMware 15 Pro,Ubuntu 18.04.4 2.配置环境 1)在电脑上先下载好以下两个文件,之后通过共享文件夹, ...
- 基于mykernel2.0编写一个操作系统内核
基于mykernel2.0编写一个操作系统内核 一. 实验准备 详细要求 基于mykernel 2.0编写一个操作系统内核 按照https://github.com/mengning/mykernel ...
- 国产化之路-统信UOS + Nginx + Asp.Net MVC + EF Core 3.1 + 达梦DM8实现简单增删改查操作
专题目录 国产化之路-统信UOS操作系统安装 国产化之路-国产操作系统安装.net core 3.1 sdk 国产化之路-安装WEB服务器 国产化之路-安装达梦DM8数据库 国产化之路-统信UOS + ...
随机推荐
- 如何优化JAVA代码及提高执行效率
可供程序利用的资源(内存.CPU时间.网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务.优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率.本文讨论的主要是如何提高代 ...
- 如何用Project2010制作WBS
如何用Project2010制作WBS: http://www.projectup.net/blog/index.php?option=com_content&view=article& ...
- LVS负载均衡服务
LVS负载均衡服务 LVS负载均衡调度技术是在Linux内核中实现的,因此被称为Linux虚拟服务器.使用LVS时,不能直接配置内核中的ipvs,而需要使用ipvs的管理工具ipvsadm进行管理. ...
- SQL server 数据库升级版本问题解决办法
在升级或安装数据库的时候,会遇到数据库版本不对的问题,无论怎么升级,升级提示成功了,但打开数据库发现还是原来那个版本.甚至出现重装数据库之后,登陆页面已经提示安装的是新版本了,但登陆进去之后,发现数据 ...
- 在 Java 应用程序中绑定 Bean 和数据
本指南介绍了 NetBeans IDE 对 Java 应用程序中 Bean 绑定和数据绑定的支持. 要学完本教程,您需要具备以下软件和资源. 软件或资源 要求的版本 NetBeans IDE 版本 7 ...
- 用wamp实现前端和php的交互效果
我们今天来用php来做一下前台与后台的交互效果,首先我们先打开这个软件. 看一下电脑右下角的小图标 当变成之后鼠标左键 打开这个之后点击第二个之后会打开一个网站 点击右面页面的数据库打开新建数据库,填 ...
- 管理源代码的工具SVN与GIT
如何看待源代码 源代码是公司的重要资产 对应软件公司来说,源代码相当于固定资产>人才 所以源代码管理对于公司来说是最重要的事物之一 一.管理源代码的工具 SVN:集中式的源代码管理工具,通常必须 ...
- 删除Android自带的系统软件注意事项
教程类 知识分享 [转]好多童鞋在ROOT手机后,大刀阔斧的就开始砍系统里面的东西,有些事删不得的,删除错了就成砖头了! 以下是对照表: 注意:打*号的千万别删,打-号的是建议删的(大多要穿墙才能 ...
- Python中的Numpy
引用Numpy import numpy as np 生成随机数据 # 200支股票 stock_cnt = 200 # 504个交易日 view_days = 504 # 生成服从正态分布:均值期望 ...
- 剑指Offer——数组中只出现一次的数字
题目描述: 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字. 分析: 数组中一共有偶数个数.两个数字只出现过一次. 相同数异或在一起等于0,那么将所有数异或 ...