unix信号处理

一、基本概念

  1. 中断

     中止(注意不是终止)当前正在执行的程序,转而执行其它任务。 
     硬件中断:来自硬件设备的中断。 
     软件中断:来自其它程序的中断。
    
  2. 信号是一种软件中断

     信号提供了一种以异步方式执行任务的机制。
    

3.常见信号

信号产生条件
SIGINT(2)用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程
SIGINT(3)用户按退出键(Ctrl+\),产生此信号,并送至前台进程组的所有进程
SIGINT(9)不能被捕获或忽略。常用于杀死进程

4.不可靠信号(非实时信号)

	(1)小于SIGRTMIN(34)的信号都是不可靠信号
	(2)不支持排队,可能会丢失。同一个信号产生多次,进程可	能只收到一次该信号。
	(3)进程每次处理完这些信号后,对相应信号的响应被自动恢复为默认动作,除非显示地通过signal函数重新设置一次信号处理程序。
	
5.可靠信号(实时信号)
	(1)位于[SIGRTMIN(34),SIGRTMAX(64)]区间的信号都是可靠信号。
	(2)支持排队,不会丢失。
	(3)无论可靠信号还是不可靠信号,都可以通过sigqueue/sigaction函数发送/安装,以获得比其早期版本kill/signal函数更可靠的使用效果。

二、signal.c

(1) 在某些Unix系统上,通过signal函数注册的信号处理函数只一次有效。 即内核每次调用信号处理函数前,会将对该信号的处理自动恢复为默认方式。 为了获得持久有效的信号处理,可以在信号处理函数中再次调用signal函数,重新注册一次。
(2) SIGKILL/SIGSTOP信号不能被忽略,也不能被捕获。
(3) 普通用户只能给自己的进程发送信号,root用户可以给任何进程发送信号。

void sigtstp_proc(int sig){		//信号处理函数 当接收对应的信号时 执行的代码
	printf("%d进程接收到一个信号:%d\n",getpid(),sig);
}
void sig_proc(int sig){
	printf("%d进程接收到一个信号:%d\n",getpid(),sig);
}
int main(){
	printf("进程%d",getpid());
	//SIGINT 忽略 终止 终止+core 添加处理方式
	if(signal(SIGINT,SIG_IGN)==SIG_ERR){ //忽略
		perror("signal");
		return -1;
	}
	if(signal(SIGQUIT,SIG_DFL)==SIG_ERR){//默认处理
		perror("signal");
		return -1;
	
	}
	if(signal(SIGTSTP,sigtstp_proc)==SIG_ERR){  //自定义处理方式 
		perror("signal");		//当进程接收到SIGTSTP时 无论在什么地方都会去执行sigtstp_proc
		return -1;
	
	}
	if(signal(6,sig_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}
	if(signal(9,SIG_IGN)==SIG_ERR){
		perror("signal");
	}
	for(;;);
	return 0;
}

三、子进程的信号处理

子进程会继承父进程的信号处理方式,直到子进程调用exec函数。 
进程调用exec函数后, exec函数将被父进程设置为捕获的信号恢复至默认处理, 其余保持不变。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void sigint_proc(int sig){
	printf("%d进程接收到%d信号!\n",getpid(),sig);	
}

int main(){
	//父进程的注册的信号处理函数
	if(signal(SIGINT,sigint_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}
	pid_t id = fork();
	if(id == -1){
		perror("fork");
		return -1;
	}
	if(id == 0){//子进程继承父进程的信号处理函数
		printf("子进程:%d\n",getpid());	
		execve("rundead",NULL,NULL);		
	}else{
		printf("父进程:%d\n",getpid());	
	}
	for(;;);
	return 0;	
}

四、发送信号

Ctrl+C - SIGINT(2),终端中断 
Ctrl+\ - SIGQUIT(3),终端退出 
Ctrl+Z - SIGTSTP(20),终端暂停

命令

kill -信号 进程号

(1)kill

#include <signal.h>
int kill (pid_t pid, int sig);
成功返回0,失败返回-1。
pid >  0 - 向pid进程发送sig信号。
pid =  0 - 向同进程组的所有进程发送信号。
pid = -1 - 向所有进程发送信号,
前提是调用进程有向其发送信号的权限。
pid < -1 - 向绝对值等于pid的进程组发信号。
0信号为空信号。
若sig取0,则kill函数仍会执行错误检查,
但并不实际发送信号。这常被用来确定一个进程是否存在。
向一个不存在的进程发送信号,会返回-1,且errno为ESRCH。

kill.c

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc,char *argv[]){
	if(argc < 3){
		printf("%s -sig pid\n",argv[0]);
		return -1;
	}
	int sig = -atoi(argv[1]);
	pid_t pid = atoi(argv[2]);
	
	int ret = kill(pid,sig);
	if(ret == -1){
		if(sig == 0 && errno == ESRCH){
			printf("%d进程不存在!\n",pid);
		}
		perror("kill");
		return -1;
	}
	return 0;	
}

(2) raise
#include <signal.h>
int raise (int sig);
向调用进程自身发送sig信号。成功返回0,失败返回-1。

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

void sigint_proc(int sig){
	printf("%d进程收到%d信号!\n",getpid(),sig);	
}

int main(){
	if(signal(SIGINT,sigint_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}

	raise(SIGINT);//向本进程发送一个信号
	kill(getpid(),SIGINT);//向自己发送一个信号SIGINT
	for(;;);
	return 0;	
}

五、pause

1.使调用进程进入睡眠状态,直到有信号终止该进程或被捕获。 
2.只有调用了信号处理函数并从中返回以后,该函数才会返回。
3.相当于没有时间限制的sleep函数。 
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint_proc(int sig){
	
}

int main(){
	if(signal(SIGINT,sigint_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}
	printf("begin...\n");
	pause();//停住了  收到信号之后被唤醒
	printf("end!\n");
	return 0;	
}

六、sleep

1.使调用进程睡眠seconds秒,除非有信号终止该进程或被捕获。 
2.只有睡够seconds秒,或调用了信号处理函数并从中返回以后,该函数才会返回。 
3.该函数要么返回0(睡够),要么返回剩余秒数(被信号中断)。 
4.相当于有时间限制的pause函数。 
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sigint_proc(int sig){
	
}

int main(){
	if(signal(SIGINT,sigint_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}
	unsigned int ret = sleep(10);//返回剩余的秒数  可能被信号唤醒
	printf("ret = %u\n",ret);
	return 0;	
}

七、alarm

1.使内核在seconds秒之后,向调用进程发送SIGALRM(14)闹钟信号。 
2.若之前已设过定时且尚未超时,则调用该函数会重新设置定时,并返回之前定时的剩余时间。
3.seconds取0表示取消之前设过且尚未超时的定时。 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>

void sigalarm_proc(int sig){
	system("clear");
	time_t t = time(NULL);
	struct tm *lt = localtime(&t);
	printf("	%2d:%2d:%2d \n",lt->tm_hour,lt->tm_min,lt->tm_sec);
	alarm(1);//1秒之后又发出SIGALRM信号
}

int main(){
	if(signal(SIGALRM,sigalarm_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}
	alarm(3);
	int ret = alarm(1);
	printf("%d \n",ret);
	for(;;);
	return 0;	
}

八、信号集与信号阻塞(信号屏蔽)

  1. 信号集
    (1) 多个信号的集合类型: sigset_t,128个二进制位 每个位代表一个 信号。
#include <signal.h>
2.
3.// 将信号集set中的全部信号位置1
4.int sigfillset (sigset_t* set);
5.
6.// 将信号集set中的全部信号位清0
7.int sigemptyset (sigset_t* set);
8.
9.// 将信号集set中与signum对应的位置1
10.int sigaddset (sigset_t* set, int signum);
11.
12.// 将信号集set中与signum对应的位清0
13.int sigdelset (sigset_t* set, int signum);
14.
15.成功返回0,失败返回-116.
17.// 判断信号集set中与signum对应的位是否为1
18.int sigismember (const sigset_t* set, int signum);
19.
20.若信号集set中与signum对应的位为1,则返回1,否则返回0
  1. 信号屏蔽
    (1)当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程称为递送(delivery)。
    (2)信号从产生到完成递送之间存在一定的时间间隔。处于这段时间间隔中的信号状态,称为未决(pending)。
    (3)每个进程都有一个信号掩码(signal mask)。 它实际上是一个信号集,其中包括了所有需要被屏蔽的信号。

    注意:对于不可靠信号,通过sigprocmask函数设置信号掩码以后,相同的被屏蔽信号只会屏蔽第一个,并在恢复信号掩码后被递送,其余的则直接忽略掉。
    而对于可靠信号,则会在信号屏蔽时按其产生的先后顺序排队,一旦恢复信号掩码,这些信号会依次被信号处理函数处理。

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

sigset_t set;
sigset_t oldset;
void sig_proc(int sig){
	printf("收到了%d信号\n",sig);	
}
void sig5_proc(int sigs){
	//获取处于未决状态的信号集
	sigset_t sigpset;
	sigpending(&sigpset);
	int sig = 1;
	for(;sig<65;sig++){
		if(sigismember(&sigpset,sig)==1){
			printf("%d 信号处于未决!\n",sig);	
		}	
	}
	//解除信号掩码
	sigprocmask(SIG_SETMASK,&oldset,NULL);
	//在屏蔽期间的信号将会被递送
}
int main(){
	printf("进程:%d\n",getpid());
	sigemptyset(&set);
	//不可靠信号
	sigaddset(&set,2);
	sigaddset(&set,3);
	sigaddset(&set,4);
	//可靠信号
	sigaddset(&set,40);
	sigaddset(&set,41);
	sigaddset(&set,42);
	signal(2,sig_proc);
	signal(3,sig_proc);
	signal(4,sig_proc);
	signal(40,sig_proc);
	signal(41,sig_proc);
	signal(42,sig_proc);
	signal(5,sig5_proc);//收到5信号 解除信号掩码 解除之后,在屏蔽期间收到的信号就会被递送
	//int ret = sigprocmask(SIG_SETMASK,&set,&oldset);
	int ret = sigprocmask(SIG_BLOCK,&set,NULL);
	if(ret == -1){
		perror("sigprocmask");	
	}
	for(;;);
	//sigprocmask(SIG_SETMASK,&oldset,NULL);//恢复
	sigprocmask(SIG_UNBLOCK,&set,NULL);//不太科学
	return 0;	
}

九、sigaction

#include <signal.h>
int sigaction (
    int                     signum, // 信号码
    const struct sigaction* act,    // 信号处理方式
    struct sigaction*       oldact  // 原信号处理方式
                                    // (可为NULL)
);
struct sigaction {
   void     (*sa_handler) (int);
                               // 信号处理函数指针1
    void     (*sa_sigaction) (int, siginfo_t*, void*);
                                // 信号处理函数指针2
    sigset_t sa_mask;           // 信号掩码
    int      sa_flags;          // 信号处理标志
   void     (*sa_restorer)(void);
                                // 保留,NULL
};
成功返回0,失败返回-1typedef struct siginfo {
   pid_t    si_pid;   // 发送信号的PID
   sigval_t si_value; // 信号附加值(需要配合sigqueue函数)
    ...
}   siginfo_t;

typedef union sigval {
    int   sival_int;
    void* sival_ptr;
}   sigval_t;


sigaction::sa_flags可为以下值的位或: 
SA_ONESHOT/SA_RESETHAND 
执行完一次信号处理函数后,即将对此信号的处理恢复为 
默认方式(这也是老版本signal函数的缺省行为).
SA_NODEFER/SA_NOMASK 
在信号处理函数的执行过程中,不屏蔽这个正在被处理的信号。
SA_NOCLDSTOP 
若signum参数取SIGCHLD,则当子进程暂停时,不通知父进程。
SA_RESTART 
系统调用一旦被signum参数所表示的信号中断,会自行重启。
SA_SIGINFO 
使用信号处理函数指针2,通过该函数的第二个参数,提供更多信息。 
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint_proc(int sig){//在处理sig信号时 自动屏蔽该信号
	printf("收到信号%d\n",sig);
	sleep(10);
	printf("proc end!\n");
}


int main(){
	//signal(2,sigint_proc);
	sigset_t set;
	sigemptyset(&set);
	struct sigaction act={};
	act.sa_handler = sigint_proc;
	act.sa_mask = set;//没有屏蔽任何信号
	act.sa_flags = SA_NOMASK|SA_ONESHOT;
	int ret = sigaction(2,&act,NULL);
	if(ret == -1){
		perror("sigaction");
		return -1;
	}
	for(;;);
	return 0;	
}

十、sigqueue

#include <signal.h>
int sigqueue (pid_t pid, int sig,const union sigval value);
向pid进程发送sig信号,附加value值(整数或指针)。
成功返回0,失败返回-1。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

//sigq -sig pid data
int main(int argc,char *argv[]){
	if(argc < 4){
		printf("%s -sig pid data\n",argv[0]);
		return -1;
	}
	printf("%d\n",getpid());
	int sig = -atoi(argv[1]);
	int pid = atoi(argv[2]);
	int data = atoi(argv[3]);
	union sigval val;
	val.sival_int = data;
	int ret = sigqueue(pid,sig,val);
	if(ret == -1){
		perror("sigqueue");
		return -1;
	}
	return 0;
}

注意:sigqueue函数对不可靠信号不做排队,会丢失信号。

十一、计时器

1.系统为每个进程维护三个计时器
 真实计时器: 
 程序运行的实际时间。
 虚拟计时器: 
 程序运行在用户态所消耗的时间。
 实用计时器: 
 程序运行在用户态和内核态所消耗的时间之和。

实际时间(真实计时器) = 用户时间(虚拟计时器) + 内核时间 + 睡眠时间 
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
//看电影 打游戏  以一定的频率刷新
//定时任务   每隔一个时间间隔需要执行一次
//alarm
void sigalarm_proc(int sig){
	system("clear");
	time_t t = time(NULL);	
	struct tm *lt = localtime(&t);
	printf("%2d:%2d:%2d\n",lt->tm_hour,lt->tm_min,lt->tm_sec);
	//alarm(1);
}

int main(){
	//定时器  
	if(signal(SIGALRM,sigalarm_proc)==SIG_ERR){
		perror("signal");
		return -1;
	}
	struct itimerval t = {{1,0},{3,0}};//每隔1秒发送一次 3秒之后发送第一个
	int ret = setitimer(ITIMER_REAL,&t,NULL);
	if(ret == -1){
		perror("setitmer");
		return -1;
	}
	for(;;);
	return 0;	
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值