Linux 進程通信 -下 (信號)

在這裏插入圖片描述

1、信號的概述

信號的概念 信號是 Linux 進程間通信的最古老的方式。信號是軟件中斷,它是在軟件層次上對中斷機制的一種模擬,是一種異步通信的方式 。信號可以導致一個正在運行的進程被另一個正在運行的異步進程中斷,轉而處理某一個突發事件。
信號的特點 簡單 不能攜帶大量信息 滿足某個特設條件才發送。
一個完整的信號週期包括四個部分:信號的產生,信號在進程中的註冊,信號在進程中的註銷,執行信號處理函數

在這裏插入圖片描述

查看信號:kill -l

在這裏插入圖片描述
其中1-31號信號稱之爲常規信號(也叫普通信號或標準信號),34-64稱之爲實時信號,驅動編程與硬件相關。名字上區別不大。而前32個名字各不相同.

每個信號必備4要素,分別是:

1)編號 2)名稱 3)事件 4)默認處理動作 可通過man 7 signal查看幫助文檔獲取:
當一個信號發生的時候:忽略、執行默認動作、執行自定義的動作

未決信號集合 信號阻塞集

信號的實現手段導致信號有很強的延時性,但對於用戶來說,時間非常短,不易察覺。 Linux內核的進程控制塊PCB是一個結構體,task_struct, 除了包含進程id,狀態,工作目錄,用戶id,組id,文件描述符表,還包含了信號相關的信息,主要指阻塞信號集和未決信號集。

  1. 阻塞信號集(信號屏蔽字) 將某些信號加入集合,對他們設置屏蔽,當屏蔽x信號後,再收到該信號,該信號的處理將推後(處理髮生在解除屏蔽後)。
  2. 未決信號集 信號產生,未決信號集中描述該信號的位立刻翻轉爲1,表示信號處於未決狀態。當信號被處理對應位翻轉回爲0。這一時刻往往非常短暫。 信號產生後由於某些原因(主要是阻塞)不能抵達。這類信號的集合稱之爲未決信號集。在屏蔽解除前,信號一直處於未決狀態

2、信號的API

2.1、 kill函數

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:給指定進程發送指定信號(不一定殺死)
參數:
    pid : 取值有 4 種情況 :
        pid > 0:  將信號傳送給進程 ID 爲pid的進程。
        pid = 0 :  將信號傳送給當前進程所在進程組中的所有進程。
        pid = -1 : 將信號傳送給系統內所有的進程。
        pid < -1 : 將信號傳給指定進程組的所有進程。
        這個進程組號等於 pid 的絕對值。
    sig : 信號的編號,這裏可以填數字編號,也可以填信號的宏定義,
    可以通過命令 kill - l("l" 爲字母)進行相應查看。不推薦直接使用數字,應使用宏名,因爲不同操作系統信號編號可能不同,但名稱一致。
返回值:
    成功:0
    失敗:-1

案例:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)//子進程
	{
		while(1)
		{
			static i=0;
			printf("子進程%d中的i=%d\n", getpid(), i++);
			sleep(1);
		}
	}
	else if(pid > 0)//父進程
	{
		printf("父進程 將在5秒後殺死子進程\n");
		sleep(5);
		kill(pid, SIGINT);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

2.2、raise函數 (自殺)

#include <signal.h>
int raise(int sig);
功能:給當前進程發送指定信號(自己給自己發),等價於 kill(getpid(), sig)
參數:
    sig:信號編號
返回值:
    成功:0
    失敗:非0值

在這裏插入圖片描述

2.3、abort函數

#include <stdlib.h>
void abort(void);
功能:給自己發送異常終止信號 6) SIGABRT,併產生core文件,等價於kill(getpid(), SIGABRT);
參數:無
返回值:無

在這裏插入圖片描述

2.4、alarm函數(鬧鐘)

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
    設置定時器(鬧鐘)。在指定seconds後,內核會給當前進程發送14)SIGALRM信號。
    進程收到該信號,默認動作終止。每個進程都有且只有唯一的一個定時器。
    取消定時器alarm(0),返回舊鬧鐘餘下秒數。
參數:
    seconds:指定的時間,以秒爲單位
返回值:
    返回0或剩餘的秒數
定時,與進程狀態無關(自然定時法)!就緒、運行、掛起(阻塞、暫停)、終止、殭屍……
無論進程處於何種狀態,alarm都計時

案例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
	printf("進程%d將在5秒後結束\n",getpid());
	
	//s本質上是鬧鐘上一次剩餘的秒數 但是第一次調用返回的是0
	//第二次調用 返回的是 上一次的剩餘描述
	int s = alarm(5);//不阻塞
	printf("s = %d\n",s);
	printf("sleep 3秒\n");
	sleep(3);
	
	//重新設置鬧鐘
	s = alarm(5);
	printf("s = %d\n",s);//2秒
	
	while(1)
	{
		static int i=0;
		printf("i=%d\n",i++);
		sleep(1);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

2.5、setitimer函數(定時器)

#include <sys/time.h>
int setitimer(int which,  const struct itimerval *new_value, struct itimerval *old_value);
功能:
    設置定時器(鬧鐘)。 可代替alarm函數。精度微秒us,可以實現週期定時。
參數:
    which:指定定時方式
        a) 自然定時:ITIMER_REAL → 14)SIGALRM計算自然時間
        b) 虛擬空間計時(用戶空間):ITIMER_VIRTUAL → 26)SIGVTALRM  只計算進程佔用cpu的時間
        c) 運行時計時(用戶 + 內核):ITIMER_PROF → 27)SIGPROF計算佔用cpu及執行系統調用的時間
    new_value:struct itimerval, 負責設定timeout時間
        struct itimerval {
            struct timerval it_interval; // 鬧鐘觸發週期  第一次以後 週期時間
            struct timerval it_value;    // 鬧鐘觸發時間 第一次觸發的時候
        };
        struct timeval {
            long tv_sec;            // 秒
            long tv_usec;           // 微秒
        }
        itimerval.it_value: 設定第一次執行function所延遲的秒數
        itimerval.it_interval:  設定以後每幾秒執行function
    old_value: 存放舊的timeout值,一般指定爲NULL
返回值:
    成功:0
    失敗:-1
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
void my_func(int sig)
{
	printf("hello\n");
}
int main()
{
	struct itimerval new_value;
	//第一次執行執行的時間
	new_value.it_value.tv_sec = 4;//4秒
	new_value.it_value.tv_usec=0;//微妙
	
	//第一次以後的週期時間
	new_value.it_interval.tv_sec = 1;//4秒
	new_value.it_interval.tv_usec=0;//微妙
	
	//更改SIGALRM的處理方式
	signal(SIGALRM, my_func);
	
	setitimer(ITIMER_REAL, &new_value , NULL);
	
	while(1);
	return 0;
}

運行結果:
在這裏插入圖片描述

3、修改信號的處理動作(瞭解)

信號處理方式

一個進程收到一個信號的時候,可以用如下方法進行處理:
1)執行系統默認動作 對大多數信號來說,系統默認動作是用來終止該進程。
2)忽略此信號(丟棄) 接收到此信號後沒有任何動作。
3)執行自定義信號處理函數(捕獲) 用用戶定義的信號處理函數處理該信號。

【注意】:SIGKILL 和 SIGSTOP 不能更改信號的處理方式,因爲它們向用戶提供了一種使進程終止的可靠方法
內核實現信號捕捉過程
在這裏插入圖片描述

捕捉信號並且信號信號的處理方式有兩個函數,signal 和sigaction

1、signal函數

#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
    註冊信號處理函數(不可用於 SIGKILL、SIGSTOP 信號),即確定收到信號後處理函數的入口地址。此函數不會阻塞。
參數:
    signum:信號的編號,這裏可以填數字編號,也可以填信號的宏定義,可以通過命令 kill - l("l" 爲字母)進行相應查看。
    handler : 取值有 3 種情況:
          SIG_IGN:忽略該信號
          SIG_DFL:執行系統默認動作
          信號處理函數名:自定義信號處理函數,如:func
          回調函數的定義如下:
            void func(int signo)
            {
                // signo 爲觸發的信號,爲 signal() 第一個參數的值
            }
返回值:
    成功:第一次返回 NULL,下一次返回此信號上一次註冊的信號處理函數的地址。如果需要使用此返回值,必須在前面先聲明此函數指針的類型。
    失敗:返回 SIG_ERR

在這裏插入圖片描述
結束方法:
在這裏插入圖片描述

2、sigaction函數

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
    檢查或修改指定信號的設置(或同時執行這兩種操作)。
參數:
    signum:要操作的信號。
    act:   要設置的對信號的新處理方式(傳入參數)。
    oldact:原來對信號的處理方式(傳出參數)。
    如果 act 指針非空,則要改變指定信號的處理方式(設置),如果 oldact 指針非空,則系統將此前指定信號的處理方式存入 oldact。
返回值:
    成功:0
    失敗:-1
struct sigaction結構體:
struct sigaction {
    void(*sa_handler)(int); //舊的信號處理函數指針
    void(*sa_sigaction)(int, siginfo_t *, void *); //新的信號處理函數指針
    sigset_t   sa_mask;      //信號阻塞集
    int        sa_flags;     //信號處理的方式
    void(*sa_restorer)(void); //已棄用
};
1) sahandler、sasigaction:信號處理函數指針,和 signal() 裏的函數指針用法一樣,應根據情況給sasigaction、sahandler 兩者之一賦值,其取值如下:
	a) SIGIGN:忽略該信號 
	b) SIGDFL:執行系統默認動作 
	c) 處理函數名:自定義信號處理函數
2) samask:信號阻塞集,在信號處理函數執行過程中,臨時屏蔽指定的信號。
3) saflags:用於指定信號處理的行爲,通常設置爲0,表使用默認屬性。它可以是一下值的“按位或”組合: Ø SARESTART:使被信號打斷的系統調用自動重新發起(已經廢棄) Ø SANOCLDSTOP:使父進程在它的子進程暫停或繼續運行時不會收到 SIGCHLD 信號。 Ø SANOCLDWAIT:使父進程在它的子進程退出時不會收到 SIGCHLD 信號,這時子進程如果退出也不會成爲殭屍進程。 Ø SANODEFER:使對信號的屏蔽無效,即在信號處理函數執行期間仍能發出這個信號。 Ø SARESETHAND:信號處理之後重新設置爲默認的處理方式。 Ø SASIGINFO:使用 sasigaction 成員而不是 sahandler 作爲信號處理函數。
信號處理函數:
	void(*sa_sigaction)(int signum, siginfo_t *info, void *context);
參數說明:
    signum:信號的編號。
    info:記錄信號發送進程信息的結構體。
    context:可以賦給指向 ucontext_t 類型的一個對象的指針,以引用在傳遞信號時被中斷的接收進程或線程的上下文

案例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
void my_func(int sig)
{
	printf("ctrl+c被按下了\n");
}
int main()
{
	struct sigaction act;
	
	//act存放回調函數
	act.sa_handler = my_func;
	//act添加阻塞集 act.sa_mask 
	sigemptyset(&act.sa_mask);
	//SA_RESETHAND:信號處理之後重新設置爲默認的處理方式
	act.sa_flags=0;//默認方式
	//act.sa_flags |= SA_RESETHAND;
	
	sigaction(SIGINT, &act, NULL);
	
	while(1);
	return 0;
}

運行結果:
在這裏插入圖片描述

4、信號集

在PCB中有兩個非常重要的信號集。一個稱之爲“阻塞信號集”,另一個稱之爲“未 決信號集”。 這兩個信號集都是內核使用位圖機制來實現的。但操作系統不允許我 們直接對其進行位操作。而需自定義另外一個集合,藉助信號集操作函數來對PCB 中的這兩個信號集進行修改。
在這裏插入圖片描述
sigset_t set,set即一個信號集:

#include <signal.h>  
int sigemptyset(sigset_t *set);       //將set集合置空
int sigfillset(sigset_t *set);          //將所有信號加入set集合
int sigaddset(sigset_t *set, int signo);  //將signo信號加入到set集合
int sigdelset(sigset_t *set, int signo);   //從set集合中移除signo信號
int sigismember(const sigset_t *set, int signo); //判斷信號是否存在
除sigismember外,其餘操作函數中的set均爲傳出參數。sigset_t類型的本質是位圖。
但不應該直接使用位操作,而應該使用上述函數,保證跨系統操作有效
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
int main()
{
	//定義一個信號集合
	sigset_t set;
	
	//清空集合
	sigemptyset(&set);
	
	//判斷SIGINT信號時候在set集合中
	if(sigismember(&set, SIGINT))
	{
		printf("SIGINT在集合中\n");
	}
	else
	{
		printf("SIGINT不在集合中\n");
	}
	
	//將SIGINT信號 添加到集合set中
	sleep(3);
	sigaddset(&set, SIGINT);
	if(sigismember(&set, SIGINT))
	{
		printf("SIGINT在集合中\n");
	}
	else
	{
		printf("SIGINT不在集合中\n");
	}
	sleep(3);
	//將SIGINT信號 從集合set中刪除
	sigdelset(&set, SIGINT);
	if(sigismember(&set, SIGINT))
	{
		printf("SIGINT在集合中\n");
	}
	else
	{
		printf("SIGINT不在集合中\n");
	}
	
	return 0;
}

運行結果:
在這裏插入圖片描述

5、信號阻塞集

所謂阻塞並不是禁止傳送信號, 而是暫緩信號的傳送。若將被阻塞的信號從信號阻塞集中刪除,且對應的信號在被阻塞時發生了,進程將會收到相應的信號。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
    檢查或修改信號阻塞集,根據 how 指定的方法對進程的阻塞集合進行修改,新的信號阻塞集由 set 指定,而原先的信號阻塞集合由 oldset 保存。
參數:
    how : 信號阻塞集合的修改方法,有 3 種情況:
        SIG_BLOCK:向信號阻塞集合中添加 set 信號集,新的信號掩碼是set和舊信號掩碼的並集。²相當於 mask = mask | set。
        SIG_UNBLOCK:從信號阻塞集合中刪除 set 信號集,從當前信號掩碼中去除 set 中的信號。相當於 mask = mask & ~ set。
        SIG_SETMASK:將信號阻塞集合設爲 set 信號集,相當於原來信號阻塞集的內容清空,然後按照 set 中的信號重新設置信號阻塞集。相當於mask = set。
    set : 要操作的信號集地址。
        若 set 爲 NULL,則不改變信號阻塞集合,函數只把當前信號阻塞集合保存到 oldset 中。
    oldset : 保存原先信號阻塞集地址
返回值:
    成功:0,
    失敗:-1,失敗時錯誤代碼只可能是 EINVAL,表示參數 how 不合法

案例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
int main()
{
	//定義一個集合
	sigset_t set;
	sigset_t old_set;
	
	//清空集合
	sigemptyset(&set);
	sigemptyset(&old_set);
	
	
	//將SIGINT添加到集合中
	sigaddset(&set, SIGINT);
	
	//將集合set添加到 阻塞集中
	sigprocmask(SIG_BLOCK, &set, &old_set);
	printf("SIGINT已經在阻塞集中\n");
	
	sleep(10);
	//將集合從阻塞集中刪除
	printf("SIGINTc從阻塞集中刪除了\n");
	sigprocmask(SIG_UNBLOCK, &set, NULL);
	//sigprocmask(SIG_SETMASK, &old_set, NULL);//恢復以前的集合
	
	while(1);
	
	return 0;
}

運行結果:
在這裏插入圖片描述

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