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