Linux信号

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也是发送一个信号;

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值