作者:raoxianhong
代碼:
1、到底想干什么
了解linux的啟動過程,制作一個自己的Linux啟動程序,可以增加對Linux的了解,還能學習PC機的啟動機制,增進對計算機結構的了解,增強對Linux內核學習的信心。
也可以在某些專用產品中使用(比如專用的服務器)。為此,我嘗試在原來代碼的基礎上修改制作了一個用網絡卡從并口上啟動Linux的程序,以博一笑,其中有許多問題值得研究。
2、Linux對啟動程序的要求
Linux(bzImage Kernel)對啟動程序的要求比較簡單,你只要能夠建立一個啟動頭(setup.S),
給出一些信息,然后將kernel(/usr/src/linux/arch/i386/boot/comPRessed/bvmlinux.out)調到
絕對地址0x100000(1M地址處),假如有initrd,則將它調到內存高端(離0x100000越遠越好,比如假如
initrd小于4M,就可以將它調到地址0xB00000,即12M處,相信現在已經很少有少于16M內存的機器了),
然后執行一些初始化操作,跳到內核處就行了。
當然,說起來輕易做起來還有點麻煩,以下分幾個問題解釋。
3、PC機開機流程--啟動程序放在何處
PC機加電后,進入實模式,先進行自檢,然后初始化各個總線擴展設備(ISA, EISA,PCI,AGP),
全部初始化做完后,從當前啟動設備中讀一個塊(512字節)到07C0:0000處,將控制轉到該處。
了解這個過程,我們可以決定將啟動程序放在何處:
1)放在啟動設備的MBR(主啟動記錄中),比如磁盤的啟動扇區。這是一般的啟動方式。
2)放在總線擴展設備的擴展rom中,比如網卡的boot rom就行,這里制作的啟動程序就是放在網卡中,可以支持 16K字節。
3)哪位高手能夠修改ROMBIOS,讓BIOS在做完初始化后不要馬上從啟動設備讀數據,而是調用一段外面 加入的程序(2K字節就夠了,當然也必須與修改后的BIOS一起燒在BIOS ROM中),就可以從BIOS啟動!
4)先啟動一個操作系統,再在此操作系統中寫啟動程序(比如lodlin16就是從DOS中啟動Linux,好象中軟
提供了一個從Windows下啟動Linux的啟動程序)。
4、操作系統放在何處
操作系統(一般內核在500K-1M之間,加上應用程序可以控制在2M以內,當然都經過壓縮了)的數據選擇余地就大了,
可以從軟盤、硬盤、CDROM、網絡、磁帶機、并口(軟件狗上燒個內核和應用程序?)、串口(外接你的設備)、 USB設備(?)、PCI擴展卡、IC卡等等上面來讀;各位還有什么意見,提醒提醒。有位老兄說實在不行可以用鍵盤啟動,每次啟動時把內核敲進去,還有int 16h支持呢,做起來也不難,應該是最節省的方案了。
反正一個原則是,在啟動程序中能夠從該設備上讀就行了,這里最簡單的就是并口了,簡單的端口操作,不需 要任何驅動程序支持,不需要BIOS支持,比磁盤還簡單(磁盤一般使用int 13h,主要是計算柱面啊、磁頭啊、磁道啊、扇區啊好麻煩,幸好有現成的源代碼,可以學習學習)。
好了,我們挑個簡單的方案,將啟動代碼(bootsect.S+setup.S)放到網絡卡的boot rom中,內核數據和應用數據放到另外一臺計算機上,用并口提供。下面談談幾個相關的問題。
5、將數據移動到絕對地址處
第一個問題,我們得到數據,因為是在實模式下,所以一般是放在1M地址空間內,怎樣將它移動到指定的地方去,
在setup.S 的源代碼中,使用了int 15h(87h號功能)。這里將該段代碼稍加改動,做了些假設,列到下面:
流程是:
if (%cs:move_es==0)/*由于使用前move_es初始化為0,因此這是第一次調用,此時es:bx是要移動的數據
存放處bx=0,es低四為位為零表示es:bx在64K邊界上,fs的低8位指定目的地地址,
也以64K字節為單位,用不著那么精確,以簡化操作*/
{
將es右移四位,得到64K單位的8位地址(這樣一來,最多只能將數據移動到16M以下了),作為源數據
描述符中24位地址的高8位,低16位為零。
將fs的低8位作為目的地的描述符中24位地址的高8位,同樣,它的低16位為零。
將es存放在move_es中,es自然不會是零,因此以后再調用該例程時就進行正常的移動操作了。
ax清零返回。
}
else
{
if (bx==0)/*bx為零,表示數據已經滿64K了,應該進行實際的移動*/
{
調用int15h 87h號功能,進行實際的數據移動(64K, 0x8000個16字節塊)。
目的地址(24位)高8位增一,往后走64K
ax = 1
return;
}
else
{
ax = 0;
return;
}
}
# we will move %cx bytes from es:bx to %fs(64Kbytes per unit)
# when we first call movetohigh(%cs:move_es is zero),
# the es:bx and %edx is valid
# we configure the param first
# follow calls will move data actually
# %ax return 0 if no data really moved, and return 1 if there is data
# really to be moved
#
movetohigh:
cmpw $0, %cs:move_es
jnz move_second
# at this point , es:bx(bx = 0) is the source address
# %edx is the destination address
movb $0x20, %cs:type_of_loader
movw %es, %ax
shrw $4, %ax
movb %ah, %cs:move_src_base+2
movw %fs, %ax
movb %al, %cs:move_dst_base+2
movw %es, %ax
movw %ax, %cs:move_es
xorw %ax, %ax
ret # nothing else to do for now
move_second:
xorw %ax, %ax
testw %bx, %bx
jne move_ex
pushw %ds
pushw %cx
pushw %si
pushw %bx
movw $0x8000, %cx # full 64K, INT15 moves Words
pushw %cs
popw %es
leaw %cs:move_gdt, %si
movw $0x8700, %ax
int $0x15
jc move_panic # this, if INT15 fails
movw %cs:move_es, %es # we reset %es to always point
incb %cs:move_dst_base+2 # to 0x10000
popw %bx
popw %si
popw %cx
popw %ds
movw $1, %ax
move_ex:
ret
move_gdt:
.word 0, 0, 0, 0
.word 0, 0, 0, 0
move_src:
.word 0xffff
move_src_base:
.byte 0x00, 0x00, 0x01 # base = 0x010000
.byte 0x93 # typbyte
.word 0 # limit16,base24 =0
move_dst:
.word 0xffff
move_dst_base:
.byte 0x00, 0x00, 0x10 # base = 0x100000
.byte 0x93 # typbyte
.word 0 # limit16,base24 =0
.word 0, 0, 0, 0 # BIOS CS
.word 0, 0, 0, 0 # BIOS DS
move_es:
.word 0
move_panic:
pushw %cs
popw %ds
cld
leaw move_panic_mess, %si
call prtstr
move_panic_loop:
jmp move_panic_loop
move_panic_mess:
.string "INT15 refuses to access high mem, giving up."
6、用并口傳輸數據
用并口傳輸數據,可以從/usr/src/linux/driver/net/plip.c中抄一段,我們采用半字節協議,
并口線連接參考該文件。
字節收發過程如下:
#define PORT_BASE 0x378
#define data_write(b) outportb(PORT_BASE, b)
#define data_read() inportb(PORT_BASE+1)
#define OK 0
#define TIMEOUT 1
#define FAIL 2
int sendbyte(unsigned char data)
{
unsigned char c0;
unsigned long cx;
data_write((data & 0x0f));
data_write((0x10 (data & 0x0f)));
cx = 32767l * 1024l;
while (1) {
c0 = data_read();
if ((c0 & 0x80) == 0)
break;
if (--cx == 0)
return TIMEOUT;
}
data_write(0x10 (data >> 4));
data_write((data >> 4));
cx = 32767l * 1024l;
while (1) {
c0 = data_read();
if (c0 & 0x80)
break;
if (--cx == 0)
return TIMEOUT;
}
return OK;
}
int rcvbyte(unsigned char * pByte)
{
unsigned char c0, c1;
unsigned long cx;
cx = 32767l * 1024l;
while (1) {
c0