zoukankan      html  css  js  c++  java
  • 编写和运行简单的"Hello World"操作系统内核

    通常编写一个操作系统内核是一项浩大的工程。但我今天的目标是制作一个简单的内核,用比较方便的方法在虚拟机上验证它能够被grub装载和运行,并且可通过gdb进行调试,为接下去的工作创造一个基础环境。

    首先,为了方便运行和调试我们需要一个虚拟机。虚拟机有很多选择,这里用最简单的qemu。

    先用dd创建一个文件作为虚拟盘,100MB就可以了:

    $ dd if=/dev/zero of=disk.img count=204800 bs=512

    然后对这个虚拟磁盘进行分区:

    $ fdisk -c=dos disk.img

    用命令n创建一个分区就可以了。通常情况下分区的起始扇区是2048(不带选项-c=dos),如果用老式的msdos格式分区表(命令c),就可以选择从63扇区开始。以前文章里提到63个扇区足够塞下grub的核心映像,所以为了测试下grub就选择了msdos模式。用命令w把变动写入虚拟盘。

    把这个分区虚拟成设备文件:

    $ sudo losetup -o 32256 /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 1024 -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 != 0 )  {
            *buffer++ = *s++;
            *buffer++ = color;
        }
    }
    
    /* 主函数,程序入口。最后用个死循环,把代码指针困在那里。*/
    void main (void) {
        puts("Hello World!", 0x7);
        while (1) {}
    }

    有必要再写个链接模板,确保编译好的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
     100052:c7 44 24 04 07 00 00 movl   $0x7,0x4(%esp)
     100059:00
     10005a:c7 04 24 68 00 10 00 movl   $0x100068,(%esp)
     100061:e8 a6 ff ff ff       call   10000c <puts>
     100066:eb fe                jmp    100066 <main+0x1a>

    0x100066处的指令正好是死循环的那条jmp指令。

  • 相关阅读:
    Java线程的几种状态
    常用几种Java Web容器
    数据库触发器
    SQL优化及注意事项
    Oracle中rownum和rowid的区别
    数据库及SQL优化
    如何安装使用Impala
    Impala:新一代开源大数据分析引擎
    开源大数据查询分析引擎
    Google Dremel 原理
  • 原文地址:https://www.cnblogs.com/silmerusse/p/3550173.html
Copyright © 2011-2022 走看看