陳民禾 原創作品轉載請注明出處 ——《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一.復習上周內容
上周主要學習了內核的啟動過程可以簡單地這么來看:start_kernel從內核一啟動的時候它會一直存在,這個就是0號進程,idle就是一個while0,一直在循環着,當系統沒有進程需要執行的時候就調度到idle進程,我們在windows系統上會經常見到,叫做system idle,這是一個一直會存在的0號進程,然后呢就是0號進程創建了1號進程,這個init_process是我們的1號進程也就是第一個用戶態進程,也就是它默認的就是根目錄下的程序,也就是常會找默認路徑下的程序來作為1號進程,1號進程接下來還創建了kthreadd來管理內核的一些線程,這樣整個程序就啟動起來了。
二.內核態、用戶態、中斷等概念的介紹
用戶態和內核態的區分:
現代計算機機中都有幾種不同的指令級別,在高執行級別下,代碼可以執行特權指令,訪問任意的物理地址,這種CPU執行級別就對應着內核態,而在相應的低級別執行狀態下,代碼的掌控范圍會受到限制,只能在對應級別允許的范圍內活動。舉例:Intrel x86 CPU有四種不同的執行級別0-3,Linux只使用了其中的0級和3級來分別表示內核態和用戶態。操作系統讓系統本身更為穩定的方式,這樣程序員自己寫的用戶態代碼很難把整個系統都給搞崩潰,內核的代碼經過仔細的分析有專業的人員寫的代碼會更加健壯一些,整個程序會更加穩定一些,注意:這里所說的地址空間是邏輯地址而不是物理地址。
用戶態和內核態的很顯著的區分就是:CS和EIP, CS寄存器的最低兩位表明了當前代碼的特權級別;CPU每條指令的讀取都是通過CS:EIP這兩個寄存器:其中CS是代碼段選擇寄存器,EIP是偏移量寄存器,上述判斷由硬件完成。一般來說在Linux中,地址空間是一個顯著的標志:0xc0000000以上的地址空間只能在內核態下訪問,0xc00000000-0xbfffffff的地址空間在兩種狀態下都可以訪問。
中斷處理是從用戶態進入到內核態的主要的方式:
#define SAVE_ALL RESTORE_ALL
"cld\n\t"\ popl %ebx;
"pushl %es\n\t"\ popl %ecx;
"pushl %ds\n\t"\ popl %ebx;
"pushl %eax\n\t"\ popl %edx;
"pushl %ebp\n\t"\ popl %esi;
"pushl %edi\n\t"\ popl %edi;
"pushl %esi\n\t"\ popl %ebp;
"pushl %edx\n\t"\ popl %eax;
"pushl %ecx\n\t"\ popl %ds;
"pushl %ebx\n\t"\ popl %es;
"movl $" STR(_KERNEL_DS)",%edx\n\t"\ addl $4,%esp;
"movl %edx,%ds\n\t"\ iret;
"movl %edx,%es\n\t"
iret指令與中斷信號(包括int指令),發生時的CPU的動作正好相反。
仔細分析一下中斷處理的完整過程:
SAVE_ALL -...//內核代碼,完成中斷服務,可能會發生進程調度 RESTOER_ALL //完成之后再返回到原來的狀態 iret-pop cs:eip/ss:eip/eflag from kernel stack
三.系統調用概述

#include<stdio.h> #include<unistd.h> int main(void) { write(1,"hello world!5124\n",13); return 0; }
下面是我的命令行內容:
其中,write有三個參數,第一個是表示寫到終端屏幕上,1可以認為是屏幕的代號,第二個參數是寫的內容,我是把hello world!寫到屏幕上,並換行,第三個參數是寫入的字符串長度,長度要大於等於要輸出的字符串長度,否則只能輸出字符串的一部分。程序執行結果如下:
_asm_( 匯編語句模塊: 輸出部分:函數調用時候的參數 輸入部分:函數調用時候的參數 破壞描述部分): 即格式為asm("statements":output_regs:input_regs:clobbered_regs); 可以看成是一個函數,有時候可以加一個_volatile_來選擇讓編譯器優化或者不讓編譯器優化。
代碼如下:
#include<stdio.h> #include<unistd.h> int main() { int a; char *ch="hello world!\n"; asm volatile( "movl $0x4,%%eax\n\t" "movl $0x1,%%ebx\n\t" "movl $0x1,%%ecx\n\t" "movl $0xd,%%edx\n\t" "int $0x80\n\t" "movl %%eax,%0\n\t" :"=m"(a) :"s"(ch) ); return 0; }
write系統調用有三個參數,分別是:寫入的位置,內容和長度,所以轉化為匯編對應的寄存器為eax(系統調用號為4),ebx(參數),ecx(輸出位置),edx(參數長度)

