linux系統編程手冊閱讀筆記-c20:信號的基本概念

chapeter 20 :信號的基本概念

內核信號機制實現

http://www.spongeliu.com/165.html
當進程P2向p1發送信號後,內核接受到信號,並將其放在p1的信號隊列中,當p1再次陷入內核態時,會檢查信號隊列,並根據相應的信號調取相應的信號處理函數。
- p1什麼時候會陷入內核態?

當前進程由於系統調用、中斷或異常而進入系統空間,到返回用戶空間的前夕。
當前進程在內核中進入睡眠以後剛被喚醒的時候(必定是在系統調用中),或者由於不可忽略信號的存在而提前返回到用戶空間
- 信號處理

當進程由於中斷等陷入內核態之後,寄存器等信息會被壓入內核棧,保存現場用於返回到用戶棧時繼續執行程序,在完成了系統調用後,會調用do_signal()函數(棧會被壓入相關的信息),檢查信號隊列,如果有信號,則會根據信號向量表找到信號處理函數入口位置,將保存在內核棧上的eip指令寄存器修改至該入口,然後內核棧上的相應內容拷貝到用戶棧上,跳轉到用戶態執行信號處理函數,執行完畢之後,根據(跳轉時)用戶堆棧記錄的信息返回到內核態上,繼續檢查信號隊列,反覆這個過程,直到所有信號處理完畢。

概念

等待狀態(pending):信號在產生後,會於稍後傳遞給某一進程,在產生和到達期間,就被稱之爲等待狀態(pending)。如將信號添加到進程的信號掩碼中,就會阻塞信號的到達,信號將處於等待狀態。
信號處理器程序:用於爲響應傳遞來的信號而執行適當的任務,可以通過signal等函數進行設置

常見linux信號

信號在linux中以數字進行編號,爲了更方便使用,linux也爲之起了變量別名

名稱 信號值 描述 SUSv3 默認
SIGINT 2 終端中斷 yes term

signal()

更改信號處理函數

#include<signal.h>
void (*signal(int sig, void (*handler)(int))) (int);

更改sig的信號處置函數爲handler,返回舊的信號處理函數指針,如果失敗將返回SIG_ERR(一個函數指針)

除了自定義的handler信號處理函數之外,還提供重置和忽略函數

void handler(int sig){ //自定義
    /*-----*/
}

SIG_DFL //重置默認處理函數
SIG_IGN //忽略信號,內核會直接丟棄該信號

使用樣例

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<error.h>
#include<string.h>
#include<unistd.h>
#include<typeinfo>
#include<iostream>
#include<cxxabi.h>
using namespace std;

void errExit(const char * str){
    fprintf( stderr,"%s\n",str);
    exit(-1);
}

void handler(int sig){
    printf("caputre ternimal interrupt\n");
}

int main(int argc, char*argv[]){
    void (*old_handler)(int);
    old_handler= signal(SIGINT,handler);   //爲SIGINT綁定新的信號處理函數。

    if(old_handler == SIG_ERR)          //如果更改信號處理函數失敗
    errExit("signal err");

    sleep(10);
    /*在此時按下contrl + c*/
    sleep(10);

    if(signal(SIGINT,old_handler)==SIG_ERR) //恢復舊的信號處理函數
    errExit("signal err");
return 0; 
}

>>>>>>>>>>>>>>>>>>>>>>>>>要使用g++進行編譯

woder@ubuntu:~/project/osprogram$ ./test
^Ccaputre ternimal interrupt
^Ccaputre ternimal interrupt

上文程序一個需要注意的地方就是使用了兩次sleep(),如果只使用了一個sleep(),本來處於睡眠狀態的進程會因爲信號的到來被喚醒,進行信號處理然後順序執行結束進程,讓人誤以爲執行了新的處理器函數後,還執行了原來的中斷處理函數,其實沒有。

kill()

發送信號函數

#include<signal.h>

int kill(pid_t pid, int sig);   向進程pid發送信號sig,成功返回0,失敗返回-1
  • pid不同情況的含義
    pid > 0: 發送給pid進程
    pid = 0: 發送給同組進程的每個進程,包括自己
    pid <-1: 髮型到每個有權利可以發送的進程,除了init(pid=1)和自身進程

  • 發送信號的權限
    1.特權級,可以向所有進程發送任何信號
    2.root用戶和init進程是特例,只能接受已經安裝了處理器函數的信號,防止init被殺死
    3.發送進程的實際用戶id或有效用戶id等於接受進程的實際用戶id或保存設置用戶id。
    如果無權發送的話,kill()會返回-1,並將errno置爲EPERM (發送組的時候成功一個就算成功)。

raise()

向自身發送信號

#include<signal.h>

int raise(int sig);給自身發送sig信號

raise以及kill()向自身發送信號的時候,信號立即傳遞(因爲發送信號的本質是通過調用系統內核進行信號發送,所以進程會馬上處於內核態
raise出錯將返回非0值(不一定是-1)。唯一可能發生的錯誤是sig無效 errno 變爲EINVAL

strsignal(int sig)

顯示信號的描述

#include<signal.h>
extern const char * const sys_siglist[];//存儲着相應信號描述的數組

char *strsignal(int sig);//返回對應的信號描述字符串的地址

下列使用兩種方式獲取關於 contrl+c即終端中斷信號的描述

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<error.h>
#include<string.h>
#include<unistd.h>

extern const char * const sys_siglist[];

int main(int argc, char*argv[]){
    char *sig_desc=strsignal(SIGINT);
    printf("%s\n",sig_desc);
    printf("%s\n",sys_siglist[SIGINT]); //兩種結果都一樣
return 0; 
}

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>使用g++
woder@ubuntu:~/project/osprogram$ ./test
Interrupt
Interrupt

信號集

信號集存儲着一組不同的信號,是一種數據結構,數據類型爲sigset_t

  • 初始化函數
#include<signal.h>
int sigemptyset(sogset_t * set);//將信號集set中的所有信號清空
int sigfillset(sigset_t *set);//將信號集set中的添加所有信號
  • 添加刪除函數
#include<signal.h>
int sigaddset(sigset_t *set,int sig);//向set中添加信號sig
int sigdelset(sigset_t *set,int sig);//向set中移除信號sig
  • 判斷是否包含成員
#include<signal.h>
int sigismember(const sigset_t * set,int sig);判斷sig是否爲set成員,如果是返回1否則0

可以通過此函數對信號1~NSIG-1(信號數量)進行一個遍歷,判斷是否爲當前信號集中的信號

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<error.h>
#include<string.h>
#include<unistd.h>

void print_sig_set(const sigset_t *set){
    for(int i=1;i<NSIG;i++){
    if(sigismember(set,i)){
        printf("signal %d in set\n",i);
    }
    }
}

int main(int argc, char*argv[]){
    sigset_t set;
    sigfillset(&set);
    print_sig_set(&set);    
    return 0; 
}

信號掩碼

每個進程都有一個信號掩碼,即一個用來阻塞部分信號對於該進程的傳遞的信號集,如果被阻塞的信號發送給了進程,該信號會進入進程的等待隊列,直到掩碼不再對該型號進行阻塞,進程才從等待信號集中獲取該信號,引發處理器函數的信號會被自動的添加到等待信號集中,等處理器函數處理完畢後,纔對該信號接觸阻塞

sigprocmask

顯示對向當前進程的信號掩碼中添加,或移除信號

#include<signal.h>
int sigprocmask (int how, const sigset_t *set,sigset_t *oldset);成功返回0,失敗返回-1
  • 對於how有三個參數描述如下
    SIG_BLOCK:將信號集set中的信號添加到信號掩碼中
    SIG_UNBLOCK:將信號集set中的信號從信號掩碼中移除
    SIG_SETMASK:將信號集set中的信號直接賦值給信號掩碼

    • oldset是返回的之前的信號掩碼

sigpending()

獲取當前處於等待隊列中的信號

#include<signal.h>
int sigpending(sigset_t* set);成功返回0,失敗-1

同一信號記錄在等待信號集中,阻塞多次的情況下,在解除阻塞後只傳遞一次

sigaction()

除了signal()可以指定信號處理函數之外,sigaction()也可以

#include<signal.h>
int sigaction(int sig,const struct sigaction * act, struct sigaction * oldact);成功返回0,失敗-1

sigaction結構如下
struct sigaction{
void (*sa_handler)(int);    //對應於signal的handler,信號處理函數的地址
sigset sa_mask;             //信號掩碼

int sa_flags;               
void (*sa_restorer)(void);
};

pause

調用pause將暫停進程的執行,直至信號處理器函數終端該調用

#include<unistd.h>
int pause(void)l
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章