在終端中編譯一個示例C語言小程序,保存到文件 gdb-sample.c 中,用GCC編譯之前端
#include <stdio.h> int nGlobalVar = 0; int tempFunction(int a, int b) { printf("tempFunction is called, a = %d, b = %d /n", a, b); return (a + b); } int main() { int n; n = 1; n++; n--; nGlobalVar += 100; nGlobalVar -= 12; printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar); n = tempFunction(1, 2); printf("n = %d", n); return 0; }
在上面的命令行中,使用 -o 參數指定了編譯生成的可執行文件名爲 gdb-sample,使用參數 -g 表示將源代碼信息編譯到可執行文件中。若是不使用參數 -g,會給後面的GDB調試形成不便。linux
下面輸入「gdb」命令啓動GDB,將首先顯示GDB說明:程序員
下面使用「file」命令載入被調試程序 gdb-sample(這裏的 gdb-sample 即前面 GCC 編譯輸出的可執行文件)正則表達式
上圖中最後一行「(gdb) 」爲GDB內部命令引導符,等待用戶輸入GDB命令。 編程
上圖倒數第二行提示已經加載成功。小程序
下面使用「r」命令執行(Run)被調試文件,由於還沒有設置任何斷點,將直接執行到程序結束vim
以後使用「b」命令在 main 函數開頭設置一個斷點(Breakpoint)windows
以後一行提示已經成功設置斷點,並給出了該斷點信息:在源文件 gdb-sample.c 第14行處設置斷點;這是本程序的第一個斷點(序號爲1);斷點處的代碼地址爲 0x8048418。向上看源代碼,第14行中的代碼爲「n = 1」,剛好是 main 函數中的第一個可執行語句(由於前面的「int n;」爲變量定義語句,並不是可執行語句)。 數組
以後, 再次使用「r」命令執行(Run)被調試程序:數據結構
程序中斷在gdb-sample.c第14行處,即main函數是第一個可執行語句處。 上面最後一行信息爲:下一條將要執行的源代碼爲「n = 1;」,它是源代碼文件gdb-sample.c中的第14行。
下面使用「s」命令(Step)執行下一行代碼(即第14行「n = 1;」):
上面的信息表示已經執行完「n = 1;」,並顯示下一條要執行的代碼爲第15行的「n++;」。
既然已經執行了「n = 1;」,即給變量 n 賦值爲 1,那咱們用「p」命令(Print)看一下變量 n 的值是否是 1 :
果真是 1。($1表示這是第一次使用「p」命令——再次執行「p n」將顯示「$2 = 1」。)
下面咱們分別在第21行打印處、tempFunction 函數開頭各設置一個斷點(分別使用命令「b 21」「b tempFunction」):
使用「c」命令繼續(Continue)執行被調試程序,程序將中斷在第二個斷點(21行),此時全局變量 nGlobalVar 的值應該是 88;再一次執行「c」命令,程序將中斷於第三個斷點(7行,tempFunction 函數開頭處),此時tempFunction 函數的兩個參數 a、b 的值應分別是 1 和 2:
再一次執行「c」命令(Continue),由於後面再也沒有其它斷點,程序將一直執行到結束:
命令 | 解釋 | 示例 |
file <文件名> | 加載被調試的可執行程序文件。 由於通常都在被調試程序所在目錄下執行GDB,於是文本名不須要帶路徑。 |
(gdb) file gdb-sample |
r | Run的簡寫,運行被調試的程序。 若是此前沒有下過斷點,則執行完整個程序;若是有斷點,則程序暫停在第一個可用斷點處。 |
(gdb) r |
c | Continue的簡寫,繼續執行被調試程序,直至下一個斷點或程序結束。 | (gdb) c |
b <行號> b <函數名稱> b *<函數名稱> b *<代碼地址> d [編號] |
b: Breakpoint的簡寫,設置斷點。兩可使用「行號」「函數名稱」「執行地址」等方式指定斷點位置。 其中在函數名稱前面加「*」符號表示將斷點設置在「由編譯器生成的prolog代碼處」。若是不瞭解彙編,能夠不予理會此用法。 d: Delete breakpoint的簡寫,刪除指定編號的某個斷點,或刪除全部斷點。斷點編號從1開始遞增。 |
(gdb) b 8 (gdb) b main (gdb) b *main (gdb) b *0x804835c (gdb) d |
s, n | s: 執行一行源程序代碼,若是此行代碼中有函數調用,則進入該函數; n: 執行一行源程序代碼,此行代碼中的函數調用也一併執行。 s 至關於其它調試器中的「Step Into (單步跟蹤進入)」; 這兩個命令必須在有源代碼調試信息的狀況下才可使用(GCC編譯時使用「-g」參數)。 |
(gdb) s (gdb) n |
si, ni | si命令相似於s命令,ni命令相似於n命令。所不一樣的是,這兩個命令(si/ni)所針對的是彙編指令,而s/n針對的是源代碼。 | (gdb) si (gdb) ni |
p <變量名稱> | Print的簡寫,顯示指定變量(臨時變量或全局變量)的值。 | (gdb) p i (gdb) p nGlobalVar |
display ... undisplay <編號> |
display,設置程序中斷後欲顯示的數據及其格式。 例如,若是但願每次程序中斷後能夠看到即將被執行的下一條彙編指令,可使用命令 「display /i $pc」 其中 $pc 表明當前彙編指令,/i 表示以十六進行顯示。當須要關心彙編代碼時,此命令至關有用。 undispaly,取消先前的display設置,編號從1開始遞增。 |
(gdb) display /i $pc (gdb) undisplay 1 |
i | Info的簡寫,用於顯示各種信息,詳情請查閱「help i」。 | (gdb) i r |
q | Quit的簡寫,退出GDB調試環境。 | (gdb) q |
help [命令名稱] | GDB幫助命令,提供對GDB名種命令的解釋說明。 若是指定了「命令名稱」參數,則顯示該命令的詳細說明;若是沒有指定參數,則分類顯示全部GDB命令,供用戶進一步瀏覽和查詢。 |
(gdb) help display |
cgdb能夠看做gdb的界面加強版,cgdb主要功能是在調試時進行代碼的同步顯示,這增長了調試的方便性,提升了調試效率。其餘功能則與gdb同樣,可以使用其經常使用命令。因此這裏只作簡單介紹,經常使用命令等參見gdb。
相比GDB,增長了語法加亮的代碼窗口,顯示在GDB窗口的上部,隨GDB的調試位置代碼同步顯示。
斷點設置可視化 。
在代碼窗口中可以使用GDB經常使用命令 。
在代碼窗口可進行代碼查找,支持正則表達式 。
代碼窗口
調試時同步顯示被調試程序源代碼,自動標記出程序運行到的位置。當焦點在代碼窗口時,能夠瀏覽代碼、查找代碼以及執行命令 ,操做方式同vi 。經常使用命令以下:
i : 焦點切換到GDB窗口 。 o :打開文件選擇框,可選擇要顯示的代碼文件 。 空格 :設置/取消斷點 。 k:向上移動 j:向下移動 /:查找
狀態條窗口
同vi的狀態條,通常顯示當前打開的源文件名,當代碼窗口進入命令狀態時,顯示輸入的命令等信息
GDB窗口
CGDB的操做界面,同GDB ,按ESC鍵則焦點切換到代碼窗口 。
啓動&退出——啓動:cgdb;退出:在代碼窗口或GDB窗口,執行quit命令 。
「(gdb)」表示GDB已經啓動,等待咱們輸入命令。此時程序並未開始運行,輸入「run」開始運行程序。這種方式在GDB內部運行程序:
List n,m表示顯示n到m行的代碼
設置斷點,break n,用step單步執行(這裏break 21,結果首先打印出 「hello!」,再次s,打印出「Who are you ?」):
彙編級的調試或跟蹤,須要用到display命令「display /i $pc」,如上表所示,
「display /i $pc」 其中 $pc 表明當前彙編指令,/i 表示以十六進行顯示。當須要關心彙編代碼時,此命令至關有用。 undispaly,取消先前的display設置,編號從1開始遞增。
看到了彙編代碼,「n = 1;」對應的彙編代碼是「movl $0x1,0x1c(%esp)」。
而且之後程序每次中斷都將顯示下一條彙編指定(「si」命令用於執行一條彙編代碼——區別於「s」執行一行C代碼)
接下來咱們試一下命令「b *<函數名稱>」。 爲了更簡明,有必要先刪除目前全部斷點(使用「d」命令——Delete breakpoint)
當被詢問是否刪除全部斷點時,輸入「y」並按回車鍵便可。
下面使用命令「b *main」在 main 函數的 prolog 代碼處設置斷點(prolog、epilog,分別表示編譯器在每一個函數的開頭和結尾自行插入的代碼):
此時可使用「i r」命令顯示寄存器中的當前值———「i r」即「Infomation Register」,
也能夠輸入「i r 寄存器名」顯示任意一個指定的寄存器值:
最後輸入命令「q」,退出(Quit)GDB調試環境
DDD,全稱是Data Display Debugger,對於Linux系統中的編程人員來講,它就是windows系統下面的visual studio ,功能強大,是Linux世界中少數有圖形界面的程序調試工具。DDD是命令行調試器的圖形前端,除了通常的程序調試功能之外,還具備交互式圖形數據顯示的功能。它在嵌入式應用開發中也十分出色。DDD最初源於1990年Andreas Zeller編寫的VSL結構化語言,後來通過一些程序員的努力,演化成今天的模樣。DDD的功能很是強大,能夠調試用C\C++、Ada、 Fortran、Pascal、Modula-2和Modula-3編寫的程序;能夠超文本方式瀏覽源代碼;可以進行斷點設置、回溯調試和歷史紀錄編輯;具備程序在終端運行的仿真窗口,並在遠程主機上進行調試的能力;圖形數據顯示功能(Graphical Data Display)是建立該調試器的初衷之一,可以顯示各類數據結構之間的關係,並將數據結構以圖形化形式顯示;具備GDB/DBX/XDB的命令行界面,包括徹底的文本編輯、歷史紀錄、搜尋引擎。
首先,咱們製做一個程序文檔,做爲咱們後面調試的對象。
打開終端命令行窗口,輸入命令vi testddd.c,創建testddd.c文件:
在testddd.c文件中輸入一些C語言的程序數據,DDD工具能夠調試不少種程序設置基於的代碼,本次調試以C語言做爲說明對象。
把testddd.c文件編譯成能夠執行的文件testddd,命令:gcc -g -o testddd testddd.c,注意必定要帶-g參數,不然生成的可執行文件中沒有必要的調試信息,最終使用DDD工具不能調試。
運行DDD調試工具,直接輸入命令ddd就能夠打開DDD工具。
DDD工具打開後以下圖所示,上面較大空白部分爲代碼區,和工具區,分割線下面是調試生成信息區。
點擊菜單欄上的「文件」----->「打開程序」,準備打開咱們上面準備的testddd.c文件
在打開程序框中,定位到咱們要調試的程序的目錄下,在Files列表下選擇咱們要調試 信息,以後點擊左下方的打開按鈕。
調試程序打開後,在代碼區能夠看到咱們的代碼,右邊的一些按鈕是咱們調試要用的工具。
在代碼區點鼠標右鍵,會彈出如圖所示的菜單:
咱們能夠給程序設置斷點等,點擊工具區裏面的Run按鈕,能夠執行程序,在下面的調試信息區能夠看到程序的執行結果。
如上圖所示:在鼠標右鍵點擊的地方設置了斷點,在下方調試信息生成區顯示了程序運行的輸入信息。
PS:也能夠在Terminal中輸入ddd 文件名來直接打開ddd調試該文件的界面:
在懷疑程序哪一個變量爲可疑變量時,能夠在控制檯輸入以下命令
或者在主窗口原程序中點擊某個變量如sum選中該變量,右擊後選擇display sum 選項就會看到該變量的值在主窗口的上方。 接着往下單步運行,屢次點擊工具欄中的「Step」按鈕,觀察變量sum的結果。
若是問題出在count上。這時點擊命令工具欄上的「Kill」按鈕將程序斷掉,把初始化sum的那一句改正確。從新運行以後,發現結果正確,調試過程完畢。
run 執行程序
step 單步調試
kill 殺死正在運行的程序
interrupt 退出這次調試回到原始狀態
DDD的數據顯示功能很是強大。
對於固定大小的數組,用鼠標選中數組名,點擊plot按鈕便可畫出圖形。
對於變長數組,可使用graph plot數組名[起始索引] @ 數組大小的命令來顯示。
對於複雜的數據結構,DDD也能夠用圖形方式解析: DDD有一個detect aliases的選項,能夠智能的判別數據是否會被重複顯示。這種方式經過內存地址的檢測來實現的。
段錯誤產生的緣由
(1) 訪問不存在的內存地址
(2) 訪問系統保護的內存地址
(3) 訪問只讀的內存地址
(4) 棧溢出
首先,編寫一段代碼,訪問不存在內存地址。編譯後進入CGDB,運行程序(我這裏使用CGDB,能夠看到源代碼,更加方便。):
從輸出中能夠看出,程序收到SIGSEGV信號,觸發段錯誤,並提示0x080483c四、調用main報的錯,在Derro.c中23行。而且在代碼窗口第23行被標記出來。
適用場景
僅當能肯定程序必定會發生段錯誤的狀況下使用。
當程序的源碼能夠得到的狀況下,使用-g參數編譯程序。
通常用於測試階段,生產環境下gdb會有反作用:使程序運行減慢,運行不夠穩定,等等。
即便在測試階段,若是程序過於複雜,gdb也不能處理。
提到段錯誤會觸發SIGSEGV信號,經過man 7 signal,能夠看到SIGSEGV默認的handler會打印段錯誤出錯信息,併產生core文件,由此咱們能夠藉助於程序異常退出時生成的core文件中的調試信息,使用gdb工具來調試程序中的段錯誤。
查看core文件發現不存在:
查看系統core文件的大小限制,發現爲0,這樣不會自動生成core文件。把大小設置爲1000。運行程序後再次查看可看到存在core文件:
加載core文件,使用gdb工具進行調試。從輸出中能夠看出一樣的段錯誤信息:
在調試會話期間,修改代碼是永遠不要退出GDB,這樣能夠保留斷電等。 咱們保持文本編輯器打開。在調試時的兩次編譯之間留在同一個編輯器會話中,充分利用編輯器的「撤銷」功能。在屏幕上會有一個GDB窗口,以及一個編輯器窗口。再打開第三個窗口用於執行編譯器命令。我把第二和第三個窗口合併了,即經過編輯器執行命令。咱們能夠用vim編輯器,並且在vim中,能夠執行make命令,它會保存所作的編輯修改,並在一個步驟中從新編譯程序。 當修復完程序錯誤後,再次從gdb中運行程序。當gdb注意到從新編譯了程序後,它會自動加載新的可執行文件,所以一樣不須要推出和重啓gdb。
併發,在操做系統中,是指一個時間段中有幾個程序都處於已啓動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。
進程定義了一個計算的基本單元,能夠認爲是一個程序的一次運行。它是一個動態實體,是獨立的任務。它擁有獨立的地址空間、執行堆棧、文件描述符等。 每一個進程擁有獨立的地址空間,進程間正常狀況下,互不影響,一個進程的崩潰不會形成其餘進程的崩潰。 當進程間共享某一資源時,需注意兩個問題:同步問題和通訊問題。
父進程經過調用fork函數來建立一個新的運行子進程。fork函數定義以下:
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
fork函數只被調用一次,可是會返回兩次:父進程返回子進程的PID,子進程返回0.若是失敗返回-1。
fork後,子進程和父進程繼續執行fork()函數後的指令。子進程是父進程的副本。子進程擁有父進程的數據空間、堆棧的副本。但父、子進程並不共享這些存儲空間部分。若是代碼段是隻讀的,則父子進程共享代碼段。若是父子進程同時對同一文件描述字操做,而又沒有任何形式的同步,則會出現混亂的情況; 父進程中調用fork以前打開的全部描述字在函數fork返回以後子進程會獲得一個副本。fork後,父子進程均須要將本身不使用的描述字關閉,有兩方面的緣由:(1)以避免出現不一樣步的狀況;(2)最後能正常關閉描述字
在BSD3.0中開始出現,主要爲了解決fork昂貴的開銷。它是徹底共享的建立,新老進程共享一樣的資源,徹底沒有拷貝。 二者的基本區別在於當使用vfork()建立新進程時,父進程將被暫時阻塞,而子進程則能夠借用父進程的地址空間。這個奇特狀態將持續直到子進程退出或調用execve()函數,至此父進程才繼續執行。
進程的終止存在兩個可能: 父進程先於子進程終止(init進程領養) 子進程先於主進程終止 對於後者,系統內核爲子進程保留必定的狀態信息:進程ID、終止狀態、CPU時間等;當父進程調用wait或waitpid函數時,獲取這些信息; 當子進程正常或異常終止時,系統內核向其父進程發送SIGCHLD信號;缺省狀況下,父進程忽略該信號,或者提供一個該信號發生時即被調用的函數。
#include <stdlib.h> void exit(int status);
本函數終止調用進程。關閉全部子進程打開的描述符,向父進程發送SIGCHLD信號,並返回狀態。
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc);
返回:終止子進程的ID-成功;-1-出錯;statloc存儲子進程的終止狀態(一個整數);
若是沒有終止的子進程,可是有一個或多個正在執行的子進程,則該函數將堵塞,直到有一個子進程終止或者wait被信號中斷時,wait返回。 當調用該系統調用時,若是有一個子進程已經終止,則該系統調用當即返回,並釋放子進程全部資源。
pidt waitpid(pidt pid, int *statloc, int options);
返回:終止子進程的ID-成功;-1-出錯;statloc存儲子進程的終止狀態;
當pid=-1,option=0時,該函數等同於wait,不然由參數pid和option共同決定函數行爲,其中pid參數意義以下:
-1:要求知道任何一個子進程的返回狀態(等待第一個終止的子進程); >0:要求知道進程號爲pid的子進程的狀態; <-1: wait for any child process whose process group ID is equal to the absolute value of pid.
Options最經常使用的選項是WNOHANG,它通知內核在沒有已終止進程時不要堵塞。
調用wait或waitpid函數時,正常狀況下,可能會有如下幾種狀況:
阻塞(若是其全部子進程都還在運行); 得到子進程的終止狀態並當即返回(若是一個子進程已終止,正等待父進程存取其終止狀態); 出錯當即返回(若是它沒有任何子進程)
通常狀況下,父進程fork一個子進程,gdb只會繼續調試父進程而不會管子進程的運行。若是想跟蹤子進程進行調試,可使用set follow-fork-mode mode來設置fork跟隨模式。
set follow-fork-mode 所帶的mode參數能夠是如下的一種:
parent gdb只跟蹤父進程,不跟蹤子進程,這是默認的模式。 child gdb在子進程產生之後只跟蹤子進程,放棄對父進程的跟蹤。
進入gdb之後,咱們可使用show follow-fork-mode來查看目前的跟蹤模式。
能夠看到目前使用的模式是parent。
然而,有時,咱們想同時調試父進程和子進程,以上的方法就不能知足了。Linux提供了set detach-on-fork mode命令來供咱們使用。其使用的mode能夠是如下的一種:
on 只調試父進程或子進程的其中一個(根據follow-fork-mode來決定),這是默認的模式。 off 父子進程都在gdb的控制之下,其中一個進程正常調試(根據follow-fork-mode來決定)
另外一個進程會被設置爲暫停狀態。
一樣,show detach-on-fork顯示了目前是的detach-on-fork模式,如上圖所示。
以上是調試fork產生子進程的狀況,可是若是子進程使用exec系統函數而裝載了新程序執行,咱們就使用set follow-exec-mode mode提供的模式來跟蹤這個exec裝載的程序。mode能夠是如下的一種:
new 當發生exec的時候,若是這個選項是new,則新建一個inferior給執行起來的子進程,而父進程的inferior仍然保留,當前保留的inferior的程序狀態是沒有執行。 same 當發生exec的時候,若是這個選項是same(默認值),由於父進程已經退出,因此自動在執行exec的inferior上控制子進程。
線程由內核自動調度,每一個線程都有它本身的線程上下文(thread context),包括一個唯一的整數線程ID(Thread ID,TID),棧,棧指針,程序計數器,通用目的寄存器和條件碼。每一個線程和其餘線程一塊兒共享進程上下文的剩餘部分,包括整個用戶的虛擬地址空間,它是由只讀文本(代碼),讀/寫數據,堆以及全部的共享庫代碼和數據區域組成的,還有,線程也共享一樣的打開文件的集合。
線程不像進程那樣,不是按照嚴格的父子層次來組織的。和一個進程相關的線程組成一個對等線程池,獨立於其餘線程建立的線程。進程中第一個運行的線程稱爲主線程。對等(線程)池概念的主要影響是,一個線程能夠殺死它的任何對等線程,或者等待它的任意對等線程終止;進一步來講,每一個對等線程都能讀寫相同的共享數據。
線程是可執行代碼的可分派單元。這個名稱來源於「執行的線索」的概念。在基於線程的多任務的環境中,全部進程有至少一個線程,可是它們能夠具備多個任務。這意味着單個程序能夠併發執行兩個或者多個任務。
簡而言之,線程就是把一個進程分爲不少片,每一片均可以是一個獨立的流程。這已經明顯不一樣於多進程了,進程是一個拷貝的流程,而線程只是把一條河流截成不少條小溪。它沒有拷貝這些額外的開銷,可是僅僅是現存的一條河流,就被多線程技術幾乎無開銷地轉成不少條小流程,它的偉大就在於它少之又少的系統開銷。
函數pthread_create用來建立一個線程,它的原型爲:
extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));
第一個參數爲指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。
函數pthread_join用來等待一個線程的結束。函數原型爲:
2extern int pthread_join __P ((pthread_t __th, void **__thread_return));
第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它能夠用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。
一個線程的結束有兩種途徑,一種是象咱們上面的例子同樣,函數結束了,調用它的線程也就結束了;另外一種方式是經過函數pthread_exit來實現。它的函數原型爲:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
惟一的參數是函數的返回代碼,只要pthread_ join中的第二個參數thread_ return不是NULL,這個值將被傳遞給 thread_return。
最後要說明的是,一個線程不能被多個線程等待,不然第一個接收到信號的線程成功返回,其他調用pthread_join的線程則返回錯誤代碼ESRCH。
Linux系統下的多線程遵循POSIX線程接口,稱爲pthread。編寫Linux下的多線程程序,須要使用頭文件pthread.h,鏈接時須要使用庫libpthread.a。Linux下pthread的實現是經過系統調用clone()來實現的。clone()是Linux所特有的系統調用,它的使用方式相似fork。
下面代碼示例:
主線程作本身的事情,生成2個子線程,task1爲分離,任其自生自滅,而task2仍是繼續送外賣,須要等待返回。
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<pthread.h> void* task1(void*); void* task2(void*); void usr(); int p1,p2; int main() { usr(); getchar(); return 1; } void usr() { pthread_t pid1, pid2; pthread_attr_t attr; void *p; int ret=0; pthread_attr_init(&attr); //初始化線程屬性結構 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //設置attr結構爲分離 pthread_create(&pid1, &attr, task1, NULL); //建立線程,返回線程號給pid1,線程屬性設置爲attr的屬性,線程函數入口爲task1,參數爲NULL pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&pid2, &attr, task2, NULL); //前臺工做 ret=pthread_join(pid2, &p); //等待pid2返回,返回值賦給p printf("after pthread2:ret=%d,p=%d/n", ret,(int)p); } void* task1(void *arg1) { printf("task1/n"); //艱苦而沒法預料的工做,設置爲分離線程,任其自生自滅 pthread_exit( (void *)1); } void* task2(void *arg2) { int i=0; printf("thread2 begin./n"); //繼續送外賣的工做 pthread_exit((void *)2); }
編譯運行:
屢次運行發現結果並不相同,這是不一樣的線程搶佔CPU的結果。
gdb運行代碼咱們能夠看到建立的線程。結果發現程序被掛起了,Ctrl+C中斷它,使用info threads查看每一個線程的狀態:
使用bt查看線程是作什麼的,這樣就能夠具體到哪一行進而找出錯誤:
能夠發現它執行main()。沒有工做線程,因此程序掛起。這樣足以查明錯誤的位置和性質。
經過此次實踐,咱們小組深刻研究了GDB,對CGDB、DDD都作了一些瞭解;主要是對它們的經常使用技巧進行學習。並進行彙編代碼調試、段錯誤分析。最後對併發中多進程和多線程均加以實踐。由於沒能買到和借到老師推薦的那本參考書,因此咱們只能在網上看它的PDF格式,另外經過查找其餘資料進行學習。因爲已經學過GDB,因此入手難度不大,可是經過深刻了解,發現了更多有用的知識。還學到了更加好用的CGDB和圖形化界面的DDD。也是對之前所學知識的一個鞏固。而後對於併發編程,咱們發現《深刻理解計算機系統》書中第十二章有所介紹,因此提早進行了學習。老師也沒有在題目中提出明確的要求,因此咱們根據本身的理解進行了學習,可能還不全面,還須要進一步深刻。
總體來講這次實踐是一次很愉快的學習過程,小組兩我的能夠一同窗習,互相協助。在學習過程當中咱們不只分工合做,提升了效率,也能夠互相提出問題而後互相解答問題。但願之後都可以這樣快樂的學習。