<h1 >0 理解異常控制流</h1>
<p>作為程序員若贮,理解異常控制流(Exceptional Control Flow)ECF很重要,原因:</p>
<ul>
<li><p>理解ECF將幫助你理解重要的系統(tǒng)概念。ECF是操作系統(tǒng)實現(xiàn)I/O次氨、進程和虛擬內(nèi)存的基本機制</p>
</li>
<li><p>理解ECF將幫助你理解應(yīng)用和系統(tǒng)是如何交互的。程序通過<code>trap</code>或<code>syscall</code>的ECF形式摘投,向系統(tǒng)請求服務(wù)</p>
</li>
<li><p>理解ECF將幫助編寫有趣的新應(yīng)用程序</p>
</li>
<li><p>理解ECF將有助于理解并發(fā)煮寡,ECF是系統(tǒng)中實現(xiàn)并發(fā)的基本機制。在運行中的并發(fā)的例子:</p>
<ul>
<li>中斷程序執(zhí)行的異常處理程序</li>
<li>時間上重疊執(zhí)行的進程和線程</li>
<li>中斷應(yīng)用程序執(zhí)行的異常處理程序</li>
</ul>
</li>
</ul>
<h1 >1 異诚簦控制流</h1>
<h2 >1.1 控制流</h2>
<p>從開機到關(guān)機幸撕,處理器做的工作很簡單,每個CPU內(nèi)核只是簡單地讀和執(zhí)行指令外臂,每次一條坐儿。整個指令執(zhí)行的序列就是CPU<strong>控制流</strong></p>
<p>前面已經(jīng)學過兩種改變控制流的方式:</p>
<ul>
<li>跳轉(zhuǎn)和分支</li>
<li>調(diào)用和返回</li>
</ul>
<p>只適用于程序狀態(tài)的改變,很難應(yīng)對系統(tǒng)狀態(tài)的改變宋光,比如:</p>
<ul>
<li>數(shù)據(jù)從磁盤或網(wǎng)絡(luò)適配器讀取</li>
<li>指令除零</li>
<li>用戶按ctrl-c</li>
<li>系統(tǒng)定時器超時</li>
</ul>
<p>因此系統(tǒng)需要叫做<strong>異趁部螅控制流</strong>的機制。它存在于系統(tǒng)每個層面:</p>
<ul>
<li><p><strong>底層機制</strong>:</p>
<ul>
<li><strong>異常(Exceptions)</strong>:用以響應(yīng)系統(tǒng)事件罪佳,通常由硬件和操作系統(tǒng)實現(xiàn)</li>
</ul>
</li>
<li><p><strong>高層機制</strong>:</p>
<ul>
<li><strong>進程切換(Process Context Switch)</strong>:系統(tǒng)軟件和硬件定時器實現(xiàn)</li>
<li><strong>信號(Signal)</strong>:系統(tǒng)軟件實現(xiàn)</li>
<li><strong>非本地跳轉(zhuǎn)(Nonlocal Jumps)</strong>:包括setjmp()和longjmp()逛漫。C運行時庫實現(xiàn)</li>
</ul>
</li>
</ul>
<h1 >2 異常</h1>
<p>異常指的是<strong>把控制權(quán)交給系統(tǒng)內(nèi)核以響應(yīng)某些事件</strong>(如處理器狀態(tài)的改變),系統(tǒng)內(nèi)核是操作系統(tǒng)常駐內(nèi)存的一部分赘艳,響應(yīng)的事件包括:除零酌毡、運算溢出、頁錯誤蕾管、IO請求完成或用戶按ctrl-c等系統(tǒng)級別的事件枷踏。過程如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119122413641.png" referrerpolicy="no-referrer" alt="image-20220119122413641"></p>
<p>注意:<strong>中斷</strong>和<strong>陷阱</strong>(最重要用途是<strong>syscall</strong>)都是返回到下條指令,<strong>故障</strong>若被處理程序修正錯誤則返回當前指令重新執(zhí)行掰曾,否則返回到內(nèi)核中<strong>abort例程</strong>旭蠕,它會<strong>終止</strong>應(yīng)用程序,它從不將控制權(quán)返回給應(yīng)用程序旷坦。</p>
<p>每種事件都對應(yīng)唯一的異常編號掏熬,當異常發(fā)生時,系統(tǒng)通過查找異常表(Exception Table)中對應(yīng)的異常編號確定異常處理代碼塞蹭,過程如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119122532519.png" referrerpolicy="no-referrer" alt="image-20220119122532519"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119122613855.png" referrerpolicy="no-referrer" alt="image-20220119122613855"></p>
<h2 >2.1 異步異常(中斷)</h2>
<p>異步異常(Asynchronous Exception)也叫<strong>中斷</strong>(Interrupt)孽江,是由處理器外部事件引起。對執(zhí)行程序而言番电,“中斷”的發(fā)生完全是<strong>異步</strong>的岗屏,因為不知道什么時候會發(fā)生。CPU對其響應(yīng)也是被動的漱办,但可以屏蔽这刷。這種情況下:</p>
<ul>
<li>需設(shè)置CPU中斷指針</li>
<li>處理完后返回之前控制流下條指令</li>
</ul>
<p>常見中斷有:</p>
<ul>
<li>計時器中斷:每隔幾毫秒外部定時器芯片就會觸發(fā)一次中斷,被內(nèi)核用來從用戶程序拿回控制權(quán)</li>
<li>I/O中斷:類型較多:如鍵盤輸入ctrl-c娩井、來自網(wǎng)絡(luò)中包到達暇屋、來自磁盤的數(shù)據(jù)到達</li>
</ul>
<h2 >2.2 同步異常</h2>
<p>同步異常(Synchronous Exceptions)由執(zhí)行指令的結(jié)果導致的事件,包括三類:</p>
<figure><table>
<thead>
<tr><th style='text-align:center;' >類型</th><th style='text-align:center;' >說明</th><th style='text-align:center;' >行為</th><th style='text-align:center;' >示例</th></tr></thead>
<tbody><tr><td style='text-align:center;' >Trap</td><td style='text-align:center;' >為某事有意設(shè)置</td><td style='text-align:center;' >返回到先前下條指令</td><td style='text-align:center;' >系統(tǒng)調(diào)用洞辣、調(diào)試斷點</td></tr><tr><td style='text-align:center;' >Fault</td><td style='text-align:center;' >潛在可恢復錯誤</td><td style='text-align:center;' >返回當前指令或終止</td><td style='text-align:center;' >頁故障(page faults)</td></tr><tr><td style='text-align:center;' >Abort</td><td style='text-align:center;' >不可恢復的錯誤</td><td style='text-align:center;' >終止當前執(zhí)行的程序</td><td style='text-align:center;' >非法指令咐刨、硬件錯誤</td></tr></tbody>
</table></figure>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119122824882.png" referrerpolicy="no-referrer" alt="image-20220119122824882"></p>
<h2 >2.3 系統(tǒng)調(diào)用</h2>
<p>在X86-64系統(tǒng)中昙衅,每個系統(tǒng)調(diào)用都有唯一的ID號,如:</p>
<figure><table>
<thead>
<tr><th style='text-align:center;' >編號</th><th style='text-align:center;' >名稱</th><th style='text-align:center;' >說明</th></tr></thead>
<tbody><tr><td style='text-align:center;' >0</td><td style='text-align:center;' ><code>read</code></td><td style='text-align:center;' >讀取文件</td></tr><tr><td style='text-align:center;' >1</td><td style='text-align:center;' ><code>write</code></td><td style='text-align:center;' >寫入文件</td></tr><tr><td style='text-align:center;' >2</td><td style='text-align:center;' ><code>open</code></td><td style='text-align:center;' >打開文件</td></tr><tr><td style='text-align:center;' >3</td><td style='text-align:center;' ><code>close</code></td><td style='text-align:center;' >關(guān)閉文件</td></tr><tr><td style='text-align:center;' >4</td><td style='text-align:center;' ><code>stat</code></td><td style='text-align:center;' >文件信息</td></tr><tr><td style='text-align:center;' >57</td><td style='text-align:center;' ><code>fork</code></td><td style='text-align:center;' >創(chuàng)建進程</td></tr><tr><td style='text-align:center;' >59</td><td style='text-align:center;' ><code>execve</code></td><td style='text-align:center;' >執(zhí)行程序</td></tr><tr><td style='text-align:center;' >60</td><td style='text-align:center;' ><code>_exit</code></td><td style='text-align:center;' >關(guān)閉進程</td></tr><tr><td style='text-align:center;' >62</td><td style='text-align:center;' ><code>kill</code></td><td style='text-align:center;' >發(fā)送信號</td></tr></tbody>
</table></figure>
<h3 >2.3.1 系統(tǒng)調(diào)用示例:open</h3>
<p>用戶調(diào)用open打開文件定鸟,系統(tǒng)實際通過__open函數(shù)執(zhí)行編號為2的syscall而涉,返回值為負則出錯,匯編代碼如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123200826.png" referrerpolicy="no-referrer" alt="image-20220119123200826"></p>
<h3 >2.3.2 故障示例</h3>
<p>以Page Fault為例說明联予,Page Fault發(fā)生的前提是:用戶寫入內(nèi)存位置啼县,但該位置暫時不在內(nèi)存,系統(tǒng)榮過Page Fault異常把對應(yīng)的頁從磁盤拷貝到內(nèi)存沸久,代碼和流程如圖:</p>
<pre><code class='language-c' lang='c'>int a[1000];
int main()
{
? ? a[500] = 13;
}
</code></pre>
<p>但若代碼改成非法地址季眷,則整個流程會變成如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123418124.png" referrerpolicy="no-referrer" alt="image-20220119123418124"></p>
<p>系統(tǒng)向用戶進程發(fā)送SIGSEGV信號,用戶進程以segmentation fault的標記退出卷胯。</p>
<h1 >3 進程</h1>
<p>進程是程序的運行實例子刮,它是計算機科學中最重要的思想之一,進程給每個程序提供兩個關(guān)鍵的抽象诵竭,使得具體的進程不需操心處理器和內(nèi)存等細節(jié)话告,也保證不同情況下運行同樣的程序能得到相同的結(jié)果。兩個關(guān)鍵抽象如下:</p>
<ul>
<li>邏輯控制流:通過上下文切換(context switching)的內(nèi)核機制讓每個程序都感覺在獨占處理器</li>
<li>私有地址空間卵慰。通過虛擬內(nèi)存(virtual memory)的機制讓每個程序都感覺在獨占內(nèi)存</li>
</ul>
<p>盡管和每個私有地址空間相關(guān)聯(lián)的內(nèi)存的內(nèi)容一般不同沙郭,但每個這樣的空間都有相同的通用結(jié)構(gòu),如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123450788.png" referrerpolicy="no-referrer" alt="image-20220119123450788"></p>
<h2 >3.1 多進程</h2>
<p>多進程就是計算機同時運行多個進程裳朋,如web瀏覽器病线、email客戶端、編輯器鲤嫡、檢測網(wǎng)絡(luò)和IO設(shè)備等送挑。分為兩大類:</p>
<ul>
<li>傳統(tǒng):單處理器交錯執(zhí)行多個進程(但可把并發(fā)進程看作并行運行),虛擬內(nèi)存系統(tǒng)管理地址空間暖眼,未運行進程的寄存器值存儲在內(nèi)存中惕耕,以便下次執(zhí)行時恢復(即上下文切換),切換時會載入已保存的將要執(zhí)行的進程的寄存器值</li>
<li>現(xiàn)代:現(xiàn)代處理器都有多個核心诫肠,多核處理器即單個芯片有多個CPU司澎,共享主要內(nèi)存和某些緩存,具體調(diào)度由內(nèi)核控制栋豫,每個CPU都可執(zhí)行單獨進程</li>
</ul>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123527065.png" referrerpolicy="no-referrer" alt="image-20220119123527065"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123555006.png" referrerpolicy="no-referrer" alt="image-20220119123555006"></p>
<p>進程切換時挤安,由內(nèi)核負責調(diào)度,如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123627743.png" referrerpolicy="no-referrer" alt="image-20220119123627743"></p>
<h2 >3.2 用戶態(tài)模式和內(nèi)核模</h2>
<p>處理器提供限制應(yīng)用可執(zhí)行的指令及可訪問的地址空間范圍的機制丧鸯,通常用某個控制寄存器中的一個<strong>模式位</strong>(mode biit)提供蛤铜,設(shè)置模式位時,進程就運行在內(nèi)核模式,可執(zhí)行任何指令且訪問系統(tǒng)任何內(nèi)存位置围肥,否則是用戶模式剿干,<strong>必須通過系統(tǒng)調(diào)用接口間接訪問內(nèi)核代碼和數(shù)據(jù)</strong>。</p>
<p>進程初始時在用戶模式虐先,改變?yōu)閮?nèi)核模式的唯一方法是通過中斷怨愤、陷入系統(tǒng)調(diào)用、故障這樣的異常蛹批,異常發(fā)生時,控制傳遞到異常處理程序篮愉,處理器將用戶模式變?yōu)閮?nèi)核模式腐芍,程序在內(nèi)核模式中運行,當控制返回到進程時试躏,處理器將模式從內(nèi)核模式變?yōu)橛脩裟J?lt;/p>
<h2 >3.3 上下文切換</h2>
<p>內(nèi)核用一種被稱為<strong>上下文切換</strong>(context switch)的較高層形式的異持碛拢控制流實現(xiàn)多任務(wù),是建立在較低層異常機制上的颠蕴。內(nèi)核為每個進程維持一個上下文(context)泣刹,即重新啟動被搶占的進程所需的狀態(tài):寄存器、用戶棧犀被、內(nèi)核棧及各種內(nèi)核數(shù)據(jù)結(jié)構(gòu)(如頁表椅您、進程表、已打開文件的信息的文件表)</p>
<p>內(nèi)核可決定搶占當前進程寡键,重新開始先前被搶占的進程掀泳,該決策叫調(diào)度(scheduling),由內(nèi)核中的調(diào)度器(scheduler)處理西轩。內(nèi)核調(diào)度新的進程運行后员舵,搶占當前進程,使用上下文切換機制將控制權(quán)轉(zhuǎn)移到新進程:</p>
<ol>
<li>保存當前進程上下文</li>
<li>恢復先前被搶占進程保存的上下文</li>
<li>控制權(quán)傳遞給該新恢復的進程</li>
</ol>
<h1 >4 進程控制</h1>
<h2 >4.1 系統(tǒng)調(diào)用錯誤處理</h2>
<p>出錯時藕畔,Linux系統(tǒng)函數(shù)通常返回-1且設(shè)置全局變量errno表示錯誤原因马僻。使用系統(tǒng)函數(shù)時牢記兩個原則:</p>
<ul>
<li>每個系統(tǒng)調(diào)用都應(yīng)檢查返回值</li>
<li>唯一例外是少數(shù)返回void的函數(shù)</li>
</ul>
<p>如對于fork()函數(shù),應(yīng)檢查返回值:</p>
<pre><code class='language-c' lang='c'>if ((pid = fork()) < 0) {
fprintf(stderr, "fork error: %s\n", strerror(errno));
exit(-1);
}
</code></pre>
<h2 >4.2 錯誤處理包裝</h2>
<p>若嫌麻煩可用下面錯誤處理包裝函數(shù)注服,可進一步簡化代碼韭邓,Stevens首先提出該方法,定義相同參數(shù)的包裝函數(shù)但首字母大寫祠汇,包裝函數(shù)內(nèi)調(diào)用基本函數(shù)并檢查錯誤:</p>
<pre><code class='language-c' lang='c'>void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(-1);
}
//則fork()返回錯誤值的部分可改寫為
if ((pid = fork()) < 0)
unix_error("fork error");
</code></pre>
<p>更進一步仍秤,可把整個fork()包裝起來,就可自帶錯誤處理可很,如:</p>
<pre><code class='language-c' lang='c'>pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}
pid = Fork();//調(diào)用時則直接調(diào)用包裝的函數(shù)
</code></pre>
<p> </p>
<h2 >4.3 獲取進程ID</h2>
<p>使用以下兩個函數(shù)獲取進程相關(guān)信息:</p>
<ul>
<li><code>pid_t getpid(void)</code>:返回當前進程PID</li>
<li><code>pid_t getppid(void)</code> - 返回當前進程的父進程的 PID</li>
</ul>
<h2 >4.4 進程生命周期</h2>
<p>從程序員的角度看诗力,可將進程看作是處于三種狀態(tài)之一:</p>
<figure><table>
<thead>
<tr><th style='text-align:left;' >狀態(tài)</th><th style='text-align:left;' >說明</th></tr></thead>
<tbody><tr><td style='text-align:left;' >運行 Running</td><td style='text-align:left;' >進程要么正運行,要么等待被執(zhí)行或最終將會被內(nèi)核調(diào)度執(zhí)行</td></tr><tr><td style='text-align:left;' >停止 Stopped</td><td style='text-align:left;' >進程執(zhí)行被掛起(suspended),并且在進一步收到<code>SIGCONT</code>信號前不會被調(diào)度執(zhí)行</td></tr><tr><td style='text-align:left;' >終止 Terminated</td><td style='text-align:left;' >進程被永久停止</td></tr></tbody>
</table></figure>
<p>當然還包括新建(new)和就緒(ready)苇本,待補充袜茧。</p>
<h3 >4.4.1 終止進程</h3>
<p>包括三種情況:</p>
<ul>
<li>接收到終止信號</li>
<li>從<code>main</code>函數(shù)返回</li>
<li>調(diào)用<code>exit</code>函數(shù),注意該函數(shù)被調(diào)用一次但從不返回</li>
</ul>
<h3 >4.4.2 創(chuàng)建進程</h3>
<p>父進程調(diào)用<code>fork</code>創(chuàng)建新進程瓣窄,注意該函數(shù)執(zhí)行一次笛厦,但返回兩次,子進程返回0俺夕,父進程返回子進程PID裳凸,函數(shù)原型:</p>
<pre><code class='language-c' lang='c'>// 子進程,返回 0劝贸,父進程姨谷,返回子進程的 PID
int fork(void)
</code></pre>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123702412.png" referrerpolicy="no-referrer" alt="image-20220119123702412"></p>
<p>注意:子進程和父進程幾乎完全相同,子進程獲得父進程虛擬地址空間相同但獨立的副本映九,獲得相同的父進程打開的文件描述符拷貝梦湘,但子進程有和父進程不同的PID</p>
<p>完全拷貝執(zhí)行狀態(tài):</p>
<ul>
<li>指定一個為父,一個為子</li>
<li>恢復父或子的執(zhí)行</li>
</ul>
<h3 >4.4.3 重新審視<code>fork</code>函數(shù)</h3>
<ul>
<li>虛擬內(nèi)存和內(nèi)存映射解釋<code>fork</code>如何為每個進程提供私有地址空間</li>
<li>創(chuàng)建當前進程的<code>mm_struct</code>件甥、<code>vm_area_struct</code>和頁表的精確拷貝捌议,標記每個進程的每個頁為只讀,標記每個進程的每個<code>vm_area_struct</code>為私有COW引有,每個進程有精確的虛擬內(nèi)存拷貝</li>
<li>后續(xù)寫操作使用COW機制創(chuàng)建新頁</li>
</ul>
<h3 >4.4.4 <code>fork</code>示例</h3>
<pre><code class='language-c' lang='c'>int main()
{
? ? pid_t pid;
? ? int x = 1;
? ? pid = Fork();
? ? if (pid == 0) {? // Child
? ? ? ? printf("I'm the child!? x = %d\n", ++x);
? ? ? ? return 0;
? ? }
? ? // Parent
? ? printf("I'm the parent! x = %d\n", --x);
? ? return 0;
}
</code></pre>
<p>注意:</p>
<ul>
<li>調(diào)用一次返回兩次</li>
<li>并行執(zhí)行瓣颅,無法預計父進程和子進程執(zhí)行順序,因此執(zhí)行結(jié)果有兩種</li>
<li>雙方有相同的但獨立的地址空間(變量獨立)</li>
<li>共享文件轿曙,繼承所有打開文件弄捕,如都把它們的輸出顯示在屏幕,因為父調(diào)fork時导帝,stdout是打開守谓,子繼承并輸出到指向的屏幕</li>
</ul>
<p>補充:</p>
<ul>
<li>問題:Linux調(diào)度器不會產(chǎn)生太多run-to-run的變化,在不確定中隱藏潛在的競爭關(guān)系您单,如<code>fork</code>是先返回child還是parent?</li>
<li>解決:創(chuàng)建自定義的庫例程斋荞,在不同的分支中插入隨機延遲;使用運行時定位使程序使用特殊版本的庫代碼</li>
</ul>
<pre><code class='language-c' lang='c'>/* fork wrapper function */
pid_t fork(void) {
initialize();
int parent_delay = choose_delay();
int child_delay = choose_delay();
pid_t parent_pid = getpid();
pid_t child_pid_or_zero = real_fork();
if (child_pid_or_zero > 0) {
/* Parent */
if (verbose) {
printf("Fork. Child pid=%d, delay = %dms. Parent pid=%d, delay = %dms\n",child_pid_or_zero, child_delay,parent_pid, parent_delay);
fflush(stdout);
}
ms_sleep(parent_delay);
} else {
/* Child */
ms_sleep(child_delay);
}
return child_pid_or_zero;
}
</code></pre>
<p> </p>
<h2 >4.5 進程圖</h2>
<p>進程圖是一個很有用的工具虐秦,可捕獲并發(fā)程序中部分語句的順序:For the process graph, as long as topological sorting is satisfied, it is a possible output</p>
<ul>
<li>每個節(jié)點表示一條執(zhí)行的語句</li>
<li>a -> b 表示 a 在 b 前面執(zhí)行</li>
<li>邊可以用當前變量的值來標記</li>
<li><code>printf</code> 節(jié)點可用輸出來標記</li>
<li>每個圖由一個入度為 0 的點起始</li>
</ul>
<p>對于進程圖來說平酿,只要滿足拓撲排序,就是可能的輸出悦陋。嵌套的<code>fork</code>示例:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119123906902.png" referrerpolicy="no-referrer" alt="image-20220119123906902"></p>
<p> </p>
<h2 >4.6 回收子進程</h2>
<h3 >4.6.1 回收子進程</h3>
<ul>
<li>定義:即使進程已終止蜈彼,但還未被回收的進程還在消耗系統(tǒng)資源,如<code>exit</code>狀態(tài)俺驶、系統(tǒng)表幸逆,稱之為僵尸進程,”半死不活“。</li>
<li>回收:可以采用回收(Reaping) 的方法还绘。父進程用 <code>wait</code> 或 <code>waitpid</code> 回收已終止的子進程楚昭,然后父進程給系統(tǒng)提供退出狀態(tài)信息,kernel 就會刪除 zombie child process拍顷。</li>
<li>父進程不回收:若父進程不回收子進程的話抚太,通常會被 <code>init</code> 進程(pid == 1)回收(所以一般不必顯式回收),除非ppid==1昔案,然后需要重啟尿贫。所以僅長期運行的進程,需要顯式回收(例如 shells 和 servers)爱沟。</li>
</ul>
<h3 >4.6.2 僵尸進程和孤兒進程示例</h3>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119124008845.png" referrerpolicy="no-referrer" alt="image-20220119124008845"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119124040020.png" referrerpolicy="no-referrer" alt="image-20220119124040020"></p>
<p><strong>孤兒進程</strong>指子進程正運行帅霜,父進程突然退出,子進程就是孤兒進程呼伸。進程都需要一父進程,否則進程退出后無法回收進程描述符钝尸,消耗資源括享,該進程會找到一個父進程,若所在進程組沒進程收養(yǎng)珍促,就作為<code>init</code>進程的子進程</p>
<h3 >4.6.3 <code>wait</code>和<code>waitpid</code></h3>
<ul>
<li><code>wait</code>:父進程回收子進程調(diào)用該函數(shù)铃辖,函數(shù)聲明為:<code>int wait(int *child_status)</code>,由<code>syscall</code>實現(xiàn)猪叙,等待當前進程直到它的一個子進程終止娇斩,返回終止進程的PID,如果<code>child_status</code>非空穴翩,那么它所指向的整數(shù)將被設(shè)置為表明子進程終止的原因和退出狀態(tài)的值犬第,可用<code>WIFEXITED</code>等宏檢查,shlab中有用到芒帕。若有多個孩子進程則按任意順序歉嗓,同樣可用宏檢查退出狀態(tài)。</li>
</ul>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119141030777.png" referrerpolicy="no-referrer" alt="image-20220119141030777"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119141050821.png" referrerpolicy="no-referrer" alt="image-20220119141050821"></p>
<pre><code class='language-c' lang='c'>void fork10() {
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
</code></pre>
<p>注意:若多個子進程完成背蟆,按任意順序鉴分;可用宏<code>WIFEXITED</code>和<code>WEXITSTATUS</code>獲取退出狀態(tài)的信息</p>
<ul>
<li><code>waitpid</code>:暫停當前進程直到進程描述符為pid的進程終止,函數(shù)聲明為:<code>pid_t waitpid(pid_t pid, int *status, int options)</code></li>
</ul>
<pre><code class='language-c' lang='c'>void fork11() {
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
</code></pre>
<p>可修改函數(shù)中的options為:WNOHANG带膀、WUNTRACED志珍、WCONTINUED各種組合修改默認行為。同樣也可像<code>wait</code>一樣垛叨,若<code>status</code>參數(shù)非空伦糯,該函數(shù)就會在<code>status</code>指向的地方放上導致返回的子進程的狀態(tài)信息,可用WIFEXITED、WEXITSTATUS舔株、WIFSIGNALED莺琳、WITERMSIG、WIFSTOPPED载慈、WSTOPSIG惭等、WIFCONTINUED宏檢查已回收子進程的退出狀態(tài)。</p>
<h3 >4.6.4 execve</h3>
<p>想在進程載入其他的程序办铡,就需要該函數(shù)辞做。函數(shù)聲明:<code>int execve(char *filename, char *argv[], char *envp[])</code>,執(zhí)行<code>filename</code>的程序(可以是二進制文件或腳本)寡具,參數(shù)和環(huán)境變量分別為<code>argv</code>和<code>envp</code>秤茅,重寫code、data和stack童叠,保留PID框喳、打開文件和信號上下文,調(diào)用一次且不返回除非出錯厦坛。</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119140326232.png" referrerpolicy="no-referrer" alt="image-20220119140326232"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119140351196.png" referrerpolicy="no-referrer" alt="image-20220119140351196"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119140414419.png" referrerpolicy="no-referrer" alt="image-20220119140414419"></p>
<h1 >5 signal</h1>
<h2 >5.1 信號</h2>
<p>已經(jīng)學習硬件和軟件合作提供基本的底層異常機制五垮,也看到利用異常來支持進程上下文切換的異常控制流形式杜秸,更高層的軟件形式的異常被稱為信號放仗,允許進程和內(nèi)核中斷其他進程。</p>
<p><strong>信號</strong>提醒進程一個事件已經(jīng)發(fā)生撬碟,類似異常和中斷诞挨,異常和中斷是從硬件發(fā)往內(nèi)核,信號由內(nèi)核(在其他進程的請求下)向進程發(fā)出呢蛤,可能異步也可能同步發(fā)生惶傻。例如:硬件異常、硬件中斷顾稀、另一個進程中的事件达罗、另一個進程的顯式請求。</p>
<p>每個信號都有name和ID静秆,大多信號能被進程處理粮揉,就像中斷處理程序,函數(shù)指針表抚笔,每個進程都有一個默認的動作扶认,若信號沒被處理,通常要么被忽略(ignore)要么中斷進程(terminate process)</p>
<p>常用信號的編號及簡介:</p>
<figure><table>
<thead>
<tr><th>事件</th><th>名稱</th><th>ID</th><th>默認動作</th></tr></thead>
<tbody><tr><td>用戶按ctrl-c</td><td>SIGINT</td><td>2</td><td>終止</td></tr><tr><td>強制中斷(不能被處理)</td><td>SIGKILL</td><td>9</td><td>終止</td></tr><tr><td>段沖突</td><td>SIGSEGV</td><td>11</td><td>終止且dump</td></tr><tr><td>時鐘信號</td><td>SIGALRM</td><td>14(可變)</td><td>終止</td></tr><tr><td>子進程停止或終止</td><td>SIGCHLD</td><td>17(可變)</td><td>忽略</td></tr></tbody>
</table></figure>
<h2 >5.2 信號概念</h2>
<h3 >5.2.1 發(fā)送和傳遞</h3>
<p>當事件發(fā)生時殊橙,內(nèi)核<strong>發(fā)送</strong>信號辐宾,如硬件異常狱从、硬件中斷、另一個進程發(fā)生某些事(如exit)叠纹、另一個進程要求發(fā)送信號季研。</p>
<p>當內(nèi)核使目標進程對信號作出響應(yīng)時,內(nèi)核<strong>傳遞</strong>(delivers)信號誉察。如執(zhí)行處理程序与涡、執(zhí)行默認操作。</p>
<p>發(fā)送和傳遞信號之間可能存在延遲持偏,通常是因為進程不能立刻被調(diào)度驼卖,延遲期間,信號處于待處理(pending)狀態(tài)</p>
<h3 >5.2.2 待處理和阻塞信號</h3>
<p>如果信號已被發(fā)送但是未被接收鸿秆,那么處于<strong>待處理狀態(tài)</strong>(pending)酌畜,注意:信號不排隊,任何時刻一個類型至多只有一個待處理信號卿叽,因此若進程有一個類型為K的待處理的信號桥胞,則后續(xù)的被發(fā)送給進程的類型為K的信號都被直接丟棄。進程也可以<strong>阻塞</strong>特定信號的接收考婴,直到信號被解除阻塞埠戳。</p>
<h3 >5.2.3 接收信號</h3>
<p>目標進程以某種方式對信號的傳遞做出響應(yīng)時,進程<strong>接收</strong>信號蕉扮,可能響應(yīng)的操作:</p>
<ul>
<li><strong>忽略</strong>(ignore):忽略該型號,不作任何響應(yīng)</li>
<li><strong>終止</strong>(terminate):終止進程颗圣,可能core dump</li>
<li><strong>捕獲</strong>(catch):執(zhí)行用戶層的函數(shù):信號處理器(signal handler)喳钟,類似響應(yīng)異步中斷而調(diào)用的硬件異常處理程序(exception handler)</li>
</ul>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119140511533.png" referrerpolicy="no-referrer" alt="image-20220119140511533"></p>
<h3 >5.2.4 待處理和阻塞位</h3>
<p>內(nèi)核在每個進程的上下文中維護等待(pending)和阻塞(blocked)位集合</p>
<ul>
<li>等待信號集合:表示等待信號集合,當類型為k的信號被傳遞在岂,內(nèi)核將位k置為pending奔则,當接收到類型為k的信號時,內(nèi)核清除待處理的位k蔽午,任何時刻每種類型為k的信號只有一個</li>
<li>阻塞信號集合:表示阻塞信號集合易茬,可用<code>sigprocmask</code>函數(shù)設(shè)置和清除,也稱為信號掩碼</li>
</ul>
<h2 >5.3 進程組</h2>
<p>每個進程都只屬于一個進程組及老,如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119141214993.png" referrerpolicy="no-referrer" alt="image-20220119141214993"></p>
<p>一般使用如下函數(shù):</p>
<ul>
<li><code>getpgrp()</code> - 返回當前進程的進程組</li>
<li><code>setpgid()</code> - 設(shè)置一個進程的進程組</li>
</ul>
<p>可以通過 <code>kill</code> 來發(fā)送信號給進程組或進程(包括自己)抽莱,如圖:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119141233949.png" referrerpolicy="no-referrer" alt="image-20220119141233949"></p>
<pre><code class='language-c' lang='c'>void fork12()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1)
;
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
}
</code></pre>
<p> </p>
<h3 >5.3.1 從鍵盤發(fā)送信號</h3>
<p>可通過鍵盤讓內(nèi)核向每個前臺進程發(fā)送 SIGINT(SIGTSTP) 信號</p>
<ul>
<li>SIGINT - <code>ctrl+c</code> 默認終止進程</li>
<li>SIGTSTP - <code>ctrl+z</code> 默認停止(掛起)進程</li>
</ul>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119141804157.png" referrerpolicy="no-referrer" alt="image-20220119141804157"></p>
<h3 >5.3.2 信號傳遞細節(jié)</h3>
<p>假定內(nèi)核正從一個異常處理程序返回,且準備將控制權(quán)傳給進程p骄恶,內(nèi)核會計算進程 p 的 pnb 值:<code>pnb = pending & ~blocked</code></p>
<ul>
<li><p>如果 <code>pnb == 0</code>食铐,那么就把控制交給進程 p 的邏輯流中的下一條指令</p>
</li>
<li><p>否則</p>
<ul>
<li>選擇 <code>pnb</code> 中最小的非零位 k,并強制進程 p 接收信號 k</li>
<li>接收到信號之后僧鲁,進程 p 會執(zhí)行對應(yīng)的動作</li>
<li>對 <code>pnb</code> 中所有的非零位進行這個操作</li>
<li>最后把控制交給進程 p 的邏輯流中的下一條指令</li>
</ul>
</li>
</ul>
<p>每個信號類型都有一個默認動作虐呻,可能是以下的情況:</p>
<ul>
<li>忽略信號</li>
</ul>
<ul>
<li>終止進程并 dump core</li>
<li>停止進程象泵,收到 <code>SIGCONT</code> 信號之后重啟</li>
<li>若進程停止則進程重啟</li>
</ul>
<h2 >5.4 信號控制</h2>
<h3 >5.4.1 注冊信號處理程序</h3>
<p><code>sigaction</code>函數(shù)改變與接收信號相關(guān)的程序,原型是:<code>int sigaction(int signum,const struct sigaction *sa,struct sigaction *old_sa)</code>斟叼,sa結(jié)構(gòu)提設(shè)置新的處理程序偶惠,選項包括<code>ignore</code>、默認處理器朗涩、調(diào)用該函數(shù)忽孽,也有控制信號傳遞細節(jié)的選項,若old_sa非空馋缅,則old action存儲在這扒腕。</p>
<pre><code class='language-c' lang='c'>#include <signal.h>
#include <stdio.h>
void sigint_handler(int sig) {
// Doesn’t do anything but interrupt the call to pause() below.
}
int main(void) {
struct sigaction sa;
// Sensible defaults. Use these unless you have a reason not to.
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
// The handler for SIGINT will be sigint_handler.
sa.sa_handler = sigint_handler;
? ? if (sigaction(SIGINT, &sa, 0) != 0)
unix_error("signal error");
/* Wait for the receipt of a signal */
pause();
puts("Ctrl-C received, exiting.");
return 0;
}
</code></pre>
<p> </p>
<h3 >5.4.2 作為并發(fā)流的信號處理程序</h3>
<p>信號處理程序是獨立的邏輯流(不是進程),與主程序并發(fā)運行萤悴,但該流只存在直到返回主程序</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119142040551.png" referrerpolicy="no-referrer" alt="image-20220119142040551"></p>
<p>此外瘾腰,信號處理程序也可被其他信號處理程序中斷,控制流如下:</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119142052313.png" referrerpolicy="no-referrer" alt="image-20220119142052313"></p>
<h3 >5.4.3 阻塞和非阻塞信號</h3>
<ul>
<li><p>隱式阻塞機制:內(nèi)核會阻塞與當前在處理的信號同類型的其他正等待的信號覆履,如一個 SIGINT 信號處理器是不能被另一個 SIGINT 信號中斷的蹋盆。</p>
</li>
<li><p>顯式阻塞機制:使用 <code>sigprocmask</code> 函數(shù),以及其他輔助函數(shù):</p>
<ul>
<li><code>sigemptyset</code> :創(chuàng)建空集</li>
<li><code>sigfillset</code> :把所有的信號都添加到集合中(因為信號數(shù)目不多)</li>
<li><code>sigaddset</code> :添加指定信號到集合中</li>
<li><code>sigdelset</code> :刪除集合中的指定信號</li>
</ul>
</li>
</ul>
<p>臨時阻塞信號示例:</p>
<pre><code class='language-c' lang='c'>sigset_t mask, prev_mask;
Sigemptyset(&mask); // 創(chuàng)建空集
Sigaddset(&mask, SIGINT); // 把 SIGINT 信號加入屏蔽列表中
// 阻塞對應(yīng)信號硝全,并保存之前的集合
Sigprocmask(SIG_BLOCK, &mask, &prev_mask);
... // 這部分代碼不會被 SIGINT 中斷
// 取消阻塞信號栖雾,恢復原來的狀態(tài)
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);
</code></pre>
<h3 >5.4.4 安全處理信號</h3>
<p>Handler是十分棘手,因為它們和主程序并發(fā)執(zhí)行且共享相同的全局數(shù)據(jù)結(jié)構(gòu)伟众,所以不被保護的數(shù)據(jù)可能會被破壞析藕,這里提供一些基本的指南幫助避免問題:</p>
<ul>
<li><p>規(guī)則 1:信號處理器越簡單越好</p>
<ul>
<li>例如:設(shè)置一個全局的標記,并返回</li>
</ul>
</li>
<li><p>規(guī)則 2:信號處理器中只調(diào)用異步且信號安全(async-signal-safe)的函數(shù)</p>
<ul>
<li>諸如 <code>printf</code>, <code>sprintf</code>, <code>malloc</code> 和 <code>exit</code> 都是不安全的凳厢!</li>
</ul>
</li>
<li><p>規(guī)則 3:在進入和退出的時候保存和恢復 <code>errno</code></p>
<ul>
<li>這樣信號處理器就不會覆蓋原有的 <code>errno</code> 值</li>
</ul>
</li>
<li><p>規(guī)則 4:臨時阻塞所有的信號以保證對于共享數(shù)據(jù)結(jié)構(gòu)的訪問</p>
<ul>
<li>防止可能出現(xiàn)的數(shù)據(jù)破壞</li>
</ul>
</li>
<li><p>規(guī)則 5:用 <code>volatile</code>關(guān)鍵字聲明全局變量</p>
<ul>
<li>這樣編譯器就不會把它們保存在寄存器中账胧,保證一致性</li>
</ul>
</li>
<li><p>規(guī)則 6:用 <code>volatile sig_atomic_t</code>來聲明全局標識符(flag)</p>
<ul>
<li>flag是只能讀或?qū)懙淖兞浚@樣定義后就不需像其他全局變量被保護</li>
</ul>
</li>
</ul>
<h3 >5.4.5 異步信號安全</h3>
<p>指兩類函數(shù):</p>
<ul>
<li>所有變量都保存在幀棧中的可重入函數(shù)</li>
<li>不會被信號中斷的函數(shù)</li>
</ul>
<p>Posix 標準指定了 117 個異步信號安全(async-signal-safe)的函數(shù)(可通過 <code>man 7 signal-safety</code> 查看)先紫,常用的<code>printf治泥、sprintf、malloc遮精、exit</code>都不是居夹。</p>
<h3 >5.4.6 信號安全代碼示例</h3>
<ul>
<li>同步避免父子競爭:</li>
</ul>
<pre><code class='language-c' lang='c'>int main(int argc, char **argv)
{
int pid;
sigset_t mask_all, mask_one, prev_one;
int n = N; /* N = 5 */
Sigfillset(&mask_all);
Sigemptyset(&mask_one);
Sigaddset(&mask_one, SIGCHLD);
Signal(SIGCHLD, handler);
initjobs(); /* Initialize the job list */
while (n--) {
Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); /* Block SIGCHLD */
if ((pid = Fork()) == 0) { /* Child process */
Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
Execve("/bin/date", argv, NULL);
}
Sigprocmask(SIG_BLOCK, &mask_all, NULL); /* Parent process */
addjob(pid); /* Add the child to the job list */
Sigprocmask(SIG_SETMASK, &prev_one, NULL); /* Unblock SIGCHLD */
}
exit(0);
}
</code></pre>
<p> </p>
<ul>
<li>顯式等待信號:</li>
</ul>
<pre><code class='language-c' lang='c'>volatile sig_atomic_t pid;
void sigchld_handler(int s)
{
int olderrno = errno;
pid = Waitpid(-1, NULL, 0); /* Main is waiting for nonzero pid */
errno = olderrno;
}
void sigint_handler(int s)
{
}
</code></pre>
<pre><code class='language-c' lang='c'>int main(int argc, char **argv) {
sigset_t mask, prev;
int n = N; /* N = 10 */
Signal(SIGCHLD, sigchld_handler);
Signal(SIGINT, sigint_handler);
Sigemptyset(&mask);
Sigaddset(&mask, SIGCHLD);
//Similar to a shell waiting for a foreground job to terminate.
while (n--) {
Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */
if (Fork() == 0) /* Child */
exit(0);
/* Parent */
pid = 0;
Sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock SIGCHLD */
? ? ? ? /* Wait for SIGCHLD to be received (wasteful!) */
while (!pid)
;
/* Do some work after receiving SIGCHLD */
printf(".");
}
printf("\n");
exit(0);
}
</code></pre>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119142649442.png" referrerpolicy="no-referrer" alt="image-20220119142649442"></p>
<ul>
<li>使用<code>sigsuspend</code>等價于不可中斷版本:</li>
</ul>
<pre><code class='language-c' lang='c'>sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);
</code></pre>
<pre><code class='language-c' lang='c'>int main(int argc, char **argv) {
sigset_t mask, prev;
int n = N; /* N = 10 */
Signal(SIGCHLD, sigchld_handler);
Signal(SIGINT, sigint_handler);
Sigemptyset(&mask);
Sigaddset(&mask, SIGCHLD);
while (n--) {
Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */
if (Fork() == 0) /* Child */
exit(0);
? ? ? ? /* Wait for SIGCHLD to be received */
pid = 0;
while (!pid)
Sigsuspend(&prev);
/* Optionally unblock SIGCHLD */
Sigprocmask(SIG_SETMASK, &prev, NULL);
/* Do some work after receiving SIGCHLD */
printf(".");
}
printf("\n");
exit(0);
}
</code></pre>
<p> </p>
<h2 >5.5 非本地跳轉(zhuǎn)</h2>
<h3 >5.5.1 非本地跳轉(zhuǎn)</h3>
<p>本地跳轉(zhuǎn)的限制在于不能從一個函數(shù)跳轉(zhuǎn)到另一個函數(shù)中。若突破限制本冲,C語言提供用戶級異匙贾控制流形式,叫非本地跳轉(zhuǎn)眼俊,就要使用 <code>setjmp</code> 或 <code>longjmp</code> 來進行<strong>非本地跳轉(zhuǎn)</strong>(Nonlocal Jumps)意狠。強大(但危險)用戶級機制,用于將控制權(quán)轉(zhuǎn)移到任意位置疮胖,打破<code>call/return</code>調(diào)用機制环戈,對錯誤恢復和信號處理有幫助闷板。</p>
<p><code>setjmp</code> 聲明是:<code>int setjmp(jmp_buf j)</code>,必須在<code>longjmp</code>前調(diào)用院塞,為后續(xù)<code>longjmp</code>標識返回地址遮晚,調(diào)用一次返回一次或多次,保存當前程序的寄存器上下文(register context)拦止、棧指針县遣、PC寄存器值在<code>jmp_buf</code>中,注意汹族,保存的堆棧上下文環(huán)境僅在調(diào)用 <code>setjmp</code> 的函數(shù)內(nèi)有效萧求,如果調(diào)用 <code>setjmp</code> 的函數(shù)返回,保存的上下文環(huán)境就失效了顶瞒。直接返回值為 0夸政。</p>
<p><code>longjmp</code> 聲明是:<code>void longjmp(jmp_buf j, int i)</code>,<code>setjmp</code>后被調(diào)用榴徐,調(diào)用一次但永遠不返回守问,將會從緩存j中恢復由 <code>setjmp</code> 保存的程序堆棧上下文,跳轉(zhuǎn)到j(luò)中保存的地址坑资,設(shè)置<code>%eax</code>(返回值)為i耗帕,而不是<code>setjmp</code>的0</p>
<pre><code class='language-c' lang='c'>/* Deeply nested function foo */
void foo(void)
{
if (error1)
longjmp(buf, 1);
bar();
}
void bar(void)
{
if (error2)
longjmp(buf,2);
}
jmp_buf buf;
int error1 = 0;
int error2 = 1;
void foo(void), bar(void);
int main()
{
switch(setjmp(buf)) {
case 0:
foo();
break;
case 1:
printf("Detected an error1 condition in foo\n");
break;
case 2:
printf("Detected an error2 condition in foo\n");
break;
default:
printf("Unknown error condition in foo\n");
}
exit(0);
}
</code></pre>
<h3 >5.5.2 非本地跳轉(zhuǎn)的限制</h3>
<p>只能跳轉(zhuǎn)到已經(jīng)調(diào)用但尚未完成(函數(shù)還在棧中)的函數(shù)環(huán)境</p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119142859909.png" referrerpolicy="no-referrer" alt="image-20220119142859909"></p>
<p><img src="https://gitee.com/wwgswqg/picgopics/raw/master/img/image-20220119142911613.png" referrerpolicy="no-referrer" alt="image-20220119142911613"></p>
<p>P2在跳轉(zhuǎn)的時候已返回,棧幀在內(nèi)存中已被清理袱贮,所以P3中的 <code>longjmp</code> 并不能實現(xiàn)期望的操作</p>
<h1 >6 操作進程的工具</h1>
<ul>
<li>STRACE:打印正運行的程序和它子進程調(diào)用的每個系統(tǒng)調(diào)用的軌跡仿便,靜態(tài)編譯程序可得到干凈不帶大量與共享庫相關(guān)的輸出的軌跡</li>
<li>PS:列出當前系統(tǒng)中所有進程(包括僵尸進程)</li>
<li>TOP:打印當前進程資源使用信息</li>
<li>PMAP:進程內(nèi)存映射</li>
<li>/proc:虛擬文件系統(tǒng),以ASCII碼輸出大量內(nèi)核數(shù)據(jù)結(jié)構(gòu)內(nèi)容</li>
</ul>
<h1 >7 總結(jié)</h1>
<p>異吃芪。控制流(ECF)發(fā)生在系統(tǒng)各層次探越,是系統(tǒng)提供并發(fā)的基本機制:</p>
<ul>
<li>硬件層:四種類型異常</li>
<li>操作系統(tǒng)層:內(nèi)核用ECF提供進程基本概念,進程提供兩個重要抽象:邏輯控制流和私有地址空間</li>
<li>操作系統(tǒng)和程序之間的接口:程序可創(chuàng)建子進程窑业,等進程停止或終止,運行新程序以及捕獲其他進程的信號</li>
<li>應(yīng)用層:C程序可用非本地跳轉(zhuǎn)規(guī)避正常調(diào)用/返回棧規(guī)則枕屉,直接從一個函數(shù)分支道另一個函數(shù)</li>
</ul>
<p> </p>