进程间通讯-信号

目录

​一、信号基本概念

1.1信号定义

1.2信号的来源

1.3信号种类

1.4信号处理流程

二、信号发送

2.1kill函数

2.2raise函数

三、信号等待

四、信号处理

1、基本概念

2、用户自定义处理

五,介绍两种较为常用的信号

​定时器信号

子进程退出信号


​一、信号基本概念

1.1信号定义

信号是一种软件中断,用于告知一个进程发生了某个事件。信号可以由操作系统、由其他进程,或者由进程本身发出。是一种异步通信方式,一般具有如下特点:

(1)进程在运行过程中,随时可能被各种信号打断

(2)进程可以忽略,或者去调用相应的函数去处理信号

(3)进程无法预测到达的精准时间

再补充说明下是异步通信方式。异步通信是一种计算机通信方式,其中数据的发送和接收不需要同时发生。在异步通信模式中,发送方和接收方不需要同步工作;发送方可以在任何时候发送数据,而接收方则在数据到达时处理数据。这种方式与同步通信不同,在同步通信中,发送方和接收方必须同时参与数据交换过程。打个比方,上学时老师说今天要给家长打电话做家访,我们不知道老师会打来电话,家访时间以电话铃声为准。

1.2信号的来源

linux中信号来源如下:

程序执行错误,如内存访问越界,数学运算除0

由其他进程发送

通过控制终端发送 如ctrl + c

等......

1.3信号种类

在linux中可以用kill -l查看系统中设置的所有信号,常用的信号列举如下:

SIGINT:该信号在用户键入INTR字符(通常是Ctrl+C)时发出,终端驱动程序发送此信号并送到前台进>程中的每一个进程

d 是一个用于Unix和类Unix操作系统的信号,用于暂停一个进程的执行。当进程接收到 SIGSTOP 信号时,它会立即停止执行,并且进程的状态变为停止(stopped)状态。SIGSTOP 信号不能被捕获、阻塞或忽略,这意味着任何进程都不能忽视这个信号,并且总是被迫停止执

等......

1.4信号处理流程

信号处理流程:发送、投递、处理,其中进程只需要执行发送操作,而投递和处理都是在内核中完成的

Linux中对信号的常见处理方式有以下几种:

(1)忽略信号

(2)默认处理方式

(3)自定义处理方式

二、信号发送

进程中发送信号可以调用kill()函数与raise()函数

2.1kill函数

定义:kill函数是一个系统调用,用于允许一个进程向另一个进程(或一组进程)发送信号。它提供了一种进程间通信(IPC)的方式。

函数头文件

#include

#include

函数原型

int kill(pid_t pid, int sig);

函数功能

向指定的进程发送一个信号

函数参数

pid : 进程的 id

sig : 信号的 id

函数返回值

成功: 返回 0

失败: 返回 -1,并设置 errno

2.2raise函数

定义raise函数是一个标准C库函数,用于允许进程向自己发送信号。它是自我通信的一种形式,允许进程触发自己的信号处理程序。

函数头文件

#include

#include

函数原型

int raise(int sig);

函数参数

sig : 信号编号

函数返回值

成功 : 返回 0

失败 : 返回 -1,并设置 errno

示例 :

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

#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    pid_t cpid;
    cpid = fork();  //创建子进程
    if (cpid < 0) {
        perror("[ERROR] fork():\n");
        exit(EXIT_FAILURE);
    }
    else if(cpid == 0){
        //进入子进程
        fprintf(stdout, "child %d is running.\n", getpid());
        //SIGSTOP:暂停一个进程,不能被忽略,阻塞,只能执行默认操作
        raise(SIGSTOP);
        fprintf(stdout, "child %d exit.\n", getpid());
        exit(EXIT_SUCCESS);
    }
    else {
        //进入父进程
        fprintf(stdout, "father %d is running.\n", getpid());
        sleep(1);
        int ret;
        ret = kill(cpid, SIGKILL);
        if (ret == 0) {
            fprintf(stdout, "father %d kill child %d.\n", getpid(), cpid);
        }
        waitpid(cpid, NULL, 0);
        fprintf(stdout, "father %d exit.\n", getpid());
        exit(EXIT_SUCCESS);
    }
    return 0;
}

三、信号等待

在进程没有结束时,进程在任何时间点都可以接受到信号

需要阻塞等待信号时,则可以调用 pause() 函数,具体如下

函数头文件

#include

函数原型

int pause(void);

函数功能

阻塞进程,直到收到信号后唤醒

函数返回值

成功 : 返回 0

失败 : 返回 -1,并设置 errno

示例

创建创建一个子进程,父进程调用 pause 函数,子进程给父进程发送信号

#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    pid_t cpid;
    cpid = fork();
    if (cpid < 0) {
        perror("[ERROR] fork():\n");
        exit(EXIT_FAILURE);
    }
    else if(cpid == 0){
        //进入子进程
        fprintf(stdout, "child %d is running.\n", getpid());
        sleep(3);
        int ret;
        ret = kill(getppid(), SIGKILL);
        if (ret == 0) {
            fprintf(stdout, "child %d kill father %d.\n", getpid(), getppid());
        }
        fprintf(stdout, "child %d exit.\n", getpid());
        exit(EXIT_SUCCESS);
    }
    else {
        //进入父进程
        fprintf(stdout, "father %d is running.\n", getpid());
        sleep(1);
        pause();
        waitpid(cpid, NULL, 0);
        fprintf(stdout, "father %d exit.\n", getpid());
        exit(EXIT_SUCCESS);
    }
    return 0;
}

四、信号处理

1、基本概念

上文提到信号是由内核发送给指定的程序,进程收到信号后则需要进行处理

处理信号的三种方式反别为:默认、忽略、自定义处理方式

我觉得每种信号的及默认处理方式在使用时查阅使用,这里只列举些常用信号的默认处理方式

进程退出:SIGALRM,SIGINT,SIGKILL

进程忽略:SIGCHLD

进程暂停:SIGSTOP

2、用户自定义处理

(1)实现自定义处理函数

定义形式:void 函数名(int)

(2)将处理函数在内核中进行注册,通过signal函数

函数头文件 #include

函数原型 sighandler_t signal(int signum, sighandler_t handler);

函数参数 signum : 信号编号

handler : 信号处理方式

SIG_IGN : 忽略信号

SIG_DFL : 按照默认方式处理

自定义处理函数的地址

SIG_IGN : 忽略信号

SIG_DFL : 按照默认方式处理

自定义处理函数的地址

函数返回值

成功 : 返回信号处理函数地址

失败 : 返回 SIG_ERR ,并设置 errno

示例

创建一个子进程,父进程给子进程发送SIGUSR1信号,并使用自定义的方式处理

#include 
#include 
#include 
#include 
#include 
#include 
#include 

void signal_usr1(int sig) {
    printf("Receive %s \n", strsignal(sig));
}

int main(void)
{
    __sighandler_t handle_flag = signal(SIGUSR1, signal_usr1);
    if (handle_flag == SIG_ERR){
        perror("[ERROR] signal(): \n");
        exit(EXIT_FAILURE);
    }
    pid_t cpid;
    cpid = fork();
    if (cpid < 0) {
        perror("[ERROR] fork():\n");
        exit(EXIT_FAILURE);
    }
    else if(cpid == 0){
        //进入子进程
        fprintf(stdout, "child %d is running.\n", getpid());
        pause();
        exit(EXIT_SUCCESS);
    }
    else {
        //进入父进程
        fprintf(stdout, "father %d is running.\n", getpid());
        sleep(3);
        kill(cpid, SIGUSR1);
        wait( NULL);
        fprintf(stdout, "father %d exit.\n", getpid());
        exit(EXIT_SUCCESS);
    }
    return 0;
}

五,介绍两种较为常用的信号

​定时器信号

在 Linux 系统中提供了 alarm 函数,用于设置定时器,具体信息如下

函数头文件

 #include

函数原型 

unsigned int alarm(unsigned int seconds);

函数功能 设置定时器的秒数

函数参数 

seconds : 定时的时间秒数

函数返回值 

返回上一次进程设置定时器剩余的秒数

要点:

定时器的定时任务由内核完成,当定时时间到达后,内核会向进程发出SIGALARM信号

示例一

验证 alram 函数的返回值

#include 
#include 
#include 

int main(void)
{
    unsigned int ret;
    ret = alarm(5);
    sleep(2);
    printf("ret = %d\n", ret); //ret的值为0,因为上一次进程没有设置计时器
    ret = alarm(5);
    sleep(1);
    printf("ret = %d\n", ret);//ret的值为3,因为上一次设置计时器,还剩3s(5s - 2s)
    return 0;
}

示例二

设置定时器的定时时间为 3s ,并处理 SIGALRM 信号

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void process_sigalarm(int sig) {
    printf("Receive signal %s\n", strsignal(sig));
}

int main(void)
{
    __sighandler_t sigret;
    sigret = signal(SIGALRM, process_sigalarm);
    if (sigret == SIG_ERR) {
        perror("[ERROR] signal():");
        exit(EXIT_FAILURE);
    }
    unsigned int ret;
    ret = alarm(5);
    pause();
    return 0;
}

输出结果

Receive signal Alarm clock

子进程退出信号

问题

在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源,并且在阻塞情况下,父进程不能执行其他逻辑

解决办法:

子进程是异步事件,利用子进程结束时发出的子进程结束信号解决

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void process_sigchld(int sig) {
    printf("Receive signal %s\n", strsignal(sig));
    wait(NULL);
}

int main(void)
{
    __sighandler_t sigret;
    sigret = signal(SIGCHLD, process_sigchld);
    if (sigret == SIG_ERR) {
        perror("[ERROR] signal():");
        exit(EXIT_FAILURE);
    }
    pid_t cpid = fork();
    if (cpid < 0) {
        perror("[ERROR] fork():");
        exit(EXIT_FAILURE);
    }
    else if (cpid == 0) {
        printf("child process <%d> start.\n", getpid());
        sleep(2);
        exit(EXIT_SUCCESS);
    }
    else {
        while(1) {

        }
    }
    return 0;
}

运行结果

child process <47240> start.
Receive signal Child exited

你可能感兴趣的:(Linux,linux,服务器,嵌入式)