運行模式
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中宏定義轉換
- _syscall0中0表示無參數的系統調用
- type是返回值類型,name是函數名稱
- volatile 表示編譯器不作優化,int $0x80進入軟中斷
- ‘=’表示__res爲輸出參數,‘a’表示佔用寄存器eax
- 下一行無‘=’表示輸入,此時系統調用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); \
}
- _syscall4中4表示有4個參數,每個參數有一個類型
- 參數通過寄存器ebx,ecx等傳遞
- 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); \
}