五、系統調用(1)

運行模式
    Inter系列處理器有實模式和保護模式。剛啓動處於實模式只能使用實地址訪問內存。保護模式下可以使用段頁機制,虛地址尋址等,保護模式下還提供4個特權級,linux只使用特權級0(內核模式)和特權級3(用戶模式)。

地址空間
    Linux虛存管理機制下,進程使用虛擬地址,進程都有自己的虛擬空間,通過地址轉換機制轉換成對物理地址的引用。每個進程的虛擬地址空間可以劃分爲兩個部分:用戶空間和系統空間。用戶模式下只能訪問用戶空間,核心模式下可以訪問這兩個空間。系統空間中每個進程的虛擬地址都是3G-4G,所有進程的系統空間都會映射到單一內核地址空間。使得內核可以訪問任何進程的地址空間。
    進程執行系統調用時,通過特殊指令使系統陷入內核,並將控制權交給內核,由內核代替進程完成操作。完成後內核執行一組特徵指令返回到用戶態。

上下文

  • 用戶級上下文:正文、數據、用戶棧、共享存儲區等
  • 寄存器上下文:通用寄存器、程序寄存器、狀態寄存器、棧等
  • 系統級上下文:進程控制塊、內存管理信息、核心棧等
        發生進程調度時,操作系統必須對上面全部上下文進行切換。系統調用進行的是模式切換(只進行寄存器上下文切換)比進程切換容易得多。

地址裝換

這裏寫圖片描述
Linux更偏向使用頁機制,但是分段是Intel硬件機制所規定的所有使用兩級地址轉換。硬件除了使用自己的寄存器外還使用駐留在內存的描述符表、頁表等數據結構來進行地址轉換。

邏輯地址轉線性地址
這裏寫圖片描述
這裏寫圖片描述

  • 段選擇符:索引部分+TI部分+RPL部分(當前特權級)。RPL決定當前進程訪問相應段的權限;TI部分選擇局部描述符表(LDT)還是全局描述符表(GDT)。描述表中每個描述符描述一個段的段基址,加偏移地址就唯一確定一個線性地址。

    線性地址轉物理地址
    這裏寫圖片描述
    Linux分頁機制採用兩級分頁,段機制描述可大可小的存儲塊,分頁機制管理的是固定大小的頁(一般爲4K)。整個物理空間和線性空間都看成是由一個一個頁面組成。通過分頁機制可以把線性空間的一個頁面映射到物理空間的一個頁。

    arch/i386/kernel/entry.S

    進程執行int指令陷入到內核,系統對堆棧進行切換,首先獲取核心堆棧信息,再把用戶堆棧信息保存到核心棧,這幾步由硬件完成。

//寄存器入棧和出棧
#define SAVE_ALL \
    cld; \
    pushl %es; \
    pushl %ds; \
    pushl %eax; \
    pushl %ebp; \
    pushl %edi; \
    pushl %esi; \
    pushl %edx; \
    pushl %ecx; \
    pushl %ebx; \
//__KERNEL_DS在include/asm-i386/segment.h中定義爲
//#define __KERNEL_DS   0x18,即使用內核堆棧
    movl $(__KERNEL_DS),%edx; \
    movl %edx,%ds; \
    movl %edx,%es;

#define RESTORE_ALL \
    popl %ebx;  \
    popl %ecx;  \
    popl %edx;  \
    popl %esi;  \
    popl %edi;  \
    popl %ebp;  \
    popl %eax;  \
1:  popl %ds;   \
2:  popl %es;   \
    addl $4,%esp;  \
//iret返回指令,如果同級別,則彈出eip,cs,EFLAGS;
//如果是不同級別,則還需彈出堆棧指針esp,ss
3:  iret;       \

系統調用表依次保存着所有系統調用的函數指針

.data
ENTRY(sys_call_table)
    .long SYMBOL_NAME(sys_ni_syscall)   /* 0  -  old "setup()" system call*/
    .long SYMBOL_NAME(sys_exit)
    .long SYMBOL_NAME(sys_fork)
    .long SYMBOL_NAME(sys_read)
    .long SYMBOL_NAME(sys_write)
    .long SYMBOL_NAME(sys_open)     /* 5 */
    ..............
    .long SYMBOL_NAME(sys_ni_syscall)   /* reserved for fremovexattr */
//'.'表示當前地址,sys_call_table爲數組首地址
//(.-sys_call_table)/4表示已用系統調用數
//NR_syscalls-(.-sys_call_table)/4爲沒有定義的系統調用數
    .rept NR_syscalls-(.-sys_call_table)/4
//往剩餘空間填充sys_ni_syscall
        .long SYMBOL_NAME(sys_ni_syscall)
    .endr

這裏寫圖片描述

//linux核心棧的位置在task_struct之後的兩個頁面(8192處),
//通過-8192與上棧指針得到task_struct結構指針
#define GET_CURRENT(reg)
    movl $-8192, reg
    andl %esp, reg
 - 陷入進內核機器會自動保存和轉化堆棧
ENTRY(system_call)
    pushl %eax          #保存系統調用號
    SAVE_ALL
    GET_CURRENT(%ebx)//得到當前進程結構控制塊的指針
    testb $0x02,tsk_ptrace(%ebx)   # 若是受監視進程則跳轉
    jne tracesys
    cmpl $(NR_syscalls),%eax//判斷是否是可用系統調用
    jae badsys
    //根據系統調用號查詢系統調用表,調用對應函數
    call *SYMBOL_NAME(sys_call_table)(,%eax,4)
    movl %eax,EAX(%esp)     # save the return value
//從系統調用返回,判斷是否需要重新調度
//是否有進程向其發送信號,執行處理函數
//中斷也從這裏返回
ENTRY(ret_from_sys_call)
    cli             # need_resched and signals atomic test
    cmpl $0,need_resched(%ebx)
    jne reschedule
    cmpl $0,sigpending(%ebx)
    jne signal_return
restore_all:
    RESTORE_ALL

系統調用實現過程

  • arch/i386/kernel/traps.c中trap_init函數初始化中斷描述符表。
  • include/asm-i386/hw_irq.h中定義#define SYSCALL_VECTOR 0x80
  • 即在中斷描述表的第0x80項填入陷阱門描述符,使控制安全轉移到system_call
  • 這就保證每次執行int 0x80時,系統把控制權轉移到system_call
void __init trap_init(void)
{
    .........
    set_system_gate(SYSCALL_VECTOR,&system_call);
    .........
    cpu_init();
}
  • include/asm-i386/unistd.h定義了NR開頭系統調用對應的數字
  • 後面會通過宏把用戶調用的系統轉換成以NR開頭,再轉換成對應數字通過eax寄存器作爲syscall_table的索引
#define __NR_exit         1
#define __NR_fork         2
#define __NR_read         3
#define __NR_write        4
#define __NR_open         5
#define __NR_close        6

include/asm-i386/unistd.h中宏定義轉換

  1. _syscall0中0表示無參數的系統調用
  2. type是返回值類型,name是函數名稱
  3. volatile 表示編譯器不作優化,int $0x80進入軟中斷
  4. ‘=’表示__res爲輸出參數,‘a’表示佔用寄存器eax
  5. 下一行無‘=’表示輸入,此時系統調用name變爲__NR_name
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name)); \
__syscall_return(type,__res); \
}

int pause(void)
{
    long __res; \
    __asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_pause)); \
    __syscall_return(int,__res); \
}
  1. _syscall4中4表示有4個參數,每個參數有一個類型
  2. 參數通過寄存器ebx,ecx等傳遞
  3. SAVE_ALL那裏把寄存器值壓棧,一方面保護環境,另外把系統調用的參數也入棧,則系統調用處理函數可以從棧中得到參數
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
      "d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
} 
發佈了149 篇原創文章 · 獲贊 29 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章