深入理解计算机系统:进程
導(dǎo)語:這是篇讀書筆記,每次重讀CSAPP都有新的認(rèn)知,尤其是在進(jìn)入了后臺通道之后才感受到每天和進(jìn)程打交道的感覺是如此深刻。
0x00 What is Process?[ system structure ]
進(jìn)程(Process)一個(gè)執(zhí)行中的程序的實(shí)例,操作系統(tǒng)對一個(gè)正在運(yùn)行的程序的一種抽象。并發(fā)運(yùn)行,指的是一個(gè)進(jìn)程的指令和另一個(gè)進(jìn)程的指令交錯(cuò)執(zhí)行。操作系統(tǒng)實(shí)現(xiàn)這種交錯(cuò)執(zhí)行的機(jī)制稱為上下文切換。
線程(Thread)
內(nèi)核(Kernel)
外殼(Shell)
搶占(Preemption)
異常控制流(ECF,Exceptional Control Flow)
Exceptions
任何情況下,處理器檢測到event發(fā)生,通過異常表(exception table)跳轉(zhuǎn)到專門處理這類事件的操作系統(tǒng)子程序(exception handler)。
異步異常由事件產(chǎn)生,同步異常是執(zhí)行一條指令的直接產(chǎn)物。
中斷(異步),陷阱(同步),故障(同步),終止(同步)。
中斷——異步發(fā)生,處理器IO設(shè)備信號的結(jié)果。
陷阱——有意的異常。最重要的用途是在用戶程序和內(nèi)核之間提供一個(gè)像過程一樣的接口,叫做系統(tǒng)調(diào)用。
故障——潛在可恢復(fù)的錯(cuò)誤造成的結(jié)果。如果能被修復(fù),則重新執(zhí)行引起故障的指令,否則終止。
終止——不可恢復(fù)的致命錯(cuò)誤造成的結(jié)果。
有高達(dá)256種不同的異常類型,如出發(fā)錯(cuò)誤(0)、一般保護(hù)故障(13)、缺頁(14)、機(jī)器檢查(18)、操作系統(tǒng)定義的異常(32-127,129-255)、系統(tǒng)調(diào)用(0x80)。
[ Examples of popular system calls ]
Processes
邏輯控制流(Logical Control Flow)
并發(fā)流(Concurrent Flows)
私有地址空間(Private Address Space)
[ Process address space ]
用戶模式和內(nèi)核模式(User and Kernel Modes)
上下文切換(Context Switches)
通用目的的寄存器、浮點(diǎn)寄存器、程序計(jì)數(shù)器、用戶棧、狀態(tài)寄存器、內(nèi)核棧和各種內(nèi)核數(shù)據(jù)結(jié)構(gòu)(地址空間的頁表、有關(guān)當(dāng)前進(jìn)程信息的進(jìn)程表、進(jìn)程已打開文件的信息的文件表)
內(nèi)核調(diào)度器(scheduler)負(fù)責(zé)調(diào)度進(jìn)程,搶占當(dāng)前進(jìn)程,重新開始先前被搶占的進(jìn)程。
Process Control
如何控制進(jìn)程?
PID
pid > 0
#include <sys/types.h> // for pid_t #include <unistd.h> pid_t getpid(void); // 獲取進(jìn)程ID pid_t getppid(void); // 獲取父進(jìn)程IDCreating and Terminating Process
從程序角度來看,進(jìn)程總處于以下三種狀態(tài):
Running——要么處于CPU執(zhí)行中,要么處于等待被執(zhí)行且最終會被內(nèi)核調(diào)度。
Stopped——進(jìn)程被掛起(suspend),且不會被調(diào)度。當(dāng)收到SIGSTOP、SIGTSTP、SIGTTIN或者SIGTTOU信號時(shí),進(jìn)程停止,直到收到SIGCONT信號,進(jìn)程再次開始運(yùn)行。
Terminated——進(jìn)程永遠(yuǎn)停止了。三種原因?qū)е陆K止:
父進(jìn)程通過調(diào)用fork創(chuàng)建一個(gè)新的運(yùn)行子進(jìn)程,最大的區(qū)別在于不同的PID。
fork():一次調(diào)用,返回兩次。
并發(fā)執(zhí)行:父子進(jìn)程是并發(fā)運(yùn)行的獨(dú)立進(jìn)程。
相同但是獨(dú)立的地址空間。子進(jìn)程與父進(jìn)程用戶級虛擬地址空間相同的拷貝,相同的本地變量值、堆、全局變量、以及代碼。如代碼中print出來不一樣的x。
共享文件:任何打開文件描述符相同的拷貝,如stdout。
out:
child |————x=2———— father ——————————x=0———— fork exitReap Child Process
進(jìn)程終止時(shí),保持位已終止?fàn)顟B(tài),直到被父進(jìn)程回收(reap)。當(dāng)父進(jìn)程回收已終止的子進(jìn)程,內(nèi)核將子進(jìn)程的退出狀態(tài)傳遞給父進(jìn)程,然后拋棄已終止的進(jìn)程,此刻進(jìn)程不復(fù)存在。
僵尸進(jìn)程(zombie):一個(gè)終止了但還未被回收的進(jìn)程。但是如果父進(jìn)程沒有回收就終止了,則內(nèi)核安排init進(jìn)程(PID=1)回收僵尸進(jìn)程。
#include <sys/types.h> #include <sys/wait.h> /* 進(jìn)程可以調(diào)用waitpid等待子進(jìn)程終止或者結(jié)束。 * 默認(rèn)options=0,掛起調(diào)用進(jìn)程,直到它等待集合中的一個(gè)子進(jìn)程終止。如果等待集合中的一個(gè)進(jìn)程在剛調(diào)用的時(shí)刻就已經(jīng)終止了,那么waitpid立即返回。返回已終止的子進(jìn)程PID,并去除該子進(jìn)程。 *輸入?yún)?shù)pid: pid>0,等待集合就是一個(gè)單獨(dú)的子進(jìn)程,進(jìn)程ID等于pid。 pid=-1,等待集合是由父進(jìn)程所有的子進(jìn)程組成。 *輸入?yún)?shù)options: WNOHANGE:等待集合中任何子進(jìn)程都還沒有終止,立即返回0;默認(rèn)行為還是掛起調(diào)用進(jìn)程直到子進(jìn)程終止。 WUNTRACED:掛起調(diào)用進(jìn)程執(zhí)行,直到集合中有一個(gè)進(jìn)程終止或停止。返回該進(jìn)程PID。 WNOHANGE|WUNTRACED:立刻返回,0=如果沒有終止或停止的子進(jìn)程;PID=終止或停止的子進(jìn)程PID。 *輸入?yún)?shù)status: WIFEXITED:True=子進(jìn)程是通過return或者exit終止的; WEXITSTATUS:返回exit狀態(tài),只有WIFEXITED=True時(shí)被定義; WIFSIGNALED:True=子進(jìn)程是因?yàn)橐粋€(gè)未被捕獲的信號終止的; WTERMSIG:返回導(dǎo)致子進(jìn)程終止信號量,只有WIFSIGNALED=True被定義; WIFSTOPPED:True=返回的子進(jìn)程是停止的; WSTOPSIG:返回引起子進(jìn)程停止的信號的數(shù)量,只有WIFSTOPPED=True被定義; 返回: 成功=子進(jìn)程PID;if WNOHANG=0; 其他錯(cuò)誤=-1(errno=ECHILD,沒有子進(jìn)程;errno=EINTR,被一個(gè)信號中斷) */ pid_t waitpid(pid_t pid, int *status, int options); pid_t wait(int *status); //等價(jià)于waitpid(-1, &status, 0);Sleep
#include <unistd.h> // 返回:seconds left to sleep unsigned int sleep(unsigned int secs); // 讓調(diào)用函數(shù)休眠,直到收到一個(gè)信號 // 返回:-1 int pause(void);Loading and Running Programs
execve函數(shù)在當(dāng)前進(jìn)程的上下文中加載并運(yùn)行一個(gè)新的程序,覆蓋當(dāng)前進(jìn)程的地址空間,但并沒有創(chuàng)建一個(gè)新進(jìn)程,進(jìn)程PID沒有改變。
#include <unistd.h> // 返回:成功=不返回;出錯(cuò)=-1 int execve(const char *filename, const char *argv[], const char *envp[]); // 程序主入口: int main(int argc, char **argv, char **envp); int main(int argc, char *argv[], char *envp[]);Signal
[ Linux Signal(`man 7 signal`) ]
信號傳遞到目的進(jìn)程包括兩個(gè)步驟:1)發(fā)送;2)接收。
一個(gè)發(fā)出卻沒被接收的信號叫做待處理信號(Pending Signal)。
一個(gè)進(jìn)程有一個(gè)類型為k的待處理信號,后面發(fā)送到這個(gè)進(jìn)程的k信號都會被丟棄。
也可以選擇性阻塞接收某個(gè)信號,信號被阻塞時(shí)仍可以發(fā)送,但產(chǎn)生的待處理信號不會被接收,直到進(jìn)程取消對這種信號的阻塞。
一個(gè)待處理信號最多只能被接收一次,內(nèi)核為每個(gè)進(jìn)程在pending位向量中維護(hù)待處理信號集合,而在blocked位向量中維護(hù)被阻塞的信號集合。
只有接收了k信號,內(nèi)核才會清除pending中的k位。
Sending Signal
每個(gè)進(jìn)程都只屬于一個(gè)進(jìn)程組,進(jìn)程組ID標(biāo)識。unix所有發(fā)送信號的機(jī)制都是基于進(jìn)程組(process group)/
用/bin/kill程序發(fā)送信號
/bin/kill -9 15213
發(fā)送信號9到進(jìn)程組15213中的每個(gè)進(jìn)程。
/bin/kill -9 -15213
從鍵盤發(fā)送信號
[ 前臺進(jìn)程子進(jìn)程和父進(jìn)程具有相同的進(jìn)程組ID。]
用KILL函數(shù)發(fā)送信號。
alarm函數(shù)發(fā)送信號
Receiving Signals
wtf: 當(dāng)異常處理程序返回時(shí),準(zhǔn)備轉(zhuǎn)移控制權(quán)給進(jìn)程p時(shí),會檢查非被阻塞的待處理信號的集合(pending&~blocked) if 集合為空: 進(jìn)程p的邏輯控制流下一跳指令 else: 選擇某個(gè)最小信號k,強(qiáng)制p接收信號k goto wtf每個(gè)信號類型預(yù)定義的默認(rèn)行為(查看Figure8.25):
進(jìn)程終止
進(jìn)程終止并轉(zhuǎn)存儲器(dump core)
進(jìn)程停止直到被SIGCONT信號重啟
進(jìn)程忽略該信號
Signal Handling Issues
當(dāng)程序需要捕獲多個(gè)信號時(shí),問題產(chǎn)生了。
待處理信號被阻塞。Unix信號處理程序通常會阻塞當(dāng)前處理程序正在處理的類型的待處理信號k。如果另一個(gè)信號k傳遞到該進(jìn)程,則信號k將變成待處理,但是不會被接收,直到處理程序返回。再次檢查發(fā)現(xiàn)仍有待處理信號k,則再次調(diào)用信號處理函數(shù)。
待處理信號不會排隊(duì)等待。任意類型最多只有一個(gè)待處理信號。當(dāng)目的進(jìn)程正在執(zhí)行信號k的處理程序時(shí)是阻塞的,當(dāng)發(fā)送兩個(gè)信號k,僅第一個(gè)信號k會變成待處理,第二個(gè)則直接被丟棄,不會排隊(duì)等待。
系統(tǒng)調(diào)用可以被中斷。像read、wait和accept調(diào)用過程會阻塞進(jìn)程的稱謂慢速系統(tǒng)調(diào)用,當(dāng)捕獲到一個(gè)信號時(shí),被中斷的慢速系統(tǒng)調(diào)用在信號處理返回時(shí)不再繼續(xù),而是立即返回用戶一個(gè)錯(cuò)誤條件,并將errno設(shè)置為EINTR。(即使sleep被信號處理捕獲后仍會返回)
Explicitly Blocking and Unblocking Signals
#include <signal.h> // how = SIG_BLOCK, blocked=blocked | set // how = SIG_UNBLOCK, blocked=blocked &~ set // how = SIG_SETMASK, blocked = set int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigemptyset(sigset_t *set); // 將每個(gè)信號添加到set int sigfillset(sigset_t *set); // 添加signum到set int sigaddset(sigset_t *set, int signum); // 從set中刪除signum int sigdelset(sigset_t *set, int signum); //Returns: 0 if OK, ?1 on error int sigismember(const sigset_t *set, int signum); //Returns: 1 if member, 0 if not, ?1 on errorNonlocal Jump
作用允許從一個(gè)深層嵌套的函數(shù)調(diào)用中立即返回。
#include <setjmp.h> int setjmp(jmp_buf env); int sigsetjmp(sigjmp_buf env, int savesigs); // Returns: 0 from setjmp, nonzero from longjmps void longjmp(jmp_buf env, int retval); void siglongjmp(sigjmp_buf env, int retval); // Never returns jmp_buf env; rc=setjmp(env); // 保存當(dāng)前調(diào)用環(huán)境 if(rc == 0) dosomething(); else if (rc == 1) dosomething1(); // 如果 else if (rc == 2) dosomething2(); int dosomething() { longjmp(buf,1); // 跳轉(zhuǎn)到setjmp,返回1 // longjmp(buf,2); // 跳轉(zhuǎn)到setjmp,返回2 }操作進(jìn)程工具
STRACE:打印一個(gè)正在運(yùn)行的程序和它的子程序調(diào)用的每個(gè)系統(tǒng)調(diào)用的軌跡。
PS:列出當(dāng)前系統(tǒng)中的進(jìn)程(包括僵尸進(jìn)程)。
TOP:打印關(guān)于當(dāng)前進(jìn)程資源使用的信息。
PMAP:顯示進(jìn)程的存儲器映射。
/proc:一個(gè)虛擬文件系統(tǒng),以ASCII輸出大量內(nèi)核數(shù)據(jù)結(jié)構(gòu)的內(nèi)容。如cat /proc/loadavg,觀察Linux系統(tǒng)上的當(dāng)前的平均負(fù)載。
超強(qiáng)干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的深入理解计算机系统:进程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一文读懂数据库最新技术趋势:TDSQL带
- 下一篇: 腾讯物联网操作系统正式开源,最小体积仅1