4 信号

1、信号基础

信号是提供异步处理机制的软件中断
信号的名称与编号

  • 信号是很短的消息,本质就是一个整数,用以区分代表不同事件的不同信号
  • 通过kill -l 命令可以查看信号
  • 一共有62个信号,其中前31个信为不可靠的非实时信号,后31个为可靠的实时信号
    常用信号
    在这里插入图片描述

2、信号处理

  • 忽略:什么也不做,但是SIGKILL(9)和SIGSTOP(19)不能被忽略
  • 默认:在没有人为设置的情况,系统缺省的处理行为。
  • 捕获:接收到信号的进程会暂停执行,转而执行一段事先编写好的处理代码,执行完毕后再从暂停执行的地方继续运行。
// 头文件 signal.h
typedef void (* sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
- 功能:设置调用进程针对特定信号的处理方式
- 参数:
	- signum信号编号
	- handler信号的处理方式,可以如下取值
		SIG_IGN - 忽略
		SIG_DFL - 默认
		信号处理函数指针 - 捕获
- 返回值:成功返回原信号处理方式,如果之前未处理过则返回NULL,失败返回SIG_ERR。
  • 案例
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 信号处理函数
void sigfun(int signum){
        printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}
int main(){
	// 对2号信号进行忽略处理
	if(signal(SIGINT,SIG_IGN)==SIG_ERR){
		perror("signal");
		return -1;
	}
	// 对20号进行进行捕获处理
	if(signal(SIGTSTP,sigfun)==SIG_ERR){
		perror("sigfun");
		return -1;
	}
	for(;;){}
	return 0;
}
  • 主控制流程、信号处理流程和内核处理流程
    • 当有信号到来时,内核会保存当前进程的栈帧,然后再执行信号处理函数
    • 当信号处理函数结束后,内核会恢复之前保存的进程的栈帧,使之继续执行

3、太平间信号

无论一个进程是正常终止还是异常终止,都会通过系统内核向其父进程发送SIGCHLD(17)信号。父进程完全可以在针对SIGCHLD(17)信号的信号处理函数中,异步地回收子进程的僵尸,简洁而又高效

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
// 信号处理函数
void sigchild(int signum){
	printf("%d进程:捕获到%d号信号\n",getpid(),signum);
	pid_t pid = wait(NULL);
	if(pid == -1){
		perror("wait");
		return ;
	}else{
		printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
	}
}

int main(){
	// 父进程对17号信号进行捕获处理
	if(signal(SIGCHLD,sigchild)==SIG_ERR){
			perror("signal");
			return -1;
	}
	// 父进程创建多个字进程
	for(int i=0;i<5;i++){
		pid_t pid = fork();
		if(pid == -1){
			perror("fork");
			return  -1;
		}
		if(pid == 0){
			printf("%d进程:我是子进程\n",getpid());
			sleep(i+1);
			return 0;
		}
	}
	for(;;){printf("zpyl\n");sleep(1);}
}

但这样处理存在一个潜在的风险,就是在sigchld信号处理函数执行过程中,又有多个子进程终止,由于SIGCHLD(17)信号不可靠,可能会丢失,形成漏网僵尸,因此有必要在一个循环过程中回收尽可能多的僵尸

  • 在信号处理函数执行期间,如果有多个相同的信号到来,只保留一个,其余统统丢弃
// 信号处理函数
void sigchild(int signum){
	printf("%d进程:捕获到%d号信号\n",getpid(),signum);
	for(;;){
		pid_t pid = waitpid(-1,NULL,WNOHANG);
		if(pid == -1){
			if(errno == ECHILD){
				printf("没有子进程了\n");
				break;
			}else{
				perror("waitpid");
				return ;
			}
		}else if(pid == 0){
			printf("%d进程在运行\n",getpid());
			break;
		}else{
			printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
		}
	}
	/*for(;;){
		// wait是个阻塞的方法,进程在这里等待子进程死亡,如果子进程一直不死,则会一直等待
		pid_t pid = wait(NULL);
		if(pid == -1){
			if(errno == ECHILD){
				printf("没有子进程了\n");
				break;
			}else{
				perror("wait");
				return ;
			}
		}else{
			printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
		}
	}*/
}

4、信号的继承与恢复

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 信号处理函数
void sigfun(int signum){
    printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}
int main(){
	// 父进程忽略2号信号
	if(signal(SIGINT,SIG_IGN) == SIG_ERR){
		perror("SIGINT");
		return 0;
	}
	// 父进程捕获3号信号
	if(signal(SIGQUIT,sigfun) == SIG_ERR){
		perror("SIGQUIT");
		return -1;
	}
	// 父进程创建子进程
	pid_t pid = fork();
	if(pid==-1){
		perror("fork");
		return -1;
	}
	// 子进程代码
	if(pid == 0){
		printf("%d进程:我是子进程\n",getpid());
		for(;;){}
		return 0;
	}
	// 父进程代码
	else{
		 printf("%d进程:父进程要变身为新进程啦\n",getpid());
		if(execl("./new","./new",NULL) == -1){
			perror("execl");
			return -1;
		}
		return 0;
	}
}
// new
#include <stdio.h>
#include <unistd.h>
int main(){
	printf("%d进程:我是新进程\n",getpid());
	for(;;);
	return 0;
}
  • fork函数创建的子进程会继承父进程的信号处理方式。
    • 父进程中对某个信号进行捕获,则子进程中对该信号依然捕获
    • 父进程中对某个信号进行忽略,则子进程中对该信号依然忽略
  • excc家族函数创建的新进程对信号的处理方式和原进程稍有不同
    • 原进程中被忽略的信号,在新进程时依然被忽略
    • 原进程中被捕获的信号,在新进程中被默认处理

5、发送信号

·用专门的系统命令发送信号

	kill [-信号] PID

若不指明具体信号,缺省发送SIGTERM(15)信号
若要指明具体信号,可以使用信号编号,也可以使用信号名称,而且信号名称中的“SIG”前缀可以省略不写。

kill -9 1234
kill -SIGKILL 1234 5678
kill -KILL -1

超级用户可以发给任何进程,而普通用户只能发给自己的进程
相关函数
kill

// 头文件 signal.h
int kill(pid_t pid,int signum);
- 功能:向指定的进程发送信号
- 参数:
	- pid可以如下取值
		-1 - 向系统中的所有进程发信号
		\>0 - 向特定进程(由pid标识)发送信号
	- signum:信号编号,取0可用于检查pid进程是否存在,如不存在kill函数会返回-1,且errno为ESRCH
- 返回值:成功(至少发出去一个信号)返回0,失败返回-1

案例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 信号处理函数
void sigfun(int signum){
	printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}
int main(){
	// 父进程创建子进程
	pid_t pid = fork();
	if(pid == -1){
		perror("fork");
		return -1;
	}
	// 子进程代码,对2号信号进行捕获
	if(pid == 0){
		if(signal(SIGINT,sigfun) == SIG_ERR){
			perror("signal");
			return -1;
		}
		for(;;);
			return 0;
	}
	// 父进程代码,给子进程发送2号信号
	getchar();// 等待
	if(kill(pid,2)==-1){
		perror("kill");
		return -1;
	}
	return 0;
}

判断进程存不存在是不以进程是否死亡为判断标准,当一个子进程死亡时,但是父进程没有进行收尸,则该子进程还是存在的
raise

// 头文件 signal.h
int raise (int signum);
- 功能:向调用进程自己发送信号
- 参数:signum信号编号
- 返回值:成功返回0,失败返回非0
/*kill(getpid(),signum)等价于该函数*/

6、暂停、睡眠与闹钟

6.1 暂停

pause

// 头文件 unistd.h
int pause(void);
- 功能:无限睡眠
- 返回值:成功阻塞,失败返回-1
- 该函数使调用进(线)程进入无时限的睡眠状态,直到有信号终止了该进程或被其捕获。如果有信号被调用进程捕获,在信号处理函数返回以后,pause函数才会返回,其返回值-1,同时置errno为EINTR,表示阻塞的系统调用被信号打断。pause函数要么不返回,要么返回-1,永远不会返回0

案例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 信号处理函数
void sigfun(int signum){
	printf("%d进程:%d号信号开始处理\n",getpid(),signum);
	sleep(3);
	printf("%d进程:%d号信号处理结束\n",getpid(),signum);
}
int main(){
	// 对2号信号进行捕获
	if(signal(SIGINT,sigfun)==SIG_ERR){
		perror("signal");
		return -1;
	}
	printf("%d进程:一睡不醒\n",getpid());
	int res = pause();// 直到有信号了,才会被唤醒执行下面的语句
	printf("%d进程:psuse函数返回%d\n",getpid(),res);
	return 0;
}

6.2 睡眠

sleep

// 头文件 unistd.h
unsigned int sleep(unsigned int seconds);
- 功能:有限睡眠
- 参数:seconds 以秒为单位的睡眠时限
- 返回值:返回0或剩余秒数。
/* 该函数使调用进程睡眠seconds秒,除非有信号终止了调用进程或被其捕获,如果有信号被调用进程捕获,在信号处理函数返回以后,sleep函数才会返回,且返回值为剩余的秒数,否则该函数将返回0,表示睡眠充足*/

案例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 信号处理函数
void sigfun(int signum){
	printf("%d进程:%d号信号开始处理\n",getpid(),signum);
	sleep(3);
	printf("%d进程:%d号信号处理结束\n",getpid(),signum);
}
int main(){
	// 对2号信号进行捕获
	if(signal(SIGINT,sigfun)==SIG_ERR){
		perror("signal");
		return -1;
	}
	printf("%d进程:一睡不醒\n",getpid());
	int res = sleep(10);
	printf("%d进程:sleep函数返回%d\n",getpid(),res);
	return 0;
}

usleep

// 头文件 unistd.h
int usleep (useconds_t usec);
- 功能:更精确的有限睡眠
- 参数:usec以微秒为单位的睡眠时限
- 返回值:成功返回0,失败返回-1
- 如果有信号被调用进程捕获,在信号处理函数返回以后,usleep函数才会返回,且返回值为-1,同时置errno为EINTR,表示阻塞的系统调用被信号中断

6.3 闹钟

alarm

// 头文件 unistd.h
unsigned int alarm(unsigned int seconds);
- 功能:设置闹钟
- 参数:seconds以秒为单位的闹钟时间。
- 返回值:返回0或先前所设闹钟的剩余秒数。
- alarm函数使系统内核在该函数被调用以后seconds秒的时候,向调用进程发送SIGALRM(14)信号
- 若在调用该函数前已设过闹钟且尚未到期,则该函数会重设闹钟,并返回先前所设闹钟的剩余秒数,否则返回0
- 若seconds!0,则表示取消先前设过且尚未到期的闹钟

案例

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
// 信号捕获函数
void sigfun(int signum){
        time_t now = time(NULL);
        struct tm *t;
        t = localtime(&now);
        printf("\r%4d-%02d-%02d %02d:%02d:%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
        alarm(1);
}
int main(){
        setbuf(stdout,NULL);// 关闭输出缓冲区
        if(signal(SIGALRM,sigfun)==SIG_ERR){
                perror("signal");
                return -1;
        }
        sigfun(1);
        for(;;);
        return 0;
}

7、信号集

  • 多个信号组成的信号集合谓之信号集
  • 系统内核用sigset_t类型表示信号集
    • 在<signal.h>中又被定义为typedef __sigset_t sigset_t;
    • 在<sigset.h>中有如下类型定义
      #define _SIGSET_NWORDS (1024/(8*sizeof(unsigned long int)))
      typedef struct{
      unsigned long int __val[_SIGSET_NWORDS];
      }__sigset_t;
  • sigset_t类型是一个结构体,但该结构体中只有一个成员,是一个包含32个元素的整数数组(针对32位系统而言)
    ·可以把sigset_t类型看成一个由1024个二进制位组成的大整数,其中的每一位对应一个信号,其实目前远没有那么多信号,某位为1就表示信号集中有此信号,反之为0就是无此信号,当需要同时操作多个信号时,常以sigset_t作为函数的参数或返回值的类型
    相关函数
    sigfillset
// 头文件 signal.h
int sigfillset (sigset_t* sigset);
- 功能:填满信号集,即将信号集的全部信号位置1
- 参数:sigset 信号集
- 返回值:成功返回0,失败返回-1

sigemptyset

// 头文件 signal.h
int sigemptyset(sigset_t* sigset);
- 功能:清空信号集,即将信号集的全部信号位清0
- 参数:sigset信号集
- 返回值:成功返回0,失败返回-1

sigaddset

// 头文件 signal.h
int sigaddset (sigset_t* sigset,int signum);
- 功能:加入信号,即将信号集中与指定信号编号对应的信号位置1
- 参数:
	- sigset信号集
	- signum:信号编号
- 返回值:成功返回0,失败返回-1

sigdelset

// 头文件 signal.h
int sigdelset (sigset_t* sigset,int signum);
- 功能:删除信号,即将信号集中与指定信号编号对应的信号位清0
- 参数:
	- sigset 信号集
	- signum 信号编号
- 返回值:成功返回0,失败返回-1

sigismember

// 头文件 signal.h
int sigismember (const sigset_t* sigset,int signum);
- 功能:判断信号集中是否有某信号,即检查信号集中与指定信号编号对应的信号位是否为1
- 参数:
	- sigset 信号集
	- signum 信号编号
- 返回值:有则返回1,没有返回0,矢败返回-1

案例

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 打印一个字节的8位内容
void printb(char byte){
	for(int i=0;i<8;i++){
		printf("%d",(byte>>(7-i))&0x01);
	}
	printf(" ");
}
// 打印一块存储区所有字节的比特位
void printm(void* buf,size_t size){
	for(int i=0;i<size;i++){
		printb(((char*)buf)[size-1-i]);
		if((i+1)%8==0){
			printf("\n");
		}
	}
}
int main(){
	// 信号集
	sigset_t set;
	printf("填满信号集\n");
	sigfillset(&set);
	printm(&set,sizeof(set));
	printf("清空信号集\n");
	sigemptyset(&set);
	printm(&set,sizeof(set));
	printf("设置2号信号\n");
	sigaddset(&set,SIGINT);
	printm(&set,sizeof(set));
	printf("删除2号信号\n");
	sigdelset(&set,SIGINT);
	printm(&set,sizeof(set));
	printf("判断2号信号是否存在\n");
	printf("%s存在\n",sigismember(&set,SIGINT)==1?" ":"不");
	return 0;
}

8、信号屏蔽

  • 当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程就叫做递送(delivery)
  • 信号从产生到完成递送之间存在一定的时间间隔,处于这段时间间隔中的信号状态称为未决(pending)
  • 每个进程都有一个信号掩码(signal mask),它实际上是一个信号集,位于该信号集中的信号一旦产生,并不会被递送给相应的进程,而是会被阻塞(block)在未决状态
  • 在信号处理函数执行期间,这个正在被处理的信号总是处于信号掩码中,如果又有该信号产生,则会被阻塞,直到上一个针对该信号的处理过程结束以后才会被递送
  • 当进程正在执行类似更新数据库这样敏感任务时,可能不希望被某些信号中断。这时可以通过信号掩码暂时屏蔽而非忽略掉这些信号,使其一旦产生即被阻塞于未决状态,待特定任务完成后,再回过头来处理这些信号
    设置掩码
    sigprocmask
// 头文件 signal.h
int sigprocmask (int how,const sigset_t* sigset,sigset_t* oldset);
- 功能:设置调用进程的信号掩码
- 参数:
	- how:修改信号掩码的方式,可取以下值
		SIG_BLOCK - 将sigset中的信号加入当前信号掩码
		SIG_UNBLOCK - 从当前信号掩码中删除sigset中的信号
		SIG_SETMASK - 把sigset设置成当前信号掩码
	- sigset:信号集,取NULL则忽略此参数
	- oldset:输出原信号掩码,取NULL则忽略此参数
- 返回值:成功返回0,失败返回-1
  • 案例
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

void sigfun(int signum){
	printf("%d进程:捕获到%d号信号\n",getpid(),signum);
}

int main(){
	int signum = /*SIGINT*/ 50;
	// 对2号信号进行捕获处理
	if(signal(signum,sigfun)==SIG_ERR){
			perror("signal");
	}
	// 对2号信号进行屏蔽
	printf("父进程增加对信号的屏蔽\n");
	sigset_t set;
	sigemptyset(&set);// 清空
	sigaddset(&set,signum);// 设置信号
	sigset_t oldset;// 用来保存设置前的信号集
	if(sigprocmask(SIG_BLOCK,&set,&oldset)==-1){
		perror("sigprocmake");
		return -1;
	}
	// 创建子进程
	pid_t pid = fork();
	if(pid == -1){
		perror("fork");
		return -1;
	}
	// 子进程向父进程发送2号信号
	if(pid == 0){
		printf("子进程发送信号\n");
		for(int i=0;i<5;i++){
			kill(getppid(),signum);
			printf("发送%d此信号\n",(i+1));
		}
		return 0;
	}
	// 父进程更新数据库
	for(int i=0;i<5;i++){
		printf("父进程保存%d条数据\n",(i+1));
		sleep(1);
	}
	// 父进程接触对2号信号的屏蔽
	printf("父进程接触对信号的屏蔽\n");
	if(sigprocmask(SIG_SETMASK,&oldset,NULL)==-1){
		perror("sigprocmake");
		return -1;
	}
	// 父进程收尸
	if(wait(NULL)==-1){
		perror("wait");
		return -1;
	}
	return 0;
}
  • 对于可靠信号,通过sigprocmask函数设置信号掩码以后,每种被屏蔽信号中的每个信号都会被阻塞,并按先后顺序排队,一旦解除屏蔽,这些信号会被依次递送
  • 对于不可靠信号,通过sigprocmask函数设置信号掩码以后,每种被屏蔽信号中只有第一个会被阻塞,并在解除屏蔽后被递送,其余的则全部丢失

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

启航zpyl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值