# 练习4

## 练习4：分析bootloader加载ELF格式的OS的过程

[练习4文档](https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_2_1_4_ex4.html) [相关阅读：硬盘访问概述](https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_3_dist_accessing.html) [相关阅读：ELF文件格式概述](https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_4_elf.html)

* bootloader如何读取硬盘扇区的
* bootloader是如何加载ELF格式的OS

### 相关文件

* [bootmain.c](/ucore-analysis/ucore/boot/bootmain_c.md)
* [elf.h](https://github.com/oscourse-tsinghua/ucore-analysis/tree/bbbcc266654cdbad4fbd4ccde76178f7d4148c42/ucore/libs/elf.h)

## bootloader如何读取硬盘扇区的

### 背景知识

1. 什么是`bootloader`

   `bootloader`是为了加载内核而编写的程序。
2. `bootloader`的作用是什么
   1. 将内核加载到内存
   2. 向内核提供正常工作所需的信息
   3. 切换到内核所需的环境
   4. 将控制权转移给内核
3. 什么是扇区

   磁盘上的每个磁道被等分为若干个弧段，这些弧段便是磁盘的扇区。硬盘的读写以扇区为基本单位。
4. 如何读写扇区

   硬盘相关IO地址及其对应功能：

   `0x1f0`: 读数据，当0x1f7不为忙状态时，可以读

   `0x1f2`: 要读写的扇区数，每次读写前，你需要表明你要读写几个扇区。最小是1个扇区

   `0x1f3`: 如果是LBA模式，就是LBA参数的0-7位

   `0x1f4`: 如果是LBA模式，就是LBA参数的8-15位

   `0x1f5`: 如果是LBA模式，就是LBA参数的16-23位

   `0x1f6`: 第0\~3位：如果是LBA模式就是24-27位 第4位：为0主盘；为1从盘

   `0x1f7`: 状态和命令寄存器。操作时先给命令，再读取，如果不是忙状态就从0x1f0端口读数据

   读取扇区的流程大致如下：

   1. 等待磁盘准备好
   2. 发出读取扇区的命令
   3. 等待磁盘准备好
   4. 把磁盘扇区数据读到指定内存

### 问题分析

1. `bootloader`是如何读取硬盘扇区的

   参考以下代码：

   ```c
   //    File:    bootmain.c
   //    Line:    43-61

   /* readsect - read a single sector at @secno into @dst */
   static void
   readsect(void *dst, uint32_t secno) {
       // wait for disk to be ready
       waitdisk();

       outb(0x1F2, 1);                         // count = 1
       outb(0x1F3, secno & 0xFF);
       outb(0x1F4, (secno >> 8) & 0xFF);
       outb(0x1F5, (secno >> 16) & 0xFF);
       outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
       outb(0x1F7, 0x20);                      // cmd 0x20 - read sectors

       // wait for disk to be ready
       waitdisk();

       // read a sector
       insl(0x1F0, dst, SECTSIZE / 4);
   }
   ```

   其中，`waitdisk`函数的代码为：

   ```c
   //    File:    bootmain.c
   //    Line:    36-41

   /* waitdisk - wait for disk ready */
   static void
   waitdisk(void) {
       while ((inb(0x1F7) & 0xC0) != 0x40)
           /* do nothing */;
   }
   ```

   由以上代码可知，`bootloader`读取硬盘扇区步骤为

   1. 从`0x1F7`端口读硬盘状态，直到硬盘不忙
   2. 向`0x1F2`端口发送读写的扇区数量
   3. 向`0x1F3`\~`0x1F6`端口发送LBA参数
   4. 向`0x1F7`端口发送读扇区的指令
   5. 从`0x1F7`端口读硬盘状态，直到硬盘不忙
   6. 从`0x1F0`端口读入扇区内容至`dst`

### 参考文献

* [硬盘访问概述](https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_3_dist_accessing.html)
* [Bootloader](https://wiki.osdev.org/Bootloader)

## bootloader是如何加载ELF格式的OS

### 背景知识

1. `ELF`是什么

   `ELF`是类Unix系统上的一种文件格式，常用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
2. `ELF`的格式是什么

   参考代码：

   ```c
   //    File:    elf.h
   //    Line:    8-25

   /* file header */
   struct elfhdr {
       uint32_t e_magic;     // must equal ELF_MAGIC
       uint8_t e_elf[12];
       uint16_t e_type;      // 1=relocatable, 2=executable, 3=shared object, 4=core image
       uint16_t e_machine;   // 3=x86, 4=68K, etc.
       uint32_t e_version;   // file version, always 1
       uint32_t e_entry;     // entry point if executable
       uint32_t e_phoff;     // file position of program header or 0
       uint32_t e_shoff;     // file position of section header or 0
       uint32_t e_flags;     // architecture-specific flags, usually 0
       uint16_t e_ehsize;    // size of this elf header
       uint16_t e_phentsize; // size of an entry in program header
       uint16_t e_phnum;     // number of entries in program header or 0
       uint16_t e_shentsize; // size of an entry in section header
       uint16_t e_shnum;     // number of entries in section header or 0
       uint16_t e_shstrndx;  // section number that contains section name strings
   };
   ```

### 问题分析

1. `bootloader`是如何加载`ELF`格式的OS

   参考代码：

   ```c
   //    File:    bootmain.c
   //    Line:    85-115

   /* bootmain - the entry of bootloader */
   void
   bootmain(void) {
       // read the 1st page off disk
       readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);

       // is this a valid ELF?
       if (ELFHDR->e_magic != ELF_MAGIC) {
           goto bad;
       }

       struct proghdr *ph, *eph;

       // load each program segment (ignores ph flags)
       ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
       eph = ph + ELFHDR->e_phnum;
       for (; ph < eph; ph ++) {
           readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
       }

       // call the entry point from the ELF header
       // note: does not return
       ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();

   bad:
       outw(0x8A00, 0x8A00);
       outw(0x8A00, 0x8E00);

       /* do nothing */
       while (1);
   }
   ```

   由代码可知，`bootloader`加载OS的流程为：

   1. 将硬盘第一个扇区的内容读入`ELFHDR`
   2. 验证`ELFHDR`是否为合格的`ELF`格式
   3. 加载`ELF`的每个程序段
   4. 调用`ELF`头内的OS入口，加载OS成功

### 参考文献

* [ELF文件格式概述](https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_4_elf.html)
* [ELF](https://wiki.osdev.org/ELF)
* [关于lab1中的bootloader和ucore的连接问题](https://piazza.com/class/i5j09fnsl7k5x0?cid=72)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://oscourse-tsinghua.gitbook.io/ucore-analysis/labs/lab1/practice4.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
