跳到主要內容

發表文章

目前顯示的是 3月, 2012的文章

6.828 homework 2

Q:在開機後,在0x10000c設breakpoint,以"x/24x %esp"觀察附近的記憶體,確認何處是stack,以及每一個非零值的意義。 Ans: 觀察到此時的暫存器與記憶體為 (gdb) info reg eax            0x0 0 ecx            0x0 0 edx            0x1f0 496 ebx            0x10074 65652 esp            0x7bbc 0x7bbc ebp            0x7bf8 0x7bf8 esi            0x0 0 edi            0x1126fc 1124092 eip            0x10000c 0x10000c eflags         0x46 [ PF ZF ] cs             0x8 8 ss             0x10 16 ds             0x10 16 es             0x10 16 fs             0x0 0 gs             0x0 0 (gdb) x/24x $esp 0x7bbc: 0x000 07dc4 0x00000000 0x00000000 0x00000000 0x7bcc: 0x00000000 0x00000000 0x00000000 0x00000000 0x7bdc: 0x00010074 0x00000000 0x00000000 0x00000000 0x7bec: 0x00000000 0x00000000 0x00000000 0x00000000 0x7bfc: 0x00007c4d 0x8ec031fa 0x8ec08ed8 0xa864e4d0 0x7c0c: 0xb0fa7502 0xe464e6d1 0x7502a864 0xe6dfb0fa 紅字部份為bootblock在bootasm.S的部份,對照bootblock.asm即可一一對應(BIOS會將first sector載入0x7c00)。

6.828 Lab1 part 2

Ex4:複習C語言中的pointer,並確認了解pointer.c的每一個位址與現象。 Ans: Too easy to describe... :P Ex5:如果bootloader的link address設定錯誤(不是0x7c00),第1行會出錯的指令為何? Ans: boot/boot.S的"ljmp    $PROT_MODE_CSEG, $protcseg",因為ljmp會將絕對位址載入。(先出現的jnz不會出錯是因為jnz是相對跳轉,透過gdb與boot.asm便可發現jnz後的相對位址都是一致的)。 Ex6:分別在BIOS進入到bootloader時與bootloader進入到kernel時觀察0x100000的前8個word,說明為何在這兩個時間點,同樣的記憶體位置的值會不同? Ans: 因為bootloader會根據kernel image所指定的位置(ELF program header中的LMA)將kernel的每個segment載入記憶體,所以新的值是kernel image的text segment。 Ex7:追蹤JOS kernel,觀察0x100000與0xf0100000兩處的記憶體。如果mapping沒有建立正確,哪條指令會出錯? Ans: kernel/entry.S中:         mov $relocated, %eax         jmp *%eax relocated: "jmp *%eax"會錯。因為link address定為0xf0100000,所以relocated的值為0xf01xxxxx,當mapping沒有正確設置,就會無法讓EIP正確地從0x1xxxxx跳轉到0xf01xxxxx。 Ex8:將八進制的顯示加進cprintf()中。 Ans: //in lib/printfmt.c void vprintfmt ( void   ( * putch ) ( int ,  void * ) ,  void   * putdat,  const   char   * fmt, va_list ap ) { ...          // (unsigned) octal        

What can we learn from digging stack?

stack 是資訊科學裡的基本概念之一,許多 精妙的演算法 依賴stack的先進後出特性來實現,而其演算法實現因為function call已經依賴於stack的機制,並且由編譯器幫我們產生相關 機器碼 ,所以往往不需在代碼中手動操作stack。 精妙的演算法將function call視為理所當然,以此為基礎往上發展遞迴算法,但除了往上看高階演算法之餘,不妨花點時間往下從機器的角度看看 function call的實現 ,理解了其實現原理後,我們就會發現,許多巧妙的系統程式機制其實都可以此為基礎開展而來: variable numbers of function parameters:透過解析stack上的參數,獲知使用者到底在參數列指定了幾個參數,然後再依序從stack上讀取參數來進行後續動作,可以參考對岸好手 李先靜 的 文章 。 backtrace:由於返回的位址與前一個stack frame的base address都可以從目前的stack frame中獲得,所以要實現backtrace就只需反覆讀取前一個stack frame所存的base address即可,可以參考我之前的 文章 。 OS bootstrap:當CPU一開機時並沒有高階語言可以執行的環境,所以只能使用assembly去實現一開始的部份。有趣的是,要如何從assembly切換到C function?基本上只要先stack frame可以正常使用,就能夠完成這項任務。然後,其他功能就繼續慢慢bootstrap~JOS或許是最容易理解的 實現 ,逐步的設定說明在 這份文檔 的appendice B。 context switch:作業系統中的多個task要進行切換才能方便實現多工與保護。而這個切換的工作概念上需要兩個步驟:scheduling(選出下一個task)以及context-switch(從目前task切換到下一個task)。在context switch中很重要的一步就是回到之前暫停的指令位址以及stack frame,所以必須從某個per-task的記憶體讀取出相關暫存器來恢復context。 ClearRTOS 的實作或許最容易理解,因為有 專書 說明。:-) coroutine:jserv的 好文章 講得非常清楚,也附上了簡易實作。 ... 很

Why do fork and exec separate from each other?

在UNIX的世界中,我們在shell下執行一個新的程式時,shell基本上會先fork(),然後再用exec*()來替換child process以執行指定的程式。但是既然fork()之後往往都要喚起exec*(),為什麼UNIX要將這兩個動作分開呢? 原因很簡單: 因為要讓使用者為child process設定好運行環境,而這個運行環境作業系統事先並不知道 。 舉例來說,如果要實現I/O redirection(Ex:cat < input.txt ),我們會有類似如下程式(從MIT的6.828 課程文件 擷取): char   * argv [ 2 ] ; argv [ 0 ]   =   "cat" ; argv [ 1 ]   =   0 ; if ( fork ( )   ==   0 )   {   close ( 0 ) ;   open ( "input.txt" , O_RDONLY ) ;   exec ( "cat" , argv ) ; } 而如果要實現pipe(Ex:echo "hello world" | wc),則會有類似如下程序: int  p [ 2 ] ; char   * argv [ 2 ] ; argv [ 0 ]   =   "wc" ; argv [ 1 ]   =   0 ; pipe ( p ) ; if ( fork ( )   ==   0 )   {   close ( 0 ) ;   dup ( p [ 0 ] ) ;   close ( p [ 0 ] ) ;   close ( p [ 1 ] ) ;   exec ( "/bin/wc" , argv ) ; }   else   {   write ( p [ 1 ] ,  "hello world \n " ,  12 ) ;   close ( p [ 0 ] ) ;   close ( p [ 1 ] ) ; } 從這兩個例子我們