Linux系統編程-信號

    0x00

    信號和中斷類似,中斷是硬件發出,而信號由軟件發出。

    信號常用於進程間通信,一個信號常見的處理如下:

    1、設置對應信號的信號處理函數。

    2、當信號來臨時,打斷正常執行的程序(本質上是在系統調用前檢查是否有信號的到來),去執行信號處理函數。

    3、信號處理函數執行完,繼續執行原程序。


    0x01

    我們先來看一下linux中一共支持多少種信號。使用命令kill -l,可以列出linux支持的所有信號。

    

   

    每個信號都有對應的默認信號處理函數,使用命令man 7 signal,可以查看到。

    


    如果信號沒有定義對應的信號處理函數,並且沒有忽略信號,那麼則採用默認的信號處理函數。

    這裏要注意,信號SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。


    0x02

    我們看一個自定義信號處理函數的例子,來學習如何自定義信號處理,並且學習如何發送信號。

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	} while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
	if (signal(SIGINT, handler) == SIG_ERR)
		ERR_EXIT("signal error");
	for (;;) 
		pause();
	return 0;
}

void handler(int sig)
{
	printf("recv a sig=%d\n", sig);
}
    這個程序中我們自定義了信號處理函數,對應的信號是SIGINT,那麼什麼時候會發送這個這個信號呢?答案是當用戶按下Ctrl + C時,我們看控制檯輸出的結果。

    
    對照信號表,SIGINT是2號信號。在程序中pause,可以用來等待下一次信號的來臨。

    在這個例子中發送信號是通過鍵盤按Ctrl + C。

    這裏順便說一下三號信號SIGQUIT,發送該信號,按鍵盤Ctrl + \。

  

    0x03

    我們接着講另一種發送信號的方式,通過kill,列出代碼:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	} while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
	if (signal(SIGUSR1, handler) == SIG_ERR)
		ERR_EXIT("signal error");
	pid_t pid = fork();
	if (pid == -1)
		ERR_EXIT("fork error");

	if (pid == 0) //子進程
	{
		kill(getppid(), SIGUSR1);
		exit(EXIT_SUCCESS);
	}

	int n = 5; //父進程
	do
	{
		n = sleep(n);
	} while (n > 0);
	return 0;
}

void handler(int sig)
{
	printf("recv a sig=%d\n", sig);
}
    這個程序中使用了SIGUSR1這個用戶自定義信號。使用kill向對應的父進程發送信號,父進程sleep一旦接收到信號,會立刻退出,所以就不會睡眠5秒鐘,那麼如何讓父進程睡眠5秒鐘呢?我們採用了循環的方式,即使被子進程發來的信號打斷,也可以繼續睡眠剩餘的時間。

    這個例子中發送信號,我們採用kill的方式。

    0x04

    下面再看一個通過alarm發送信號的例子:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	} while(0)

void handler(int sig);
int main(int argc, char *argv[])
{
	if (signal(SIGALRM, handler) == SIG_ERR)
		ERR_EXIT("signal error");

	alarm(1);
	for (;;)
		pause();
	return 0;
}

void handler(int sig)
{
	printf("recv a sig=%d\n", sig);
	alarm(1);
}
    通過alarm函數向自身進程發送SIGALRM的信號,在信號處理函數中再一次發送ALRM信號,起到了一個定時器的作用。


    0x05

    接下來的例子,我們講解下結束進程的幾種方式,結合waitpid函數,進一步理解waitpid函數。

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	} while(0)

int main(int argc, char *argv[])
{
	pid_t pid;
	pid = fork();
	if (pid == -1)
		ERR_EXIT("fork error");

	if (pid == 0) //子進程
	{
		sleep(3);
		printf("this is child\n");
		exit(100);
		//return 0;
		//abort();
		//raise(SIGKILL);
	}

	int ret;
	printf("this is parent\n");
	int status;
	//ret = wait(&status);
	//ret = waitpid(-1, &status, 0);
	ret = waitpid(pid, &status, 0); //父進程
	printf("ret = %d pid = %d\n", ret, pid);
	if (WIFEXITED(status))
		printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
	//else
	//	printf("child exited abnormal\n");

	else if (WIFSIGNALED(status))
		printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
	else if (WIFSTOPPED(status))
		printf("child stoped signal number=%d\n", WSTOPSIG(status));
	return 0;
}
    這個例子說明了結束一個進程的幾種方式:

    1、exit(100) ---> 控制檯輸出:child exited normal exit status=100 

    2、return 0 ---> 控制檯輸出:child exited normal exit status=0

    3、abort(),向當前進程發送SIGABRT,SIGABRT信號的默認處理函數爲終止進程 ---> 控制檯輸出:child exited abnormal signal number=6 (SIGABRT是6號信號)

    4、raise(SIGKILL),向當前進程發送SIGKILL信號,SIGKILL信號的默認處理函數是終止進程 --> 控制檯輸出:child exited abnormal signal number=9 (SIGKILL是9號信號)


    0x06

    總結:

    信號既可以被捕獲(自定義信號處理函數),也可以忽略,也可以走默認信號處理函數。

    發送信號有以下幾種方式:

    1、鍵盤操作,Ctrl + C,Ctrl + \

    2、kill

    3、alarm

    4、abort

    5、raise

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