操作系统源码阅读 - 4: bootload启动(一)

从bootload开始。

主机在bios启动后,会继续调用一小段汇编代码,进行系统配置和内核导入,称该代码为bootload。因此,一个系统启动的流程应该为bios->bootload->kernel。

bootlaod程序主要包含两个文件,一个为bootasm,S,另一个为bootmain.c。

#include "asm.h"
#include "memlayout.h"
#include "mmu.h"

# Start the first CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.code16                       # Assemble for 16-bit mode
.globl start
start:
  cli                         # BIOS enabled interrupts; disable

  # Zero data segment registers DS, ES, and SS.
  xorw    %ax,%ax             # Set %ax to zero
  movw    %ax,%ds             # -> Data Segment
  movw    %ax,%es             # -> Extra Segment
  movw    %ax,%ss             # -> Stack Segment

  # Physical address line A20 is tied to zero so that the first PCs 
  # with 2 MB would run software that assumed 1 MB.  Undo that.
seta20.1:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

  # Switch from real to protected mode.  Use a bootstrap GDT that makes
  # virtual addresses map directly to physical addresses so that the
  # effective memory map doesn't change during the transition.
  lgdt    gdtdesc
  movl    %cr0, %eax
  orl     $CR0_PE, %eax
  movl    %eax, %cr0

//PAGEBREAK!
  # Complete the transition to 32-bit protected mode by using a long jmp
  # to reload %cs and %eip.  The segment descriptors are set up with no
  # translation, so that the mapping is still the identity mapping.
  ljmp    $(SEG_KCODE<<3), $start32

.code32  # Tell assembler to generate 32-bit code now.
start32:
  # Set up the protected-mode data segment registers
  movw    $(SEG_KDATA<<3), %ax    # Our data segment selector
  movw    %ax, %ds                # -> DS: Data Segment
  movw    %ax, %es                # -> ES: Extra Segment
  movw    %ax, %ss                # -> SS: Stack Segment
  movw    $0, %ax                 # Zero segments not ready for use
  movw    %ax, %fs                # -> FS
  movw    %ax, %gs                # -> GS

  # Set up the stack pointer and call into C.
  movl    $start, %esp
  call    bootmain

  # If bootmain returns (it shouldn't), trigger a Bochs
  # breakpoint if running under Bochs, then loop.
  movw    $0x8a00, %ax            # 0x8a00 -> port 0x8a00
  movw    %ax, %dx
  outw    %ax, %dx
  movw    $0x8ae0, %ax            # 0x8ae0 -> port 0x8a00
  outw    %ax, %dx
spin:
  jmp     spin

# Bootstrap GDT
.p2align 2                                # force 4 byte alignment
gdt:
  SEG_NULLASM                             # null seg
  SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)   # code seg
  SEG_ASM(STA_W, 0x0, 0xffffffff)         # data seg

gdtdesc:
  .word   (gdtdesc - gdt - 1)             # sizeof(gdt) - 1
  .long   gdt                             # address gdt

这段汇编代码,主要实现功能:(1) 寄存器置零,(2) 将0x64数据读取到al寄存器,(3) 判断0x64位数据后两位为10,如果不是,将0xd1写到0x60处。实际原理就是:引导加载器用 I/O 指令控制端口 0x64 和 0x60 上的键盘控制器,使其输出端口的第2位为高位,来使第21位地址正常工作,将地址从20位变为32位。(4) 加载全局变量表(gdtdesc)的指针。(5) 开始保护模式的标识,CR0_PE=1。(6) ljmp 指令,将跳转到start32去执行。

补充:对GDT和ljmp更加深刻的认识后,逐渐认识到,为什么ljmp指令是启动保护模式的关键。ljmp跳转到了全局描述符表中,第一个表项所指向的段地址,中的start32的位置。至于为什么要左移3,因为表项还需要1bit的ti标志位和2bit的权限。因此要左移三位。[GDT说明](http://www.techbulo.com/708.html)

 

start32主要功能:(1) 寄存器清零。(2) 将start程序保存入栈。 (3) call bootmain.即为bootmain.c文件中的程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
我们整个ARM课程就分为三部分,这是第一部分,实现一个自己的最小bootloader 1.Read Me 一、实现功能 1.硬件初始化 2.延时判断加载操作系统还是进入Bootloader Shell 3.加载操作系统 4.Bootloadershell 二、Bootloader Shell 支持的命令 1.help 帮助,显示所有支持的命令,及命令格式 2.loadx 下载文件到开发板的内存,默认到0x32000000 3.led_on 点亮一个led灯 4.led_off 关闭一个led灯 5.led_test 测试所有led灯,全亮全灭循环3次 6.beep_test 测试蜂鸣器,响3声 7.seg7_test 测试7段数码管 8.dip4_test 测试4位拨码开关 9.flash_load 将NandFlash中的文件搬移到SDARAM中 10.flash_write 将SDRAM中的内容下载到NandFlash中 11.GO 跳到某地址执行,默认到0x32000000 三、文件结构 1.start.s 程序入口,负责硬件初始化,Bootloader自搬移 2.uart.c uart.h 串口驱动的实现 3.load.c 选择加载操作系统还是进入Shell 4.stdlib.h stdlib.c 标准库函数的实现 5.stdio.h stdio.c 标准输入输出函数的实现 6.shell.c shell.h shell命令的实现 7.dip4.h dip4.c 拨码开关相关底层函数 8.seg7.h seg7.c 7段数码管相关底层函数 9.copy_myself.c nan.h NandFlash底层函数 10.xmodem.h xmodem.c xmodem协议实现 11.Datatype.h 数据定义 12.os/os.c 模拟操作系统 13.Makefile 四、流程及设计思想 1.硬件初始化 2.Bootloader自搬移 3.延时,判断是否有输入 4.(1)无输入则加载操作系统操作系统烧写于Nand Flash的第100块,即位于100*32*512 = 0x190000 操作系统加载到内存的Sdram中 (2)有输入则进入shell命令模式 5.解释命令,使用自己实现的标准库函数来匹配输入的命令 6.匹配函数,定义了一个包含字符指针以及函数指针的结构体,可以通过对应关系迅速调用命令对应的函数 所有函数为void fun(void *)形式。 五、测试条件及结果 1. 打开超级终端,给开发板上电,超级终端上打印提示信息 2. 超级终端上开始3秒倒计时,3秒内不动键盘,提示加载操作系统,模拟操作系统的闪灯程序运行,可观察到LED等一闪一灭 3. 重启开发板,3秒内按下任意键,可看到有T-Boot#提示符,程序进入Shell模式 4. 输入help,可看到10条命令的使用方法 5. 输入led_on 1可看到第一个led灯亮 6. 输入led_off 1可看到第一个led灯灭 7. 输入led_test 可看到所有led一闪一灭3次 8. 输入beep_test 可听到蜂鸣器响3声 9. 输入seg7_test 可看到7段数码管每个led循环点亮 10.输入dip4_test 拨动拨码开关可观察到7段数码管对应的LED亮 11.输入loadx,发送文件0x/0s.bin 12.输入go 0x32000000 可观察到led灯一亮一灭 13.输入flash_load 0x190000 0x32000000 0x1000 (0x190000模拟操作系统烧写位置) 14.go 0x32000000 可观察到led一亮一灭 16.输入flash_write 0x32000000 0x200000 0x1000 17.输入flash_load 0x200000 0x31500000 0x1000 18.输入go 0x31500000 可观察到led灯一亮一灭
基于STM32F103的CAN Bootloader程序源码上位机是用于将固件文件通过CAN总线传输给目标设备的工具。下面是一个简单的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> // 定义CAN消息结构体 typedef struct { uint8_t message_id; uint8_t data[8]; } CAN_Message; // 定义函数用于发送CAN消息 void send_can_message(uint8_t message_id, uint8_t* data, uint8_t length) { CAN_Message message; message.message_id = message_id; memcpy(message.data, data, length); // TODO: 使用STM32的CAN库发送CAN消息 // 示例代码中,假设使用CAN1发送消息 // CAN_Transmit(CAN1, &message); } int main() { uint8_t firmware_data[256]; // 存储固件数据的缓冲区 uint8_t firmware_length = 0; // 固件数据的长度 // TODO: 从上位机读取固件文件,将其保存到firmware_data缓冲区中 // 发送固件数据给目标设备 for (int i = 0; i < firmware_length; i += 8) { uint8_t length = (firmware_length - i < 8) ? firmware_length - i : 8; send_can_message(i/8, &firmware_data[i], length); // TODO: 等待一段时间,以允许目标设备接收和处理CAN消息 // 示例代码中,假设等待10ms // delay_ms(10); } return 0; } ``` 以上代码定义了一个函数`send_can_message`用于发送CAN消息,以及一个主函数`main`用于发送固件文件给目标设备。固件文件使用一个缓冲区`firmware_data`保存,并通过循环发送CAN消息,每次发送8字节的数据。在实际使用时,需要根据具体的硬件平台和需求来实现CAN发送函数,并在发送每个CAN消息后等待足够的时间以允许目标设备接收和处理CAN消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值