Linux 信号的使用

信号的使用

1.信号的基本概念

信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作。与信号有关的系统调用在“signal.h”头文件中有声明。

1.1图示信号发送和处理

在这里插入图片描述

1.2常见的信号

在这里插入图片描述

1.3信号的值在系统源码中的定义

  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

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 信号的响应方式

  1. 写一个代码,在终端屏幕上一直打印"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默认的话就是无作为。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WYSCODER

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值