GDB深刻研究

GDB深刻研究

1、GDB代碼調試

(一)GDB調試實例

在終端中編譯一個示例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),由於後面再也沒有其它斷點,程序將一直執行到結束:

(二)GDB經常使用命令

 命令  解釋  示例
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 (單步跟蹤進入)」;
n 至關於其它調試器中的「Step Over (單步跟蹤)」。

這兩個命令必須在有源代碼調試信息的狀況下才可使用(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

 

 

2、CGDB代碼調試

cgdb能夠看做gdb的界面加強版,cgdb主要功能是在調試時進行代碼的同步顯示,這增長了調試的方便性,提升了調試效率。其餘功能則與gdb同樣,可以使用其經常使用命令。因此這裏只作簡單介紹,經常使用命令等參見gdb。

主要功能介紹:

  1. 相比GDB,增長了語法加亮的代碼窗口,顯示在GDB窗口的上部,隨GDB的調試位置代碼同步顯示。

  2. 斷點設置可視化 。

  3. 在代碼窗口中可以使用GDB經常使用命令 。

  4. 在代碼窗口可進行代碼查找,支持正則表達式 。

界面及使用說明

  1. 代碼窗口

    調試時同步顯示被調試程序源代碼,自動標記出程序運行到的位置。當焦點在代碼窗口時,能夠瀏覽代碼、查找代碼以及執行命令 ,操做方式同vi 。經常使用命令以下:

    i : 焦點切換到GDB窗口 。 o :打開文件選擇框,可選擇要顯示的代碼文件 。 空格 :設置/取消斷點 。 k:向上移動 j:向下移動 /:查找
  2. 狀態條窗口

    同vi的狀態條,通常顯示當前打開的源文件名,當代碼窗口進入命令狀態時,顯示輸入的命令等信息

  3. GDB窗口

    CGDB的操做界面,同GDB ,按ESC鍵則焦點切換到代碼窗口 。

    啓動&退出——啓動:cgdb;退出:在代碼窗口或GDB窗口,執行quit命令 。

代碼實現:

  1. 「(gdb)」表示GDB已經啓動,等待咱們輸入命令。此時程序並未開始運行,輸入「run」開始運行程序。這種方式在GDB內部運行程序:

  2. List n,m表示顯示n到m行的代碼

  3. 設置斷點,break n,用step單步執行(這裏break 21,結果首先打印出 「hello!」,再次s,打印出「Who are you ?」):

3、彙編代碼調試

彙編級的調試或跟蹤,須要用到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調試環境

 

 

 

4、DDD代碼調試

(一)DDD簡介

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的命令行界面,包括徹底的文本編輯、歷史紀錄、搜尋引擎。

(二)DDD調試過程

首先,咱們製做一個程序文檔,做爲咱們後面調試的對象。

打開終端命令行窗口,輸入命令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的選項,能夠智能的判別數據是否會被重複顯示。這種方式經過內存地址的檢測來實現的。

 

5、段錯誤

  1. 定義:段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等等狀況。
  2. 段錯誤產生的緣由

    (1) 訪問不存在的內存地址

    (2) 訪問系統保護的內存地址

    (3) 訪問只讀的內存地址

    (4) 棧溢出

下面以緣由一訪問不存在的內存地址爲例,進行實踐。

(一)使用gcc和gdb(對於簡單代碼)

首先,編寫一段代碼,訪問不存在內存地址。編譯後進入CGDB,運行程序(我這裏使用CGDB,能夠看到源代碼,更加方便。):

從輸出中能夠看出,程序收到SIGSEGV信號,觸發段錯誤,並提示0x080483c四、調用main報的錯,在Derro.c中23行。而且在代碼窗口第23行被標記出來。

適用場景

  1. 僅當能肯定程序必定會發生段錯誤的狀況下使用。

  2. 當程序的源碼能夠得到的狀況下,使用-g參數編譯程序。

  3. 通常用於測試階段,生產環境下gdb會有反作用:使程序運行減慢,運行不夠穩定,等等。

  4. 即便在測試階段,若是程序過於複雜,gdb也不能處理。

(二)使用core文件和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。

6、併發(多進程、多線程)

併發,在操做系統中,是指一個時間段中有幾個程序都處於已啓動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。

(一)多進程

一、進程的基本概念

進程定義了一個計算的基本單元,能夠認爲是一個程序的一次運行。它是一個動態實體,是獨立的任務。它擁有獨立的地址空間、執行堆棧、文件描述符等。 每一個進程擁有獨立的地址空間,進程間正常狀況下,互不影響,一個進程的崩潰不會形成其餘進程的崩潰。 當進程間共享某一資源時,需注意兩個問題:同步問題和通訊問題。

二、建立進程

父進程經過調用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上控制子進程。 

(二)多線程

  1. 線程:運行在單一進程上下文中的邏輯流,由內核進行調度,共享同一進程的虛擬地址空間。

基於線程的併發編程

線程由內核自動調度,每一個線程都有它本身的線程上下文(thread context),包括一個唯一的整數線程ID(Thread ID,TID),棧,棧指針,程序計數器,通用目的寄存器和條件碼。每一個線程和其餘線程一塊兒共享進程上下文的剩餘部分,包括整個用戶的虛擬地址空間,它是由只讀文本(代碼),讀/寫數據,堆以及全部的共享庫代碼和數據區域組成的,還有,線程也共享一樣的打開文件的集合。

線程不像進程那樣,不是按照嚴格的父子層次來組織的。和一個進程相關的線程組成一個對等線程池,獨立於其餘線程建立的線程。進程中第一個運行的線程稱爲主線程。對等(線程)池概念的主要影響是,一個線程能夠殺死它的任何對等線程,或者等待它的任意對等線程終止;進一步來講,每一個對等線程都能讀寫相同的共享數據。

線程是可執行代碼的可分派單元。這個名稱來源於「執行的線索」的概念。在基於線程的多任務的環境中,全部進程有至少一個線程,可是它們能夠具備多個任務。這意味着單個程序能夠併發執行兩個或者多個任務。

簡而言之,線程就是把一個進程分爲不少片,每一片均可以是一個獨立的流程。這已經明顯不一樣於多進程了,進程是一個拷貝的流程,而線程只是把一條河流截成不少條小溪。它沒有拷貝這些額外的開銷,可是僅僅是現存的一條河流,就被多線程技術幾乎無開銷地轉成不少條小流程,它的偉大就在於它少之又少的系統開銷。

linux提供的多線程的系統調用:
  1. 函數pthread_create用來建立一個線程,它的原型爲:

    extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));

    第一個參數爲指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。

  2. 函數pthread_join用來等待一個線程的結束。函數原型爲:

    2extern int pthread_join __P ((pthread_t __th, void **__thread_return));

    第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它能夠用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。

  3. 一個線程的結束有兩種途徑,一種是象咱們上面的例子同樣,函數結束了,調用它的線程也就結束了;另外一種方式是經過函數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具體調試:

gdb運行代碼咱們能夠看到建立的線程。結果發現程序被掛起了,Ctrl+C中斷它,使用info threads查看每一個線程的狀態:

使用bt查看線程是作什麼的,這樣就能夠具體到哪一行進而找出錯誤:

能夠發現它執行main()。沒有工做線程,因此程序掛起。這樣足以查明錯誤的位置和性質。

 

心得體會:

經過此次實踐,咱們小組深刻研究了GDB,對CGDB、DDD都作了一些瞭解;主要是對它們的經常使用技巧進行學習。並進行彙編代碼調試、段錯誤分析。最後對併發中多進程和多線程均加以實踐。由於沒能買到和借到老師推薦的那本參考書,因此咱們只能在網上看它的PDF格式,另外經過查找其餘資料進行學習。因爲已經學過GDB,因此入手難度不大,可是經過深刻了解,發現了更多有用的知識。還學到了更加好用的CGDB和圖形化界面的DDD。也是對之前所學知識的一個鞏固。而後對於併發編程,咱們發現《深刻理解計算機系統》書中第十二章有所介紹,因此提早進行了學習。老師也沒有在題目中提出明確的要求,因此咱們根據本身的理解進行了學習,可能還不全面,還須要進一步深刻。

總體來講這次實踐是一次很愉快的學習過程,小組兩我的能夠一同窗習,互相協助。在學習過程當中咱們不只分工合做,提升了效率,也能夠互相提出問題而後互相解答問題。但願之後都可以這樣快樂的學習。

相關文章
相關標籤/搜索