1. 程式人生 > >2018-2019-1 20189210 《LInux核心原理與分析》第五週作業

2018-2019-1 20189210 《LInux核心原理與分析》第五週作業

一、系統呼叫實驗
1、庫函式將系統呼叫封裝起來,大多數程式設計師使用庫函式進行系統呼叫。
2、Linux作業系統的體系架構分為使用者態和核心態。CPU的執行級別對應的就是核心態,所有指令都可以執行。使用者態對應的就是低級別指令。
3、系統呼叫也是一種中斷,中斷處理是從使用者態進入核心態的主要方式,系統呼叫是一種特殊的中斷。
4、庫函式就是作業系統提供的API(應用程式程式設計介面),API只是函式定義。系統呼叫是通過軟中斷向核心發出了中斷請求,int指令的執行就會觸發一箇中斷請求。libc函式庫定義的一些API內部使用了系統呼叫的封裝例程(用於釋出系統呼叫),使程式設計師不需要彙編指令來觸發系統呼叫。
5、大部分的系統呼叫的封裝例程會返回一個整數,其值的含義依賴於對應的系統呼叫,返回值-1表示核心不能滿足程序的請求,0表示能夠滿足。
6、系統呼叫的三層機制:xyz()(API)、system_call(所有系統呼叫的入口) 、 sys_xyz()(中斷服務程式)。
7、系統呼叫是使用者進入核心的介面,並非核心函式。使用者程式通過某個系統呼叫進入核心後,會接著去執行這個系統呼叫對應的核心函式。這個核心函式 sys_xyz() 稱為系統呼叫的服務例程。
8、系統呼叫號將 xyz() 與 sys_xyz() 關聯起,系統呼叫號由 eax 暫存器傳遞。

  本實驗使用的是系統呼叫號為 38 的 sys_rename(),功能實現設計為將hello.c 檔案重新命名 newhello.c



(1)庫函式API實現:
系統呼叫rename,在核心中的系統呼叫處理函式為 sys_rename,其 函式原型:
asmlinkage long sys_rename(const char __user *oldname,const char __user *newname);
可以看出需要傳遞兩個引數,一個是舊檔名,一個是新檔名。由於是指標型別,所以此時的檔名也代表該檔案,也就是說,先找到叫 oldname 的檔案,然後把其名改為 newname。rename() 是 glibc 對sys_rename 的封裝,使用者在使用者態呼叫 rename(),將 oldname和newname 引數傳入,系統會產生中斷陷入核心態執行sys_rename。當重新命名成功時,函式返回0。
(2)嵌入彙編程式碼實現:
asm volatile("movl %2,%%ecx\n\t" //newname存入ecx
"movl %1,%%ebx\n\t" //oldname存入ebx
"movl $0x26,%%eax\n\t" //系統呼叫號存入eax
"int $0x80" //執行系統呼叫
:"=a"(ret)
:"b"(oldname),"c"(newname)
);
  把系統呼叫號38(16進位制是0x26)存入 eax,將 oldname 存入 ebx,將 newname 存入 ecx,通過執行 int $0x80 來執行系統呼叫,使應用程式陷入核心態,system_call 根據傳入的系統呼叫號在系統呼叫列表中查詢對應的核心函式,根據 ebx 和 ecx 中儲存的引數呼叫核心函式 sys_rename,執行完後將執行結果存放到 eax 中,最後返回 eax 中的值。
2、將“=a”換成 “=m“。


可以看出helko.c確實變成了newhello.c,但卻顯示沒有修改成功,即ret不等於0.既然確實執行了sys_rename,返回值0就應該儲存在eax中,返回採用的是“=m”,並沒有將0儲存進記憶體。

API方法實現系統呼叫實現非常便捷,只需知道函式原型即可,有很好的移植性。但是,如果 glibc 沒有封裝某個核心提供的系統呼叫時,就沒辦法通過此方法來呼叫核心函式。我可以利用 glibc 提供的syscall 函式直接呼叫。函式原型為long int syscall (long int sysno, ...)sysno 是系統呼叫號,在 sys/syscall.h 中有所有可能的系統呼叫號的巨集定義。程式碼改為(SYS_rename換成38也正確):

小結:
本週學習了系統呼叫的原理和實驗,如果使用者態要涉及核心態的操作,就需要通過系統呼叫來實現。應用程式在使用者態呼叫 API 函式,系統呼叫號和引數儲存到 eax,ebx 等暫存器中,通過 0x80 中斷向量觸發中斷陷入核心態,中斷服務程式根據系統呼叫號呼叫並執行對應的核心函式,執行完畢後將結果存放的 eax 中並返回給程式,程式返回使用者態。