csapp之第8章:異趁槐觯控制流

<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()) &lt; 0) {

fprintf(stderr, &quot;fork error: %s\n&quot;, 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, &quot;%s: %s\n&quot;, msg, strerror(errno));

exit(-1);

}

//則fork()返回錯誤值的部分可改寫為

if ((pid = fork()) &lt; 0)

unix_error(&quot;fork error&quot;);

</code></pre>

<p>更進一步仍秤,可把整個fork()包裝起來,就可自帶錯誤處理可很,如:</p>

<pre><code class='language-c' lang='c'>pid_t Fork(void)

{

pid_t pid;

if ((pid = fork()) &lt; 0)

unix_error(&quot;Fork error&quot;);

return pid;

}

pid = Fork();//調(diào)用時則直接調(diào)用包裝的函數(shù)

</code></pre>

<p>&nbsp;</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(&quot;I&#39;m the child!? x = %d\n&quot;, ++x);

? ? ? ? return 0;

? ? }


? ? // Parent

? ? printf(&quot;I&#39;m the parent! x = %d\n&quot;, --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 &gt; 0) {

/* Parent */

if (verbose) {

printf(&quot;Fork. Child pid=%d, delay = %dms. Parent pid=%d, delay = %dms\n&quot;,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>&nbsp;</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 -&gt; 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>&nbsp;</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 &lt; N; i++)

if ((pid[i] = fork()) == 0) {

exit(100+i); /* Child */

}

for (i = 0; i &lt; N; i++) { /* Parent */

pid_t wpid = wait(&amp;child_status);

if (WIFEXITED(child_status))

printf(&quot;Child %d terminated with exit status %d\n&quot;,

wpid, WEXITSTATUS(child_status));

else

printf(&quot;Child %d terminate abnormally\n&quot;, 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 &lt; N; i++)

if ((pid[i] = fork()) == 0)

exit(100+i); /* Child */

for (i = N-1; i &gt;= 0; i--) {

pid_t wpid = waitpid(pid[i], &amp;child_status, 0);

if (WIFEXITED(child_status))

printf(&quot;Child %d terminated with exit status %d\n&quot;,

wpid, WEXITSTATUS(child_status));

else

printf(&quot;Child %d terminate abnormally\n&quot;, 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 &lt; N; i++)

if ((pid[i] = fork()) == 0) {

/* Child: Infinite Loop */

while(1)

;

}

for (i = 0; i &lt; N; i++) {

printf(&quot;Killing process %d\n&quot;, pid[i]);

kill(pid[i], SIGINT);

}

for (i = 0; i &lt; N; i++) {

pid_t wpid = wait(&amp;child_status);

if (WIFEXITED(child_status))

printf(&quot;Child %d terminated with exit status %d\n&quot;,

wpid, WEXITSTATUS(child_status));

else

printf(&quot;Child %d terminated abnormally\n&quot;, wpid);

}

}

</code></pre>

<p>&nbsp;</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 &amp; ~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 &lt;signal.h&gt;

#include &lt;stdio.h&gt;

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(&amp;sa.sa_mask);

sa.sa_flags = SA_RESTART;

// The handler for SIGINT will be sigint_handler.

sa.sa_handler = sigint_handler;

? ? if (sigaction(SIGINT, &amp;sa, 0) != 0)

unix_error(&quot;signal error&quot;);

/* Wait for the receipt of a signal */

pause();

puts(&quot;Ctrl-C received, exiting.&quot;);

return 0;

}

</code></pre>

<p>&nbsp;</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(&amp;mask); // 創(chuàng)建空集

Sigaddset(&amp;mask, SIGINT); // 把 SIGINT 信號加入屏蔽列表中

// 阻塞對應(yīng)信號硝全,并保存之前的集合

Sigprocmask(SIG_BLOCK, &amp;mask, &amp;prev_mask);

... // 這部分代碼不會被 SIGINT 中斷

// 取消阻塞信號栖雾,恢復原來的狀態(tài)

Sigprocmask(SIG_SETMASK, &amp;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(&amp;mask_all);

Sigemptyset(&amp;mask_one);

Sigaddset(&amp;mask_one, SIGCHLD);

Signal(SIGCHLD, handler);

initjobs(); /* Initialize the job list */


while (n--) {

Sigprocmask(SIG_BLOCK, &amp;mask_one, &amp;prev_one); /* Block SIGCHLD */

if ((pid = Fork()) == 0) { /* Child process */

Sigprocmask(SIG_SETMASK, &amp;prev_one, NULL); /* Unblock SIGCHLD */

Execve(&quot;/bin/date&quot;, argv, NULL);

}

Sigprocmask(SIG_BLOCK, &amp;mask_all, NULL); /* Parent process */

addjob(pid); /* Add the child to the job list */

Sigprocmask(SIG_SETMASK, &amp;prev_one, NULL); /* Unblock SIGCHLD */

}

exit(0);

}

</code></pre>

<p>&nbsp;</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(&amp;mask);

Sigaddset(&amp;mask, SIGCHLD);

//Similar to a shell waiting for a foreground job to terminate.

while (n--) {

Sigprocmask(SIG_BLOCK, &amp;mask, &amp;prev); /* Block SIGCHLD */

if (Fork() == 0) /* Child */

exit(0);

/* Parent */

pid = 0;

Sigprocmask(SIG_SETMASK, &amp;prev, NULL); /* Unblock SIGCHLD */

? ? ? ? /* Wait for SIGCHLD to be received (wasteful!) */

while (!pid)

;

/* Do some work after receiving SIGCHLD */

printf(&quot;.&quot;);

}

printf(&quot;\n&quot;);

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, &amp;mask, &amp;prev);

pause();

sigprocmask(SIG_SETMASK, &amp;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(&amp;mask);

Sigaddset(&amp;mask, SIGCHLD);

while (n--) {

Sigprocmask(SIG_BLOCK, &amp;mask, &amp;prev); /* Block SIGCHLD */

if (Fork() == 0) /* Child */

exit(0);

? ? ? ? /* Wait for SIGCHLD to be received */

pid = 0;

while (!pid)

Sigsuspend(&amp;prev);

/* Optionally unblock SIGCHLD */

Sigprocmask(SIG_SETMASK, &amp;prev, NULL);

/* Do some work after receiving SIGCHLD */

printf(&quot;.&quot;);

}

printf(&quot;\n&quot;);

exit(0);

}

</code></pre>

<p>&nbsp;</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(&quot;Detected an error1 condition in foo\n&quot;);

break;

case 2:

printf(&quot;Detected an error2 condition in foo\n&quot;);

break;

default:

printf(&quot;Unknown error condition in foo\n&quot;);

}

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>&nbsp;</p>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末常柄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子搀擂,更是在濱河造成了極大的恐慌西潘,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绷雏,死亡現(xiàn)場離奇詭異衙四,居然都是意外死亡骡尽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門品姓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寝并,“玉大人,你說我怎么就攤上這事腹备〕牧剩” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵植酥,是天一觀的道長镀岛。 經(jīng)常有香客問我,道長友驮,這世上最難降的妖魔是什么漂羊? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮卸留,結(jié)果婚禮上走越,老公的妹妹穿的比我還像新娘。我一直安慰自己艾猜,他們只是感情好买喧,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匆赃,像睡著了一般淤毛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上算柳,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天低淡,我揣著相機與錄音,去河邊找鬼瞬项。 笑死蔗蹋,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的囱淋。 我是一名探鬼主播猪杭,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妥衣!你這毒婦竟也來了皂吮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤税手,失蹤者是張志新(化名)和其女友劉穎蜂筹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芦倒,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡艺挪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兵扬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麻裳。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡口蝠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掂器,到底是詐尸還是另有隱情亚皂,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布国瓮,位于F島的核電站灭必,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏乃摹。R本人自食惡果不足惜禁漓,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望孵睬。 院中可真熱鬧播歼,春花似錦、人聲如沸掰读。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹈集。三九已至烁试,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拢肆,已是汗流浹背减响。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留郭怪,地道東北人支示。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像鄙才,于是被迫代替她去往敵國和親颂鸿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 學習目標 1.了解異步異常與同步異常,以及異承鸬椋控制流與平時的邏輯控制流的差異2.理解進程的工作機制,如何通過異常來...
    KEEEPer閱讀 328評論 0 2
  • 異常 當處理器檢測到有事件發(fā)生時,他就會通過一張叫做異常表的跳轉(zhuǎn)表糖驴,進行一個間接的過程調(diào)用僚祷,轉(zhuǎn)到專門用于處理這類事...
    userheng閱讀 412評論 0 0
  • 學習目標 了解異步異常與同步異常辙谜,以及異嘲秤埽控制流與平時的邏輯控制流的差異 理解進程的工作機制,如何通過異常來進行進...
    西部小籠包閱讀 307評論 0 1
  • 實驗介紹 完成一個簡單的shell程序装哆,總體的框架和輔助代碼都已經(jīng)提供好了罐脊,我們需要完成的函數(shù)主要以下幾個: ev...
    leon4ever閱讀 8,389評論 1 4
  • 8.1 引言 在理解線程之前,首先需要了解UNIX/Linux進程蜕琴。 進程是由操作系統(tǒng)創(chuàng)建的萍桌,需要相當數(shù)量的“開銷...
    MachinePlay閱讀 394評論 0 0