Linux 函數(shù)調(diào)用的用戶態(tài)與內(nèi)核態(tài)
在用戶態(tài)中,程序的執(zhí)行往往是一個函數(shù)調(diào)用另一個函數(shù)。函數(shù)調(diào)用都是通過棧來進行的。
在進程的內(nèi)存空間里面,棧是一個從高地址到低地址,往下增長的結(jié)構(gòu),也就是上面是棧底,下面是棧頂,入棧和出棧的操作都是從下面的棧頂開始的。
32 位操作系統(tǒng)在 CPU 里,ESP(Extended Stack Pointer)是棧頂指針寄存器,入棧操作 Push 和出棧操作 Pop 指令,會自動調(diào)整 ESP 的值。另外有一個寄存器 EBP(Extended Base Pointer),是?;刂分羔樇拇嫫?,指向當前棧幀的最底部。
例如,A 調(diào)用 B,A 的棧里面包含 A 函數(shù)的局部變量,然后是調(diào)用 B 的時候要傳給它的參數(shù),然后返回 A 的地址,這個地址也應該入棧,這就形成了 A 的棧幀。接下來就是 B 的棧幀部分了,先保存的是 A 棧幀的棧底位置,也就是 EBP。因為在 B 函數(shù)里面獲取 A 傳進來的參數(shù),就是通過這個指針獲取的,接下來保存的是 B 的局部變量等等。
當 B 返回的時候,返回值會保存在 EAX 寄存器中,從棧中彈出返回地址,將指令跳轉(zhuǎn)回去,參數(shù)也從棧中彈出,然后繼續(xù)執(zhí)行 A。
對于 64 位操作系統(tǒng),模式多少有些不一樣。因為 64 位操作系統(tǒng)的寄存器數(shù)目比較多。rax 用于保存函數(shù)調(diào)用的返回結(jié)果。棧頂指針寄存器變成了 rsp,指向棧頂位置。堆棧的 Pop 和 Push 操作會自動調(diào)整 rsp,?;羔樇拇嫫髯兂闪?rbp,指向當前棧幀的起始位置。
改變比較多的是參數(shù)傳遞。rdi、rsi、rdx、rcx、r8、r9 這 6 個寄存器,用于傳遞存儲函數(shù)調(diào)用時的 6 個參數(shù)。如果超過 6 的時候,還是需要放到棧里面。
然而,前 6 個參數(shù)有時候需要進行尋址,但是如果在寄存器里面,是沒有地址的,因而還是會放到棧里面,只不過放到棧里面的操作是被調(diào)用函數(shù)做的。
以上的棧操作,都是在進程的內(nèi)存空間里面進行的。
當系統(tǒng)調(diào)用從用戶態(tài)到內(nèi)核態(tài)的時候,首先要做的第一件事情,就是將用戶態(tài)運行過程中的 CPU 上下文保存起來,其實主要就是保存在這個結(jié)構(gòu)的寄存器變量里。這樣當從內(nèi)核系統(tǒng)調(diào)用返回的時候,才能讓進程在剛才的地方接著運行下去。
在用戶態(tài),應用程序進行了至少一次函數(shù)調(diào)用。32 位和 64 的傳遞參數(shù)的方式稍有不同,32 位的就是用函數(shù)棧,64 位的前 6 個參數(shù)用寄存器,其他的用函數(shù)棧。
在內(nèi)核態(tài),32 位和 64 位都使用內(nèi)核棧,格式也稍有不同,主要集中在 pt_regs 結(jié)構(gòu)上。
在內(nèi)核態(tài),32 位和 64 位的內(nèi)核棧和 task_struct 的關(guān)聯(lián)關(guān)系不同。32 位主要靠 thread_info,64 位主要靠 Per-CPU 變量。