1. 程式人生 > 其它 >Linux多程序開發III

Linux多程序開發III

1.訊號,事件發生時對程序的通知機制,有時稱為軟體中斷,是在軟體層次上對中斷機制的一種模擬,是一種非同步通訊方式。傳送程序的訊號一般來源於核心。 引發核心為程序產生訊號的事件:
  • 對於前臺程序,使用者可以通過鍵盤輸入特殊的中斷字元來給它傳送訊號。
  • 硬體發生異常,即硬體檢測到一個錯誤條件並通知核心,再由核心傳送相應訊號給相關程序。
  • 系統狀態變化,如alarm定時器到期,程序執行的CPU時間超限等。
  • 執行kill命令或呼叫kill函式。
2.使用訊號的目的:
  • 讓程序知道發生了特定的事情。
  • 強迫程序執行它自己程式碼中的訊號處理程式。
3.訊號的特點:
  • 簡單
  • 不能攜帶大量資訊
  • 滿足特定條件才傳送
  • 優先順序比較高
4.檢視系統定義的訊號列表。前31為常規訊號,其餘為實時訊號。
kill -l
5.常用訊號。
SIGIN          <Ctrl + C>組合鍵,使用者終端向正在執行的由此終端啟動的程式傳送訊號          終止程序
SIGQUIT        <Ctrl + \>組合鍵,使用者終端向正在執行的由此終端啟動的程式傳送訊號          終止程序
SIGKILL        無條件終止程序,不能被忽略,處理和阻塞                                  終止程序,可以殺死任何程序
SIGSEGV        知識程序進行了無效記憶體訪問(段錯誤)                                   終止程序併產生core檔案
SIGPIPE        破裂管道,向一個沒有讀端的管道寫資料                                   終止程序
SIGCHILD       子程序結束時,父程序會收到該訊號                                       忽略該訊號
SIGCONT        如果程序已停止,則使其繼續執行                                         繼續/忽略
SIGSTOP        停止程序的執行。訊號不能被忽略,處理和阻塞                               終止程序

  

6.訊號的5中預設處理動作。
  • Term 終止程序
  • Ign 當前程序忽略該訊號
  • Core 終止程序並生成一個core檔案
  • stop 暫停當前程序
  • Cont 繼續執行當前被暫停的程序
7.訊號的幾種狀態。
  • 產生
  • 未決
  • 遞達
8.SIGKILL和SIGSTOP訊號不能被捕捉、阻塞和忽略。只能執行預設的動作。 9.生成core檔案並檢視。
ulimit -a                     // 檢視core檔案大小
ulimit -c 1024                // 修改core檔案大小
(gdb) core-file core          // 在gdb除錯下檢視core檔案中的錯誤

  

10.訊號相關的函式。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
    - 作用:給某個程序pid傳送某個訊號
    - pid: 需要傳送訊號的程序,>0 傳送給指定程序;=0 傳送給當前程序組;=-1 傳送給每一個有許可權接收該訊號的程序;<-1 pid=某個程序組的ID取反
    - sig: 需要傳送訊號的編號或巨集值,0表示不傳送任何訊號
 
int raise(int sig);
    - 作用:給當前的程序傳送訊號
    - sig: 要傳送的訊號
    - 返回值:成功返回0,失敗返回非0
    - kill(getpid(), sig);
 
void abort(void);
    - 作用:傳送SIGABRT訊號給當前程序,殺死當前程序
    - kill(getpid(), SIGABRT);
 
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
    - 作用:設定定時器,函式呼叫,開始倒計時,當倒計時為0時,函式會給當前的程序傳送一個訊號:SIGALARM
    - seconds: 倒計時的時長,單位:秒。如果引數為0,定時器無效(不進行倒計時,不傳送訊號)。取消一個定時器,通過alarm(0)。
    - 返回值:之前沒有定時器返回0,之前有定時器返回之前的定時器剩餘的時間
  -SIGALARM:預設終止當前程序,每一個程序都有且只有唯一一個定時器。alarm(100) -> 該函式是非阻塞的
定時器,與程序的狀態無關(自然定時法),無論程序處於什麼狀態,alarm都會計時。
 
int setitimer(int which, const struct itimerval *new_val, struct itimerval *old_val);
    - 作用:設定定時器。可以代替alarm函式,精讀微秒,可以實現週期性定時
    - which: 定時器漆以什麼時間計時,ITIMER_REAL 真實時間,時間到達,傳送SIGALRM;ITIMER_VIRTUAL 使用者時間,時間到達,傳送SIGVTALRM;ITMIER_PROF 以該程序在使用者態和核心態所消耗的時間計算,               時間到達,傳送SIGPROF;
    - new_val: 設定定時器的屬性
    - old_val: 上一次定時的時間引數,一般不使用指定為NULL
    - 返回值:成功返回0,失敗返回-1並設定errno
struct itimerval {                   // 定時器
    struct timeval it_interval;      // 週期時間
    struct timeval it_value;         // 第一次延遲多長時間執行定時器 
};
struct timeval {                    // 時間
    time_t      tv_sec;             // 秒數
    suseconds_t tv_usec;            // 微秒
};

  

11.執行程式的時間 實際的時間 =核心時間 +使用者時間 +消耗的時間 進行檔案IO操作的時候比較浪費時間 12.訊號捕捉。訊號捕捉要註冊在訊號之前。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
    - 作用:設定某個訊號的捕捉行為
    - signum: 要捕捉的訊號
    - handler: 捕捉到的訊號要如何處理,SIG_IGN:忽略訊號;SIG_DFL:使用訊號預設的行為;回撥函式:這個函式是系統呼叫,程式設計師只負責寫,捕捉到訊號後如何處理訊號
    - 返回值:成功返回上一次註冊的訊號處理函式的地址,第一次呼叫返回NULL;失敗返回SIG_ERR,並設定錯誤號
    - 回撥函式:程式設計師提前實現,函式型別根據需求,看函式指標定義;當訊號產生時,由核心呼叫;函式指標是實現回撥的手段,函式實現後,將函式名放到函式指標的位置
 
int sigaction(int signum, const struct sigaction *act, struct sigaction *odlact);
    - 作用:檢查或者改變訊號的處理。訊號捕捉
    - signum: 需要捕捉的訊號的編號或者巨集值
    - act: 捕捉到訊號之後的處理動作
    - oldact: 上一次對訊號捕捉相關的設定,一般設定為NULL
    - 返回值:成功返回0,石板返回-1
struct sigaction {
    void     (*sa_handler)(int);                                        // 函式指標,指向的函式是訊號捕捉到之後的處理函式
    void     (*sa_sigaction)(int, siginfo_t *, void *);                 // 不常用
    sigset_t   sa_mask;                                                 // 臨時阻塞訊號集,訊號捕捉函式執行過程中臨時阻塞某些訊號
    int        sa_flags;                                                // 使用哪一個訊號處理對捕捉到的訊號進行處理,0表示使用sa_handler,SA_SIGINFO表示使用sa_sigaction
    void     (*sa_restorer)(void);                                      // 被廢棄了
};
13.訊號集。多個訊號課使用一個稱之為訊號集的資料結構表示,系統資料型別為 sigset_t。PCB中有兩個訊號集。
  • 阻塞訊號集, 訊號的阻塞是讓系統暫時保留訊號留待以後傳送。由於另外有辦法讓系統忽略訊號,所以一般情況下訊號的阻塞只是暫時的,只是為了防止訊號打斷敏感操作。
  • 未決訊號集,訊號的“未決”是一種狀態,指的是從訊號的產生到訊號被處理前的一段時間。
  • 這兩個訊號集都是內二使用點陣圖機制實現的,無法直接進行操作。需要藉助訊號集操作函式來對這兩個訊號集修改。
14.訊號處理過程。
  1. 使用者通過鍵盤 Ctrl +C,產生2號訊號SIGINT(訊號被建立)
  2. 訊號產生但是沒有被處理(未決)
    1. 在核心中將所有沒有被處理的訊號儲存在一個集合中(未決訊號集)
    2. SIGINT訊號狀態被儲存在第二盒標誌位上
      1. 這個標誌位的值為0,表示訊號不是未決
      2. 這個標誌位的值為1,表示訊號處於未決
  3. 這個未決狀態的訊號,需要被處理,處理之前需要和另一個訊號集(阻塞訊號集),進行比較:
    1. 阻塞訊號集預設不阻塞任何訊號集
    2. 如果想要阻塞某些訊號需要使用者呼叫系統API
  4. 在處理的時候和阻塞訊號集中的標誌位進行查詢,看是不是對該訊號設定了阻塞:
    1. 如果沒有阻塞,這個訊號被處理
    2. 如果阻塞了,這個訊號就繼續處於未決狀態,直到阻塞解除,這個訊號就被處理
// 以下訊號集相關的函式都是對自定義的訊號集進行操作。
 
#include <signal.h>
int sigemptyset(sigset_t *set);
    - 作用:清空訊號集中的資料,將訊號集中的所有標誌位置為0
    - set: 傳出引數,需要操作的訊號集
    - 返回值:成功返回0,失敗返回-1並設定errno
 
int sigfillset(sigset_t *set);
    - 作用:將訊號集中的所有標誌位置為1
    - set: 傳出引數,需要操作的訊號集
    - 返回值:成功返回0,失敗返回-1並設定errno
 
int sigaddset(sigset_t *set, int signum);
    - 作用:設定訊號集中的某一個訊號對應的標註為1,表示阻塞該訊號
    - set: 傳出引數,需要操作的訊號集
    - signum: 需要設定阻塞的訊號
    - 返回值:成功返回0,失敗返回-1並設定errno
 
int sigdelset(sigset_t *set, int signum);
    - 作用:設定訊號集中的某一個訊號對應的標註為0,表示不阻塞該訊號
    - set: 傳出引數,需要操作的訊號集
    - signum: 需要設定不阻塞的訊號
    - 返回值:成功返回0,失敗返回-1並設定errno
 
int sigismember(const sigset_t *set, int signum);
    - 作用:判斷某個訊號是否屬於訊號集set
    - set: 需要操作的訊號集
    - signum: 需要判斷的訊號
    - 返回值:屬於返回1,不屬於返回0,呼叫失敗返回-1並設定errno

  

15.處理系統訊號集。(只能改變核心阻塞訊號集,不能改變未決訊號集)
int sigprocmask(int how, cosnt sigset_t *set, sigset_t *oldset);
    - 作用:將自定義訊號集中的資料設定到核心中(設定阻塞,解除阻塞,替換)
    - how: 如何對核心阻塞訊號集進行處理,
        - SIG_BLOCK 將使用者設定的阻塞訊號集新增到核心中,核心原來的資料不變,即:mask | set;(假設核心中預設阻塞訊號集為mask)
        - SIG_UNBLOCK 根據使用者設定的資料,對核心中的資料進行解除阻塞,即:mask &= ~set
        - SIG_SETMASK 覆蓋核心中原來的值
    - set: 已經初始化好的使用者自定義的訊號集
    - old_set: 儲存設定之前的核心中的阻塞訊號集的狀態,可以使NULL
    - 返回值:成功返回0,失敗返回-1並設定錯誤號:EFAULT、EINVAL
 
int sigpending(sigset_t *set);
    - 作用:獲取核心中的未決訊號集
    - set: 傳出引數,儲存的是核心中的未決訊號集中的資訊 
16.以下三種條件都會給父程序傳送SIGCHILD訊號,父程序預設忽略該訊號。SIGCHILD訊號產生的條件:
  • 子程序終止
  • 子程序接收到SIGSTOP訊號停止時
  • 子程序處在停止態,接收到SIGCONT後喚醒
17.在父程序註冊完訊號捕捉之前,子程序就結束了,會出現段錯誤。所以需要提前設定好阻塞訊號集,阻塞SIGCHILD。
sigset_t set;
sigemptyset(&set);
sigeaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);        // 阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);      // 解除阻塞