1. 程式人生 > >Linux核心分析——扒開系統呼叫的三層皮(下)

Linux核心分析——扒開系統呼叫的三層皮(下)

張瑜
《Linux核心分析》MOOC課程
http://mooc.study.163.com/course/USTC-1000029000

一、實驗內容

1. 通過核心的方式使用系統呼叫

上週是從使用者態來看系統呼叫,這周是從核心方面來看這個問題

用到的命令:

rm menu -rf //強制刪除當前menu
git clone http://git.shiyanlou.com/mengning/menu.git //重新克隆新版本的menu
cd menu
ls
make rootfs //rootfs是事先寫好的一個指令碼,自動編譯自動生成根檔案系統,同時自動啟動MenuOS

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

2. 將上週選擇的系統呼叫新增到MenuOS中

在menu資料夾中的 test.c檔案中,新增上週寫的Gitpid和Gitpidasm程式碼

int Getpid(int argc , char * argv[])
{
int pid;
pid=getpid();
printf("pid=%d\n",pid);
return 0;
}

int Getpidasm(int argc , char *argv[])
{
int pid;
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x14,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax, %0\n\t"
:"=m"(pid)
);
printf
("pid = %d\n",pid); return 0; } //在main函式中新增 MenuConfig(“getpid","Show pid",Getpid); MenuConfig("getpid-asm","Show pid(asm)",Getpidasm);

這裡寫圖片描述
重新make
這裡寫圖片描述

3.使用gdb跟蹤分析這該系統呼叫核心函式

用到的命令:

qemu -kernel  linux.3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S 除錯。

file linux-3.18.6/vmlinux 載入除錯核心符號表。

b 設定斷點   n 單步執行

這裡寫圖片描述
這裡寫圖片描述

二、系統呼叫過程分析 

1. 系統呼叫在核心程式碼中的工作機制和初始化

int 0x80——>system call:通過中斷向量匹配

system call——>sys_xyz():通過系統呼叫號匹配

一旦執行int 0x80後立刻跳轉到system_call執行

2. 系統呼叫system_call的處理過程

  syscall_call 函式到系統呼叫服務例程通過系統呼叫號聯絡起來:

在上面執行軟中斷 0x80 時,系統呼叫號會被放入eax暫存器(引數的傳遞);
  函式讀取eax暫存器獲取引數(當前系統呼叫的呼叫號),將其乘以4生成偏移地址。
其中 sys_call_table 基址在檔案arch/x86/kernel/syscall_table_32.S 中定義,同時表中每一項例程的地址佔用4個位元組,所以上面乘以4。

  由於系統呼叫例程在定義時時用 asmlinkage 標記了的,所以編譯器僅從堆疊中獲取該函式的引數。在進入system_call函式前,使用者應用會把引數存放到暫存器中,system_call 函式執行時會首先把這些暫存器壓入堆疊。這樣對系統呼叫服務例程可以直接從堆疊照片能夠獲取引數。

3.從system_call開始到iret結束之間的過程

  從整體過程來看,系統通過 int 0x80 從使用者態進入核心態。在這個過程中系統先儲存了中斷環境,然後執行系統呼叫函式。system_call() 函式通過系統呼叫號查詢系統呼叫表 sys_cal_table 來查詢到具體的系統呼叫服務程序。在執行完系統呼叫後在執行 iret 之前,核心做了一系列檢查,用於檢查是否有新的中斷產生。如果沒有新的中斷,則通過已儲存的系統中斷環境返回使用者態。這樣就完成了一個系統呼叫過程。系統呼叫通過 INT 0x80 進入核心,跳轉到system_call() 函式,然後執行相應服務程序。因為代表了使用者程序,所以這個過程並不屬於中斷上下文,而是屬於程序上下文。
這裡寫圖片描述

4.系統呼叫處理過程的彙編程式碼分析

 無論是中斷返回(ret_from_intr) ,還是系統呼叫返回,都使用了 work_pending 和resume_userspace。對於巨集SAVE_ALL來說,這條語句會把將暫存器的值壓入堆疊當中,壓入堆疊的順序對應struct pt_regs ,出棧時,這些值傳遞到struct pt_regs的成員,實現從彙編程式碼向C程式傳遞引數。struct pt_regs可以在arch/x86/include/asm/ptrace.h中檢視。使用者態到核心態需要int 0x80進行中斷,只有生成了中斷向量後才可以切換狀態。中斷處理讓CPU停止當前工作轉為執行系統核心中預設的一些任務,因此必須要對當前CPU執行的任務進行執行現場的保護工作,並對一些其他工作進行檢查,完成呼叫後,再進行檢查,才能執行iret返回。系統內部呼叫涉及CPU架構等內容,不同的CPU對於系統呼叫的彙編具體程式碼是不一樣的。

.macro INTERRUPT_RETURN  ; 中斷返回
    iret
.endm
.macro SAVE_ALL          ; 保護現場
    ...
.macro RESTORE_INT_REGS
    ...
.endm

ENTRY(system_call)
    SAVE_ALL
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax, PT_EAX(%esp)  ; store the return value
syscall exit:
    testl $_TIF_ALLWORK_MASK, %ecx # current->work
    jne syscall_exit_work
restore_all:
    RESTORE_INT_REGS
irq_return:
    INTERRUPT_RETURN      
ENDPROC(system_call)

syscall_exit_work:
    testl  $_TIF_WORK_SYSCALL_EXIT, %ecx
    jz work_pending
END(syscall_exit_work)

work_pending:
    testb $_TIF_NEED_RESCHED, %cl
    jz work_notifysig
work_resched:
    call schedule
    jz restore_all
work_notifysig:
    ...                  ; deal with pending signals
END(work_pending)