三十天自制操作系統(tǒng)(10)

第22天

CPU中的IRQ中斷是從0x20號開始的,0x20也就是定時器中斷。0x0~0x1f都是CPU異常所使用的中斷。0x00是除零異常;0x06是非法指令異常;之前介紹的0x0d是一般保護異常;0x0c是棧異常。

我們一開始先講棧異常。所謂的棧異常,就是定義一個數(shù)組比如 a[100],但是卻在程序中訪問a[101],這就是棧異常。CPU不會因為應(yīng)用程序訪問超出定義的棧發(fā)出警告,但是如果應(yīng)用程序訪問超出操作系統(tǒng)定義的數(shù)據(jù)段或者代碼段就會產(chǎn)生0x0c號中斷。

int *inthandler0c(int *esp)
{
  struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
  struct TASK *task = task_now();
  char s[30];
  cons_putstr0(cons, "\nINT 0C :\n Stack Exception.\n");
  sprintf(s, "EIP = %08X\n", esp[11]);
  cons_putstr0(cons, s);
  return &(task->tss.esp0);
}

_asm_inthandler0c:
  STI
  PUSH  ES
  PUSH  DS
  PUSHAD
  MOV       EAX,ESP
  PUSH  EAX  
  MOV       AX,SS
  MOV       DS,AX
  MOV       ES,AX
  CALL  _inthandler0c
  CMP       EAX,0
  JNE       end_app
  POP       EAX
  POPAD
  POP       DS
  POP       ES
  ADD       ESP,4
  IRETD

0x0c異常的處理程序?qū)懞?,再把asm_inthandler0c注冊進idt里就可以了。下面的匯編代碼中把esp的值push到棧里,當作參數(shù)傳進inthandler0c函數(shù)中。在這個函數(shù)中把傳進去的esp作為int數(shù)據(jù)類型的指針,并加上了11,現(xiàn)在看看加上11后表示什么值。esp[07]為_asm_inthandler0c中pushad壓進去的值;esp[89]為_asm_inthandler0c中push進去的ds和es。esp[10~15]為異常產(chǎn)生時CPU自動push的結(jié)果,其中esp[11]為eip。

我們的操用系統(tǒng)已經(jīng)能處理2種操作系統(tǒng)的異常了。為了完善操作系統(tǒng)我們再增加一種功能:用戶按下某個按鍵時候,強制執(zhí)行當前正在運行的應(yīng)用程序。

我們現(xiàn)在有2個任務(wù)同時在運行,1個是一開機就運行的task_a,還有一個就是命令行任務(wù)task_console。如果task_console啟一個應(yīng)用程序,這個應(yīng)用程序很復雜,要運行很久,但是過了一段時間之后,用戶后悔了,不想再運行了,然后按下ctrl+f1結(jié)束應(yīng)用程序。那么處理ctrl+f1就不能在task_console里執(zhí)行,因為應(yīng)用程序運行的時候,task_console就沒有辦法處理消息隊列里的數(shù)據(jù)了,所以只能在task_a里處理。當a_task任務(wù)接收到ctrl+f1的時候察看task_console里是不是運行應(yīng)用程序,如果在運行應(yīng)用程序那么就把寄存器強制轉(zhuǎn)換為task_console的寄存器就可以了。

我們已經(jīng)實現(xiàn)了用C語言調(diào)用顯示字符的api,接下來可以寫顯示字符串的api了。

_api_putstr0:   ; void api_putstr0(char *s);
PUSH    EBX
MOV     EDX,2
MOV     EBX,[ESP+8]     ; s
INT     0x40
POP     EBX
RET

但是編繹執(zhí)行的時候程序無法正常運行。這里牽涉到可執(zhí)行文件格式的問題,接下來講講編繹,鏈接之后可執(zhí)行文件的格式了。

  • 0x0000:請求操作系統(tǒng)為應(yīng)用程序準備的數(shù)據(jù)段的大小
  • 0x0004:“Hari”字符串
  • 0x0008:數(shù)據(jù)段內(nèi)預(yù)備空間的大小
  • 0x000c:esp初始值和數(shù)據(jù)部分傳送目的的地址
  • 0x0010:hrb文件內(nèi)數(shù)據(jù)部分的大小
  • 0x0014:hrb文件內(nèi)數(shù)據(jù)部分從哪里開始
  • 0x0018:0xe9000000
  • 0x001c:應(yīng)用程序入口地址 - 0x20
  • 0x0020:malloc空間的起始地址

重點看0x000c中存放的esp的值,esp為棧地址,也就是說esp之前部分被認為是棧空間,要顯示的字符串也會被放到棧里。0x0018這個地址中的數(shù)據(jù)是按雙字存放的,以intel8086CPU存放數(shù)據(jù)的規(guī)定是高高低低,也就是說0x0018~0x0001b的內(nèi)存分別是00,00,00,e9。而e9就是jmp指令的機器碼。e9后面跟著的就是應(yīng)用程序入口地址-0x20的值。如果我們把這個文件讀入內(nèi)存然后從0x001b開始執(zhí)行的話,就可以正常jmp到程序的入口地址,然后就可以正常執(zhí)行了,在正常執(zhí)行之前把ss和sp,ds之類的寄存器設(shè)置好,使能正常指向程序的數(shù)據(jù)段就行了。下在是代碼段。

p = (char *) memman_alloc_4k(memman, finfo->size);
file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
  segsiz = *((int *) (p + 0x0000));
  esp    = *((int *) (p + 0x000c));
  datsiz = *((int *) (p + 0x0010));
  dathrb = *((int *) (p + 0x0014));
  q = (char *) memman_alloc_4k(memman, segsiz);
  *((int *) 0xfe8) = (int) q;
  set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
  set_segmdesc(gdt + 1004, segsiz - 1,      (int) q, AR_DATA32_RW + 0x60);
  for (i = 0; i < datsiz; i++) {
    q[esp + i] = p[dathrb + i];
  }
  start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
  memman_free_4k(memman, (int) q, segsiz);
  } else {
    cons_putstr0(cons, ".hrb file format error.\n");
  }
  memman_free_4k(memman, (int) p, finfo->size);
  cons_newline(cons);
  return 1;
}

這段代碼先判斷文件中的0x0004開始的4個字節(jié)是不是"Hari”字符串,如果不就認為不是可執(zhí)行文件。一個可執(zhí)行文件至少有36個字節(jié),因為文件頭就是36字節(jié)了,如果文件小于36字節(jié)肯定不是可執(zhí)行文件。由于鏈接程序自動將數(shù)據(jù)段調(diào)整為4K的倍數(shù) ,所以文件第1個字節(jié)肯定是0x00,所以也判斷一下第1個字節(jié),如果不為0的話肯定不是可執(zhí)行文件。

接下來寫顯示窗口的應(yīng)用程序。

  • edx = 5
  • ebx = 窗口緩沖區(qū)
  • esi = 窗口寬度
  • edi = 窗口高度
  • eax = 透明色
  • ecx = 窗口名稱

返回值eax = 用于操作窗口的句柄。

我們先寫應(yīng)用程序

int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_end(void);
char buf[150 * 50];
void HariMain(void)
{
  int win;
  win = api_openwin(buf, 150, 50, -1, "hello");
  api_end();
 }

這里api_openwin函數(shù)返回的是用于操作窗口的句柄,其實就是SHEET類型的指針。

再來寫api_openwin函數(shù)

_api_openwin:   ; int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
  PUSH  EDI
  PUSH  ESI
  PUSH  EBX
  MOV       EDX,5
  MOV       EBX,[ESP+16]    ; buf
  MOV       ESI,[ESP+20]    ; xsiz
  MOV       EDI,[ESP+24]    ; ysiz
  MOV       EAX,[ESP+28]    ; col_inv
  MOV       ECX,[ESP+32]    ; title
  INT       0x40
  POP       EBX
  POP       ESI
  POP       EDI
  RET

這程序很簡單,就是根據(jù)我們之前設(shè)計的寄存器的功能,賦值,然后調(diào)用0x40中斷

else if (edx == 5) {
  sht = sheet_alloc(shtctl);
  sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);
  make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);
  sheet_slide(sht, 100, 50);
  sheet_updown(sht, 3); 
  reg[7] = (int) sht;
}

我們默認把窗口放到(100,50)的位置。reg[7]就是中斷返回時候eax的值。

設(shè)計在窗口上寫字符串和畫方塊的api

顯示字符串a(chǎn)pi

  • edx = 6
  • ebx = 窗口句柄
  • esi = x座標
  • edi = y座標
  • eax = 色號
  • ecx = 字符串長度
  • ebp = 字符串

描繪方塊api

  • edx = 7
  • ebx = 窗口句柄
  • eax = x0
  • ecx = y0
  • esi = x1
  • edi = y1
  • ebp = 色號

接下來跟之前顯示窗口的功能差不多。

寫好之后應(yīng)用程序就可以這么調(diào)用;

int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);

char buf[150 * 50];

void HariMain(void)
{
  int win;
  win = api_openwin(buf, 150, 50, -1, "hello");
  api_boxfilwin(win,  8, 36, 141, 43, 3 /*黃色 */);
  api_putstrwin(win, 28, 28, 0 /* ?黑色 */, 12, "hello, world");
  api_end();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容