12 [虚拟化] 进程抽象;fork,execve,exit
12 [虛擬化] 進(jìn)程抽象;fork,execve,exit
南京大學(xué)操作系統(tǒng)課蔣炎巖老師網(wǎng)絡(luò)課程筆記。
視頻:https://www.bilibili.com/video/BV1N741177F5?p=12
講義:http://jyywiki.cn/OS/2021/slides/8.slides#/
本講概述
回到“操作系統(tǒng)是管理程序運(yùn)行的軟件”
- 操作系統(tǒng)中的進(jìn)程
- 程序 = 狀態(tài)機(jī) (M,R)
- 操作系統(tǒng) = 多個狀態(tài)機(jī)
- 進(jìn)程管理API
- fork:狀態(tài)機(jī)的復(fù)制
- execve:狀態(tài)機(jī)的重置
- exit
再次強(qiáng)調(diào),一定要深入理解:程序(進(jìn)程)就是一個狀態(tài)機(jī)。
操作系統(tǒng)中的進(jìn)程
復(fù)習(xí):應(yīng)用程序
應(yīng)用程序 = 代碼 + 數(shù)據(jù)(文件) = 狀態(tài)機(jī)
- a.out, bash, ls ,grep
- gcc(cc1, as, collect2, ld)
- xedit, vscode
復(fù)習(xí):操作系統(tǒng)
操作系統(tǒng)是管理多個應(yīng)用程序執(zhí)行的軟件。
- 應(yīng)用視角:操作系統(tǒng)就是一組系統(tǒng)調(diào)用
- 硬件視角:操作系統(tǒng)就是個狀態(tài)機(jī)(C程序)
理解“最小”操作系統(tǒng):
如果硬件提供一些機(jī)制(如虛擬存儲來虛擬化內(nèi)存M和寄存器R,即(M,R))使得各個“線程”不能訪問其他“線程”、操作系統(tǒng)的內(nèi)存,就得到了虛擬化的“進(jìn)程”,仿佛獨(dú)占CPU運(yùn)行。注意:這里是將運(yùn)行在操作系統(tǒng)上的各個程序(進(jìn)程)看做了是運(yùn)行在操作系統(tǒng)這個大程序(進(jìn)程)上的一些”線程“。
操作系統(tǒng):狀態(tài)機(jī)的虛擬化
操作系統(tǒng)“模擬”了其中所有程序的狀態(tài)機(jī)
- 這就是“虛擬化”
- 程序仿佛自己獨(dú)占CPU運(yùn)行,但它獨(dú)占的只是CPU的一部分,其他部分它“看不見”。
進(jìn)程:運(yùn)行的程序。任意時刻,進(jìn)程都可以看做是狀態(tài)機(jī)的狀態(tài)。
操作系統(tǒng)在終端以后,可以選擇將進(jìn)程(狀態(tài)機(jī))調(diào)度到CPU上運(yùn)行。而進(jìn)程執(zhí)行系統(tǒng)調(diào)用,會使用指令(syscall)等回到操作系統(tǒng)。
“操作系統(tǒng)是一個中斷處理程序”
- 被動的中斷:硬件(時鐘、I/O設(shè)備、NMI,)
- 主動的中斷:系統(tǒng)調(diào)用
操作系統(tǒng)運(yùn)行的兩種模式
- 用戶態(tài)(ring 3):應(yīng)用程序運(yùn)行在用戶態(tài)
- 內(nèi)核態(tài)(ring 0):操作系統(tǒng)運(yùn)行在內(nèi)核態(tài)
二者的切換如上面所述:
- 中斷、系統(tǒng)調(diào)用:用戶態(tài) -> 內(nèi)核態(tài)
- 操作系統(tǒng)調(diào)度:內(nèi)核態(tài) -> 用戶態(tài)
就是這樣的切換,使得我們的應(yīng)用程序(在用戶態(tài))實現(xiàn)了虛擬化,同時操作系統(tǒng)仿佛就是一個中斷處理程序。
操作系統(tǒng)課的三種調(diào)用
-
進(jìn)程(狀態(tài)機(jī))管理
fork, execve, exit:進(jìn)程(狀態(tài)機(jī))的創(chuàng)建、改變和刪除
-
存儲(地址空間)管理
mmap:對進(jìn)程虛擬地址空間的一部分進(jìn)行映射
brk:虛擬地址空間管理
-
文件(數(shù)據(jù)對象)管理
open, close:文件訪問管理
read, write:數(shù)據(jù)管理
mkdir, link, unling:目錄管理
fork() 狀態(tài)機(jī)管理:創(chuàng)建狀態(tài)機(jī)
如果需要創(chuàng)建狀態(tài)機(jī),我們需要什么樣的API?
UNIX的答案:fork()
- 做一份狀態(tài)機(jī)的完整的復(fù)制(內(nèi)存M,寄存器現(xiàn)場R)
- 父進(jìn)程返回子進(jìn)程的PID,子進(jìn)程返回0
fork bomb
fork bomb代碼解析:
:(){:|:&};: # 一行版本的fork bomb:(){ # 格式化一下: | : & };:fork(){ # 這其實在bash中定義了一個函數(shù),bash允許以冒號作為標(biāo)識符fork | fork & }; fork父子進(jìn)程、進(jìn)程樹
因為狀態(tài)機(jī)是復(fù)制的,因此總能找到”父子關(guān)系“。
因此有了進(jìn)程樹(pstree),如下:
systemd─┬─ModemManager───2*[{ModemManager}]├─NetworkManager─┬─dhclient│ └─2*[{NetworkManager}]├─accounts-daemon───2*[{accounts-daemon}]├─acpid├─avahi-daemon───avahi-daemon├─boltd───2*[{boltd}]├─colord───2*[{colord}]├─cron├─cups-browsed───2*[{cups-browsed}]├─cupsd───dbus├─dbus-daemon├─gdm3─┬─gdm-session-wor─┬─gdm-x-session─┬─Xorg───{Xorg}│ │ │ ├─gnome-session-b─┬─gnome-shell─┬─ibus-daemon─┬─ibus-dconf───3*[{ibus-dconf}]│ │ │ │ │ │ ├─ibus-engine-sim───2*[{ibus-engine- ...進(jìn)程樹的存在,是我們用fork()復(fù)制創(chuàng)造子進(jìn)程,所得到的一個很自然的結(jié)果。
例程1
猜猜會打印出什么呢?(提示:可以試著畫一下狀態(tài)機(jī),線程樹)
#include <unistd.h> #include <stdio.h>int main(){pid_t pid1 = fork();pid_t pid2 = fork();pid_t pid3 = fork();printf("Hello World from (%d, %d, %d)\n", pid1, pid2, pid3); }例程2
猜猜會打印出什么呢?
#include <unistd.h> #include <stdio.h>#define N 2 int main(){for (int i=0; i<N; i++){fork();printf("Hello\n");} }還是可以逐步畫一下程序的狀態(tài)機(jī),
gcc test.c ./a.out輸出結(jié)果應(yīng)該是6。
有趣的是,如果我們將輸出重定向到管道,再通過wc -l命令打印出行數(shù),這時會輸出8,這不禁令我們大為驚奇。
為什么會這樣呢?這其實是因為printf函數(shù)將內(nèi)容直接輸出到標(biāo)準(zhǔn)輸出stdout時是直接輸出的,會按照我們的理解打印出6個Hello。但是如果要重定向到管道(或文本文件等),printf則會將要輸出的內(nèi)容先放置到緩沖區(qū),到最后一起打印,在本例中,由于我們fork()創(chuàng)建進(jìn)程的時候,會將全部的內(nèi)存M和寄存器現(xiàn)場R復(fù)制一份,導(dǎo)致每次fork()時,緩沖區(qū)也被完整地復(fù)制,最后我們每個進(jìn)程的緩沖區(qū)有2個Hello,最后共有四個進(jìn)程,故會有8個Hello。(也可以重定向到文本文件中試一下,確實是有8個Hello)。這恰好進(jìn)一步驗證了我們所說的fork()是對整個(M,R)的完整復(fù)制。
可以嘗試?yán)斫庖幌翹=3時正常打印和重定向打印會有多少個Hello。筆者認(rèn)為分別是 ∑i=1N2i\sum_{i=1}^N2^i∑i=1N?2i 和 2N×N2^N\times N2N×N。
機(jī)器永遠(yuǎn)是對的,計算機(jī)系統(tǒng)的世界沒有魔法,一切都是按部就班地進(jìn)行的
execve() 狀態(tài)機(jī)管理:替換狀態(tài)機(jī)(執(zhí)行)
只有fork新建還不夠,我們還需要能夠執(zhí)行別的程序。
UNIX的答案:execve
execve(filename, argv, enpv)
- 執(zhí)行名為filename的程序
- 分別傳入?yún)?shù)argv(v)和環(huán)境變量enpv(e)
這剛好對應(yīng)了main函數(shù)的參數(shù):
int main(int argc, char** argv, char** enpv){// ... }關(guān)于main函數(shù)的參數(shù):Linux中 C++ main函數(shù)參數(shù)argc和argv含義及用法
execve可以看作狀態(tài)機(jī)的重置。
環(huán)境變量
環(huán)境變量即應(yīng)用程序執(zhí)行的環(huán)境。
- 使用env命令來查看
- PATH:可執(zhí)行文件的搜索路徑
- PWD:當(dāng)前路徑
- HOME:home目錄
- DISPLAY:圖形輸出
- PS1:shell的提示符
- export:告訴shell在創(chuàng)建子進(jìn)程時設(shè)置環(huán)境變量
PATH環(huán)境變量
PATH環(huán)境變量是可執(zhí)行文件的搜索路徑
還記得gcc的strace結(jié)果嗎
[pid 28369] execve("/usr/local/sbin/as", [“as”, “–64”, …
[pid 28369] execve("/usr/local/bin/as", [“as”, “–64”, …
[pid 28369] execve("/usr/sbin/as", [“as”, “–64”, …
[pid 28369] execve("/usr/bin/as", [“as”, “–64”, …
這個搜索順序恰好是PATH環(huán)境變量中指定的順序:
$ PATH="" /usr/bin/gcc fork-demo.c
gcc: error trying to exec ‘a(chǎn)s’: execvp: No such file or directory
$ PATH="/usr/bin/" gcc fork-demo.c
在gcc被execve時,將環(huán)境變量PATH傳給gcc,它就會按照其順序來搜索可執(zhí)行文件的路徑。
計算機(jī)系統(tǒng)里沒有魔法,機(jī)器永遠(yuǎn)是對的
exit() 狀態(tài)機(jī)管理:終止?fàn)顟B(tài)機(jī)
有了fork,execve,我們可以自由地創(chuàng)建、執(zhí)行程序(狀態(tài)機(jī))了,還缺一個銷毀狀態(tài)機(jī)的函數(shù)
UNIX的答案:exit
- 銷毀當(dāng)前狀態(tài)機(jī),并允許有一個返回值
- 子進(jìn)程終止會通知父進(jìn)程(之后會講)
問題是一個進(jìn)程(狀態(tài)機(jī))中有多個線程啊。
結(jié)束程序執(zhí)行的三種方法
exit的幾種寫法,它們是不同的:
- exit(0) - stdlib.h中聲明的libc函數(shù)
- 它會調(diào)用atexit
- _exit(0) - glibc中的syscall wrapper
- 執(zhí)行exit_group系統(tǒng)調(diào)用,終止整個進(jìn)程(所有線程)
- 不會調(diào)用atexit
- syscall(SYS_exit, 0)
- 執(zhí)行exit系統(tǒng)調(diào)用終止當(dāng)前線程
- 不會調(diào)用atexit
最起碼要區(qū)分好庫函數(shù)(應(yīng)用程序的一部分)和系統(tǒng)調(diào)用。
可以用strace觀察各種結(jié)束方式的執(zhí)行。
Fork-Exec vs. Spawn
我們既然fork創(chuàng)建了一個子進(jìn)程,那我們絕大多情況下肯定是要execve執(zhí)行這個進(jìn)程的,也就是說fork后面幾乎一定會跟著execve,那為什么不直接把它們合成一個系統(tǒng)調(diào)用 spawn(path, argv, enpv) 呢?即spawn = fork + execve
實際上,fork + execve是一個非常優(yōu)雅的實現(xiàn),因為要考慮到進(jìn)程可以持有操作系統(tǒng)中的對象,這使fork、execve、exit還要涉及到操作系統(tǒng)的對象的管理。
例如,在上面用到過的管道技術(shù)中:
./a.out | wc -l其中./a.out持有了操作系統(tǒng)中的對象——管道的寫口,而wc -l則持有了管道的讀口,從而能夠?qū)⑶罢叩妮敵鲎鳛楹笳叩妮斎搿6绻?/a.out這個進(jìn)程(狀態(tài)機(jī))需要fork一個子進(jìn)程,那么這個子進(jìn)程就可以自然地復(fù)制拿到父進(jìn)程的全部(M,R)。這樣,對于操作系統(tǒng)中的對象——管道,子進(jìn)程就持有了其寫口。
Take aways and Wrap-up
虛擬化
- 程序 = 狀態(tài)機(jī)
- 操作系統(tǒng) = 狀態(tài)機(jī)的管理者
- 用硬件(物理狀態(tài)機(jī))實現(xiàn)多個并發(fā)執(zhí)行的虛擬狀態(tài)機(jī)
- API:fork,execve,exit
Ref:
http://jyywiki.cn/OS/2021/slides/8.slides#/
https://www.bilibili.com/video/BV1N741177F5?p=12
總結(jié)
以上是生活随笔為你收集整理的12 [虚拟化] 进程抽象;fork,execve,exit的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 雪铁龙气门油封老化内外部因素是什么?
- 下一篇: win 10 java 安装_win10