使用C語言開發操作系統(1)

怎樣用C語言寫操作系統
儘管我的題目是“用C語言寫操作系統”,但是,僅僅使用C語言是寫不出操作系統的。我看到很多篇關於自己動手開發操作系統的文章,幾乎全來自一篇叫“Write Your Own Operating System Tutorial”英文文章,而且,使用的全是彙編語言。如今能夠精通彙編語言的人已屬鳳毛麟角,而且現代操作系統的主體部分是用C語言寫的,難道所謂的“Own Operating System”只能用彙編語言嗎?在下不才,使用C語言(在某些部分必須結合彙編語言)寫了一個可以與上述文章中提到的操作系統相媲美的操作系統,拋磚引玉,希望能夠將其發展成爲真正的中國人自己的操作系統。
我是在Windows環境下開發的,而不是大多數人選擇的Linux環境。開發工具也是Microsoft公司的開發工具:masm615和VC15。微軟公司的masm流傳甚廣,大家應該不陌生。對vc15可能較陌生,vc15堪稱開發DOS程序的最“高檔”編譯器(儘管有很多BUG)。如果實在找不到這些開發工具,使用TASM、Turbo C或者Borland C++都是可以的。所給出的例子以masm615和vc15爲準,轉移到TASM、TC或BC平臺,應該不難。有一條原則需要注意,在這裏,源碼必須使用TINY模式編譯,也就是說,必須生成實模式代碼。
1.          建立開發環境
這一步非常的簡單。
將masm613和vc15的壓縮包分別解壓到e:/masm615和e:/msvc15目錄下。你也可以放到其他目錄下,根據自己的情況而定,但是下面用到的編譯命令需要作相應的修改。也不需要添加或修改任何的環境變量。

2.          IBM PC的啓動及當時的內存使用情況

這一部分內容已經是老生常談了,但又不能不說。我們只說從硬盤引導的情況。
BIOS經過POSTPower On Test Self)後,將硬盤MBR讀到內存0x0000:0x7C00的位置,然後從這裏開始執行。一般的情況,MBR將選擇活動分區進行操作系統的啓動。在MBR開始執行時,內存使用的情況如下圖所示,地址數據用16進製表示:
 
               
 
這已經是老掉牙的內容了,但是,在20年前卻十分流行。如果想更詳細的瞭解這方面的內容,找本講解DOS的書看看吧。
我們自己的操作系統將被加載到0x1000:0x0100。這不是必需或者必然的,是人爲選擇的,你也可以將其放在0x4321:1234等其他地方。但是,上圖中註明有其他用途的內存區域,應該保留,否則,你會後悔的。

3.          開發操作系統

我們自己的操作系統運行在實模式環境下(如果您不知道什麼是實模式,也請看看20年前出版的當時非常流行的書,或者直接請教當時的前輩高手)。即使你的電腦是P4CPU,剛啓動時,也只相當於主頻較高的8086而已。但是,沒有關係。
首先,使用彙編語言寫一個框架,文件名是entry.asm
;
;   entry.asm
;   Copyright (C) 2004, Tian XiangYuan
;
.MODEL TINY,C
.386p
 
option expr32
option casemap:none
 
cmain PROTO NEAR C
 
.CODE
    ORG 0100h ;偏移地址
_start:
    jmp begin
    nop
    DB 'TianXiangYuan',0 ;the magic of my os
   
begin:
    cli
    mov ax,cs
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov sp,0FFFFh
    sti
   
    call cmain ;調用C語言寫的主函數
   
    mov ax,4c00h ;調用DOS的功能(爲了調試),與我們自己的操作系統無關
    int 21h
 
這段代碼非常簡單,應該沒有什麼問題。
已經說了,操作系統將從0x1000:0x0100加載,說是無心,實則有意。我們知道,TINY模式的程序,在DOS下運行時,其起始地址就是0x0100,前面的256Byte是參數部分。如果直接將操作系統在系統啓動時加載到0x1000:0x0100,調試時非常麻煩。我們將其起始地址設爲0x0100,使其可以在DOS下運行(這也是在程序的最後包含int 21h指令的原因),確認正確無誤後,再進行下一步的開發。
下面再看C語言的代碼,文件名是main.c
……
static void InitShell()
{
}
void cmain()
{
    InitShell();
    TermShell();
}
 
顧名思義,其中實現了一個簡單的shell。因爲該程序本身是操作系統的一部分,所以,平時經常使用的一些C庫函數,在這裏就不能使用了。總之,一切都要自己動手實現。幸好,在實模式下,幾乎所有的設備的驅動都包含在BIOS中了,我們可以直接使用。否則,連從鍵盤讀一個鍵值這樣的事都需要自己寫鍵盤的驅動程序,實在太難了。也是這個原因,我們自己的操作系統沒有將CPU轉到保護模式下,有心之人可以試試。
下面的事情幾乎都可以使用C語言實現了。
第一,初始化顯示模式。系統啓動時,顯卡已經被初始化成3模式了,就是80X25的彩色模式(除非你的顯示器是單色顯示器),我們不需要再做什麼了。當然,你也可以將顯卡設成VGA甚至SVGA模式,只要你的BIOS和顯卡支持。
第二,實現一個具有簡單交互功能的shell。代碼不全,請自己補齊,或參看附件。
/*
 *從鍵盤讀一個字符,如果沒有輸入,則等待;返回值的低字節爲asii碼,高字節爲鍵盤掃描碼
*/
static int getch()
{
    int chr=0;
    __asm
    {
        mov ah,00h
        int 16h
        mov chr,ax
    }
    return chr;
}
/*
*使用TTY模式向屏幕輸出一個字符
*/
static void putch(unsigned char key)
{
    __asm
    {
        mov bh,0
        mov al,key
        mov ah,0Eh
        int 10h
    }
}
 
#define KEY_BACKSPACE 0x08
#define KEY_ENTER 0x0D
#define KEY_NEWLINE 0x0A
#define KEY_ESCAPE 0x1B
 
static int printk(const char* str,...)
{
    …… //給大家一點空間,自己實現吧
}
static void endline()
{
    putch(KEY_NEWLINE); //Line Feed (LF)
    putch(KEY_ENTER); //Enter (CR)
}
static char msg_prompt[]="CMD:";
static void deal_cmd(char* cmd_line,int cmd_len)
{
…… //也請大家自己實現吧,例如,可以實現helpdirclshalt等命令
…… //其實,就是字符串比較的過程
}
static void TermShell()
{
    char cmd_line[80]={0,};
    int cmd_len=0;
   
    endline();
    printk(msg_prompt,sizeof(msg_prompt));
   
    for (;;)
    {
        cmd_line[cmd_len]=getch();
        switch(cmd_line[cmd_len])
        {
        case KEY_ENTER:
            if (cmd_len>1)
                deal_cmd(cmd_line,cmd_len);
            //break;
        case KEY_ESCAPE:
            cmd_len=0;
            endline();
            printk(msg_prompt,sizeof(msg_prompt));
            break;
        case KEY_BACKSPACE:
            if (cmd_len>0)
            {
                putch(0x08);
                putch(' ');
                putch(0x08);
                cmd_len--;
            }
            break;
        default:
            putch(cmd_line[cmd_len]);
            cmd_len++;
        }
    }
}
 
更復雜、功能更強大的方法請參考BIOS的相關文檔。也請大家發揮想象力,不斷的擴展功能。說心裏話,這個 “操作系統”比dos還原始!但畢竟是自己的操作系統。

4.          編譯方法

下面是build.bat的內容。各種編譯選項參看相關編譯器的說明文檔。
@echo off
set PATH=e:/masm615/bin;e:/msvc15/bin;
set AS=e:/masm615/bin/ml.exe
set AFLAGS=/AT /W3 /X /Gd /Zp1 /nologo
set CC=e:/msvc15/bin/cl.exe
set CFLAGS=/Od /G3 /Gd /Gs /Zl /Zp1 /X /W3 /nologo
del *.obj *.com *.cod *.dbg *.pdb *.map *.lst
%AS% /c %AFLAGS% entry.asm
if errorlevel 1 goto error
%CC% /c %CFLAGS% /Fc main.c
if errorlevel 1 goto error
%AS% %AFLAGS% /Fe"boot.com" entry.obj main.obj
if errorlevel 1 goto error
goto exit
 
:error
echo Failure......
:exit
pause
@echo on
 

5.          引導程序

我們自己的操作系統雖然寫完了,但是還沒有辦法引導,使其擁有系統的控制權。你可以使用第三方的引導程序,只要它能夠將我們的操作系統加載到內存的0x1000:0x0100,並且cpu工作在實模式下。如果實在找不到合適的引導程序,只有自己動手寫一個了。
;
;   bootsect.asm
;   Copyright(C) 2004, Tian XiangYuan
;
.MODEL TINY,C
.386p
 
option expr32
option casemap:none
 
SYSSEG EQU 1000h
SYSOFF EQU 0100h
 
.CODE
    ORG 7C00h
 
_start:
    jmp begin
    nop
    DB 'BOOTSECT',0 ;magic
   
    pack_size DB 16
    DB 0   ;reserved
    DW 60 ;sectors
    DW SYSOFF ;buf_addr_off
    DW SYSSEG ;buf_addr_seg
    DD 2 ;sector_from
    DD 0 ;sector_from_high
   
begin:
    cli
    mov ax,cs
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov sp,0FFFFh
    sti
   
    mov cx,msg_load_len ;length
    lea bp,msg_load ;es:bp
    call display_msg
    ; read disk for my OS
    lea si,pack_size
    mov dl,80h
    mov ax,4200h
    int 13h ;使用LBA方式讀硬盤
    jc error
   
    ; test magic of my os
    lea si,magic_test ;ds:si
    mov ax,SYSSEG
    mov es,ax
    mov di,SYSOFF
    add di,3 ;es:di
    mov cx,magic_test_len
    cld
test_again:
    cmpsb
    jnz error
    loop test_again
   
    push SYSSEG
    push SYSOFF
    retf ;轉入操作系統執行
 
error:
    mov ax,cs
    mov es,ax
    lea bp,msg_error ;es:bp
    mov cx,msg_error_len
    call display_msg
 
failure:
    hlt
    jmp failure
 
;cx    : length of message
;es:bp : address of message
;void display_msg();
display_msg PROC NEAR C
    ;scrollup a line
    push cx
    push bp
    mov ax,0601h
    mov bh,07h
    mov cx,0000h ;y/x
    mov dx,184Fh ;y2/x2, 24/79
    int 10h
    pop bp
    pop cx
    ;display message
    mov ax,1301h
    mov bx,000Ah
    ;mov cx,msg_error_len
    mov dl,0  ;x
    mov dh,24 ;y
    ;lea bp,msg_error ;es:bp
    int 10h
    ret
display_msg endp
 
.DATA
 
msg_load DB 'Loading......',0
msg_load_len DW $ - msg_load
 
msg_error DB 'NO BOOTER,please reboot!',0
msg_error_len DW $ - msg_error
 
magic_test DB 'TianXiangYuan',0
magic_test_len DW $ - magic_test
 
    end _start
 
上面這段代碼,是MBR的內容,他使用LBA方式讀硬盤(支持大硬盤),將我們自己的操作系統讀入0x1000:0x0100,然後轉入操作系統執行。
很顯然,我們的操作系統必須放在硬盤的No.2LBA定位方式)扇區開始的60個扇區之內(實際上我們的操作系統遠沒有這麼大),這時我們的引導程序的硬性規定,如果使用第三方的引導程序,也許會更方便。
可以自己開發安裝程序,也可以使用WinHex工具將其寫入硬盤。請一定注意數據安全,不要將硬盤分區搞壞,建議做好備份工作,或者使用沒有重要數據的測試硬盤。順便說一下,我是使用WinHex寫入測試硬盤的。
這段引導程序的編譯方式與操作系統一樣,build.dat文件如下:
@echo off
set PATH=e:/masm615/bin;
set AS=e:/masm615/bin/ml.exe
set AFLAGS=/AT /W3 /WX /Gd /Zp1 /X /nologo
del *.obj *.com *.cod *.dbg *.pdb *.map
%AS% /c %AFLAGS% bootsect.asm
if errorlevel 1 goto error
%AS% %AFLAGS% /Fe"bootsect.com" bootsect.obj
if errorlevel 1 goto error
goto exit
:error
echo Failure......
:exit
pause
@echo on
 

6.          後記

我們自己的操作系統太原始了,連DOS都不如!在這裏,主要介紹的是一種開發方法,旨在說明使用C語言如何開發像操作系統這樣底層的軟件。
閒暇時,將其移至Linux平臺下開發,並且使用cpu的保護模式,畢竟這樣才能發揮80x86系列cpu的強大功能。
歡迎提出寶貴意見:[email protected]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章