LINUX内核分析第四周——扒开系统调用的三层皮
LINUX內(nèi)核分析第四周——扒開系統(tǒng)調(diào)用的三層皮
李雪琦 + 原創(chuàng)作品轉(zhuǎn)載請(qǐng)注明出處 + 《Linux內(nèi)核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
一、用戶態(tài)、內(nèi)核態(tài)和中斷處理過程
1. 用戶態(tài)和內(nèi)核態(tài)
CPU指令執(zhí)行級(jí)別:
- 執(zhí)行特權(quán)指令,訪問任意的物理地址——內(nèi)核態(tài)。
低級(jí)別:代碼只能在級(jí)別允許的特定范圍內(nèi)活動(dòng)——用戶態(tài)。在日常操作下,執(zhí)行系統(tǒng)調(diào)用的方式是通過庫(kù)函數(shù),庫(kù)函數(shù)封裝系統(tǒng)調(diào)用,為用戶提供接口以便直接使用。
- Intel x86 CPU有四種不同的執(zhí)行級(jí)別0~3,Linux只用其中的0和3來(lái)表示內(nèi)核態(tài)和用戶態(tài)。
- 區(qū)分內(nèi)核態(tài)和用戶態(tài):CPU每條指令的讀取都是通過cs:eip,cs寄存器最低兩位表明了當(dāng)前代碼的特權(quán)級(jí)。
- 內(nèi)核態(tài)下可訪問所有地址空間。
- 0xc0000000(邏輯地址)以上的空間只能在內(nèi)核態(tài)下訪問。
- 0x00000000 ~ 0xbfffffff 內(nèi)核態(tài)和用戶態(tài)均可訪問。
用戶態(tài)轉(zhuǎn)換為內(nèi)核態(tài)的主要方式:中斷。
2. 中斷處理
- 用戶態(tài)到內(nèi)核態(tài)的切換:必須保存用戶態(tài)的寄存器上下文,包括用戶態(tài)棧頂?shù)刂?、?dāng)時(shí)的狀態(tài)字、cs:eip的值,以及內(nèi)核態(tài)的棧頂?shù)刂贰?dāng)時(shí)的狀態(tài)字、中斷處理程序入口。
- 中斷發(fā)生后的第一件事:保存現(xiàn)場(chǎng)(SAVE_ALL:保存需要用到的寄存器數(shù)據(jù))。
- 中斷處理結(jié)束前的最后一件事:恢復(fù)現(xiàn)場(chǎng)(RESTORE_ALL:退出中斷程序,恢復(fù)保存寄存器的數(shù)據(jù))。
二、系統(tǒng)調(diào)用概述
1. 系統(tǒng)調(diào)用的意義:
操作系統(tǒng)為用戶態(tài)進(jìn)程與硬件設(shè)備進(jìn)行交互提供了一組接口,就是系統(tǒng)調(diào)用。
- 遠(yuǎn)離底層硬件編程
- 安全性
- 可移植性
2. API - 應(yīng)用編程接口
與系統(tǒng)調(diào)用區(qū)別:
- API只是一個(gè)函數(shù)定義。
- 系統(tǒng)調(diào)用是通過軟中斷向內(nèi)核發(fā)出一個(gè)明確的請(qǐng)求。
- 一般每個(gè)系統(tǒng)調(diào)用對(duì)應(yīng)一個(gè)封裝例程,庫(kù)再用這些封裝例程定義出用戶的API,方便用戶使用。也就是說(shuō),API與系統(tǒng)調(diào)用不是一一對(duì)應(yīng)的。
API可以:
- 直接提供用戶態(tài)服務(wù)
- 一個(gè)單獨(dú)的API可能調(diào)用幾個(gè)系統(tǒng)調(diào)用
- 不同的API可能調(diào)用了同一個(gè)系統(tǒng)調(diào)用
返回值:
- 大部分封裝例程返回一個(gè)整數(shù)
- -1表示失敗,不能滿足請(qǐng)求
- errno 特定出錯(cuò)碼
3.所謂“扒開系統(tǒng)調(diào)用的三層皮”
- API(xyz)
- 中斷向量(system_call)
- 中斷服務(wù)程序(sys_xyz)
1.系統(tǒng)調(diào)用的服務(wù)例程中,中斷向量0x80與system_call綁定起來(lái)。(Linux中可以通過執(zhí)行int $128來(lái)執(zhí)行系統(tǒng)調(diào)用。)
2.system_call是linux中所有系統(tǒng)調(diào)用的入口點(diǎn),每個(gè)系統(tǒng)調(diào)用至少有一個(gè)參數(shù),即系統(tǒng)調(diào)用號(hào)。
3.系統(tǒng)調(diào)用號(hào)將xyz與sys_xyz關(guān)聯(lián)起來(lái)。調(diào)用號(hào)在eax中。
系統(tǒng)調(diào)用的參數(shù)傳遞:
- 函數(shù)調(diào)用——壓棧
- 用戶態(tài)到內(nèi)核態(tài)——寄存器傳遞。
每個(gè)參數(shù)長(zhǎng)度不能超過32位,個(gè)數(shù)不能超過6個(gè)。
超過的話,使某個(gè)寄存器中存儲(chǔ)指針,指向內(nèi)存,內(nèi)存中存儲(chǔ)參數(shù)。
三、使用庫(kù)函數(shù)API和C代碼中嵌入?yún)R編代碼觸發(fā)同一個(gè)系統(tǒng)調(diào)用
1.使用庫(kù)函數(shù)API獲取系統(tǒng)當(dāng)前時(shí)間
使用time(),代碼如下:
#include<stdio.h> #include<time.h> int main() { time_t tt; struct tm *t;//構(gòu)造一個(gè)結(jié)構(gòu)體,方便讀取 tt = time(NULL);//time系統(tǒng)調(diào)用 t = localtime(&tt); printf("time:%d:%d:%d:%d:%d:%d\n", t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); return 0; }2.使用C代碼中嵌入?yún)R編代碼觸發(fā)系統(tǒng)調(diào)用獲取系統(tǒng)當(dāng)前時(shí)間
代碼如下:
#include<stdio.h> #include<time.h> int main() { time_t tt; struct tm *t; asm volatile( "mov $0,%%ebx\n\t" # 把ebx清零,相當(dāng)于傳參數(shù) "mov $0xd,%%eax\n\t"# 把0xd放入eax中,即系統(tǒng)調(diào)用號(hào)13,指time "int $0x80\n\t" "mov %%eax,%0\n\t" # 返回值是在eax中,%0指tt,把返回值放到tt中去。 : "=m" (tt) ); t = localtime(&tt); printf("time:%d:%d:%d:%d:%d:%d\n", t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); return 0; }四、使用庫(kù)函數(shù)API和C代碼中嵌入?yún)R編代碼兩種方式使用同一個(gè)系統(tǒng)調(diào)用
在這里我選擇的是第7號(hào)系統(tǒng)調(diào)用,waitpid。
1.使用庫(kù)函數(shù)API:
2.嵌入?yún)R編:
3.運(yùn)行結(jié)果:
五、總結(jié)
- 即便是最簡(jiǎn)單的程序,在進(jìn)行輸入輸出等操作時(shí)也會(huì)需要調(diào)用操作系統(tǒng)所提供的服務(wù),也就是系統(tǒng)調(diào)用。
- Linux下的系統(tǒng)調(diào)用是通過中斷(int 0x80)來(lái)實(shí)現(xiàn)的。
- 在執(zhí)行int 80指令時(shí),寄存器 eax 中存放的是系統(tǒng)調(diào)用的功能號(hào),而傳給系統(tǒng)調(diào)用的參數(shù)則必須按順序放到寄存器 ebx,ecx,edx,esi,edi 中,當(dāng)系統(tǒng)調(diào)用完成之后,返回值可以在寄存器 eax 中獲得。
- Linux 采用的是 C 語(yǔ)言的調(diào)用模式,這就意味著所有參數(shù)必須以相反的順序進(jìn)棧,即最后一個(gè)參數(shù)先入棧,而第一個(gè)參數(shù)則最后入棧。
轉(zhuǎn)載于:https://www.cnblogs.com/lxq20135309/p/5296439.html
總結(jié)
以上是生活随笔為你收集整理的LINUX内核分析第四周——扒开系统调用的三层皮的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ACM学习历程—HDU2476 Stri
- 下一篇: 【Linux开发】linux设备驱动归纳