Linux中系統信號初識,和函數kill,raise,abort,alarm,setitimer



簡 述: 此處指 Linux 中系統內核發出的信號;而不是之前 Qt 學習的信號。


編程環境:

💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


信號初識:

  • 特點:

    • 簡單
    • 攜帶信息量很少
    • 用在某個特定的場景中
  • 型號的狀態:

    • 產生原因:
      1. 鍵盤 Ctrl + C
      2. 命令:kill
      3. 系統函數:kill()
      4. 軟條件:定時器
      5. 硬件:段錯誤,除 0 錯誤
    • 未決狀態 – 沒有被處理
    • 遞達狀態 – 信號被處理了
  • 處理方式:

    • 忽略
    • 捕捉,然後自定義動作
    • 執行了默認動作
  • 信號的四要素:

    • 信號名、信號編號、信號默認動作、事件描述

    • "動作(Action)"欄 的 字母 有 下列 含義:

      A 缺省動作是結束進程.
      B 缺省動作是忽略這個信號.
      C 缺省動作是結束進程, 並且核心轉儲.
      D 缺省動作是停止進程.
      E 信號不能被捕獲.
      F 信號不能被忽略.

      譯註: 這裏 “結束” 指 進程 終止 並 釋放資源, “停止” 指 進程 停止 運行, 但是 資源 沒有 釋放,有可能 繼續 運行。

  • 通過 man 文檔查看信號

    • 執行 man 7 signal
    • 注意(man 手冊中有寫):SIGKILLSIGSTOP 這兩個信號不能夠被捕捉,阻塞,忽略的

    The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

  • 概念:阻塞信號集,未決信號集

    • 是在 PCB 中
    • 阻塞信號集:讓信號處於一個未決的狀態
    • 未決信號集:如果信號被阻塞了,該信號集會對阻塞的信號做記錄
  • 圖解進程產生和處理:


kill() 函數:

作用: 發射信號給指定進程、或者同組的信號。

int kill(pid_t pid, int sig);
  • 參數:

    • pid:
      • pid > 0; 發送信號給指定的進程
      • pid = 0; 發送信號給 調用 kill 函數進程屬於同一個組的所有進程
      • pid = -1;如果用戶擁有超級用戶權限,則信號將被髮送到所有進程
      • pid < -1;取 |pid| 發給對應進程組
    • sig:
      • 推薦使用完整的宏名稱而非數字,在少數發行版下,可能指定宏對應的數值有變化
  • 返回值:

    • 成功: 0
    • 失敗:-1(ID 非法,信號非法,普通信號殺 init進程等權限級別問題,設置 errno)

寫一個小的例子驗證 kill 信號的使用,子進程在 5S 後,通知系統內核發送 SIGKILL 給它的父進程,然後系統將父進程殺死。

int main(int argc, char *argv[])
{
    pid_t pid = fork();

    if (pid > 0) {
        while (true) {
            printf("this is a parent process = %d\n", getpid());
            sleep(1);
        }
    } else if (pid == 0) {
        sleep(5);
        kill(getppid(), SIGKILL);
    }
    
    return 0;
}

運行效果:


raise() 函數:

作用: 自己給自己發射信號。在單線程程序中,它相當於 kill(getpid(), sig);

在一個多線程程序中,它等同於 pthread_kill(pthread_self(), sig)。

int raise(int sig);

比較簡單,但依舊寫一個例子使用一下:子進程給機子發射終止進程的信號。然後父進程在回收子進程的時候,答應其死亡的信號值

int main(int argc, char *argv[])
{
    pid_t pid = fork();

    if (pid > 0) {
        int s;
        pid_t wpid = wait(&s);
        printf("this is a child process dide pid = %d\n", wpid);
            
        if (WIFSIGNALED(s))
            printf("dide by signal: %d\n", WTERMSIG(s));
    } else if (pid == 0) {
        raise(SIGINT); //給自己發射信號
    }
    
    return 0;
}

代碼中 19 行,選中的 SIGINT 信號,默認動作是終止進程。其對應的事件爲 當用戶按下<ctrl + c>組合鍵時候,用戶終端向正在運行中的 (由該終端啓動的)程序 發出此信號。

運行截圖:


abort() 函數:

作用: 給自己發送異常終止的信號。該函數沒有參數和返回值,也永遠不會調用失敗。

void abort(void);

定時器:

alarm() 函數:

unsigned int alarm(unsigned int seconds);
  • 設置定時器(每一個進程只有一個定時器)

  • 使用的是自然定時法則;不受進程狀態的影響

  • 當時間到達之後,函數發出一個信號 SIGALRM

    • SIGALRM – 調用 abort() 函數時候產生該信號 – 終止進程併產生 core 文件
  • 返回值:始終是返回上一次調用此函數,還剩些的時間。

  • 寫一個例子:

    int main(int argc, char *argv[])
    {
        int ret = alarm(5);
        printf("ret = %d\n", ret);
    
        sleep(2);
        ret = alarm(10); //重新設定定時器,返回值是返回之前鬧鐘的剩餘的時間
        printf("ret = %d\n", ret);
    
        while (true) {
            printf("-------test-----\n");
            sleep(1);
        }
        
        return 0;
    }
    
  • 分析:

    這裏 ret = 0 ,是因爲第一次調用,它的上一次的剩餘時間爲 0;然後 ret = 3 是因爲 5 - 2 s,然後定時器被充重置了新的 10 s。故後面會打印 10 次輸出語句,然後收到系統的終止信號,殺死本進程。因爲是執行的 alarm() 的默認動作,爲終止進程,且沒有捕捉該函數發射的對應的信號,也沒有定義自定義動作,所以本進程會被終止。

  • 運行結果:

分析程序運行的損耗:

實際耗時 == 用戶 + 系統 + 損耗

  • 寫一個例子,檢測一下計算機在 1s 內可以計算多少個數字?

  • 代碼如下:

    int main(int argc, char *argv[])
    {
        alarm(1);
    
        int n = 0;
        while (true) {
            printf("%d\n", n++);
        }
    
        return 0;
    }
    
  • 運行演示:

    分析:其中損耗文件來自文件 IO 操作

    輸出到終端: 1.012 = 0.31 + 0.34 + 損耗 (約 70w)

    輸出到文件: 1.012 = 0.93 + 0.07 + 損耗 (約 800w)

    //執行 time ./myAlarmCount(輸出到終端)
    0.31s user 
    0.34s system 
    64%   cpu 
    1.012 total  (輸出到 692977)
      
    //time ./myAlarmCount > file(輸出到文本)
    0.93s user 
    0.07s system 
    98%   cpu 
    1.012 total  (輸出到 8686215)
    

setitimer() 函數:

  • 作用: 定時器,並且實現週期性 定時。
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

struct itimerval {
  struct timeval it_interval; /* 定時器的循環週期 */
  struct timeval it_value;    /* 第一次觸發定時器的時間 */
};

struct timeval {
  time_t      tv_sec;         /* 秒 */
  suseconds_t tv_usec;        /* 微秒 */
};
  • 參數:

    • which:
      • ITIMER_REAL: 自然定時法,會發出信號 SIGALRM
      • ITIMER_VIRTUAL:虛擬定時法,只會計算 用戶時間 ,對應信號 SIGVTALRM
      • ITIMER_PROF: 只計算 用戶時間 + 系統時間 ,對應發射 ITIMER_VIRTUAL 信號
    • new_value:我們需要設置的參數,總的定時時間是 tv_sec + tv_usec 之和
    • old_value:傳出參數,傳出上一次定時器的設置,一般用不到,用 NULL
  • 寫一個例子:

    int main(int argc, char *argv[])
    {
        itimerval time;
        // time.it_interval = 3;  //每隔 3s 一次循環定時
    
        //第一次觸發定時器的時間爲 5s + 3ms
        time.it_value.tv_sec = 5;     //5s 
        time.it_value.tv_usec = 3;    //3 ms
    
        setitimer(ITIMER_REAL, &time, NULL);
    
        while (true) {
            printf("-----printf()-----\n");
            sleep(1);
        }
        
        return 0;
    }
    
  • 運行效果:


下載地址:

14_signal

歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章