1.信号的应用举例之自己实现kill命令
1)系统调用kill与kill命令
kill也是一个命令,它底层就封装了我们的系统调用kill;
所以,man kill是1命令,man 2 kill才是系统调用;
man 2 kill得到原型:
int kill(pid_t pid,int sig);
就是向PID为pid的发送sig信号;
返回值为-1说明失败,0表示成功.
2)回顾kill命令
执行kill PID命令,这个就是系统调用,默认发送了15号信号. 比如我们sleep 500,然后打开另外一个终端kill掉它,这个kill就是默认发送了15号信号.
3)实现kill命令
自己实现kill命令,需要PID,需要信号代号.就是我们也要写一个类似kill -9 PID 的命令;
为什么需要信号代号呢?
9号信号是一个特殊的信号,它是不允许改变响应方式的.
比如暂停进程(ctrl+Z),那么kill不掉,就需要9号信号强制结束.
写一个类似kill -9 PID的命令;
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(int argc,char *argv[])
{
if(argc!=3)
{
printf("argc error\n");
return -1;
}
int pid=0;
int sig=0;
sscanf(argv[1],"%d",&pid);
sscanf(argv[2],"%d",&sig);if(kill(pid,sig)==-1)
{
perror("kill error\n");
}
exit(0);
}
a.perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和errno所对应的错误一起输出。
b.scanf是从键盘上读取数据,sscanf就是从指定位置读取数据
4)15号信号和9号信号
运行sleep 500这个进程,发现使用自己的mykill命令发送15号信号显示的是"已终止(Terminated)",发现使用自己的mykill命令发送9号信号是"已杀死(killed)",
和系统的kill命令是一样的.
那有人又说kill命令没有传递信号代号,其实是一样的,也就是mykill传递两个参数即可,把信号代号也就是argv[2]定义成15,或者9即可.
可以自己验证一下.
mycp mykill都已经自己实现了,其实其它的命令也都是可以自己实现的,只需要调用相应的系统调用即可.
2.SIGCHLD信号
(1).子进程结束,父进程会收到内核发送的SIGCHLD信号;
前面的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>int main()
{
int n=0;
char *s=NULL;
pid_t pid=fork();
if(pid==-1)
{
printf("fork err\n");
exit(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);
}
父进程没有获取退出码,是会产生僵死进程的;
在这里其实子进程结束了,已经给父进程发送了一个信号.只不过父进程忽略了;
那么在这里,我们修改一下代码,让父进程收到子进程的代码,打印一下收到的信号代号,不要忽略掉;
signal(SIGCHLD,fun); //父进程那里添加
//回调函数如下:
void fun(int sig)
{
printf("sig=%d\n",sig);
printf("child over\n");
}
由执行结果可以看出,子进程结束,确实是会给父进程发送17号信号SIGCHLD;只不过遇到默认情况,父进程不会理会而已;所以,这个17号信号的默认方式就是忽略;
再次强调一下,这个不是子进程发送的信号,是内核发送的信号;
(2)处理僵死进程
回顾前面知识点:
1)演示僵死进程
2)处理僵死进程的方法
(1).父进程先结束(孤儿进程会被收养)
(2).父进程调用wait()方法获取子进程的退出码
父进程获取子进程的退出码之后,操作系统就将这个子进程的PCB删除了,就不会产生僵死进程了.
两个方法的本质是一样的,但是方法二会阻塞,就是父进程在等子进程结束,才会获取退出码.
结合信号,如何处理,让它不再阻塞呢?
父进程调用wait是配合信号使用的.代码如下:
void fun(int sig)
{
printf("sig=%d\n",sig);
int val=0;
int id=wait(&val);
}
//注意,wait的头文件需要加一下:
#include <wait.h>
而且我们也可以简单写,就是不获取退出码,我们只要不变成僵死进程就可以;
wait(NULL);
3.补充知识点
1).发送信号的主体
也就是谁可以发送信号?内核可以进程发送信号,别的进程也可以给进程发送信号,自己也可以给自己发送信号;
大多数都是内核在发送信号;
2).ctrl+c,ctrl+z,ctrl+d的区别?
ctrl+c发送 SIGINT 信号(程序终止(interrupt)信号)给前台进程组中的所有进程。常用于终止正在运行的程序。
ctrl+z发送 SIGTSTP 信号(停止进程的运行, 但该信号可以被处理和忽略)给前台进程组中的所有进程,常用于挂起一个进程。 如果需要恢复到前台输入fg,恢复到后台输入bg
ctrl+d不是发送信号,而是表示一个特殊的二进制值,表示 EOF。EOF是一个计算机术语,为End Of File的缩写,通常在文本的最后存在此字符表示资料结束。
3)不容忽略的信号
9号信号只能按照默认方式去改变;9号信号的默认响应方式就是直接把程序kill掉;,不允许修改9号信号的响应方式;
其实19号信号也不能被忽略,它是暂停进程.
信号用的应用挺多的:
1.fork+exec产生一个子进程,结束了,然后给父进程发送一个信号;
2.kill也是发送一个信号;
3.ctrl+c也是发送一个信号;