版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。若有錯誤,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/html
解析系統調用是瞭解內核架構最有力的一把鑰匙,在這以前先搞懂xenomai與linux兩個內核共存後系統調用是如何實現的。linux
爲何須要系統調用編程
linux內核中設置了一組用於實現系統功能的子程序,稱爲系統調用。系統調用和普通庫函數調用很是類似,只是系統調用由操做系統核心提供,運行於內核態,而普通的函數調用由函數庫或用戶本身提供,運行於用戶態。api
通常的,進程是不能訪問內核的。它不能訪問內核所佔內存空間也不能調用內核函數。CPU硬件決定了這些(這就是爲何它被稱做「保護模式」。數組
爲了和用戶空間上運行的進程進行交互,內核提供了一組接口。透過該接口,應用程序能夠訪問硬件設備和其餘操做系統資源。這組接口在應用程序和內核之間扮演了使者的角色,應用程序發送各類請求,而內核負責知足這些請求(或者讓應用程序暫時擱置)。實際上提供這組接口主要是爲了保證系統穩定可靠,避免應用程序肆意妄行,惹出大麻煩。安全
系統調用在用戶空間進程和硬件設備之間添加了一箇中間層。該層主要做用有三個:網絡
Linux加上實時系統內核xenomai後,實時任務常調用xenomai系統調用來完成實時的服務,若是實時任務須要用到linux的服務,還會調用linux的系統調用。架構
linux應用程序除直接系統調用外還會由glibc觸發系統調用,glibc爲了提升應用程序的性能,對一些系統調用進行了封裝。
32位系統系統調用使用軟中斷int 0x80
指令實現,軟中斷屬於異常的一種,經過它陷入(trap)內核,trap在整理的文檔x86 Linux中斷系統
有說明。tarp_init()
中設置IDT(Interrupt Descriptor Table 每一箇中斷處理程序的地址都保存在一個特殊的位置)由關int 0x80
的IDT以下:dom
static const __initconst struct idt_data def_idts[] = { ...... SYSG(IA32_SYSCALL_VECTOR, entry_INT80_32), ...... };
當生系統調用時,硬件根據向量號在 IDT 中找到對應的表項,即中斷描述符,進行特權級檢查,發現 DPL = CPL = 3 ,容許調用。而後硬件將切換到內核棧 (tss.ss0 : tss.esp0)。接着根據中斷描述符的 segment selector 在 GDT / LDT 中找到對應的段描述符,從段描述符拿到段的基址,加載到 cs 。將 offset 加載到 eip。最後硬件將 ss / sp / eflags / cs / ip / error code 依次壓到內核棧。因而開始執行entry_INT80_32
函數,該函數在entry_32.S
定義:函數
ENTRY(entry_INT80_32) ASM_CLAC pushl %eax /* pt_regs->orig_ax */ SAVE_ALL pt_regs_ax=$-ENOSYS /* *存儲當前用戶態寄存器,保存在pt_regs結構裏*/ /* * User mode is traced as though IRQs are on, and the interrupt gate * turned them off. */ TRACE_IRQS_OFF movl %esp, %eax call do_int80_syscall_32 .Lsyscall_32_done: ....... .Lirq_return: INTERRUPT_RETURN/*iret 指令將原來用戶態保存的現場恢復回來,包含代碼段、指令指針寄存器等。這時候用戶態 進程恢復執行。*/
在內核棧的最高地址端,存放的是結構 pt_regs,首先經過 push 和 SAVE_ALL 將當前用戶態的寄存器,保存在棧中 pt_regs 結構裏面.保存完畢後,關閉中斷,將當前棧指針保存到 eax,即do_int80_syscall_32的參數1。
調用do_int80_syscall_32=>do_syscall_32_irqs_on。先看看沒有ipipe時Linux實現以下:
__always_inline void do_syscall_32_irqs_on(struct pt_regs *regs) { struct thread_info *ti = pt_regs_to_thread_info(regs); unsigned int nr = (unsigned int)regs->orig_ax; ..... if (likely(nr < IA32_NR_syscalls)) { nr = array_index_nospec(nr, IA32_NR_syscalls); regs->ax = ia32_sys_call_table[nr]( /*根據系統調用號索引直接執行*/ (unsigned int)regs->bx, (unsigned int)regs->cx, (unsigned int)regs->dx, (unsigned int)regs->si, (unsigned int)regs->di, (unsigned int)regs->bp); } syscall_return_slowpath(regs); }
在這裏,將系統調用號從pt_reges中eax 裏面取出來,而後根據系統調用號,在系統調用表中找到相應的函數進行調用,並將寄存器中保存的參數取出來,做爲函數參數。若是仔細比對,就能發現,這些參數所對應的寄存器,和 Linux 的註釋是同樣的。ia32_sys_call_table
系統調用表生成後面解析(此圖來源於網絡)。
相關內核調用執行完後,一直返回到 do_syscall_32_irqs_on ,若是系統調用有返回值,會被保存到 regs->ax 中。接着返回 entry_INT80_32 繼續執行,最後執行 INTERRUPT_RETURN 。 INTERRUPT_RETURN 在 arch/x86/include/asm/irqflags.h
中定義爲 iret ,iret 指令將原來用戶態保存的現場恢復回來,包含代碼段、指令指針寄存器等。這時候用戶態進程恢復執行。
系統調用執行完畢。
Xenomai使用I-pipe 攔截常規Linux系統調用調度程序,並將系統調用定向到實現它們的系統。
實時系統調用,除了直接系統調用外,xenomai還實現了libcoblat實時庫,至關於glibc,經過libcoblat進行xenomai系統調用,以libcoblat庫函數sem_open爲例,libcolat庫中C函數實現以下:
COBALT_IMPL(sem_t *, sem_open, (const char *name, int oflags, ...)) { ...... err = XENOMAI_SYSCALL5(sc_cobalt_sem_open, &rsem, name, oflags, mode, value); if (err == 0) { if (rsem != sem) free(sem); return &rsem->native_sem; } ....... return SEM_FAILED; }
libcolat庫調用系統調用使用宏XENOMAI_SYSCALL5
,XENOAI_SYSCALL宏在\include\asm\xenomai\syscall.h
中聲明,XENOMAI_SYSCALL5
中的'5'表明'該系統調用有五個參數:
#define XENOMAI_DO_SYSCALL(nr, op, args...) \ ({ \ unsigned __resultvar; \ asm volatile ( \ LOADARGS_##nr \ "movl %1, %%eax\n\t" \ DOSYSCALL \ RESTOREARGS_##nr \ : "=a" (__resultvar) \ : "i" (__xn_syscode(op)) ASMFMT_##nr(args) \ : "memory", "cc"); \ (int) __resultvar; \ }) #define XENOMAI_SYSCALL0(op) XENOMAI_DO_SYSCALL(0,op) #define XENOMAI_SYSCALL1(op,a1) XENOMAI_DO_SYSCALL(1,op,a1) #define XENOMAI_SYSCALL2(op,a1,a2) XENOMAI_DO_SYSCALL(2,op,a1,a2) #define XENOMAI_SYSCALL3(op,a1,a2,a3) XENOMAI_DO_SYSCALL(3,op,a1,a2,a3) #define XENOMAI_SYSCALL4(op,a1,a2,a3,a4) XENOMAI_DO_SYSCALL(4,op,a1,a2,a3,a4) #define XENOMAI_SYSCALL5(op,a1,a2,a3,a4,a5) XENOMAI_DO_SYSCALL(5,op,a1,a2,a3,a4,a5)
每一個宏中,內嵌另外一個宏DOSYSCALL,即實現系統調用的int指令:int $0x80
。
#define DOSYSCALL "int $0x80\n\t"
系統調用過程硬件處理及中斷入口上節一致,從do_syscall_32_irqs_on
開始不一樣,有ipipe後變成下面這樣子:
static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs) { struct thread_info *ti = current_thread_info(); unsigned int nr = (unsigned int)regs->orig_ax;/*取出系統調用號*/ int ret; ret = pipeline_syscall(ti, nr, regs);/*pipeline 攔截系統調用*/ ...... done: syscall_return_slowpath(regs); }
套路和ipipe接管中斷相似,在關鍵路徑上攔截系統調用,而後調用ipipe_handle_syscall(ti, nr, regs)
讓ipipe來接管處理:
int ipipe_handle_syscall(struct thread_info *ti, unsigned long nr, struct pt_regs *regs) { unsigned long local_flags = READ_ONCE(ti->ipipe_flags); int ret; if (nr >= NR_syscalls && (local_flags & _TIP_HEAD)) {/*運行在head域且者系統調用號超過linux*/ ipipe_fastcall_hook(regs); /*快速系統調用路徑*/ local_flags = READ_ONCE(ti->ipipe_flags); if (local_flags & _TIP_HEAD) { if (local_flags & _TIP_MAYDAY) __ipipe_call_mayday(regs); return 1; /* don't pass down, no tail work. */ } else { sync_root_irqs(); return -1; /* don't pass down, do tail work. */ } } if ((local_flags & _TIP_NOTIFY) || nr >= NR_syscalls) { ret =__ipipe_notify_syscall(regs); local_flags = READ_ONCE(ti->ipipe_flags); if (local_flags & _TIP_HEAD) return 1; /* don't pass down, no tail work. */ if (ret) return -1; /* don't pass down, do tail work. */ } return 0; /* pass syscall down to the host. */ }
這個函數的處理邏輯是這樣,怎樣區分xenomai系統調用和linux系統調用?每一個CPU架構不一樣linux系統調用總數不一樣,在x86系統中有300多個,用變量NR_syscalls
表示,系統調用號與系統調用一一對應。首先獲取到的系統調用號nr >= NR_syscalls
,不用多想,那這個系統調用是xenomai內核的系統調用。
另外還有個問題,若是是Linux非實時任務觸發的xenomai系統調用,或者xenomai 實時任務要調用linux的服務,這些交叉服務涉及實時任務與非實時任務在兩個內核之間運行,優先級怎麼處理等問題。這些涉及cobalt_sysmodes[]
.
首先看怎麼區分一個任務是realtime仍是no_realtime。在task_struct
結構的頭有一個成員結構體thread_info
,存儲着當前線程的信息,ipipe在結構體thread_info
中增長了兩個成員變量ipipe_flags
和ipipe_data
,ipipe_flags
用來來標示一個線程是實時仍是非實時,_TIP_HEAD置位表示已是實時上下文。對於須要切換到xenomai上下文的系統調用_TIP_NOTIFY置位。
struct thread_info { unsigned long flags; /* low level flags */ u32 status; /* thread synchronous flags */ #ifdef CONFIG_IPIPE unsigned long ipipe_flags; struct ipipe_threadinfo ipipe_data; #endif };
ipipe_handle_syscall
處理邏輯:
1.對於已經在實時上下文的實時任務發起xenomai的系統調用,使用快速調用路徑函數ipipe_fastcall_hook(regs)
;
2.須要切換到實時上下文或者非實時調用實時的,使用慢速調用路徑:
__ipipe_notify_syscall(regs)
->ipipe_syscall_hook(caller_domain, regs)
快速調用ipipe_fastcall_hook(regs)
內直接handle_head_syscall
執行代碼以下:
static int handle_head_syscall(struct ipipe_domain *ipd, struct pt_regs *regs) { .... code = __xn_syscall(regs); nr = code & (__NR_COBALT_SYSCALLS - 1); ...... handler = cobalt_syscalls[code]; sysflags = cobalt_sysmodes[nr]; ........ ret = handler(__xn_reg_arglist(regs)); ....... __xn_status_return(regs, ret); ....... }
這個函數很複雜,涉及xenomai與linux之間不少聯繫,代碼是簡化後的,先取出系統調用號,而後從cobalt_syscalls
取出系統調用入口handler,而後執行handler(__xn_reg_arglist(regs))
執行完成後將執行結果放到寄存器ax
,後面的文章會詳細分析ipipe如何處理系統調用。
咱們再來看 64 位的狀況,系統調用,不是用中斷了,而是改用 syscall 指令。而且傳遞參數的寄存器也變了。
#define DO_SYSCALL(name, nr, args...) \ ({ \ unsigned long __resultvar; \ LOAD_ARGS_##nr(args) \ LOAD_REGS_##nr \ asm volatile ( \ "syscall\n\t" \ : "=a" (__resultvar) \ : "0" (name) ASM_ARGS_##nr \ : "memory", "cc", "r11", "cx"); \ (int) __resultvar; \ }) #define XENOMAI_DO_SYSCALL(nr, op, args...) \ DO_SYSCALL(__xn_syscode(op), nr, args) #define XENOMAI_SYSBIND(breq) \ XENOMAI_DO_SYSCALL(1, sc_cobalt_bind, breq)
這裏將系統調用號使用__xn_syscode(op)
處理了一下,把最高位置1,表示Cobalt系統調用,而後使用syscall 指令。
#define __COBALT_SYSCALL_BIT 0x10000000 #define __xn_syscode(__nr) (__COBALT_SYSCALL_BIT | (__nr))
syscall 指令還使用了一種特殊的寄存器,咱們叫特殊模塊寄存器(Model Specific Registers,簡稱 MSR)。這種寄存器是 CPU 爲了完成某些特殊控制功能爲目的的寄存器,其中就有系統調用。在系統初始化的時候,trap_init 除了初始化上面的中斷模式,這裏面還會調用 cpu_init->syscall_init。這裏面有這樣的代碼:
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
rdmsr 和 wrmsr 是用來讀寫特殊模塊寄存器的。MSR_LSTAR 就是這樣一個特殊的寄存器, 當 syscall 指令調用的時候,會從這個寄存器裏面拿出函數地址來調用,也就是調entry_SYSCALL_64。
該函數在'entry_64.S'定義:
ENTRY(entry_SYSCALL_64) UNWIND_HINT_EMPTY ...... swapgs /* * This path is only taken when PAGE_TABLE_ISOLATION is disabled so it * is not required to switch CR3. */ movq %rsp, PER_CPU_VAR(rsp_scratch) movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp /* Construct struct pt_regs on stack */ pushq $__USER_DS /* pt_regs->ss */ pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */ pushq %r11 /* pt_regs->flags */ pushq $__USER_CS /* pt_regs->cs */ pushq %rcx /* pt_regs->ip *//*保存用戶太指令指針寄存器*/ GLOBAL(entry_SYSCALL_64_after_hwframe) pushq %rax /* pt_regs->orig_ax */ PUSH_AND_CLEAR_REGS rax=$-ENOSYS TRACE_IRQS_OFF /* IRQs are off. */ movq %rsp, %rdi call do_syscall_64 /* returns with IRQs disabled */ TRACE_IRQS_IRETQ /* we're about to change IF */ /* * Try to use SYSRET instead of IRET if we're returning to * a completely clean 64-bit userspace context. If we're not, * go to the slow exit path. */ movq RCX(%rsp), %rcx movq RIP(%rsp), %r11 cmpq %rcx, %r11 /* SYSRET requires RCX == RIP */ jne swapgs_restore_regs_and_return_to_usermode ....... testq $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11 jnz swapgs_restore_regs_and_return_to_usermode /* nothing to check for RSP */ cmpq $__USER_DS, SS(%rsp) /* SS must match SYSRET */ jne swapgs_restore_regs_and_return_to_usermode /* * We win! This label is here just for ease of understanding * perf profiles. Nothing jumps here. */ syscall_return_via_sysret: /* rcx and r11 are already restored (see code above) */ UNWIND_HINT_EMPTY POP_REGS pop_rdi=0 skip_r11rcx=1 /* * Now all regs are restored except RSP and RDI. * Save old stack pointer and switch to trampoline stack. */ movq %rsp, %rdi movq PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp pushq RSP-RDI(%rdi) /* RSP */ pushq (%rdi) /* RDI */ /* * We are on the trampoline stack. All regs except RDI are live. * We can do future final exit work right here. */ SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi popq %rdi popq %rsp USERGS_SYSRET64 END(entry_SYSCALL_64)
這裏先保存了不少寄存器到 pt_regs 結構裏面,例如用戶態的代碼段、數據段、保存參數的寄存器.
而後調用 entry_SYSCALL64_slow_pat->do_syscall_64
。
__visible void do_syscall_64(struct pt_regs *regs) { struct thread_info *ti = current_thread_info(); unsigned long nr = regs->orig_ax; /*取出系統調用號*/ int ret; enter_from_user_mode(); enable_local_irqs(); ret = ipipe_handle_syscall(ti, nr & __SYSCALL_MASK, regs); if (ret > 0) { disable_local_irqs(); return; } if (ret < 0) goto done; ...... if (likely((nr & __SYSCALL_MASK) < NR_syscalls)) { nr = array_index_nospec(nr & __SYSCALL_MASK, NR_syscalls); regs->ax = sys_call_table[nr]( regs->di, regs->si, regs->dx, regs->r10, regs->r8, regs->r9); } done: syscall_return_slowpath(regs); }
與32位同樣,ipipe攔截了系統調用,後面的處理流程相似因此,不管是 32 位,仍是 64 位,都會到linux系統調用表 sys_call_table
和xenomai系統調用表cobalt_syscalls[]
這裏來。
xenomai每一個系統的系統系統調用號在\cobalt\uapi\syscall.h
中:
#define sc_cobalt_bind 0 #define sc_cobalt_thread_create 1 #define sc_cobalt_thread_getpid 2 ...... #define sc_cobalt_extend 96
bind()
函數在內核代碼中對應的聲明和實現爲:
/*聲明*/ #define COBALT_SYSCALL_DECL(__name, __args) \ long CoBaLt_ ## __name __args static COBALT_SYSCALL_DECL(bind, lostage, (struct cobalt_bindreq __user *u_breq)); /*實現*/ #define COBALT_SYSCALL(__name, __mode, __args) \ long CoBaLt_ ## __name __args static COBALT_SYSCALL(bind, lostage, (struct cobalt_bindreq __user *u_breq)){......}
其中__name
表示系統調用名對應bind、__mode
表示該系統調用模式對應lostage。COBALT_SYSCALL
展開定義的bind函數後以下:
long CoBaLt_bind(struct cobalt_bindreq __user *u_breq){......}
怎麼將CoBaLt_bind
與系統調用號sc_cobalt_bind
聯繫起來後放入cobalt_syscalls[]
的呢?
在編譯過程當中Makefile使用腳本gen-syscall-entries.sh
處理各個.c
文件中的COBALT_SYSCALL宏,生成一個頭文件syscall_entries.h
,裏面是對每一個COBALT_SYSCALL宏處理後後的項,以上面COBALT_SYSCALL(bind,...)
爲例syscall_entries.h
中會生成以下兩項,第一項爲系統調用入口,第二項爲系統調用的模式:
#define __COBALT_CALL_ENTRIES __COBALT_CALL_ENTRY(bind) #define __COBALT_CALL_MODES __COBALT_MODE(lostage)
實時系統調用表cobalt_syscalls[]
定義在文件kernel\cobalt\posix\syscall.c
中:
#define __syshand__(__name) ((cobalt_syshand)(CoBaLt_ ## __name)) #define __COBALT_NI __syshand__(ni) #define __COBALT_CALL_NI \ [0 ... __NR_COBALT_SYSCALLS-1] = __COBALT_NI, \ __COBALT_CALL32_INITHAND(__COBALT_NI) #define __COBALT_CALL_NFLAGS \ [0 ... __NR_COBALT_SYSCALLS-1] = 0, \ __COBALT_CALL32_INITMODE(0) #define __COBALT_CALL_ENTRY(__name) \ [sc_cobalt_ ## __name] = __syshand__(__name), \ __COBALT_CALL32_ENTRY(__name, __syshand__(__name)) #define __COBALT_MODE(__name, __mode) \ [sc_cobalt_ ## __name] = __xn_exec_##__mode, #include "syscall_entries.h" /*該頭文件由腳本生成*/ static const cobalt_syshand cobalt_syscalls[] = { __COBALT_CALL_NI __COBALT_CALL_ENTRIES }; static const int cobalt_sysmodes[] = { __COBALT_CALL_NFLAGS __COBALT_CALL_MODES };
__COBALT_CALL_NI宏表示數組空間大小爲__NR_COBALT_SYSCALLS(128),每一項由__COBALT_CALL_ENTRIES定義,即腳本頭文件syscall_entries.h
中生成的每一項來填充:
#define __COBALT_CALL_ENTRY(__name) \ [sc_cobalt_ ## __name] = __syshand__(__name), \ __COBALT_CALL32_ENTRY(__name, __syshand__(__name))
__COBALT_CALL32_ENTRY
是定義兼容的系統調用,宏展開以下,至關於在數組的多個位置定義包含了同一項CoBaLt_bind
:
#define __COBALT_CALL32_ENTRY(__name, __handler) \ __COBALT_CALL32x_ENTRY(__name, __handler) \ __COBALT_CALL32emu_ENTRY(__name, __handler) #define __COBALT_CALL32emu_ENTRY(__name, __handler) \ [sc_cobalt_ ## __name + 256] = __handler, #define __COBALT_CALL32x_ENTRY(__name, __handler) \ [sc_cobalt_ ## __name + 128] = __handler,
最後bind系統調用在cobalt_syscalls[]中以下
static const cobalt_syshand cobalt_syscalls[] = { [sc_cobalt_bind] = CoBaLt_bind, [sc_cobalt_bind + 128] = CoBaLt_bind, /*x32 support */ [sc_cobalt_bind + 256] = CoBaLt_bind, /*ia32 emulation support*/ ..... };
相應的數組cobalt_sysmodes[]
中的內容以下:
static const int cobalt_sysmodes[] = { [sc_cobalt_bind] = __xn_exec_bind, [sc_cobalt_bind + 256] = __xn_exec_lostage, /*x32 support */ [sc_cobalt_bind + 128] = __xn_exec_lostage, /*ia32 emulation support*/ ...... };
上面說到,ipipe管理應用的系統調用時須要分清該系統調用是否合法,是否須要域切換等等。cobalt_sysmodes[]
就是每一個系統調用對應的模式,控制着每一個系統調用的調用路徑。系統調用號爲下標,值爲具體模式。每一個系統調用的sysmode如何生成見上一節,仍是以實時應用的bind
系統調用爲例:
static const int cobalt_sysmodes[] = { [sc_cobalt_bind] = __xn_exec_bind, [sc_cobalt_bind + 256] = __xn_exec_lostage, /*x32 support */ [sc_cobalt_bind + 128] = __xn_exec_lostage, /*ia32 emulation support*/ ...... };
xenomai中全部的系統調用模式定義以下:
/*xenomai\posix\syscall.c*/ #define __xn_exec_lostage 0x1 /*該系統調用必須運行在linux域*/ #define __xn_exec_histage 0x2 /*該系統調用必須運行在Xenomai域*/ #define __xn_exec_shadow 0x4 /*影子系統調用:必須映射調用方*/ #define __xn_exec_switchback 0x8 /*切換回切換; 呼叫者必須返回其原始模式*/ #define __xn_exec_current 0x10 /*在當前域中執行。*/ #define __xn_exec_conforming 0x20 /*在兼容域(Xenomai或Linux)中執行*/ #define __xn_exec_adaptive 0x40 /* 在-ENOSYS上嘗試在相反的域中從新啓動系統調用 */ #define __xn_exec_norestart 0x80 /*收到信號後不要從新啓動syscall*/ /*Shorthand初始化系統調用的簡寫*/ #define __xn_exec_init __xn_exec_lostage /*Xenomai空間中shadow系統調用的簡寫*/ #define __xn_exec_primary (__xn_exec_shadow|__xn_exec_histage) /*Linux空間中shadow系統調用的簡寫*/ #define __xn_exec_secondary (__xn_exec_shadow|__xn_exec_lostage) /*Linux空間中syscall的簡寫,若是有shadow則切換回去*/ #define __xn_exec_downup (__xn_exec_lostage|__xn_exec_switchback) /* 不可重啓主系統調用的簡寫 */ #define __xn_exec_nonrestartable (__xn_exec_primary|__xn_exec_norestart) /*域探測系統調用以一致模式啓動。*/ #define __xn_exec_probing (__xn_exec_conforming|__xn_exec_adaptive) /*將模式選擇移交給syscall。*/ #define __xn_exec_handover (__xn_exec_current|__xn_exec_adaptive)
使用一個無符號32 位數的每一位來表示一種模式,各模式註釋已經很清楚,不在解釋,後面文章解析ipipe是如何執行這些mode的。
使用一個無符號32 位數的每一位來表示一種模式,各模式註釋已經很清楚,不在解釋,後面文章解析ipipe是如何根據mode來處理的。
英特爾® 64 位和 IA-32 架構軟件開發人員手冊第 3 卷 :系統編程指南
極客時間專欄-趣談Linux操做系統 《linux內核源代碼情景分析》