linux学习笔记7 信号

信号是linux系统中一种常用的通信机制,A给B发送信号,B在收到信号之前执行自己的代码,收到信号后,不管执行什么程序,都暂停运行,去处理信号,处理完毕后再继续执行原来的程序,是一种软中断。

Linux常规信号一览表

1) SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程

2) SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动

作为终止进程。

3) SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信

号。默认动作为终止进程。

4) SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件

5) SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。

6) SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。

7) SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。

8) SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。

扫描二维码关注公众号,回复: 5357977 查看本文章

9) SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。

10) SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。

11) SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。

12) SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。

13) SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。

14) SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。

15) SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。

16) SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。

17) SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。

18) SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。

19) SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。

20) SIGTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。

21) SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。

22) SIGTTOU: 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。

23) SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。

24) SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。

25) SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。

26) SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。

27) SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。

28) SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。

29) SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。

30) SIGPWR:关机。默认动作为终止进程。

31) SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。

34) SIGRTMIN ~ (64) SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。

1.kill函数

函数原型:int kill(pid_t pid, int sig)

函数描述:给指定进程发送信号(不一定杀死),第一个参数是进程ID,是发送信号的目标,第二个参数是发送信号的类别,返回值为0,表示成功,返回值为-1,表示失败。

举例:子进程杀死父进程

#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
	int i;
	for (; i<5; i++){
		pid_t pid=fork();
		if (pid==0) break;
	}
	if (i==5){
	cout<<"i am the father"<<endl;
	sleep(1);
	}else if(i==2){
		sleep(1);
		kill(getppid(), SIGKILL);
	}
}

2.raise函数

函数原型:int raise(int sig)

函数描述:给当前的进程发送信号(自己给自己发),唯一入口参数是信号的类别,这个函数相当于kill(getpid(), sig)。成功时返回值为0,失败时返回值为非零。

3.abort函数

函数原型:void abort(void)

函数描述:给当前信号发送异常终止信号6) SIGABRT(自己把自己杀死,并留下遗书),并产生core文件

4.alarm函数

函数原型:unsigned int alarm(unsigned int seconds);

函数描述:设置定时器,在指定入口参数seconds以后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。函数返回值是上次闹钟结束时剩余时间。

举例

#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
	int ret = alarm(6);
//这个语句执行的过程中,也就是在计时过程中,继续往下执行程序的,不是这个6s执行完以后再去执行下面
//的语句
	cout<<ret<<endl;//该返回值是0,因为这是在进程中第一次使用alarm函数
	sleep(3);
	ret = alarm(2);//该返回值是3,因为上一个alarm函数计时到现在剩余3
	cout<<ret<<endl;
	while (1){
		cout<<"come to hit me!\n";
		sleep(1);
	}//当alarm计时结束时,按默认设置就会结束进程
}

5.setitimer函数

函数原型:int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

函数描述:这个函数可以周期性计时,其中

which:指定定时方式

① 自然定时:ITIMER_REAL → 14)SIGLARM   计算自然时间

② 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM    只计算进程占用cpu的时间

③ 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF  计算占用cpu及执行系统调用的时间

new_value和old_value都是结构体itimerval ,new_value是这一次设置的周期参数,old_value存储的是进程中上一个setitimer函数的参数。一般情况下只用new_value,old_value一般设为NULL。

itimerval结构体具体结构如下:

struct itimerval {
    struct timeval it_interval; /* Interval for periodic timer *///周期定时的周期
    struct timeval it_value;    /* Time until next expiration *///第一次定时开始的时间
};//这个结构体主要表达周期定时的一些参数,比如什么时候开始第一次定时,每个周期有多长。

struct timeval {
    time_t      tv_sec;         /* seconds *///秒
    suseconds_t tv_usec;        /* microseconds *///微秒
};//这个结构体就是表达一个精确到微秒的时间

返回值:在成功的时候返回0,在失败的时候返回-1。

举例:自己编写alarm函数

#include <iostream>
#include <unistd.h>
#include <sys/time.h>

using namespace std;
unsigned int myalarm(int seconds)
{
	struct itimerval oldit, myit={{0,0},{seconds,0}};
	setitimer(ITIMER_REAL, &myit, &oldit);
//这里的&oldit只是一个地址参数,用来存储进程中上一个setitimer函数中的myit中的参数
	cout<<oldit.it_value.tv_sec<<"  "<<oldit.it_value.tv_usec<<endl;
	return oldit.it_value.tv_sec;
}	

int main()
{
	int ret;
	ret=myalarm(5);
//这次setitimer函数是进程中第一个出现的,所以oldit为0
	cout<<"ret = "<<ret<<endl;
	sleep(3);
	ret=myalarm(3);
//这次的setitimer函数是进程中第二个出现的,oldit中第一个参数it_interval就是上一个setitimer
//函数中的0,第二个参数it_value就是上一个setitimer函数中还剩余的时间,本来函数运行5秒发出闹钟
//信号,但是运行三秒以后,执行另外一个setitimer函数了,所以还剩2秒,这就是it_value的值。
//不过由于还有其他语句执行需要时间,所以是接近两秒。
	cout<<"ret = "<<ret<<endl;					
	return 0; 
}

6.信号集

信号集可以理解为信号位图,在linux系统中常用的信号是1-31,全部信号是1-63,可以理解为这64个信号依次构成一个64位的二进制数,或者64 个方格组成的位图。在linux中,这个信号集用sigset_t数据结构来表示。

信号集操作函数

int sigemptyset(sigset_t *set); 将某个信号集清0   成功:0;失败:-1

int sigfillset(sigset_t *set); 将某个信号集置1    成功:0;失败:-1

int sigaddset(sigset_t *set, int signum); 将某个信号加入信号集   成功:0;失败:-1//加入信号集就是在信号对应的数位上置1

int sigdelset(sigset_t *set, int signum); 将某个信号清出信号集    成功:0;失败:-1//清出信号集就是在信号对应的数位上置0

int sigismember(const sigset_t *set, int signum);判断某个信号是否在信号集中 返回值:在集合:1;不在:0;出错:-1  

sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

其中参数signum可以理解为位图当中的第几位,或者信号前的编号。

sigprocmask函数

用来屏蔽或者解除屏蔽信号集(理解为设置阻塞信号集,屏蔽可以理解为阻塞)。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。

oldset:传出参数,保存旧的信号屏蔽集。

how参数取值: 假设当前的信号屏蔽字为mask

  1. SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
  2. SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
  3. SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽集的新屏蔽集。相当于 mask = set,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

sigpending函数

用来读取当前进程的未决信号集。

int sigpending(sigset_t *set);地址参数set就是存放读取结果。

运行举例:

1.信号集操作函数举例

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

using namespace std;

int main()
{
	sigset_t sigproc;//这个就可以看成是一个变量,并且信号集操作函数就是对这个变量数值中
//的一个或多个数位进行操作。
	sigemptyset(&sigproc);//将所有数位都置0
	sigaddset(&sigproc, SIGINT);//将第2位置1
	sigaddset(&sigproc, SIGQUIT);//将第3位置1
	int i=1;
	for (; i<32; i++){
		if (sigismember(&sigproc,i)==1)
			cout<<1;
		else cout<<0;
	}
	sleep(1);
	cout<<endl;
}

结果图

打印未决信号集

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

using namespace std;

int main()
{
	sigset_t pend, sigproc;//pend作为未决信号集,sigproc作为阻塞信号集
        /*设置阻塞信号集*/
	sigemptyset(&sigproc);//清空阻塞信号集
	sigaddset(&sigproc, SIGINT);//将第2位置1
	sigaddset(&sigproc, SIGQUIT);//将第3位置1
	sigprocmask(SIG_BLOCK, &sigproc, NULL);//将sigproc设置为阻塞信号集,其中2,3位
//是1,就是表明需要阻塞的信号。
	while (1){
		sigpending(&pend);//将未决信号读取到pend变量中
		int i=1;
		for (; i<32; i++){
			if (sigismember(&pend,i)==1)
				cout<<1;
			else cout<<0;
		}
		sleep(1);
		cout<<endl;
	}
}

执行结果

当按下ctrl+c时,2号位阻塞,未决信号显示1

当按下ctrl+/时,3号位阻塞,未决信号显示1

7.信号捕获

函数原型:int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

signum是需要捕获的信号参数,act是传入参数,传入新的处理方式,oldact是传出参数,是旧的处理方式

其中sigaction结构体如下:

truct sigaction {
        void     (*sa_handler)(int);//指定信号捕捉后的处理函数名(即注册函数)。也可赋值为
//SIG_IGN表忽略 或 SIG_DFL表执行默认动作
        void     (*sa_sigaction)(int, siginfo_t *, void *);//当sa_flags被指定为
//SA_SIGINFO标志时,使用该信号处理程序。与sa_action相比,这个函数入口参数更多,更复杂(很少使用)
        sigset_t   sa_mask;//调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在
//处理函数被调用期间屏蔽生效,是临时性设置。 
        int       sa_flags;//通常设置为0,表使用默认属性。
        void     (*sa_restorer)(void);//该元素是过时的,不应该使用,POSIX.1标准将不指定
//该元素。(弃用) 
    };

程序举例如下,用sigaction捕捉SIGALRM信号

#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

using namespace std;
void catch_sig(int sig)//注册函数,当捕捉到信号时,就调用这个函数
{
	cout<<"catch "<<sig<<endl;
}

int main()
{
	struct sigaction act;
	act.sa_flags=0;
	act.sa_handler=catch_sig;
	sigemptyset(&act.sa_mask);//全部置0,这样就没有阻塞信号
	sigaction(SIGALRM, &act, NULL);
	itimerval myit={{3,0},{2,0}};
	setitimer(ITIMER_REAL, &myit, NULL);
	while (1) {
		cout<<"kill me\n";
		sleep(1);
	}
	return 0;
}

8.SIGCHLD信号使用

SIGCHLD是在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程,可以通过这个信号来结束子进程。

举例:利用SIGCHLD回收子进程

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
using namespace std;

void catch_sig(int sig)
{
	pid_t wpid;
	while ((wpid=waitpid(-1, NULL, WNOHANG))>0)
		cout<<"wait child"<<wpid<<endl;
}

int main()
{
	int i=0;
	for (; i<10; i++){
		int pid=fork();
		if (pid==0) break;
	}
	if (i<10){
		cout<<"i am "<<i<<" child, my pid = "<<getpid()<<endl;
		sleep(i);
	}else if (i==10){
		struct sigaction act;
		act.sa_handler=catch_sig;
		act.sa_flags=0;
		sigemptyset(&act.sa_mask);
		sigaction(SIGCHLD, &act, NULL);	//在父进程中捕捉信号
		while (1){
			sleep(1);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_34489443/article/details/87934099
今日推荐