信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

内核可以发送多少种信号呢,使用命令kill -l,就可以查看

我们之前终止进程用的就是第9个

信号通信的框架

        信号的发送(发送信号进程):kill、raise、alarm

        信号的接收(接收信号进程) : pause()、 sleep、 while(1)

        信号的处理(接收信号进程) :signal

1.信号的发送(发送信号进程)

kill:

所需头文件:

#include<signal.h> 
#include<sys/types.h> 
函数原型:int kill(pid_t pid, int sig); 
参数: 
    函数传入值:pid 
                正数:要接收信号的进程的进程号 
                0:信号被发送到所有和pid进程在同一个进程组的进程 
                ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外) 
    sig:信号 
函数返回值:成功  0  出错  ‐1  

首先我们先写一个死循环进程

#include<stdio.h>

int main()
{
    while(1);

    return 0;
}

然后我们再写kill函数

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

int main(int argc,char*argv[])
{
	int sig,pid;
	if(argc < 3)
	{
		printf("input error\n");
		return -1;
	}
	sig = atoi(argv[1]);
	pid = atoi(argv[2]);
	printf("sig = %d,pid = %d",sig,pid);
	kill(pid,sig);

	return 0;
}

这个函数等同于终端命令kill -9 <pid>

raise: 发信号给自己 == kill(getpid(), sig)

所需头文件: 
#include<signal.h> 
#include<sys/types.h> 
函数原型: 
int  raise(int sig); 
参数: 
        函数传入值:sig:信号 
函数返回值: 
        成功  0  出错  ‐1
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
	pid_t pid;
	pid=fork();
	if(pid>0)
	{
		sleep(8);
		while(1);
	}
	if(pid == 0)
	{
		printf("before sig\n");
		raise(SIGTSTP);
		printf("after sig\n");
	}
	return 0;
}

父进程在sleep,子进程暂停,因为自己给自己发送了暂停的信号

8秒后

父进程runing,因为到了死循环,子进程还是暂停

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

int main()
{
	pid_t pid;
	pid=fork();
	if(pid>0)
	{
		sleep(8);
		if(waitpid(pid,NULL,WNOHANG) == 0)
		{
			kill(pid,9);
		}
		while(1);
	}
	if(pid == 0)
	{
		printf("before sig\n");
		raise(SIGTSTP);
		printf("after sig\n");
	}
	return 0;
}

父进程还是sleeping,子进程暂停了,8秒后

父进程进入了while(1),子进程被杀死了,但是因为父进程没回收,所以子进程是僵尸进程

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

int main()
{
	pid_t pid;
	pid=fork();
	if(pid>0)
	{
		sleep(8);
		if(waitpid(pid,NULL,WNOHANG) == 0)
		{
			kill(pid,9);
		}
		wait(NULL);
		while(1);
	}
	if(pid == 0)
	{
		printf("before sig\n");
		raise(SIGTSTP);
		printf("after sig\n");
	}
	return 0;
}

父进程sleeping,子进程暂停,8秒后

子进程被回收了,只有父进程了

alarm : 发送闹钟信号的函数

alarm 与 raise 函数的比较:

相同点:让内核发送信号给当前进程

不同点:

alarm 只会发送SIGALARM信号

alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号

所需头文件#include  <unistd.h> 
函数原型 unsigned int  alarm(unsigned int seconds) 
参数: 
    seconds:指定秒数 
返回值: 
    成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
    出错:‐1 
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	int i = 0;
	printf("before alarm\n");
	alarm(7);
	printf("after alarm\n");
	while(i<20)
	{
		i++;
		sleep(1);
		printf("process %d\n",i);
	}
	return 0;
}

信号名 含义 默认操作
SIGHUP 该信号在用户终端连接 (正常或非正常) 结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。 终止
SIGINT 该信号在用户键入 INTR 字符 (通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。 终止
SIGQUIT 该信号和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl-) 来控制。 终止
SIGILL 该信号在一个进程企图执行一条非法指令时 (可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时) 发出。 终止
SIGFPE 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为 0 等其它所有的算术的错误。 终止
SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。 终止
SIGALRM 该信号当一个定时器到时的时候发出。 终止
SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。 暂停进程
SIGTSTP 该信号用于暂停交互进程,用户可键入 SUSP 字符 (通常是 Ctrl-Z) 发出这个信号。 暂停进程
SIGCHLD 子进程改变状态时,父进程会收到这个信号 忽略
SIGABRT 该信号用于结束进程 终止

2.信号的接收

接收信号的进程,要有什么条件:要想使接收的进程能收到信号,这个进程不能结束 :

sleep

pause:进程状态为S

函数原型 int  pause(void); 
 
函数返回值  成功:0,出错:‐1 
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	int i = 0;
	printf("before alarm\n");
	pause();
	printf("after alarm\n");
	while(i<20)
	{
		i++;
		sleep(1);
		printf("process %d\n",i);
	}
	return 0;
}

  • 进程调用 pause () → 立刻进入休眠状态
  • 不占 CPU,不执行任何代码
  • 一直卡在这里,死等
  • 只有收到信号(比如 Ctrl+C、SIGALRM、kill)才会继续往下走

3、信号的处理

收到信号的进程,应该怎样处理? 处理的方式:

        1.进程的默认处理方式(内核为用户进程设置的默认处理方式)

A:忽略B:终止进程C: 暂停

         2.自己的处理方式:

自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式。

所需头文件  #include  <signal.h>   
函数原型  void (*signal(int signum, void (*handler)(int)))(int);   
函数传入值   
           signum:指定信号    
           handler  
                   SIG_IGN:忽略该信号。 
                   SIG_DFL:采用系统默认方式处理信号 
                   自定义的信号处理函数指针   
函数返回值   
           成功:设置之前的信号处理方式     
           出错:‐1 
 
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;这个函数的返回值是一个函数指针。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <signal.h>

void myfun(int signum)
{
	int i = 0;
	while(i<10)
	{
		printf("process signum = %d i = %d\n",signum,i);
		sleep(1);
		i++;
	}
}
int main()
{
	int i = 0;
	signal(14,myfun);
	alarm(7);
	printf("before alarm\n");
	printf("after alarm\n");
	while(i<20)
	{
		i++;
		sleep(1);
		printf("process %d\n",i);
	}
	return 0;
}

signal(14,myfun)d的意思:

告诉系统:如果收到 14 号信号,不要终止程序,而是去执行 myfun 函数!

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

void myfun(int signum)
{
	int i = 0;
	while(i<10)
	{
		printf("process signum = %d i = %d\n",signum,i);
		sleep(1);
		i++;
	}
}
int main()
{
	int i = 0;
	signal(14,myfun);
	alarm(7);
	printf("before alarm\n");
	printf("after alarm\n");
    signal(14,SIG_IGN);
	while(i<20)
	{
		i++;
		sleep(1);
		printf("process %d\n",i);
	}
	return 0;
}

signal(14,SIG_IGN)的意思:

收到14号信号直接忽略,不处理也不中断

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

void myfun(int signum)
{
	int i = 0;
	while(i<10)
	{
		printf("process signum = %d i = %d\n",signum,i);
		sleep(1);
		i++;
	}
}
int main()
{
	int i = 0;
	signal(14,myfun);
	alarm(7);
	printf("before alarm\n");
	printf("after alarm\n");
    signal(14,SIG_IGN);
    signal(14,SIG_DFL);
	while(i<20)
	{
		i++;
		sleep(1);
		printf("process %d\n",i);
	}
	return 0;
}

这段代码总共有3个signal,但是最终会执行最后一个,signal(14,SIG_DFL)的意思是:

收到14号信号,按系统默认动作执行

14 号信号(SIGALRM)的默认动作

终止进程!直接杀死程序!

也就是说:

时间一到(7 秒)内核发 14 号信号程序收到按默认动作 → 直接退出程序!

4、信号父子进程间通信

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

void myfun(int signum)
{
	int i = 0;
	while(i < 5)
	{
		printf("receive signum is %d,i=%d\n",signum,i);
		sleep(1);
		i++;
	}
}

int main()
{
	pid_t pid;
	pid = fork();
	if(pid>0)
	{
		int i = 0;
		signal(10,myfun);
		while(1)
		{
			printf("process i=%d\n",i);
			sleep(1);
			i++;
		}
	}
	if(pid == 0)
	{
		sleep(3);
		kill(getppid(),10);
		sleep(6);
	}
	return 0;
}

子进程执行3秒后发送10号信号,父进程收到10号信号,开始执行myfun,myfun执行完继续执行父进程,此时子进程执行完了,父进程死循环继续执行,就导致子进程成为了僵尸进程

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

void myfun(int signum)
{
	int i = 0;
	while(i < 5)
	{
		printf("receive signum is %d,i=%d\n",signum,i);
		sleep(1);
		i++;
	}
}
void myfun1(int signum)
{
	printf("child signal\n");
	wait(NULL);
}
int main()
{
	pid_t pid;
	pid = fork();
	if(pid>0)
	{
		int i = 0;
		signal(10,myfun);
		signal(17,myfun1);
		while(1)
		{
			printf("process i=%d\n",i);
			sleep(1);
			i++;
		}
	}
	if(pid == 0)
	{
		sleep(3);
		kill(getppid(),10);
		sleep(6);
		exit(0);//kill(getppid(),17)
	}
	return 0;
}

子进程退出发送了一个17号信号,父进程接收到17号信号,开始执行myfun1,也就回收了子进程

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐