信号:通知进程产生了某种事件
信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作。
与信号有关的文件调用在“signal.h”头文件中有声明。
信号其实就是一个软件中断。
可以通过signal()修改对信号的响应方式。
信号一般有三种响应方式:1)默认响应;2)忽略响应;3)自定义响应。
通过 kill 发送响应。
int kill(pid_t pid, int sig);
命令kill 就是对系统调用kill 的封装,命令kill就是通过系统调用kill给程序发送一个信号,进程收到信号后,自己结束自己。
一、改变信号的默认方式
信号已经提前在系统中定义好了,每个信号都有自己对应的代号。
1.1常见的信号的值:
1.2信号的值在系统源码中的定义如下:
1. #define SIGHUP 1
2. #define SIGINT 2 //键盘按下 Ctrl+c 时,会产生该信号
3. #define SIGQUIT 3
4. #define SIGILL 4
5. #define SIGTRAP 5
6. #define SIGABRT 6
7. #define SIGIOT 6
8. #define SIGBUS 7
9. #define SIGFPE 8
10. #define SIGKILL 9 //该信号的响应方式不允许改变
11. #define SIGUSR1 10
12. #define SIGSEGV 11
13. #define SIGUSR2 12
14. #define SIGPIPE 13 //读端关闭的描述符,写端写入时产生,该信号会终止程序
15. #define SIGALRM 14
16. #define SIGTERM 15 //系统 kill 命令默认发送的信号
17. #define SIGSTKFLT 16
18. #define SIGCHLD 17 //子进程结束后,会默认给父进程发送该信号
19. #define SIGCONT 18
20. #define SIGSTOP 19
21. #define SIGTSTP 20
22. #define SIGTTIN 21
23. #define SIGTTOU 22
24. #define SIGURG 23
1.3改变信号的默认响应方式
通过signal()改变
#include <signal.h>
typedef void (*sighandler_t)(int);//sighandler_t 是函数指针类型
sighandler_t signal(int signum, sighandler_t handler);//int signum 信号代号;sighandler_t handler 函数指针,指向参数为int,返回值为void的函数
需要传入一个信号代号以及响应方式
eg:#define SIGINT 2 //键盘按下 Ctrl+c 时,会产生该信号
实现改变其信号,使其不中断程序,只打印信号的值
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
1.3.1自定义响应方式
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void fun_a(int sig)
{
printf("sig =%d\n",sig);//打印信号代号
}
int main()
{
signal(2,fun_a);//signal(SIGINT,fun_a);改变信号代号2的响应方式,使其接收到信号2时,调用fun_a函数作出响应,此处不是由我们调用函数,是程序接收到信号后,内核调用fun_a(SIGINT)
while(1)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
进程接收到信号之后,会短暂的暂停,处理了信号之后又会进行执行自己的
1.3.2重新变为默认响应方式
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void fun_a(int sig)
{
printf("sig =%d\n",sig);
signal(sig,SIG_DFL);//重新约定响应方式,按默认的响应方式,后期按最新的信号响应方式处理
}
int main()
{
signal(2,fun_a);
while(1)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
1.3.3忽略响应方式
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
int main()
{
signal(2,SIG_IGN);//忽略响应方式
while(1)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
二、不允许改变响应方式的信号 SIGKILL 9
9号信号不允许被改变被忽略
命令中的 kill -9 pid 就是传的此信号
三、实现自己的kill
kill 可以给进程发送信号,其实就是系统调用的一个封装。
int kill(pid_t pid, int sig);//进程id,信号代码
sscanf(),将输入的数据转化为整型
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
固定调用2号信号,结束进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int main(int argc,int *argv[])
{
if(argc !=2)//第一个参数函数名称./mykill,第二个参数进程名
{
printf("argc erro\n");
exit(1);
}
int pid = 0;
sscanf(argv[1],"%d",&pid);
if(kill(pid,SIGINT)==-1)//固定调用2号信号
{
printf("kill err\n");
}
exit(0);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int main(int argc,int *argv[])
{
if(argc !=3)//第一个参数函数名称./mykill,第二个参数进程名,第三个参数
{
printf("argc erro\n");
exit(1);
}
int pid = 0;
int sig =0;
sscanf(argv[1],"%d",&pid);
sig = atoi(argv[2]);
if( kill(pid,sig) == -1 )//固定调用2号信号
{
printf("kill err\n");
}
exit(0);
}
kill 默认发送15号信号
三、#define SIGCHLD 17
#define SIGCHLD 17 //子进程结束后,会默认给父进程发送该信号
子进程结束后,父进程会受到此信号量,父进程对此信号量默认响应是忽略。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>//常用的库函数,包含系统调用
#include<signal.h>
void fun(int sig)
{
printf("sig =%d\n",sig);
}
int main()
{
signal(SIGCHLD,fun);//约定接收到子进程结束的信号调用函数fun
int n=0;
char *s = NULL;
pid_t id = fork();
if(id == -1)
{
exit(1);//0代表成功,1,2,3,,都是失败
}
if(id == 0)//在子进程中执行
{
n=3;
s = "child";
}
else
{
n = 7;
s = "parent";
}
for(int i = 0;i<n;i++)//父子进程都可以执行
{
printf("s =%s\n",s);
sleep(1);
}
exit(0);
}
信号是软件层次的中断,进程接收到信号后,产生中断,处理信号后继续运行。
子进程结束后,父进程还未获取它的退出码,子进程就会变成僵死进程,
3.1如何利用信号处理僵死进程:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>//常用的库函数,包含系统调用
#include<signal.h>
#include<sys/wait.h>
int main()
{
int n=0;
char *s = NULL;
pid_t id = fork();
if(id == -1)
{
exit(1);//0代表成功,1,2,3,,都是失败
}
if(id == 0)//在子进程中执行
{
n=3;
s = "child";
}
else
{
n = 7;
s = "parent";
}
for(int i = 0;i<n;i++)//父子进程都可以执行
{
printf("s =%s\n",s);
sleep(1);
}
exit(0);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>//常用的库函数,包含系统调用
#include<signal.h>
#include<sys/wait.h>
void fun(int sig)
{
printf("sig =%d\n",sig);
int val =0;
int id =wait(&val);//获取退出码
printf("id =%d,exit code:%d\n",id,WEXITSTATUS(val));//打印子进程id以及退出码
}
int main()
{
signal(SIGCHLD,fun);
int n=0;
char *s = NULL;
pid_t id = fork();
if(id == -1)
{
exit(1);//0代表成功,1,2,3,,都是失败
}
if(id == 0)//在子进程中执行
{
n=3;
s = "child";
}
else
{
n = 7;
s = "parent";
}
for(int i = 0;i<n;i++)//父子进程都可以执行
{
printf("s =%s\n",s);
sleep(1);
}
exit(0);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>//常用的库函数,包含系统调用
#include<signal.h>
#include<sys/wait.h>
void fun(int sig)
{
printf("sig =%d\n",sig);
wait(NULL);
//也可以直接用wait处理了僵死进程,父进程收到子进程结束的信号短暂的中断,调用wait,因为子进程已经结束,wait可以直接获取子进程的退出码,父进程接收后就又去执行自己的进程了,整个过程父进程不会阻塞住
//int val =0;
//int id =wait(&val);//获取退出码
//printf("id =%d,exit code:%d\n",id,WEXITSTATUS(val));//打印子进程id以及退出码
}
int main()
{
signal(SIGCHLD,fun);
int n=0;
char *s = NULL;
pid_t id = fork();
if(id == -1)
{
exit(1);//0代表成功,1,2,3,,都是失败
}
if(id == 0)//在子进程中执行
{
n=3;
s = "child";
}
else
{
n = 7;
s = "parent";
}
for(int i = 0;i<n;i++)//父子进程都可以执行
{
printf("s =%s\n",s);
sleep(1);
}
exit(0);
}