进程间的通信-信号

 一、信号

1.信号的定义

信号是在软件层面上是一种通知机制,对中断机制的一种模拟,是一种异步通信方式。
注意:
进程在运行过程中,随时可能被各种信号打断。
进程可以忽略或者去调用相应的函数去处理信号。

进程无法预测信号到达的精准时间。

2.信号的来源

1.程序执行错误,如内存访问越界,数学运算除 0
2.由其他进程发送
3.通过控制终端发送,如 ctrl + c
4.子进程结束时向父进程发送的 SIGCHLD 信号
5.程序中设定的定时器产生的 SIGALRM 信号

3 .信号的种类

Linux系统可以通过 kill -l 命令查看.

4.信号处理流程

信号处理流程包含以下两个方面:
信号的发送 :可以由进程直接发送
信号投递与处理 : 由内核进行投递给具体的进程并处理
Linux 中对信号的处理方式:
忽略信号 ,即对信号不做任何处理,但是有两个信号不能忽略:即 SIGKILL SIGSTOP
捕捉信号 ,定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作, Linux 对每种信号都规定了默认操作
--- 在内核中的用于管理进程的结构为 task_struct 

二、信号发送

当由进程来发送信号时,则可以调用 kill() 函数与 raise () 函数。

1.kill 函数

函数原型
int kill(pid_t pid, int sig);
函数功能
向指定的进程发送一个信号
函数参数
pid: 进程的 id
sig: 信号的 id

2.raise 函数

函数原型
int raise(int sig);
函数功能
send a signal to the caller
函数参数
sig: 信号编号

3.补充

具体函数内容使用Man()手册页

实例:创建一个子进程,子进程通过信号暂停,父进程向子进程发送终止信号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if(pid==0)
    {
    // 执行子进程
    fprintf(stdout,"child <%d> is running.\n",getpid());
    // SIGSTOP 暂停一个进程,不能阻塞、处理或忽略,只能采用默认处理
    raise(SIGSTOP);
    fprintf(stdout,"child <%d> is exit.\n",getpid());
    // 成功退出子进程
    exit(EXIT_SUCCESS);
    }
    else
    {
        int ret;
        sleep(1);
        ret = kill(pid,SIGKILL);//给指定的进程发送kill信号,只能采用默认操作,结束进程
        if(ret == 0)
        {
            fprintf(stdout,"parent<%d> killchild<%d>.\n",getpid(),pid);
        }
        waitpid(pid,NULL,0);
        fprintf(stdout,"father<%d> exit.\n",getpid());
        exit(EXIT_SUCCESS);
        }
    return 0;
}

三、等待信号

在进程没有结束时,进程在任何时间点都可以接受到信号。
需要阻塞等待信号时,则可以调用 pause() 函数。(注意: pause 函数一定要在收到信号之前调用,让进程进入到睡眠状态 )
函数原型
int pause ( void );
函数功能
阻塞进程,直到收到信号后唤醒
函数返回值
成功 : 返回 0
失败 : 返回 - 1 , 并设置 errno

四、信号处理

信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理

处理信号有三种方式:
忽略 : 不进行处理
默认 : 按照信号的默认方式处理
用户自定义 : 通过用户实现自定义处理函数来处理,由内核来进行调用
每种信号都有相应的默认处理方式;

五、用户自定义处理

1. 实现自定义处理函数

用户实现自定义处理函数,需要按照下面的格式
typedef void (*sighandler_t)(int);
typedef void (*)(int) sighandler_t

2. 设置信号处理处理方式

通过 signal 函数设置信号处理方式
函数头文件
#include <signal.h>
函数原型
sighandler_t signal(int signum, sighandler_t handler);
函数功能
设置信号的处理方式 , 如果是自定义处理方式,提供函数地址,注册到内核中
函数参数
signum: 信号编号
handler: 信号处理方式
SIG_IGN----> 忽略信号
SIG_DFL----> 按照默认方式处理
自定义处理函数的地址
通过 cstag SIG_DFL 查看

五、、定时器信号

Linux 系统中提供了 alarm 函数,用于设置定时器
函数头文件
#include <unistd.h>
函数原型
unsigned int alarm(unsigned int seconds);
函数功能
设置定时器的秒数
函数参数
seconds: 定时的时间秒数
函数返回值
返回上一次进程设置定时器剩余的秒数 , 如果进程上一次没有设置定时器,则返回 0
Tips:
定时器的定时任务由内核完成的, alarm 函数负责设置定时时间 , 并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号。

实例:

验证 alarm 函数的返回值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
unsigned int ret;
ret = alarm(3);
printf("ret = %d\n",ret); // 进程上一次没有设置定时器,ret = 0
sleep(1);
ret = alarm(4); // ret = 2 上一次进程设置的定时器还剩下2s
printf("ret = %d\n",ret);
printf("main process end....\n");
return 0;
}
代码 2 :设置定时器的定时时间为 3s , 并处理 SIGALRM 信号
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void sigalrm_handler(int signum)
{
    printf("sigalrm:%s\n",strsignal(signum));
}
int main()
{
    unsigned int ret;
    // 注册信号处理函数
    if(signal(SIGALRM,sigalrm_handler) == SIG_ERR)
    {
        perror("signal:");
        exit(EXIT_FAILURE);
    }
    ret = alarm(3);
    pause();
    printf("main process end....\n");
    return 0;
}

六、子进程退出信号

在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源。并且在阻塞情况下,父进程不能执行其他逻辑。
如何解决?
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号
代码1:使用信号处理僵死态进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void sigchld_handler(int signum)
{
    printf("SIGCHLD handler:<%s>\n",strsignal(signum));
    int status=0;
    wait(&status);
}
int main()
{
    pid_t pid;
    if(signal(SIGCHLD,sigchld_handler)==SIG_ERR)
    {
        perror("signal failed.");
        exit(EXIT_FAILURE);
    }
    pid = fork();
    if(pid == -1)
    {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if(pid == 0)
    {
        printf("child process<%d> is running.....\n",getpid());
        sleep(2);
        exit(EXIT_SUCCESS);
    }
    else
    {
    while(1)
    {
    }
}
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值