編寫最簡單的核心:HelloWorld,編寫helloworld
核心是作業系統最核心的內容,主要提供硬體抽象層、磁碟及檔案系統控制、多任務等功能,由於其涉及非常廣泛的電腦軟體開發的知識,很少被人們所熟悉,因而披上了一層神秘的面紗。
本文將從零開始實現一個最簡單的核心,其可以通過x86系統的GRUB引導啟動,並向螢幕輸出“Hello World!“字串。該核心代碼非常簡短,並且在本人的Debian 7系統中可以正常運行。
x86機器啟動過程
在具體實現這個核心之前,我們先看看機器具體是怎麼啟動並且把控制權交給核心的。
x86的CPU固定地在物理地址為[0xFFFFFFF0]的地方開始運行,這是32位地址空間的最後16個位元組。這裡只包含了一個跳轉指令,跳轉到BIOS把它自己拷貝到的記憶體地區的地址。
然後,BIOS開始執行。它首先根據配置的裝置啟動順序依次尋找可開機裝置(根據一個特定的魔數可以決定一個裝置是否啟動)。一旦找到一個可開機裝置,它就把該裝置第一個扇區的內容複寫到RAM中物理地址從[0x7C00]開始的地方,然後跳轉到該地址並且開始執行那裡載入的代碼。這段代碼稱為啟動引導裝載程式(bootloader)。Bootloader然後在物理地址為[0x100000]的地方載入核心,地址[0x100000]就是x86機器上核心的起始地址。
需要的工具
- 一台x86電腦
- Linux
- NASM彙編器
- gcc
- ld(GNU連結器)
- grub
彙編進入點
我們希望用C來寫所有的代碼,但免不了要寫一點彙編代碼。我們會寫一個x86組合語言的小檔案來作為核心的起始點,這段彙編所做的事情就是調用一個我們用C寫的外部函數,然後停止程式運行。
怎麼確定這段彙編代碼會作為核心的起始點呢?
我們會使用一個連結指令碼來連結所有的目標檔案來產生一個最終的核心可執行映像。在這個連結指令碼中,我們會顯式指明二進位檔案要載入在地址為[0x100000]的地方,這就是核心所在的地方。於是,bootloader會負責觸發這個核心的進入點。
以下是彙編代碼:
| 1234567891011 |
;;kernel.asm,核心彙編代碼bits 32 ;nasm偽指令section .text ;程式碼片段 global start ;全域變數extern kmain ;kmain定義在C檔案中 start: cli ;禁止中斷 call kmain ;調用kmain函數 hlt ;終止CPU運行 |
第一條指令中的bit 32不是x86彙編指令,而是NASM彙編器的偽指令,表明將會產生一段運行在32位處理器上代碼。這句代碼不是必須的,但顯示加上會是一個好的編程實踐。
第二行開始就是程式碼片段,即放置代碼的地方。
global也是NASM的偽指令,表示把原始碼中的一個符號設定成全域符號。於是連結器知道start符號在哪裡,其實這就是我們的進入點。
kmain是將會在kernel.c中實現的一個函數,extern表明這個函數會在其他地方定義。
於是,我們有了start函數,它會調用kmain函數,然後通過hlt指令停止CPU。由於中斷會從hlt指令中喚醒CPU,所以我們事先使用cli(意為clear interrupts)指令禁止中斷。
C語言核心
我們在kernel.asm中調用kmain()函數,所以C代碼會從kmain()開始執行。
| 123456789101112131415161718192021222324 |
//kernel.c檔案void kmain(void){ char *str = "Hello World!"; char *vidptr = (char*)0xb8000; //顯存開始地址 unsigned int i = 0; unsigned int j = 0; //清空螢幕,共25行,每行80個字元,每個字元2位元組 while(j < 80 * 25 * 2) { //空白字元 vidptr[j] = ' '; //屬性位元組:黑色背景,灰色前景 vidptr[j+1] = 0x07; j = j + 2; } j = 0; while(str[j] != '') { vidptr[i] = str[j]; vidptr[i+1] = 0x07; ++j; i = i + 2; } return;} |
這裡核心所做的事情就是:清空螢幕,列印字串“Hello World!”。
首先是指標vidptr指向地址[0xb8000],這是保護模式下顯存的開始地址。螢幕的文本記憶體只是地址空間的一連串記憶體地區,它從[0xb8000]開始映射螢幕的輸入輸出,支援25行,每行80個ASCII字元,每個字元用16位(2位元組)表示,而不是我們熟悉的8位(1位元組)。2位元組中第1個位元組是該字元的ASCII表示,第2個位元組是屬性位元組,描述字元的包括顏色在內的屬性。如果要讓背景為黑色而字型為綠色,可以在第1個位元組儲存字元的ASCII值,在第2個位元組儲存值[0x02]:0代表黑色背景,2代表綠色前景。
其它顏色屬性定義如下:
| 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
| Black |
Blue |
Green |
Cyan |
Red |
Magenta |
Brown |
Light Grey |
| 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
| Dark Grey |
Light Blue |
Light Green |
Light Cyan |
Light Red |
Light Magenta |
Light Brown |
White |
我們的核心使用了黑色背景以及灰色字型,所以屬性位元組為[0x07]。
在第一個while迴圈中,程式在所有的25行80列中寫入Null 字元和[0x07]屬性,從而清空了螢幕。
在第二個while迴圈中,字串”Hello World!“被寫到了顯存的開始地區,每個字元仍是擁有[0x07]屬性。這就在螢幕上列印了該字串。
連結部分
使用NASM把kernel.asm編譯成目標檔案,再使用GCC把kernel.c編譯成另一個目標檔案,然後需要把這兩個目標檔案連結成一個可以啟動的核心映像。
我們使用連結指令碼來達到這個目的,連結指令碼可以作為參數傳遞進連結器ld中以控制連結的過程。
| 12345678910 |
//link.ld檔案OUTPUT_FORMAT(elf32-i386)ENTRY(start)SECTIONS{ . = 0x100000; .text : { *(.text) } .data : { *(.data) } .bss : { *(.bss) }} |
OUTPUT_FORMAT設定輸出的可執行檔為32位的ELF檔案,ELF是x86架構上類Unix系統的標準二進位檔案格式。
ENTRY接受一個參數,指定其為最終可執行檔的進入點。
SECTION是這裡最關鍵的部分,它指定不同的段怎麼合并以及放在什麼地方,從而定義最終可執行檔的布局。
大括弧內就是SECTION的語句,句點(.)為位置計數器,一般被初始化為SECTIONS塊開始的地方[0x0],但可以任意修改。因為核心代碼需要在地址[0x100000]處開始,所以設定位置計數器為[0x100000]。
第二行中的星號是萬用字元,可以匹配任何檔案名稱,*(.text)即表示匹配所有輸入檔案的程式碼片段。於是,連結器合并所有目標檔案的程式碼片段到可執行檔的程式碼片段中,具體地址由位置計數器決定,這裡即為[0x100000]。連結器產生程式碼片段後,位置計數器會變成:0×100000 + 輸出程式碼片段的大小。
同樣地,資料區段和bss段會被合并,並放置在位置計數器指定的地方。
Grub和多重開機
現在已經準備好了構建核心的所有檔案了,但要用GRUB進行引導還需要最後一個步驟。
多重開機規範(Multiboot specification)是一個使用bootloader載入不同X86核心的標準,GRUB只會載入滿足這個規範的核心。根據這個規範,核心必須在它的前8KB位元組中包含頭資訊(Multiboot header)。這個頭資訊包含4位元組對齊的3個域,分別為:
- 魔數域:包含魔數[0x1BADB002]。
- 標誌域:這裡不關心這個域,置為0。
- 校正和域:檢驗和域和前面兩個域相加之後的結果必須為0。
於是kernel.asm應該修改為,代碼中的dd表示定義一個4位元組的雙字:
| 1234567891011121314151617 |
;;kernel.asm,核心彙編代碼 bits 32 ;nasm偽指令section .text ;程式碼片段 ;多重開機規範 align 4 dd 0x1BADB002 ;魔數 dd 0x00 ;標誌 dd - (0x1BADB002 + 0x00) ;校正和 global start ;全域變數extern kmain ;kmain定義在C檔案中 start: cli ;禁止中斷 call kmain ;調用kmain函數 hlt ;終止CPU運行 |
構建核心
現在可以從kernel.asm和kernel.c產生目標檔案,然後使用串連指令碼進行連結。
使用彙編器nasm產生ELF-32格式的目標檔案kasm.o:
| 1 |
nasm -f elf32 kernel.asm -o kasm.o |
使用編譯器gcc產生目標檔案kc.o,”-c”參數保證只編譯,不進行連結:
| 1 |
gcc -m32 -c kernel.c -o kc.o |
使用連結器ld根據連結控制指令碼產生可執行映像檔案kernel:
| 1 |
ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o |
配置GRUB並運行核心
GRUB需要核心以kernel-<version>形式命名,於是把核心kernel重新命名為kernel-701,並利用超級管理員權限放到/boot目錄下。
對於bootloader為GRUB的發行版,修改設定檔/boot/grub/grub.cfg,添加以下條目:
| 123 |
title myKernel root (hd0,0) kernel /boot/kernel-701 ro |
對於bootloader為GRUB2的發行版,添加的配置應該為:
| 1234 |
menuentry 'kernel 701' { set root='hd0,msdos1' multiboot /boot/kernel-701 ro} |
重啟電腦,選擇GRUB列表中新增加的kernel-701核心選項,這時可以看到螢幕上顯示”Hello World!“。
這就是你的核心!
文章來源:編寫最簡單的核心:HelloWorld
編寫一個輸出HelloWorld的小應用程式
那就是一個Applet程式了.
import java.awt.Graphics;
import javax.swing.JApplet;
public class HelloWorld extends JApplet
{
public void paint(Graphics g)
{
g.clearRect(0,0,getWidth(),getHeight());//清楚背景
g.drawString("HelloWorld",70,80);//在指定位置輸出"HelloWorld"
}
}
在網頁中你就可以插入一個applet,標記如下
<body>
<applet code="HelloWorld.class" width=200,height=50>
</applet>
</body>
Java編寫輸出Helloworld
程式沒有問題,還應該是JDK環境變數配置的問題
首先像安裝其他軟體一樣安裝jdk,我的安裝目錄為 D:\Program Files\Java\jdk1.6.0,下面依據這個目錄進行環境配置:
1、右擊“我的電腦”的“屬性”,彈出對話方塊,點擊“環境變數”(圖中已標識),彈出對話方塊,在系統變數中,我們進行配置
2、配置系統變數:
(1)點擊“建立”按扭,配置JAVA_HOME系統變數,如下:
變數值需要根據自己的jdk的安裝路徑而定,因為我的jdk安裝在 D:\Program Files\Java\jdk1.6.0下,所以,此處變數名為JAVA_HOME,變數值為: D:\Program Files\Java\jdk1.6.0,完了後,“確定”。
(2)配置classpath變數,點擊“建立”按扭,配置classpath系統變數,如下:
classpath=,;%JAVA_HOME%\dt.jar; %JAVA_HOME%\lib\tools.jar;
要注意的是這裡classpath的值中前面有英文IME下的,;(逗號和分號),結尾還有;(分號)
配置path變數,一般的下面都已經存在path變數,直接在裡面添加就可以了。
(3)找到path變數,點擊“編輯”,在變數值的最後面添加下面的值:
;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
到這裡,環境變數配置成功。
驗證配置成功否,直接在cmd下輸入java或javac,有許多行文字彈出就是配置成功了!