與普通函數一樣,系統調用通常需要一些輸入/輸出參數,這些參數可能包括實際值(即數字)、用戶模式進程地址空間中的變量地址,甚至包括指向用戶模式函數指針的數據結構的地址(參見第11章“信號相關的系統調用”部分)。
Linux系統下,所有系統調用的入口點都是system_call()或sysenter_entry(),這兩個函數至少需要一個參數:系統調用號,其保存在寄存器eax中。要不然,內核怎么知道你想干什么呢?比如,我們在寫多進程應用的時候,必須要用的fork()系統調用,在調用int $80或者sysenter匯編指令之前,必須設置eax寄存器的值為2(__NR_fork)。通常,這個操作都是在libc庫中的函數中完成的,對于系統調用號我們不太在意而已。
fork()系統調用不需要其他參數。然而,許多系統調用確實需要額外的參數,這些參數必須由應用程序顯式傳遞。例如,mmap()系統調用可能需要最多六個附加參數(除了系統調用號之外)。
普通C函數的參數通常將其值寫入程序棧(用戶態棧和內核態棧)來傳遞。因為系統調用是一種跨內核態和用戶態的特殊函數,所以,用戶態或內核態棧都不能使用。相反,在發起系統調用之前,將參數寫入CPU寄存器中。然后內核在調用系統調用服務例程之前將存儲在CPU寄存器中的參數復制到內核態棧上,因為后者是普通的C函數。
為什么內核不直接從用戶棧復制參數到內核棧?首先,同時處理兩個堆棧是很復雜的;其次,寄存器的使用使得系統調用處理程序的結構類似于其他異常處理程序的結構。
然而,要在寄存器中傳遞參數,必須滿足2個條件:
每個參數的長度不能超過寄存器的長度(32位)。
除了在eax中傳遞的系統調用號之外,參數的數量不能超過6個,因為80×86處理器的寄存器數量非常有限。
第一個條件總是為真,因為根據POSIX標準,不能存儲在32位寄存器中的大參數必須通過引用傳遞。一個典型的例子是settimeofday()系統調用,它必須讀取64位結構。
對于需要6個以上參數的系統調用,使用單個寄存器指向進程地址空間中包含參數值的內存區域。當然,程序員不必關心其中的細節。與每個C函數調用一樣,當調用封裝服務例程時,參數會自動保存在棧上。這個服務例程會采用合適方法將參數傳遞給內核。
傳遞系統調用號及其參數的寄存器依次為:eax(系統調用號)、ebx、ecx、edx、esi、edi和ebp。如前所述,system_call()和sysenter_entry()通過使用SAVE_ALL宏將這些寄存器的值保存在內核棧上。因此,當系統調用服務例程進入棧時,它會找到system_call()或sysenter_entry()的返回地址,然后是存儲在ebx中的參數(系統調用的第一個參數),存儲在ecx中的參數,等等(參見第4章“為中斷處理程序保存寄存器”一節)。這個堆棧配置與普通函數調用完全相同。因此,服務例程可以通過使用常用的c語言結構輕松地引用其參數。
讓我們看一個示例。sys_write()服務例程,用來處理write()系統調用,聲明如下:
intsys_write(unsignedintfd,constchar*buf,unsignedintcount)
C編譯器會將其編譯為匯編語言函數,并期望在棧頂,返回地址的正下方,也就是保存ebx,ecx和edx寄存器的地方找到fd、buf和count參數。
在許多情況下,系統調用可能不使用參數,但服務例程需要知道在系統調用發起之前CPU寄存器的內容。例如,do_fork()函數需要知道這些寄存器的值,以便將它們拷貝到子進程的thread字段域內(可以查看第3章的thread字段)。這種情況下,使用類型為pt_regs的單參數,允許服務例程訪問內核棧保存的值(使用SAVE_ALL宏保存,查看第4章的do_IRQ函數)。
intsys_fork(structpt_regsregs)
服務例程的返回值寫入到eax寄存器中,這是C編譯器自動完成的,當遇到return n;時就會自動將n保存到eax寄存器中。
1 驗證參數
內核在響應用戶系統調用之前必須檢查所有系統調用參數。檢查的類型取決于系統調用和具體參數。還是以write()系統調用為例:fd應該是一個標識文件的文件描述符,所以sys_write()必須檢查fd是之前打開的文件描述符嗎,進程是否允許對其進行寫操作。如果有條件不滿足,則返回負值,錯誤碼-EBADF。
但是,有一類型的檢查對于所有系統調用是通用的。每當參數指定地址時,內核必須檢查是否在進程的地址空間中。檢查有兩種方法:
驗證線性地址是否屬于進程地址空間,如果是,包含該地址的內存是否有正確的訪問權限。
僅驗證該線性地址小于PAGE_OFFSET(也就是沒有落在為內核保留的間隔地址范圍內)。
早期的Linux執行第一種檢查,但是這相當耗時,因為必須為系統調用中的每個地址參數執行檢查;更重要的是,這通常是無用的,因為錯誤程序并不總是常見。
因此,從2.2版本開始,Linux采用了第2種方案。該方法非常高效,因為不用對進程所使用的內存區域描述符進行掃描。很明顯,這是粗放式的檢查:驗證線性地址小于PAGE_OFFSET,是驗證其正確性的必要條件但不是充分條件。但是,使用這方法并沒有風險,因為后面會造成其它錯誤。
該方法將真正的檢查推遲到了最后時刻,也就是說,物理分頁單元將線性地址轉換成物理地址的時候。我們將在后面的“動態地址檢查:修復代碼”中,闡述頁錯誤異常處理程序如何檢測到那些用戶態傳遞給內核的錯誤地址(參數)。
此刻,可能有人會想為什么執行這種粗檢查?因為這對于保護進程地址空間和內核地址空間的非法訪問是至關重要的。第2章中我們得知,物理內存從線性地址PAGE_OFFSET開始映射。這意味著內核服務例程能夠訪問內存中的所有內存頁。因此,如果不做粗檢查,用戶進程可能會傳遞屬于內核地址空間的地址作為參數,然后,就能夠訪問這些內存而不會造成頁錯誤異常。
對系統調用的地址參數進行檢查可以使用access_ok()宏實現,主要作用于兩個參數:addr和size。它會檢查addr和addr + size - 1之前確定的間隔是否合理。本質上,等價于下面的C函數:
intaccess_ok(constvoid*addr,unsignedlongsize) { unsignedlonga=(unsignedlong)addr; if(a+sizecurrent_thread_info()->addr_limit.seg) return0; return1; }
該函數首先檢查addr + size是否大于2^32-1,因為GNU C編譯器(gcc)將無符號長整形和指針表示為32位數字,所以,相當于檢查溢出。該函數還會檢查addr + size是否超過了存儲在current的``thread_info數據結構中的addr_limit.seg字段中的值。對于普通進程,該字段為PAGE_OFFSET;對于內核線程,該值為0xffffffff。該字段可以通過get_fs和set_fs`宏動態修改;這允許內核繞過安全檢查,直接調用系統調用服務例程,直接將內核數據段中的地址傳遞給它們。
verify_area()可以執行access_ok()相同的功能。該函數已經廢棄。
2 訪問進程地址空間
系統服務例程經常需要讀寫進程地址空間中的數據。Linux提供了一組宏方便這種讀寫請求。下面我們描述其中的兩個:get_user( )和put_user( )。前者用來從用戶態地址空間讀取1、2或4個連續字節,后者則是寫入相同字節數。
函數具有兩個參數x和變量ptr。ptr決定傳輸多少個字節。因此,在get_user(x,ptr)函數中,會根據ptr指向變量的大小將函數展開為__get_user_1()、__get_user_2()或__get_user_4()匯編函數中的一個。下面以__get_user_2()為例:
__get_user_2: /*檢查用戶空間內存地址是否有效*/ addl$1,%eax/*eax+1,即用戶空間內存地址加1*/ jcbad_get_user/*如果進位標志位(carry)被設置,則跳轉到bad_get_user*/ /*用于檢查是否溢出*/ movl$0xffffe000,%edx/*棧大小設置為0xffffe000(4KB)*/ andl%esp,%edx/*將棧指針寄存器按照邊界對齊*/ cmpl24(%edx),%eax/*將棧基址+24,其所指向的內存地址與eax值進行比較*/ /*用于檢查線性地址是否小于addr_limit.seg*/ jaebad_get_user/*如果線性地址≥addr_limit.seg,則跳轉到bad_get_user*/ 2:movzwl-1(%eax),%edx/*eax中的地址減1,去除一個16位無符號數進行零擴展到32位*/ /*將結果寫入到edx中*/ xorl%eax,%eax/*異或操作,清零eax*/ ret/*返回到函數調用的下一條指令處*/ bad_get_user: /*異常處理代碼*/ xorl%edx,%edx/*清零edx*/ movl$-EFAULT,%eax/*返回結果-EFAULT寫入到eax寄存器*/ ret/*返回調用該函數的位置,結束函數的執行*/
寄存器eax包含要讀取的第一個字節的地址。前6條指令本質上執行與access_ok()宏相同的檢查:確保要讀取的2個字節地址不會超過4G,也小于當前進程addr_limit.seg字段的限制。這個字段在thread_info結構體中的偏移量是24,這就是為什么cmpl指令的第一個操作數尋址加24的原因。
如果地址合法,則執行movzwl指令,將要讀取的數據最低2個字節存儲到edx寄存器,而edx寄存器的高位設置為0,然后返回0(eax寄存器清零)。如果地址不合法,則清零edx,返回-EFAULT錯誤碼。
put_user(x,ptr)宏與get_user(x,ptr)類似,區別在于它是寫入數據。依賴x的大小,它選擇調用__put_user_asm( )宏(寫入1、2、4字節),還是調用__put_user_u64()(寫入8字節)。如果成功,這兩個宏都返回0(寫入eax寄存器),否則返回-EFAULT。
內核態下還有幾個其它函數和宏可以訪問用戶進程地址空間,如表10-1所示。注意,它們都有一個以下劃線(__)為前綴的變體。沒有下劃線的函數和宏會額外檢查想要訪問的線性地址塊是否合法,而有下劃線的則會繞過檢查。尤其是當內核需要重復訪問進程地址空間中同一片區域時,只在開始檢查一遍地址更有效率。
表10-1 可以訪問進程地址空間的函數和宏
函數 | 行為 |
---|---|
get_user __get_user |
從用戶空間讀取一個整型值(1、2、4字節) |
put_user __put_user |
寫一個整型值到用戶空間(1、2、4字節) |
copy_from_user __copy_from_user |
從用戶空間拷貝一塊任意大小的數據 |
copy_to_user __copy_to_user |
拷貝一塊任意大小的數據到用戶空間 |
strncpy_from_user __strncpy_from_user |
從用戶空間拷貝null結尾的字符串 |
strlen_user strnlen_user |
返回用戶空間中的字符串長度(以NULL結尾) |
clear_user __clear_user |
清空用戶空間的一段內存區域(填0) |
3 動態地址檢查:修復代碼
如前所示,access_ok()對系統調用的線性地址參數進行一個粗略檢查。這可以保證用戶進程不會偽造內核地址空間,但是,該線性地址仍然可能會不屬于進程地址空間。這種情況下,Page Fault異常將會發生。
在描述內核如何檢測這種類型錯誤之前,先讓我們確定內核態下可能發生Page Fault異常的4種情況。Page Fault異常處理程序必須區分這些情況,因為要采取的操作是完全不同的。
內核訪問的用戶態內存幀不存在,或是一個只讀頁,這種情況下,頁錯誤異常處理程序必須分配并初始化一個新內存幀。(參考第9章的按需分頁和寫時復制章節)
內核訪問自己的內存頁,但是相應的頁表項還沒有初始化(參考第9章的處理非連續內存區域訪問)。這種情況下,內核必須在當前進程的頁表中正確設置這些項。
某些內核函數有bug,會造成異常發生;或者,異常是由瞬時硬件錯誤導致的。當這種情況發生時,異常處理程序必須執行內核oops(參見第9章的在地址空間內處理錯誤地址一節)。
本章將要引入的情況:系統調用服務例程試圖讀寫不屬于進程地址空間的地址。
Page Fault異常處理程序可以通過確定錯誤的線性地址是否包含在進程所屬的內存區域中,就可以輕松識別出第一種情況。通過檢測相應的主內核頁表項是否包含映射該地址的非空項即可?,F在,讓我們看看如何識別余下的兩種情況。
4 異常表
確定Page Fault異常源的關鍵在于,內核可訪問進程地址空間的可用調用非常少。前面我們已經描述了,僅有一組函數和宏可以用來訪問用戶進程地址空間。因此,如果異常是由無效參數引起的,則導致異常的指令必須包含在其中的一個函數或宏展開的代碼中。所以說,處理用戶空間的指令數量相當少。
因此,將訪問進程地址空間的每個內核指令的地址放入到異常表中并不難。如果我們做到這一點,其它工作就很容易了。當Page Fault異常發生在內核態時,do_page_fault()異常處理程序會檢查異常表:如果包含觸發異常的指令,則錯誤就是由不合適的系統調用參數引起的;否則,可能就是由更嚴重的錯誤導致的。
Linux定義了幾個異常表。主異常表是在構建內核鏡像時由C編譯器自動產生的。存儲在內核代碼段的__ex_table段中,起始地址分別是__start___ex_table和__stop___ex_table,符號是由C編譯器產生的。
Linux 5.18.18中的鏈接文件定義(文件位置:include/asm-generic/vmlinux.lds.h):
/* *Exceptiontable */ #defineEXCEPTION_TABLE(align) .=ALIGN(align); __ex_table:AT(ADDR(__ex_table)-LOAD_OFFSET){ __start___ex_table=.; KEEP(*(__ex_table)) __stop___ex_table=.; }
更重要的是,內核中動態加載的模塊包含了自己獨立的異常表。這個異常表是在構建模塊鏡像時由C編譯器自動產生的,當模塊被插入到正在運行的內核中時,它會被加載到內存中。
異常表的每一項,都是一個類型為exception_table_entry的數據結構,包含兩個域:
insn
訪問進程地址空間指令的線性地址。
fixup
執行修正的匯編代碼的地址。insn指令觸發Page Fault異常時,調用這些匯編代碼。
修復代碼由一些匯編指令組成,用于解決由異常觸發的問題。正如我們將在本節后面看到的,修復代碼通常由一系列指令組成,這些指令強制服務例程向用戶態進程返回錯誤碼。這些指令通常定義在訪問進程地址空間的同一個宏或函數中,由C編譯器放置在稱為.fixup的內核代碼段的獨立部分中。
search_exception_tables()函數用于在所有異常表中搜索指定的地址:如果該地址包含在表中,則該函數返回指向相應exception_table_entry結構的指針;否則,返回NULL。因此,Page Fault處理程序do_page_fault()執行以下語句:
if((fixup=search_exception_tables(regs->eip))){ regs->eip=fixup->fixup; return1; }
正常情況下,regs->eip指向異常發生時內核態棧上保存的eip寄存器值。如果寄存器中的值(發生異常的指令地址)在異常表中,do_page_fault()則用search_exception_tables()返回的表項中的地址替換寄存器中的值。然后,Page Fault處理程序終止,被中斷的程序繼續執行修復代碼。
5 產生異常表和修復代碼
GNU Assembler的.section指令允許編程者指定可執行文件的哪個section包含后面跟隨的代碼。正如我們將在第20章看到的,一個可執行文件可以包含一個代碼segment,繼而,它又可以被分成幾個代碼section。因此,下面的代碼是將一個表項添加到異常表中;"a"屬性標識該代碼section必須和內核鏡像的其余部分一起加載到內存中:
.section__ex_table,"a" .longfaulty_instruction_address,fixup_code_address .previous
.previous指令用于回到之前的位置,也就是離開__ex_table代碼段的定義,繼續處理之前的代碼。
讓我們再次考慮前面提到的__get_user_1()、__get_user_2()和__get_user_4()函數。真正訪問進程地址空間的指令是那些標記為1、2和3處的匯編指令:
__get_user_1: [...] 1: movzbl (%eax), %edx [...] __get_user_2: [...] 2: movzwl -1(%eax), %edx [...] __get_user_4: [...] 3: movl -3(%eax), %edx [...] bad_get_user: xorl %edx, %edx movl $-EFAULT, %eax ret .section __ex_table,"a" .long 1b, bad_get_user .long 2b, bad_get_user .long 3b, bad_get_user .previous
Linux 5.18.18中的主要邏輯還是這樣,但是進行了代碼封裝。
每個異常表項由2個標簽組成。第1個是帶有b后綴的數字標簽,表示標簽是backward,表示標簽處的代碼在最近的前面。修復代碼對于這3個函數是通用的,標記為bad_get_user。如果標簽1、2或3處的匯編指令產生Page Fault異常,則執行修復代碼。此處,僅僅是向發起系統調用的進程返回一個-EFAULT錯誤碼。
查看作用于用戶地址空間的其它內核函數使用的修復代碼技術。例如,strlen_user(string)宏。該宏返回系統調用傳遞的一個以null結尾的字符串長度,如果發生錯誤,返回0。該宏實際產生以下匯編代碼:
movl $0, %eax ; 將eax設置為0, 作為計數器的初始值 movl $0x7fffffff, %ecx ; 將ecx設置為0x7fffffff, 即2^31-1, 即最大的有符號整數值 movl %ecx, %ebx ; 將ecx值拷貝到ebx中 movl string, %edi ; 將字符串string的地址賦值給edi寄存器 0: repne; scasb ; 執行重復動作,將edi指向的內存地址開始和累加器eax的值比較, ; 直到匹配到eax或ecx達到零。這個操作的目的是找到字符串中的 ; NULL字節,也就是字符串的結尾 subl %ecx, %ebx ; 用計數器ecx的值減去計數器ebx的值,得到字符串長度 movl %ebx, %eax ; 將計數器ebx的值(也就是字符串的長度)復制到累加器eax中 1: .section .fixup,"ax" ; 定義了一個為.fixup的代碼段,并指定屬性為`ax` 2: xorl %eax, %eax ; 異或操作,寄存器清零 jmp 1b ; 跳轉到標簽為1的代碼處 .previous .section __ex_table,"a" ; 定義了一個異常表, __ex_table, 屬性為a .long 0b, 2b ; 將標簽0和標簽2處的修復代碼地址存放到異常表項中 .previous
說明:
repne是重復執行指令
scas是用來搜索字符,后綴b表示按字節搜索
寄存器ecx和ebx被初始化為0x7fffffff,表示用戶態地址空間中字符串允許的最大長度。repne;scasb匯編指令迭代掃描edi指向的字符串,并尋找eax寄存器中的0值(也就是字符串?結束符)。因為scasb每次迭代都會減小ecx的值,所以eax最終存儲了字符串總字節數(也就是字符串長度)。
該宏的修復代碼被插入到.fixup段中。ax屬性表示該代碼段被加載到內存中且包含可執行代碼。如果標簽0的指令產生Page Fault異常,則執行修復代碼;修復代碼也僅僅是返回了錯誤值0,而沒有返回字符串長度,然后就跳轉到了標簽1處,標簽1后面對應的是宏后面的代碼。
第二個.section指令是將repne;scasb指令的地址和fixup代碼地址加入到異常表__ex_table所在的代碼段中。
6 架構調用約定
EABI和OABI
ABI是應用程序二進制接口,每個OS都會為運行在該OS的應用程序提供ABI。ABI包含了應用程序在這個OS下運行時必須遵守的編程約定。對于ARM架構而言,它定義了函數調用約定、系統調用形式以及目標文件格式等。
在ARM架構中,存在兩種不同的ABI形式,OABI和EABI,OABI中的O是old的意思,表示舊有的ABI,而EABI是基于OABI上的改進,或者說它更適合目前大多數的硬件,OABI和EABI的區別主要在于浮點的處理和系統調用。浮點的區別不做過多討論,對于系統調用而言,OABI和EABI最大的區別在于,OABI 的系統調用指令需要傳遞參數來指定系統調用號,而EABI中將系統調用號保存在r7中。
架構特定要求
每種結構ABI對于如何將系統調用參數傳遞到內核都有自己的要求。對于具有glibc封裝的系統調用(例如,大多數系統調用),glibc以適合架構的方式處理將參數復制到正確寄存器。然而,當使用syscall()進行系統調用時,調用者可能需要處理依賴于體系結構的細節;此要求在某些32位架構上最常遇到。
例如,對于ARM架構EABI,64位值(例如,long long)必須與偶數寄存器對對齊。因此,使用syscall()而不是glibc提供的封裝函數,readahead(2)系統調用將在ARM架構上以小端模式調用EABI,如下所示:
syscall(SYS_readahead,fd,0, (unsignedint)(offset&0xFFFFFFFF), (unsignedint)(offset>>32), count);
因為offset是64位,所以,fd占用了r0,填充0到r1,然后調用者需要手動將offset切割,以便將其存放到r2/r3這一對寄存器中。還需要注意大小端格式(依賴于平臺使用的C ABI約定)。
架構調用約定
每種架構都有調用和向內核傳遞參數的方式。細節可以參考下面的兩個表。
第一張表列出了轉換到內核態的調用指令(這可能不是最好或最快的方式,參考vdso機制),表示系統調用號的寄存器,表示返回系統調用結果的寄存器,以及表示發送錯誤信號的寄存器。
Arch/ABI | 指令 | 調用號 | 返回值 | 返回值 | 錯誤 | 注釋 |
---|---|---|---|---|---|---|
alpha | callsys | v0 | v0 | a4 | a3 | 1, 6 |
arc | trap0 | r8 | r0 | - | - | |
arm/OABI | swi NR | - | r0 | - | - | 2 |
arm/EABI | swi 0x0 | r7 | r0 | r1 | - | |
arm64 | svc #0 | w8 | x0 | x1 | - | |
blackfin | excpt 0x0 | P0 | R0 | - | - | |
i386 | int $0x80 | eax | eax | edx | - | |
ia64 | break 0x100000 | r15 | r8 | r9 | r10 | 1, 6 |
loongarch | syscall 0 | a7 | a0 | - | - | |
m68k | trap #0 | d0 | d0 | - | - | |
microblaze | brki r14,8 | r12 | r3 | - | - | |
mips | syscall | v0 | v0 | v1 | a3 | 1, 6 |
nios2 | trap | r2 | r2 | - | r7 | |
parisc | ble 0x100(%sr2, %r0) | r20 | r28 | - | - | |
powerpc | sc | r0 | r3 | - | r0 | 1 |
powerpc64 | sc | r0 | r3 | - | cr0.SO | 1 |
riscv | ecall | a7 | a0 | a1 | - | |
s390 | svc 0 | r1 | r2 | r3 | - | 3 |
s390x | svc 0 | r1 | r2 | r3 | - | 3 |
superh | trapa #31 | r3 | r0 | r1 | - | 4, 6 |
sparc/32 | t 0x10 | g1 | o0 | o1 | psr/csr | 1, 6 |
sparc/64 | t 0x6d | g1 | o0 | o1 | psr/csr | 1, 6 |
tile | swint1 | R10 | R00 | - | R01 | 1 |
x86-64 | syscall | rax | rax | rdx | - | 5 |
x32 | syscall | rax | rax | rdx | - | 5 |
xtensa | syscall | a2 | a2 | - | - |
注意:
有些架構中,可能會選擇一個寄存器當作布爾值使用(0表示無錯誤,-1表示錯誤),通過這種方式告知系統調用失敗。真正的錯誤值仍然存儲在返回寄存器中。在sparc架構上,處理器狀態寄存器psr中的進位標志位csr被用作一個完整寄存器使用。在powerpc64架構中,條件寄存器cr0中字段0中的加法溢出標志位(SO)會被當做錯誤寄存器使用。
NR是系統調用號
對于s390和s390x,如果系統調用號小于256,可能會直接通過svc NR傳遞。
對于SuperH架構,因為歷史原因支持額外的陷阱號,但是trapa #31是推薦使用的。
x32和x86-64共享系統調用表,但是有細微差別。
某些架構(如Alpha、IA-64、MIPS、SuperH、sparc/32、sparc/64)使用了額外的寄存器(返回值第二列),用其從pipe(2)系統調用中返回第二個返回值;Alpha還在系統調用getxpid(2)、getxuid(2)、getxgid(2)中使用這種技術。其它架構沒有使用第二個返回寄存器,即使在System V ABI定義了相關寄存器。
第二個表:傳遞系統調用參數的寄存器約定
Arch/ABI | arg1 | arg2 | arg3 | arg4 | arg5 | arg6 | arg7 | 注釋 |
---|---|---|---|---|---|---|---|---|
alpha | a0 | a1 | a2 | a3 | a4 | a5 | - | |
arc | r0 | r1 | r2 | r3 | r4 | r5 | - | |
arm/OABI | r0 | r1 | r2 | r3 | r4 | r5 | r6 | |
arm/EABI | r0 | r1 | r2 | r3 | r4 | r5 | r6 | |
arm64 | x0 | x1 | x2 | x3 | x4 | x5 | - | |
blackfin | R0 | R1 | R2 | R3 | R4 | R5 | - | |
i386 | ebx | ecx | edx | esi | edi | ebp | - | |
ia64 | out0 | out1 | out2 | out3 | out4 | out5 | - | |
loongarch | a0 | a1 | a2 | a3 | a4 | a5 | a6 | |
m68k | d1 | d2 | d3 | d4 | d5 | a0 | - | |
microblaze | r5 | r6 | r7 | r8 | r9 | r10 | - | |
mips/o32 | a0 | a1 | a2 | a3 | - | - | - | 1 |
mips/n32,64 | a0 | a1 | a2 | a3 | a4 | a5 | - | |
nios2 | r4 | r5 | r6 | r7 | r8 | r9 | - | |
parisc | r26 | r25 | r24 | r23 | r22 | r21 | - | |
powerpc | r3 | r4 | r5 | r6 | r7 | r8 | r9 | |
powerpc64 | r3 | r4 | r5 | r6 | r7 | r8 | - | |
riscv | a0 | a1 | a2 | a3 | a4 | a5 | - | |
s390 | r2 | r3 | r4 | r5 | r6 | r7 | - | |
s390x | r2 | r3 | r4 | r5 | r6 | r7 | - | |
superh | r4 | r5 | r6 | r7 | r0 | r1 | r2 | |
sparc/32 | o0 | o1 | o2 | o3 | o4 | o5 | - | |
sparc/64 | o0 | o1 | o2 | o3 | o4 | o5 | - | |
tile | R00 | R01 | R02 | R03 | R04 | R05 | - | |
x86-64 | rdi | rsi | rdx | r10 | r8 | r9 | - | |
x32 | rdi | rsi | rdx | r10 | r8 | r9 | - | |
xtensa | a6 | a3 | a4 | a5 | a8 | a9 | - |
注釋:mips/o32在用戶棧上傳遞5~8參數
審核編輯:湯梓紅
-
內核
+關注
關注
3文章
1363瀏覽量
40228 -
Linux
+關注
關注
87文章
11227瀏覽量
208924 -
參數
+關注
關注
11文章
1785瀏覽量
32086 -
函數
+關注
關注
3文章
4305瀏覽量
62430 -
系統調用
+關注
關注
0文章
28瀏覽量
8320
原文標題:linux內核-系統調用之參數傳遞
文章出處:【微信號:嵌入式ARM和Linux,微信公眾號:嵌入式ARM和Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論