linux——信号

17 篇文章 0 订阅

信号

信号的机制

进程A给进程B发送信号,进程B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕后在继续运行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断
man 7 signal

信号的产生

  • 按键产生:ctrl+cctrl+zctrl+\
  • 系统调用:kill、raise、abort
  • 软件条件产生:定时器alarm
  • 硬件异常产生:非法访问内存(段错误)、除0、内存对齐出错(总线错误)
  • 命令产生:kill

信号的处理动作

  • 执行默认动作
  • 忽略信号(丢弃不处理)
  • 捕捉信号(调用用户的自定义处理函数)

注意
1、SIGKILL和SIGSTOP不能被捕获、阻塞及忽略;
2、信号有很强的延时性(因为需要内核发出),但对于用户来说感觉不到;
3、A用户的进程不能给B用户的进程发送信号,但A用户下的进程可以相互发送信号;
信号处理过程


信号相关函数

  • signal——注册信号捕捉函数

给内核注册,是信号产生后内核去调用的函数,捕获后就不在执行信号原有的默认动作。

typedef void (*sighandle_t)(int);
sighandle_t signal(int signum, sighandle_t handle);
param:
	signum: 信号
	handle: 信号处理函数
			也可以是SIG_DFL表示执行默认动作;或者SIG_IGN表示忽略信号
  • kill——给指定进程发送信号
命令:
kill -signum PID

函数:
int kill(pid_t pid, int sig);
param:
	pid>0: 发送信号给指定的进程
	pid=0: 发送信号给与调用kill进程属于同组的所有进程(兄弟进程)
return:
	成功:0
	失败:-1
  • raise——给当前进程发送指定信号(自己给自己发)
int raise(int sig);
  • abort——给自己发送异常终止信号SIGABRT(6),并产生core文件
void abort();

如果没有产生core文件,先查看core文件是不是设置为0了
ulimit -a
如果是0,则需要更改大小,一般改为unlimited
ulimit -c unlimited
  • alarm——设置定时器,在指定seconds后,内核会给当前进程发送SIGALRM信号

alarm使用的是自然定时法与进程状态无关,就绪、运行、挂起、终止、僵尸……,无论进程处于何种状态,alarm都计时

进程收到该信号,默认动作终止,每个进程都有且只有唯一的一个定时器
unsigned int alarm(unsigned int seconds);
return
	返回0或者剩余的秒数
alarm(0): 取消定时器,返回旧闹钟剩余的秒数

alarm(5) --->   return 0
sleep(2)
alarm(5) --->   return 5-2

例如:1S内可以打印出多少个字符

int main()
{
	int i=0;
	alarm(1);
	while(1)
	{
		printf("[%d]\n", i++);
	}
	return 0;
}
time ./a.out
real	0m1.001s	--->   实际执行时间
user	0m0.028s	--->   用户时间
sys		0m0.268s	--->   系统时间
time ./a.out
real	0m1.001s
user	0m0.028s
sys		0m0.268s
损耗时间:1.001-0.028-0.268=0.705

//利用输出重定向到文件看能打印多少个字符
time ./a.out > test.log
real	0m1.259s
user	0m0.089s
sys		0m0.883s
损耗时间:1.259-0.089-0.883=0.287

出现这种情况的原因是:调用printf函数遇到\n就会刷新缓冲区,然后打印,会从用户区切换到内核区,切换次数越多消耗时间越长;而使用文件重定向,由于文件重定向是带有缓冲的,写满缓冲之后才会写回文件中,切换的次数也减少,从而使损耗降低。

  • setitimer——设置定时器(同alarm),精度us,可以实现周期定时触发SIGALRM信号
int setitimer(int which, const struct itimerval* newValue, struct itimerval* oldValue);
return:
	成功:0
	失败:-1
param:
	which: 指定定时方式
		自然定时法——ITIMER_REAL,计算自然时间,同alarm
		虚拟空间计时(用户)——ITIMER_VIRTUAL,只计算进程占用CPU的时间
		运行时间计时(用户+内核)——ITIMER_PROF,计算占用CPU及执行系统调用的时间
	newValue: 负责设定的时间
	oldValue: 存放旧的timeout,一般传入NULL
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/time.h>

void handle(int signum)
{
	printf("signum=[%d]\n", signum);
}

int main()
{
	signal(SIGALRM, handle);
	struct itimerval tm;
	//周期性时间赋值
	tm.it_interval.tv_sec=1;
	tm.it_interval.tv_usec=0;
	//第一次触发时间赋值
	tm.it_value.tv_sec=3;
	tm.it_value.tv_usec=0;
	setitimer(ITIMER_REAL, &tm , NULL);	

	while(1)
	{
		sleep(1);
	}
	return 0;
}
//程序在3S后,每隔一秒触发一次信号处理函数

信号集

信号集:未决信号集合和阻塞(屏蔽)信号集(系统能处理的信号集合,每个bit对应一个信号标识位)
未决信号:产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态

当进程收到信号时,首先被保留在未决信号集中,此时未决信号集中对应的标识位为1,当这个信号被处理之前,先检查阻塞信号集中对应编号位上的标识位是否为1:为1表示该信号被当前进程阻塞,此时该信号暂时不被处理,对应的未决信号集仍然为1;为0表示该信号未被当前进程阻塞,则未决信号集中的信号需要被处理(忽略、执行默认动作、捕获),当信号被处理完成后,未决信号集中的标识位从1变为0,表示该信号已经递达了(已经被处理了)。如果该信号被阻塞期间又收到多个同样的信号,最终只会保留一个(因为未决信号集中只能标识0和1)

屏蔽信号只是将信号处理延后执行(延至解除屏蔽),而忽略表示将信号丢弃处理

信号集相关函数

清空信号集,将某个信号集清0
int sigemptyset(sigset_t *set);

将信号集所有位置1
int sigfillset(sigset_t *set);

将某个信号加入信号集中
int sigaddset(sigset_t *set, int signum);

将某个信号从集合中移除
int sigdelset(sigset_t *set, int signum);

查看某个信号是否在信号集中'
int sigismember(const sigset_t *set, int signum);

屏蔽、解除屏蔽函数(修改的是阻塞信号集)
只有这个函数才是修改内核变量,其余都是修改本地变量
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
param:
	mask表示当前的信号屏蔽字
	how: SIG_BLOCK: set需要屏蔽的信号mask=mask|set
		 SIG_UNBLOCK: set需要解除屏蔽的信号mask=mask&~set
		 SIG_SETMASK: set为替换原始屏蔽集的新屏蔽集
	set: 自定义的信号集
	oldset: 传出参数,保存旧的信号集

读取当前进程的未决信号集(将内核中的数据拷贝到本地)
int sigpending(sigset_t *set);

例:用键盘产生SIGINT、SIGQUIT信号后,将它们阻塞,每10次循环解除阻塞一次,并且执行信号处理函数。多次产生信号,在解除阻塞后只会处理一次

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>

void handle(int signum)
{
	printf("signum=[%d]\n", signum);
}

int main()
{
	signal(SIGINT, handle);
	signal(SIGQUIT, handle);
	//定义信号集
	sigset_t set;
	//初始化信号集
	sigemptyset(&set);
	//将SIGINT SIGKILL加入信号集中
	sigaddset(&set, SIGINT);
	sigaddset(&set, SIGQUIT);
	//将set集中的信号加入到阻塞信号集中
	sigprocmask(SIG_BLOCK, &set, NULL);
	
	sigset_t pend;
	int i=1;
	int j=0;
	while(1)
	{
		//获取未决信号集
		sigemptyset(&pend);
		sigpending(&pend);
		
		for(i=1;i<32;i++)
		{
			if(sigismember(&pend, i)==1)
			{
				printf("1");
			}
			else
			{
				printf("0");
			}
		}
		printf("\n");
		if(++j%10==0)
		{
			sigprocmask(SIG_UNBLOCK, &set, NULL);
		}
		else
		{
			sigprocmask(SIG_BLOCK, &set, NULL);
		}
		sleep(1);
	}
	return 0;
}
//键盘输入ctrl+c和ctrl+\会将这两个信号加入阻塞集中,从而不会执行信号处理函数
//每10次循环会解除一次阻塞,然后执行相应的信号处理函数
//相同信号只算一次,不会排队

sigaction

注册一个信号处理函数,同signal函数
int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact);
param:
	act: 新的处理方式
	oldact: 旧的处理方式
struct sigaction
{
	void (*sa_handler)(int);//信号处理函数
	sigset_t sa_mask;//用来指定在信号处理函数执行期间需要被屏蔽的信号,当某个信号被处理时,这个信号不会再度发生(仅在处理函数调用期间生效)
	int sa_flags;//默认为0
}

在XXX信号处理函数执行期间,若XXX信号再次产生多次,那么信号处理函数也不会被打断,当信号处理函数执行完后,后来产生的信号只会被处理一次(信号不支持排队)

例:在按Ctrl+C后,会进入信号处理函数,执行sleep(3)期间,多次执行ctrl+c也没有任何反应,等sleep结束后,会再次调用信号处理函数

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>

void handle(int signum)
{
	printf("signum=[%d]\n", signum);
	sleep(3);
}

int main()
{
	struct sigaction act;
	act.sa_handler = handle;//信号处理函数
	sigemptyset(&act.sa_mask);//阻塞的信号
	act.sa_flags=0;
	sigaction(SIGINT, &act, NULL);
	while(1)
	{
		sleep(1);
	}
	return 0;
}

在XXX信号处理函数执行期间(前提是阻塞了YYY信号),若收到YYY信号,则YYY信号会被阻塞,当XXX信号处理函数执行完毕后,YYY信号只会被处理一次。

例:在按Ctrl+C后,会进入信号处理函数,执行sleep(3)期间,多次执行ctrl+\也没有任何反应,等sleep结束后,才会调用信号处理函数处理ctrl+\信号

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>

void handle(int signum)
{
	printf("signum=[%d]\n", signum);
	sleep(3);
}

int main()
{
	struct sigaction act;
	act.sa_handler = handle;//信号处理函数
	sigemptyset(&act.sa_mask);//阻塞的信号
	sigaddset(&act.sa_mask, SIGQUIT);//在信号处理函数执行期间阻塞SIGQUIT信号
	//如果没有将SIGQUIT加入阻塞集中,那么在处理SIGINT期间按下ctrl+\就会退出程序
	
	act.sa_flags=0;
	sigaction(SIGINT, &act, NULL);
	signal(SIGQUIT, handle);
	while(1)
	{
		sleep(1);
	}
	return 0;
}

SIGCHLD信号

产生条件

  • 子进程结束
  • 子进程收到SIGSTOP信号(暂停)
  • 当子进程停止时,收到SIGCONT信号

SIGCHLD作用:子进程退出后,内核会给它的父进程发送SIGCHLD信号,父进程收到这个信号可以对子进程进行回收。

使用SIGCHLD信号完成对子进程回收的优点是:可以避免父进程阻塞等待而不能执行其他操作,只有收到SIGCHLD信号后才会对它进行处理,没收到SIGCHLD信号之前可以处理其它操作。

例:父进程产生三个子进程,子进程结束时会产生SIGCHLD信号,父进程收到信号来回收子进程,避免产生僵尸进程

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
#include<unistd.h>

void waitchild(int signum)
{
	pid_t wpid=waitpid(-1, NULL, WNOHANG);
	//WNOHANG不能换成0,因为SIGSTOP和SIGCONT也会产生SIGCHLD信号,那样会一直阻塞在这里
	
	if(wpid>0)
	{
		printf("child is quit, wpid==[%d]\n", wpid);
	}
	else if(wpid==0)
	{
		printf("child is living\n");
	}
	else if(wpid==-1)
	{
		printf("no child is alive\n");
	}
}
int main()
{
	pid_t pid;
	int i=0;
	for(i=0;i<3;i++)
	{
		pid=fork();
		if(pid>0)
		{
		}
		else
		{
			printf("child process [%d]\n", getpid());
			break;
		}
	}
	if(i==0)
	{
		printf("the first child [%d]\n", getpid());
		sleep(1);
	}
	if(i==1)
	{
		printf("the second child [%d]\n", getpid());
		sleep(2);
	}
	if(i==2)
	{
		printf("the third child [%d]\n", getpid());
		sleep(3);
	}
	if(i==3)
	{
		struct sigaction act;
		act.sa_handler = waitchild;
		sigemptyset(&act.sa_mask);
		act.sa_flags=0;
		sigaction(SIGCHLD, &act, NULL);
		while(1)
		{
			sleep(1);
		}

	}
	return 0;
}
child process [4260]
the first child [4260]
child process [4261]
child process [4262]
the third child [4262]
the second child [4261]
child is quit, wpid==[4260]
child is quit, wpid==[4261]
child is quit, wpid==[4262]
^C

例:信号处理函数延时2S,那么在延时期间再收到SIGCHLD信号后再次收到会阻塞,最后只会处理一个。因此只有两个进程回收,有一个进程成为了僵尸进程

void waitchild(int signum)
{
	pid_t wpid=waitpid(-1, NULL, WNOHANG);
	
	if(wpid>0)
	{
		printf("child is quit, wpid==[%d]\n", wpid);
	}
	else if(wpid==0)
	{
		printf("child is living\n");
	}
	else if(wpid==-1)
	{
		printf("no child is alive\n");
	}
	sleep(2);
}
int main()
{
	pid_t pid;
	int i=0;
	for(i=0;i<3;i++)
	{
		pid=fork();
		if(pid>0)
		{
		}
		else
		{
			printf("child process [%d]\n", getpid());
			break;
		}
	}
	if(i==0)
	{
		printf("the first child [%d]\n", getpid());
		//sleep(1);
	}
	if(i==1)
	{
		printf("the second child [%d]\n", getpid());
		//sleep(2);
	}
	if(i==2)
	{
		printf("the third child [%d]\n", getpid());
		//sleep(3);
	}
	if(i==3)
	{
		struct sigaction act;
		act.sa_handler = waitchild;
		sigemptyset(&act.sa_mask);
		act.sa_flags=0;
		sigaction(SIGCHLD, &act, NULL);
		while(1)
		{
			sleep(1);
		}

	}
	return 0;
}

先生一个僵尸进程

例:解决办法:采用while循环,来将信号处理函数执行期间退出的子进程回收掉

void waitchild(int signum)
{
	while(1)//因为信号不能排队,即使收到3个信号也能全回收
	{
		pid_t wpid=waitpid(-1, NULL, WNOHANG);

		if(wpid>0)
		{
			printf("child is quit, wpid==[%d]\n", wpid);
		}
		else if(wpid==0)
		{
			printf("child is living\n");
			break;
		}
		else if(wpid==-1)
		{
			printf("no child is alive\n");
			break;
		}
	}
}
int main()
{
	pid_t pid;

	//将SIGCHLD信号阻塞(父进程故意要让子进程先退出,所以要先将信号阻塞起来)
	sigset_t mask;
	sigemptyset(&mask);
	sigaddset(&mask, SIGCHLD);
	sigprocmask(SIG_BLOCK, &mask, NULL);

	int i=0;
	for(i=0;i<3;i++)
	{
		pid=fork();
		if(pid>0)
		{
		}
		else
		{
			printf("child process [%d]\n", getpid());
			break;
		}
	}
	if(i==0)
	{
		printf("the first child [%d]\n", getpid());
	}
	if(i==1)
	{
		printf("the second child [%d]\n", getpid());
	}
	if(i==2)
	{
		printf("the third child [%d]\n", getpid());
	}
	if(i==3)
	{
		struct sigaction act;
		act.sa_handler = waitchild;
		sigemptyset(&act.sa_mask);
		act.sa_flags=0;

		sleep(5);//故意先让子进程都退出,所以要在函数开始先让SIGCHLD信号阻塞

		sigaction(SIGCHLD, &act, NULL);

		//完成SIGCHLD信号注册后,解除对SIGCHLD的阻塞
		sigprocmask(SIG_UNBLOCK, &mask, NULL);

		while(1)
		{
			sleep(1);
		}

	}
	return 0;
}

进程全部回收

综上:
1、有可能还未完成信号处理函数的注册,子进程就已经退出了;
------可以在fork之前先将SIGCHLD信号阻塞,当完成信号处理函数的注册后,再解除阻塞
2、当SIGCHLD信号处理函数执行期间,SIGCHLD信号若再次产生是被阻塞的,而且即使产生多次,该信号也只会被处理一次,这样可能会产生僵尸进程
------信号处理函数使用while循环回收,循环回收已经退出的子进程,但要注意wpid\ == 0和wpid == -1加break退出循环


利用信号完成父子进程间通信

使用用户自定义的信号SIGUSR1和SIGUSR2来完成通信,父子进程在收到各自的信号后完成计数

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
#include<unistd.h>

int num=0;
int flag;

void func1(int signum)
{
	printf("F:[%d]\n", num);
	num+=2;
	flag=0;
	sleep(1);
}
void func2()
{
	printf("C:[%d]\n", num);
	num+=2;
	flag=0;
	sleep(1);
}

int main()
{
	int ret;
	pid_t pid;
	pid=fork();
	if(pid>0)
	{
		num=0;
		flag=1;
		signal(SIGUSR1, func1);
		while(1)
		{
			if(flag==0)
			{
				kill(pid, SIGUSR2);
				flag=1;
			}
		}
	}
	else
	{
		num=1;
		flag=0;
		signal(SIGUSR2, func2);
		while(1)
		{
			if(flag==0)
			{
				kill(getppid(), SIGUSR1);
				flag=1;
			}
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值