Linux系统编程8 信号

1.基本介绍:

在使用信号时,要明白4点,信号编号,信号名,信号处理函数,信号对应默认处理行为。

LINUX系统中,信号有很明确的生命周期。首先内核产生信号,接着内核储存信号,直到可以发送信号,最后一旦空闲,就处理信号(内核发送,内核处理)。处理信号有下面三种情况:

1).忽略信号:即不采取任何操作。但有两个信号不能忽略和捕捉,即SIGKILL和SIGSTOP。因为系统管理员需要杀死或者停止进程,这两个信号会采用默认行为,甚至不能被阻塞

2).捕获并且处理信号:内核会暂停该进程正在执行的代码,并跳转到先前注册的函数中,接下来会执行这个函数。一旦进程从函数返回,其会跳回捕捉信号的地方继续执行。

经常捕捉的信号是SIGINT和SIGTERM.前者是捕捉来处理用户产生的中断符(用户CTRL-C产生),后者是在程序结束前进行必要的清理工作。SIGKILL和SIGSTOP不能被捕获。

3).执行信号的默认操作

2.常见信号介绍,下面将介绍一些常用的信号。

SIGALRM:由函数alarm()发送

SIGINT:用户产生中断符。

SIGKILL:由函数kill()发出,来给系统管理员提供一种可靠的方法无条件终止一个进程,不能被捕捉或者忽略,其结果就是杀死该进程。

SIGSTOP:由KILL()函数发出,无条件停止一个进程,不能被捕捉或者忽略。

SIGTERM:该信号由kill()函数发出,其循序用户优雅地停止进程,进程可以捕捉该函数,并在进程结束前进行清理,但是捕获该信号并不能及时地终止进程。

3.基本信号管理:

1).signal信号

#include <signal.h>

typedef void(*sighandler)(int);

sighandler signal (int signal,sighandler handler);
此函数地第一个参数是需要捕捉的信号,第二个参数有如下形式;
1.SIG_IGN,第二个参数是这个时,调用signal()函数的作用就是忽略参数一的信号
2.SIG_DFL,这个符号表示恢复对信号的系统默认处理。不写此处理函数默认也是执行系统默认操作。 
3.void(*pp)(int)函数类型的指针,函数成功调用时,会将处理信号作为参数传递给函数指针,去执行函数

signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。


当一个信号的信号处理函数执行时,如果进程又接收到了该信号(相同类型),该信号会自动被储存而
不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。
但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。

即相同类型储存依次执行,不同类型直接被中断。

2).继承和执行

调用fork()函数时,子进程会继承父进程的所有信号的处理(默认,处理,忽略等等),不是产生。

子进程不会进程父进程挂起的信号(已经产生了的信号),这点十分重要,想要子进程执行信号函数,必须再次发出信号。

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


void hander(int){
    std::cout<<"hello"<<std::endl;
}


int main(){

    pid_t id;
    alarm(1);//----------A
    signal(SIGALRM,hander);//执行一次
    id=fork();
    if(id==0){
         alarm(1);
         sleep(1);
         std::cout<<"erzi"<<std::endl;
         signal(SIGALRM,hander);
  //子进程又直接执行一次,子进程不会继承A处的信号,但会继承信号注册和信号处理函数
    }else{
         sleep(2);
         signal(SIGALRM,hander);//此处不执行,因为父进程只被alarm()一次
         //已经被捕捉
    }
std::cout<<"1"<<std::endl;
}

如上,创建的子进程不会继承父进程在A处发出的信号,但其会继承信号处理函数和信号注册函数signal()和handle().若子进程未设置alarm()函数,则父进程只会执行一次。

3).发送信号

在LINUX系统中,发送信号的函数是kill()

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,int signo);
如果pid>0,则发送信号给pid的进程,信号为signo
若pid=0,则会给进程的进程组的每一个进程发送signo信号。
pid<0情况不做介绍

调用成功后,此函数返回0,失败返回-1.


给进程自己发送信号
kill(getpid(),signo);
等价于函数
int raise(int signo);
调用成功时 函数返回0,失败返回大于0的值。

直接给进程组的进程发送所有信号
调用函数killpg()

int killpg(int pgrp,int signo);第一个参数是进程组的ID 成功时返回0,失败时返回-1.

补充:当内核发送信号时,进程可能执行任何任务。当被中断时,进程可能会处于不一致的状态。信号处理程序必须注意,不要对进程中断时可能正在执行的操作做任何假设,尤其是当涉及全局变量的时候,这个十分重要,尽量避开信号处理函数修改或者操作全局变量。比如涉及变量errno时,可以在信号处理函数最开始先保存全局变量,最后再进行恢复。

4).高级信号管理函数sigaction()  (用法见网络编程笔记)

#include <signal.h>

int sigaction(int signo,const struct sigaction* act,struct sigaction* oldact);

成功调用此函数时,等待捕捉signo信号,若参数二不为NULL,则信号行为被act取代
若是NULL,则进行默认行为,第三个参数若非空,则会保存上一次的act信息,但
一般都设置为空。

下面是sigaction结构体介绍:

sa_handler为指定的信号操纵(类型和signal函数相同,void(*name)(int),向行为函数传递捕捉的信号),sa_mask提供了应该在执行信号处理程序时被阻塞的信号集,一般没用,但需要用函数sigfullset()进行初始化。sa_flags是标注位,改变signo信号的处理方式。一般常用的处理方式有下面几种:

SA_NOCLDWAIT:若信号是SIGCHLD,则该标志会自动获取子进程,子进程结束是不会编程僵尸进程,父进程不需要wait/waitpid等待回收。

SA_RESTART:此标志是最常用的,其能使被信号中断的系统调用重新启动。

SA_RESETHAND:该信号表示“一次性类型”,一旦信号处理函数返回,给定信号会重新设置为默认行为。

5). 信号集

阻塞信号信号集:将某些信号加入集合,对他们设置屏蔽,当屏蔽X信号后,再收到信号,该信号的处理将被推后(解除屏蔽后再处理,即阻塞,即使对面发送也默认为丢弃)。(0,1)表示状态位。

未决信号集(发送信号但未解决的集合):

1.产生信号时,在未决信号集中描述该信号的位立刻翻转为1,表示该信号发送且未解决,当信号被解决时,回转为0,这时间很短暂。(0,1表示状态位)

2.信号产生后因为某些原因不能到达(比如被屏蔽)。这类信号成为未决信号集,在屏蔽解除之前,信号一直处于未处理的状态(未决信号集对应信号状态为一直为1)

管理人员只能操作阻塞信号集,不能操作未决信号结,开始时,阻塞信号集都为0位,阻塞信号集和未决信号集都在进程控制块里。

信号集操作函数:

#include <signal.h>

信号集变量: sigset_t set;

int sigemptyset(sigset_t* set);//将信号集变量全部变为0(初始化),
类似于select函数的监听集合的初始化
int sigfullset(sigset_t* set);//将信号集变量全部置为1
int sigaddset(sigset_t* set,int signum);将指定信号在信号集中设为为1
int sigdelset(sigset_t* set,int signum);将指定信号在信号集中设置为0

上述四个函数就是设置自自定义信号集

设置完自定义信号集后,可以调用sigpromask()函数,将自定义信号机和内核阻塞信号集(全为0)
进行掩码计算,最后将得到设置阻塞信号的信号集

int sigprocmask(int how,const sigset_t* set,sigset_t* oldset);
第一个参数是描述函数行为的,第二个参数是要与内核阻塞信号集进行运算的信号机
参数三是保留上一次的内核阻塞信号集的结果。


int sigpending(sigset_t* newset);//此函数会保存当前进程的未决信号集,
然后将结果存在newset中,比如掩盖二号信号后发送二号信号,
但是不会处理,newset中二号位信号始终是1。

int sigismember(sigset_t* newset,int signum)
查看信号集中被遮掩的信号,如果是1,则返回1,若未被遮掩则返回零,一般和sigpending()一起用


0~31已经被宏来表示32个信号了,这个注意。

how参数如下:


SIG_SETMASK:调用进程的信号掩码设置

SIG_BLOCK:自定义信号集set(参数二)加入到进程的信号掩码中,即信号掩码变为当前进程阻塞信号集与set的并集(二进制或运算)

SIG_UNBLOCK:set中的信号被调用进程阻塞信号集中移除,即解除对应set中的屏蔽。对非阻塞信号进行解除屏蔽是非法的

但是不允许SIGKILL和SIGSTOP函数。

如下例子:补充:LINUX信号有32个,用宏进行分别表示了。

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <iostream>


int main(){
	sigset_t set,olcset,newset;
	sigemptyset(&set);//类似于用于初始化
	sigaddset(&set,SIGALRM);//屏蔽alarm信号,未决信号集始终为1

	int ret=sigprocmask(SIG_BLOCK,&set,&olcset);//将设置的信号集作用进程阻塞信号集
    assert(ret>=0);
   
   alarm(1);

 while(1){
	alarm(1);
	sleep(1);
	ret=sigpending(&newset);//内核的未决信号集结果存在newset中
	assert(ret>=0);

	for(int i=0;i<32;i++){
		if(sigismember(&newset,i)){//查看信号集的每一位,有信号未解决 返回1,没有返回0 
        putchar('1');
		}else{
			putchar('0');
		}
		//LINUX信号32个 所以信号集中有32位,依次进行查看
	}
	std::cout<<"\n";
	 sleep(1);
 }

}

运行结果:

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值