文章目录
信号的使用
1.信号的基本概念
信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作。与信号有关的系统调用在“signal.h”头文件中有声明。
1.1图示信号发送和处理
1.2常见的信号
1.3信号的值在系统源码中的定义
- #define SIGHUP 1
- #define SIGINT 2 //键盘按下 Ctrl+c 时,会产生该信号
- #define SIGQUIT 3
- #define SIGILL 4
- #define SIGTRAP 5
- #define SIGABRT 6
- #define SIGIOT 6
- #define SIGBUS 7
- #define SIGFPE 8
- #define SIGKILL 9 //该信号的响应方式不允许改变
- #define SIGUSR1 10
- #define SIGSEGV 11
- #define SIGUSR2 12
- #define SIGPIPE 13 //读端关闭的描述符,写端写入时产生,该信号会终止程序
- #define SIGALRM 14
- #define SIGTERM 15 //系统 kill 命令默认发送的信号
- #define SIGSTKFLT 16
- #define SIGCHLD 17 //子进程结束后,会默认给父进程发送该信号
- #define SIGCONT 18
- #define SIGSTOP 19
- #define SIGTSTP 20
- #define SIGTTIN 21
- #define SIGTTOU 22
- #define SIGURG 23
2.修改信号的响应方式signal()
2.1signal()
sighandler_t signal(int signum, sighandler_t handler);
signum 代表信号的值
handler代表信号的响应方式
2.2信号的三种响应方式
默认:SIG_DFL
忽略:SIG_IGN
自定义:自己写的信号处理函数
2.3在键盘上按下 Ctrl+c 时,会给当前终端前台执行的进程发送 SIGINT 信号,用 signal() 修改 SIGINT 信号的响应方式
- 写一个代码,在终端屏幕上一直打印”hello Linux”,当在键盘上按下Ctrl + c时观察(Ctrl + c对应的信号是SIGINT,信号代号为2)
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("hello Linux\n");
sleep(1);
}
exit(0);
}
运行并在键盘上按Ctrl + c
程序终止
2.用signal()改变信号的响应方式,调用自己写的信号处理函数
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void sig_fun(int sig)
{
printf("sig = %d\n",sig);
}
int main()
{
signal(SIGINT,sig_fun);
while(1)
{
printf("hello Linux\n");
sleep(1);
}
exit(0);
}
运行并在键盘上按Ctrl + c
当按下Ctrl+c时,会给我们的进程发送一个SIGINT信号,signal(SIGINT,sin_fun)收到这个信号后,把信号的值传给sin_fun()函数并调用该函数
(我们发现Ctrl+c不能结束进程,此时可以用Ctrl+\退出进程)
3.上述代码,我们按下Ctrl+c打印了信号的代号,那么要是当第一次按下Ctrl+c会结束进程,该如何改进呢?
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void sig_fun(int sig)
{
printf("sig = %d\n",sig);
signal(sig,SIG_DFL);
}
int main()
{
signal(SIGINT,sig_fun);
while(1)
{
printf("hello Linux\n");
sleep(1);
}
exit(0);
}
运行并在键盘上连续两次按Ctrl + c
当第一次按下Ctrl+c时,打印SININT的代号,第二次按下Ctrl+c时,进程结束,这是因为在我们的sig_fun()函数中置了signal()函数
3. 发送信号kill()
3.1 kill()
kill() 可以向指定的进程发送指定的信号
int kill(pid_t pid,int sig);
pid > 0 指定将信号发送个那个进程
pid == 0 信号被发送到和当前进程在同一个进程组的进程
pid == -1 将信号发送给系统上有权限发送的所有的进程
pid < -1 将信号发送给进程组 id 等于 pid 绝对值,并且有权限发送的所有的进程
sig 指定发送信号的类型
3.2使用 kill()系统调用实现类似于系统 kill 命令(只传pid)
前面我们完成了main程序,第一次按下Ctrl+c的时候会打印信号的代号,第二次按下Ctrl+c的时候会结束进程,我们通过编写mykill.c来给main进程发送SIGINT信号(相当于键盘输入Ctrl+c),因为通过kill()函数发送信号的时候参数需要进程号,所以打印hello Linux的同时也打印一下进程号,即把main.c中代码printf(“hello Linux\n”);改为printf(“hello Linux pid = %d\n”,getpid());。
mykill.c代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int main(int argc,char* argv)
{
//只传入进程号,因为第一个参数是./mytest 所以一共有两个参数
if(argc != 2)
{
printf("mykill argc error\n");
exit(0);
}
int pid = 0;
sscanf("argv[1]","%d",&pid);
if(kill(pid,SIGINT) == -1)
{
printf("kill error\n");
exit(0);
}
exit(0);
}
一个终端运行main程序,一个终端运行,mykill程序,执行mykill程序的时候输入main程序的进程号,即通过kill()给main程序发送SIGINT信号。
执行两个程序如下:
通过执行结果我们发现,执行两次mykill和在键盘按下两次Ctrl+c效果一样,第一次打印信号的代号,第二次结束进程。
3.3使用 kill()系统调用实现类似于系统 kill 命令(传pid和信号)
因为要传入进程号和信号,所以main程序的参数个数变为3
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
int main(int argc,char* argv[])
{
if(argc != 3)
{
printf("mykill argc error\n");
exit(0);
}
int pid = 0;
int sig = 0;
sscanf(argv[1],"%d",&pid);
sscanf(argv[2],"%d",&sig);
if(kill(pid,sig) == -1)
{
printf("kill error\n");
exit(0);
}
exit(0);
}
4.处理僵尸进程
编写test.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<assert.h>
#include<string.h>
int main()
{
int n = 0;
char* s = NULL;
pid_t pid = fork();
assert( pid != -1 );
if( pid == 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);
}
在test.c中因为子进程先于父进程结束,父进程没有得到子进程的退出码,所以会产生僵尸进程,僵尸进程在僵尸进程博客中有详细介绍。子进程结束的时候,会给父进程发送一个信号:SIGCHLD
让父进程接受这个信号,并且打印SIGCHLD的代号,修改test.c如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<assert.h>
#include<string.h>
void sig_fun(int sig)
{
printf("sig = %d\n",sig);
}
int main()
{
signal(SIGCHLD,sig_fun);
int n = 0;
char* s = NULL;
pid_t pid = fork();
assert( pid != -1 );
if( pid == 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);
}
编译test.c生成可执行文件test并运行test
分析运行结果,当子进程结束的时候,是给父进程发送了一个SIGCHLD,父进程做出响应和处理。
放到后台执行,发现产生僵尸进程
因此,我们让父进程接受到SIGCHLD信号后,调用一下wait(),因为不关心子进程的退出码,只是避免产生僵尸进程,所以将wait()参数置为NULL;修改test.c中的sig_fun函数如下:
void sig_fun(int sig)
{
printf("sig = %d\n",sig);
wait(NULL);
}
重新编译运行:
通过运行结果我们发现:避免了子进程变成了僵尸进程,并且父进程并没有产生阻塞!父子进程可以并发执行
当父进程把子进程结束时发送的SIGCHLD信号忽略时,也可以避免产生僵尸进程,即, signal(SIGCHLD,SIG_IGN);因为父进程不关注子进程,子进程结束后,进程消失,释放pcb。注:SIGDFL默认的话就是无作为。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/95566.html