[TOC]
## 寫在前面
下面有不熟悉的代碼很正常,這節課主要目的是讓我們跑一下。
引用編輯恢復評論里的一句話:
初入門一個上下左右都有很多牽扯的復雜網狀系統的時候,一定要像張無忌學太極劍一樣,只記住劍意,選擇性忘記忽略一些細枝末節。不然的話,很可能就會被帶跑偏,沉溺于一些奇奇怪怪暫時又不太重要的東西。
**學習可以先抓主線,慢慢消化甚至優化細節**
## PC 機的引導流程
這里課程沒有從 PC 的引導程序開始寫起,原因是目前我們的知識儲備還不夠,所以先借用一下 GRUB 引導程序,只要我們的 PC 機上安裝了 Ubuntu Linux 操作系統,GRUB 就已經存在了。
下面附上我們要跑起來的Hello OS的引導流程圖:

## Hello OS 引導匯編代碼
```asm
;彭東 @ 2021.01.09
MBT_HDR_FLAGS EQU 0x00010003
MBT_HDR_MAGIC EQU 0x1BADB002 ;多引導協議頭魔數
MBT_HDR2_MAGIC EQU 0xe85250d6 ;第二版多引導協議頭魔數
global _start ;導出_start符號
extern main ;導入外部的main函數符號
[section .start.text] ;定義.start.text代碼節
[bits 32] ;匯編成32位代碼
_start:
jmp _entry
ALIGN 8
mbt_hdr:
dd MBT_HDR_MAGIC
dd MBT_HDR_FLAGS
dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
dd mbt_hdr
dd _start
dd 0
dd 0
dd _entry
;以上是GRUB所需要的頭
ALIGN 8
mbt2_hdr:
DD MBT_HDR2_MAGIC
DD 0
DD mbt2_hdr_end - mbt2_hdr
DD -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
DW 2, 0
DD 24
DD mbt2_hdr
DD _start
DD 0
DD 0
DW 3, 0
DD 12
DD _entry
DD 0
DW 0, 0
DD 8
mbt2_hdr_end:
;以上是GRUB2所需要的頭
;包含兩個頭是為了同時兼容GRUB、GRUB2
ALIGN 8
_entry:
;關中斷
cli
;關不可屏蔽中斷
in al, 0x70
or al, 0x80
out 0x70,al
;重新加載GDT
lgdt [GDT_PTR]
jmp dword 0x8 :_32bits_mode
_32bits_mode:
;下面初始化C語言可能會用到的寄存器
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
xor edi,edi
xor esi,esi
xor ebp,ebp
xor esp,esp
;初始化棧,C語言需要棧才能工作
mov esp,0x9000
;調用C語言函數main
call main
;讓CPU停止執行指令
halt_step:
halt
jmp halt_step
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff
k16da_dsc: dq 0x000092000000ffff
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1
GDTBASE dd GDT_START
```
1. 代碼 1~40 行,用匯編定義的 GRUB 的多引導協議頭,其實就是一定格式的數據,我們的 Hello OS 是用 GRUB 引導的,當然要遵循 GRUB 的多引導協議標準,讓 GRUB 能識別我們的 Hello OS。之所以有兩個引導頭,是為了兼容 GRUB1 和 GRUB2。
2. 代碼 44~52 行,關掉中斷,設定 CPU 的工作模式。你現在可能不懂,沒事兒,后面 CPU 相關的課程我們會專門再研究它。
3. 代碼 54~73 行,初始化 CPU 的寄存器和 C 語言的運行環境。
4. 代碼 78~87 行,GDT_START 開始的,是 CPU 工作模式所需要的數據,同樣,后面講 CPU 時會專門介紹。
## Hello OS 的主函數
```c
//彭東 @ 2021.01.09
#include "vgastr.h"
void main()
{
printf("Hello OS!");
return;
}
```
## 控制計算機屏幕
附:顯卡的字符模式的工作細節
它把屏幕分成 24 行,每行 80 個字符,把這(24*80)個位置映射到以 0xb8000 地址開始的內存中,每兩個字節對應一個字符,其中一個字節是字符的 ASCII 碼,另一個字節為字符的顏色值。如下圖所示:

## 編譯和安裝 Hello OS
下載代碼
`git clone https://gitee.com/lmos/cosmos.git`
目錄結構如下

* entry.asm:是一段匯編代碼,用作GRUB引導調用,關掉中斷,設定CPU工作模式,初始化寄存器及C語言運行環境等;
* hello.lds:進行鏈接調用,代碼簡單看看,反正也看不懂:
```lds
ENTRY(_start)
OUTPUT_ARCH(i386)
OUTPUT_FORMAT(elf32-i386)
SECTIONS
{
. = 0x200000;
__begin_start_text = .;
.start.text : ALIGN(4) { *(.start.text) }
__end_start_text = .;
?
__begin_text = .;
.text : ALIGN(4) { *(.text) }
__end_text = .;
?
__begin_data = .;
.data : ALIGN(4) { *(.data) }
__end_data = .;
?
__begin_rodata = .;
.rodata : ALIGN(4) { *(.rodata) *(.rodata.*) }
__end_rodata = .;
?
__begin_kstrtab = .;
.kstrtab : ALIGN(4) { *(.kstrtab) }
__end_kstrtab = .;
?
__begin_bss = .;
.bss : ALIGN(4) { *(.bss) }
__end_bss = .;
}
```
* install.md:需要將這個文件里的內容復制到GRUB的cfg配置文件中,才能使電腦開機時可以找到我們的Hello OS;
* main.c:我們Hello OS的主函數,它調用的printf可不是常見的C語言庫函數哦,而是我們自己實現的printf!

即下面要講的vgastr.h;
* vgastr.h:控制計算機屏幕VGABIOS固件程序顯示特定字符,后面詳細介紹;
* Makefile:利用make工具來實現編譯源代碼,主要是將entry.asm、main.c、vgastr.h編譯并鏈接。
### 編譯操作系統
### 流程

### 編譯

提示 **nasm**編譯器沒有安裝
安裝一下:`sudo apt-get install nasm`
繼續編譯:

### 安裝
#### 找到boot目錄掛載分區:
`df /boot/`

可以看到虛擬機中ubuntu16.04的系統GRUB引導是在硬盤的第**五**分區
然后**打開文件夾中的install.md,復制粘貼到\***\*/boot/grub/grub.cfg中\*\*,install.md主要是加載我們的Hello OS的啟動項:

```vim /boot/grub/grub.cfg```

將 **Hello OS.bin** 文件復制到 /boot/ 目錄下
](images/screenshot_1628234267320.png)
### 設置GRUB引導界面時間
**執行命令**:`sudo gedit /etc/default/grub`
修改圖示內容

**更新grub配置**
`sudo update-grub`

#### 重啟查看效果
**以上一切看起來都沒問題,也沒有報錯,但是,遇到坑了!!**
我驚奇的發現,我重啟系統的時候,啟動項里竟然沒有HelloOs選項,經過反復排錯(用戶權限問題,代碼段放置位置問題,等等)后,折騰一個小時后,我終于找到問題了:**設置GRUB引導界面時間,更新grub配置,一定要放在配置/boot/grub/grub.cfg后進行!**
查看一下效果:

>0x7C00?由來的一種說法。當時,搭配的操作系統是86-DOS。這個操作系統需要的內存最少是32KB。我們知道,內存地址從0x0000開始編號,32KB的內存就是0x0000~0x7FFF。8088芯片本身需要占用0x0000~0x03FF,用來保存各種中斷處理程序的儲存位置。(主引導記錄本身就是中斷信號INT?19h的處理程序。)所以,內存只剩下0x0400~0x7FFF可以使用。為了把盡量多的連續內存留給操作系統,主引導記錄就被放到了內存地址的尾部。由于一個扇區是512字節,主引導記錄本身也會產生數據,需要另外留出512字節保存(自己?和?產生)。所以,它的預留位置就變成了:
????0x7FFF?-?512?-?512?+?1?=?0x7C00
- 微服務
- 服務器相關
- 操作系統
- 極客時間操作系統實戰筆記
- 01 程序的運行過程:從代碼到機器運行
- 02 幾行匯編幾行C:實現一個最簡單的內核
- 03 黑盒之中有什么:內核結構與設計
- Rust
- 入門:Rust開發一個簡單的web服務器
- Rust的引用和租借
- 函數與函數指針
- Rust中如何面向對象編程
- 構建單線程web服務器
- 在服務器中增加線程池提高吞吐
- Java
- 并發編程
- 并發基礎
- 1.創建并啟動線程
- 2.java線程生命周期以及start源碼剖析
- 3.采用多線程模擬銀行排隊叫號
- 4.Runnable接口存在的必要性
- 5.策略模式在Thread和Runnable中的應用分析
- 6.Daemon線程的創建以及使用場景分析
- 7.線程ID,優先級
- 8.Thread的join方法
- 9.Thread中斷Interrupt方法學習&采用優雅的方式結束線程生命周期
- 10.編寫ThreadService實現暴力結束線程
- 11.線程同步問題以及synchronized的引入
- 12.同步代碼塊以及同步方法之間的區別和關系
- 13.通過實驗分析This鎖和Class鎖的存在
- 14.多線程死鎖分析以及案例介紹
- 15.線程間通信快速入門,使用wait和notify進行線程間的數據通信
- 16.多Product多Consumer之間的通訊導致出現程序假死的原因分析
- 17.使用notifyAll完善多線程下的生產者消費者模型
- 18.wait和sleep的本質區別
- 19.完善數據采集程序
- 20.如何實現一個自己的顯式鎖Lock
- 21.addShutdownHook給你的程序注入鉤子
- 22.如何捕獲線程運行期間的異常
- 23.ThreadGroup API介紹
- 24.線程池原理與自定義線程池一
- 25.給線程池增加拒絕策略以及停止方法
- 26.給線程池增加自動擴充,閑時自動回收線程的功能
- JVM
- C&C++
- GDB調試工具筆記
- C&C++基礎
- 一個例子理解C語言數據類型的本質
- 字節順序-大小端模式
- Php
- Php源碼閱讀筆記
- Swoole相關
- Swoole基礎
- php的五種運行模式
- FPM模式的生命周期
- OSI網絡七層圖片速查
- IP/TCP/UPD/HTTP
- swoole源代碼編譯安裝
- 安全相關
- MySql
- Mysql基礎
- 1.事務與鎖
- 2.事務隔離級別與IO的關系
- 3.mysql鎖機制與結構
- 4.mysql結構與sql執行
- 5.mysql物理文件
- 6.mysql性能問題
- Docker&K8s
- Docker安裝java8
- Redis
- 分布式部署相關
- Redis的主從復制
- Redis的哨兵
- redis-Cluster分區方案&應用場景
- redis-Cluster哈希虛擬槽&簡單搭建
- redis-Cluster redis-trib.rb 搭建&原理
- redis-Cluster集群的伸縮調優
- 源碼閱讀筆記
- Mq
- ELK
- ElasticSearch
- Logstash
- Kibana
- 一些好玩的東西
- 一次折騰了幾天的大華攝像頭調試經歷
- 搬磚實用代碼
- python讀取excel拼接sql
- mysql大批量插入數據四種方法
- composer好用的鏡像源
- ab
- 環境搭建與配置
- face_recognition本地調試筆記
- 虛擬機配置靜態ip
- Centos7 Init Shell
- 發布自己的Composer包
- git推送一直失敗怎么辦
- Beyond Compare過期解決辦法
- 我的Navicat for Mysql
- 小錯誤解決辦法
- CLoin報錯CreateProcess error=216
- mysql error You must reset your password using ALTER USER statement before executing this statement.
- VM無法連接到虛擬機
- Jetbrains相關
- IntelliJ IDEA 筆記
- CLoin的配置與使用
- PhpStormDocker環境下配置Xdebug
- PhpStorm advanced metadata
- PhpStorm PHP_CodeSniffer