• Bran的内核开发教程(bkerndev)-03 内核初步


    内核初步

      在这节教程, 我们将深入研究一些汇编程序, 学习创建链接脚本的基础知识以及使用它的原因。最后, 我们将学习如何使用batch(批处理)文件自动汇编、编译和链接这个最基本的受保护模式下的内核。本教程假定你已经安装了NASM和GCC, 并且了解一点点x86汇编语言。

    内核入口

      内核的入口点是当引导程序(bootloader)调用内核时最先执行的代码段。这段代码一直以来几乎都是使用汇编编写的, 因为有些工作如设置新的栈, 加载新的GDT、IDT或寄存器, 你简单地使用C语言根本没法做到。在很多初学者写的内核, 和更专业的内核中, 会将所有汇编程序代码放在一个文件中, 并将其余源代码分别放在几个C文件中。

      如果至少知道一点点汇编语言, 那么下面这段汇编代码应该非常简单明了了。就代码而言, 这个文件做的只有加载一个新的8KB栈, 然后跳转到一个死循环中。这个栈是一块很小的内存, 它用于存储或传递参数给C函数。它还可以用来保存你函数中声明和使用的局部变量。其他的全局变量则存储在BSS区域中。在mbootstublet代码块之间的代码用于生成特殊的签名, GRUB通过该签名校验即将加载的二进制输出文件, 实际上该文件就是内核。不过不用费力去理解多重引导头(multiboot header)。

      内核启动文件“start.asm”的内容如下:

    start.asm

    ; 这是内核的入口点. 我们将在这里调用main函数,并设置栈和其他东西,比如:
    ; 创建GDT和内存区域,
    ; 请注意这里中断被禁用了,更多细节将在后面讲中断的时候提到
    [BITS 32]
    global start
    start:
        mov esp, _sys_stack     ; 让当前栈指针指向我们新创建的栈
        jmp stublet             ; 跳转到stublet
    
    ; 使用'ALIGN 4'使这段代码4字节对齐
    ALIGN 4
    mboot:
        ; 多重引导的一些宏定义,使后面一些代码更具可读性
        MULTIBOOT_PAGE_ALIGN	equ 1<<0
        MULTIBOOT_MEMORY_INFO	equ 1<<1
        MULTIBOOT_AOUT_KLUDGE	equ 1<<16
        MULTIBOOT_HEADER_MAGIC	equ 0x1BADB002
        MULTIBOOT_HEADER_FLAGS	equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
        MULTIBOOT_CHECKSUM	equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
        EXTERN code, bss, end
    
        ; GRUB 多重引导头 - 启动签名
        dd MULTIBOOT_HEADER_MAGIC
        dd MULTIBOOT_HEADER_FLAGS
        dd MULTIBOOT_CHECKSUM
        
        ; 自动替换 - 必须为物理地址
        ; 注意:由链接器填充这些数据的值
        dd mboot
        dd code
        dd bss
        dd end
        dd start
    
    ; 死循环
    ; 之后我们将在'jmp $'前插入'extern _main'和'call _main'两句代码
    stublet:
        jmp $
    
    
    ; GDT加载代码(以后添加)
    
    
    ; ISR代码(以后添加)
    
    
    
    ; BSS区的定义
    ; 现在问将用它来存储栈
    ; 栈是向下生长的,所以我们在声明'_sys_stack'
    SECTION .bss
        resb 8192               ; 保留8KB内存
    _sys_stack:
    

    链接脚本

      链接器是接收所有编译器和汇编器的输出文件, 并把它们链接成一个二进制文件的工具。二进制文件有很多种格式, 常见的有: Flat、AOUT、COFF、PE、ELF等。我们在工具集中选择的链接器是LD链接器。这是一个非常好的多功能链接器。LD链接器有多种版本, 可以输出任何你想要格式的位二进制文件。无论你选择那种输出格式, 输出的文件总由3个部分组成: 1) 'Text'或'Code'是可执行段;2) 'Data'段用于存放硬编码值(hardcoded value), 比如你声明了一个变量, 并将该变量的值设为5, 这个'5'就被存储在'Data'区域;3) 'BSS'段由未初始化的数据组成, 如没有赋值的数组。'BSS'是一个虚拟段, 它在二进制镜像中是不存在的, 但是在二进制文件加载的时候存在于内存中。

      下面是LD链接脚本文件"link.ld"的内容。OUTPUT_FORMAT关键字告诉LD我们将创建哪种形式的二进制镜像, 简单起见, 我们使用'binary'二进制镜像。ENTRY用于指定哪个目标文件最先被链接。我们希望”start.asm“编译后的输出文件”start.o“为第一个链接的目标文件, 也就是没和的入口点。phys不是关键字, 而是链接脚本中使用的变量, 被用来指向内存中1MB地址的指针, 也就我们二进制文件被加载和运行的地方。SECTIONS里定义了3个主要区域: '.text'、'.data'、'.bss', 并同时定义了3个变量: 'code', 'data', 'bss', 还有 'end'。不要对此感到困惑, 其实这三个变量是我们的启动文件"start.asm"中的变量。ALIGN(4096)用来确保每个区域以4096B(4KB)为边界。在这种情况下, 每个部分将从内存中的单独"页"开始。

    link.ld

    OUTPUT_FORMAT("binary")
    ENTRY(start)
    phys = 0x00100000;
    SECTIONS
    {
      .text phys : AT(phys) {
        code = .;
        *(.text)
        *(.rodata)
        . = ALIGN(4096);
      }
      .data : AT(phys + (data - code))
      {
        data = .;
        *(.data)
        . = ALIGN(4096);
      }
      .bss : AT(phys + (bss - code))
      {
        bss = .;
        *(.bss)
        . = ALIGN(4096);
      }
      end = .;
    }
    

    汇编和链接

      这里我们必须对"start.asm"进行汇编, 并使用上面的链接脚本来创建我们内核的二进制文件, 以便GRUB加载。在Unix系统中实现上述操作的最简单的方法就是创建一个makefile脚本文件来帮你汇编、编译、链接。但是大多数人使用的是Windows系统, 在Windows系统中, 我们可以创建一个batch文件。batch文件其实就是DOS命令的集合, 你可只需输入这个batch文件的文件名就可以依次执行该batch文件中的DOS命令集。更简单的方法是, 你只需要双击该batch文件, 就会在Windows系统下自动执行DOS命令编译你的内核。

      下面是本教程使用的batch文件"build.bat"echo是一个DOS命令, 用来向终端显示字符。nasm是我们使用的汇编器, 我们以aout的格式编译, 因为LD链接器需要一种已知格式才能解析链接过程中的符号。汇编器将’start.asm'汇编成'start.o'。rem命令是注释, 在运行batch文件是会将它忽略。ld是我们的链接器, '-T'参数告诉链接器我们使用哪一个链接脚本, -o用来指定输出文件名。其他的参数都将被链接器理解为需要链接到一起并解析生成kernel.bin的文件。最后, pause命令将在屏幕上显示"Press a key to continue..."并等待我们按下键盘上的任意键, 这方便我们查看汇编器或链接器在语法错误上给出了哪些提示。

    build.bat

    echo Now assembling, compiling, and linking your kernel:
    nasm -f aout -o start.o start.asm
    rem Remember this spot here: We will add 'gcc' commands here to compile C sources
    
    
    rem This links all your files. Remember that as you add *.o files, you need to
    rem add them after start.o. If you don't add them at all, they won't be in your kernel!
    ld -T link.ld -o kernel.bin start.o
    echo Done!
    pause
    

    PS: 下面是我自己写的

    64位Linux下的编译脚本

    build.sh

    echo "Now assembling, compiling, and linking your kernel:"
    nasm -f elf64 -o start.o start.asm
    # Remember this spot here: We will add 'gcc' commands here to compile C sources
    
    # This links all your files. Remember that as you add *.o files, you need to
    # add them after start.o. If you don't add them at all, they won't be in your kernel!
    ld -T link.ld -o kernel.bin start.o
    echo "Done!"
    

    使用下面指令运行:

    bash build.sh
    

    此文原创禁止转载,转载文章请联系博主并注明来源和出处,谢谢!
    作者: Raina_RLN https://www.cnblogs.com/raina/

  • 相关阅读:
    Asp.Net : 实现一个 DataSet 或DataTable SELECT DISTINCT (字段唯一性)
    Jquery 局部刷新及 表单取值赋值 处理返回json数据 一些基本操作
    C# 自动化模型编辑Word
    泛型集合List的添加、访问、遍历和删除
    泛型转DataTable方法
    服务器按钮如何通过js验证再触发提交事件?
    Asp.Net 无限分类生成表格 &lt;后台自定义输出table&gt;
    table的innerHTML “未知运行错误”。
    js 截取字符串的方法 C# 正则判断数字及截取字符
    Microsoft Office Visio 2007 设计数据库关系图
  • 原文地址:https://www.cnblogs.com/raina/p/11529728.html
Copyright © 2020-2023  润新知