Linux 嵌入应用编程---信号

一、信号的概念

1.信号是事件发生时对进程的通知机制,称为“软件中断”,使用kill命令就是发送一种信号,使用ctrl+c退出命令也是一种信号,信号在Linux操作系统中提供了一种处理异步事件的方法,可以很好地在多个进程之间进行同步和简单的数据交互。

2.信号是异步事件的经典实例,产生信号的事件对进程而言是随机出现的,进程无法预测该事件产生的准确时间,进程不能够通过简单地测试一个变量或使用系统调用来判断是否产生了一个信号,这就如同硬件中断事件,程序是无法得知中断事件产生的具体时间,只有当产生中断事件时,才会告知程序、然后打断当前程序的正常执行流程、跳转去执行中断服务函数,这就是异步处理方式。

二、信号的产生

1.硬件发生异常,即硬件检测到错误条件并通知内核,随即再有内核发送相应的信号给相关的进程。异常可以是,数组访问越界,除数为0,就是一些编译出错时的警报。

2.用于在终端下输入了能够产生信号的特殊字符。Crtl+C产生中断信号(SIGINT),可以终止前台运行的进程。

3.进程调用kill()系统调用可将任意信号发送给另一个进程或进程组。接收和发送信号的进程的所有者必须相同,除非是超级用户root。

4.用户通过kill命令将信号发送给其他进程。其实现原理是通过kill系统调用来完成的(shell 脚本编写成的命令)。

5.发生了软件事件,即当检测到某种软件条件发生,例如,计算到达,进程设置的定时器已经超时,进程执行的cpu时间超限。

三、信号的处理方式

1.忽略信号,此做法,信号对进程没有任何影响。但有两者信号是无法忽略的SIGKILL和SIGSTOP,原因是,它们向内核超级用户提供了使进程终止或停止的可靠方法。

2.捕获信号(绑定信号处理函数)

3.执行系统默认操作。对大多数信号来说,系统默认的处理方式就是终止该进程。

四、信号的编号

1.信号本质上是int 类型的数字编号。1-31为标准信号,34-64为实时信号(SIGUSR1 (10)SIGUSR2(12))也是实时信号

2.实时信号与非实时信号其实是从时间关系上进行的分类,与可靠信号与不可靠信号是相互对的,非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。实时信号保证了发送的多个 信号都能被接收,实时信号是 POSIX 标准的一部分,可用于应用进程。

3.常见的非实时信号有如下:

Tips:上表中,term 表示终止进程;core 表示生成核心转储文件,核心转储文件可用于调试,这个便不再给介绍了;ignore 表示忽略信号;cont 表示继续运行进程;stop 表示停止进程(注意停止不等于终止,而是暂停)。

五、一些对信号的处理函数的使用例程
5.1简单接口或发送信号函数
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

/*
*函数原型:int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
*作用:Linux 系统下设置信号处理方式最简单的接口,可将信号的处理方式设置为捕获信号、忽略信号以及系统默认操作。
*signum:设置需要接受的信号,除了SIGKILL和SIGSTOP信号之外的任何信号。
*act :为一个 struct sigaction 类型的指针,指向一个struct signaction 的数据结构体。
*该数据结构描述了信号的处理方式,稍后介绍该数据结构;如果参数 act 不为 NULL,则表示需要为信号设置新的处理方式;如果参数 act 为 NULL,则表示无需改变信号当前的处理方式。
*oldact 参数也是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构。如果参数oldact 不为 NULL,则会将信号之前的处理方式等信息通过参数 oldact 返回出来;
*如果无意获取此类信息,那么可将该参数设置为 NULL。
*返回值:成功返回 0;失败将返回-1,并设置 errno。
*/

static void sig_handler(int sig)
{
    printf("Received signal: %d\n", sig);
}

int main(int argc, char *argv[])
{
    struct sigaction sig = {0}; //定义sigaction结构体变量
    int ret;
    sig.sa_handler = sig_handler; //设置接受到信号后的处理函数
    sig.sa_flags = 0;

    ret = sigaction(SIGINT, &sig, NULL); //只有接收到的SIGINT信号才会调用处理信号处理函数。
    if (-1 == ret) {
        perror("sigaction error");
        exit(-1);
    }

    /* 死循环 */
    for ( ; ; ) { }
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>

/*
*函数原型:int kill(pid_t pid, int sig);
*作用:kill()系统调用可将信号发送给指定的进程或进程组中的每一个进程
*pid:参数 pid 为正数的情况下,用于指定接收此信号的进程 pid
*     如果 pid 等于 0,则将 sig 发送到当前进程的进程组中的每个进程。
*     如果 pid 等于-1,则将 sig 发送到当前进程有权发送信号的每个进程,但进程 1(init)除外。
*     如果 pid 小于-1,则将 sig 发送到 ID 为-pid 的进程组中的每个进程。
*sig:需要发送的信号。
*返回值:成功返回 0;失败将返回-1,并设置 errno。
*/

int main(int argc, char *argv[])
{
    int pid;
    /* 判断传参个数 */
    if (2 > argc)
        exit(-1);

    /* 将传入的字符串转为整形数字 */
    pid = atoi(argv[1]);
    printf("pid: %d\n", pid);

    /* 向 pid 指定的进程发送信号 */
    if (-1 == kill(pid, SIGINT)) {
        perror("kill error");
        exit(-1);
    }

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

/*
*函数原型:unsigned int alarm(unsigned int seconds);
*作用:可以设置一个定时器(闹钟),当定时器定时时间到时,内核会向进程发送 SIGALRM信号
*seconds:设置定时时间,以秒为单位;如果参数 seconds 等于 0,则表示取消之前设置的 alarm 闹钟。
*返回值:如果在调用 alarm()时,之前已经为该进程设置了alarm闹钟还没有超时,则该闹钟的剩余值作为本次alarm()函数调用的返回值,
*之前设置的闹钟则被新的替代;否则返回 0。
*/

static void sig_handler(int sig)
{
    puts("Alarm timeout");
    exit(0);//终止应用程序
}

int main(int argc, char *argv[])
{
    struct sigaction sig = {0};
    int second;

    /* 检验传参个数 */
    if (2 > argc)
        return -1;

    /* 启动 alarm 定时器 */
    second = atoi(argv[1]);/* 将传入的字符串转为整形数字 */
    printf("定时时长: %d 秒\n", second);
    alarm(second);
    //当定时器定时时间到时,内核会向进程发送 SIGALRM信号。

    /* 为 SIGALRM 信号绑定处理函数 */
    sig.sa_handler = sig_handler;
    sig.sa_flags = 0;
 
    //接收SIGALRM信号
    if (-1 == sigaction(SIGALRM, &sig, NULL)) {
        perror("sigaction error");
        return -1;
    }

    /* 循环 */ 
    for ( ; ; )
        sleep(1);

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

/*
*int pause(void);
*pause()系统调用可以使得进程暂停运行、进入休眠状态,
*直到进程捕获到一个信号为止,只有执行了信号处理函数并从其返回时,pause()才返回。
*/

static void sig_handler(int sig)
{ 
    puts("Alarm timeout");
}

int main(int argc, char *argv[])
{
    struct sigaction sig = {0};
    int second;

    /* 检验传参个数 */
    if (2 > argc)
        exit(-1);

    /* 为 SIGALRM 信号绑定处理函数 */
    sig.sa_handler = sig_handler;
    sig.sa_flags = 0;

    if (-1 == sigaction(SIGALRM, &sig, NULL)) {
        perror("sigaction error");
        exit(-1);
    }


    /* 启动 alarm 定时器 */
    second = atoi(argv[1]);
    printf("定时时长: %d 秒\n", second);
    alarm(second);

    /* 进入休眠状态 */  
    //直到进程捕获到一个信号为止,只有执行了信号处理函数并从其返回时,pause()才返回,然后继续执行下面的程序
    //可以替代for循环中的 sleep 功能。
    pause();
    puts("休眠结束");

    return 0;
}
5.2信号掩码
        内核为每一个进程维护了一个信号掩码(其实就是一个信号集),即一组信号。当进程接收到一个属于 信号掩码中定义的信号时,该信号将会被阻塞、无法传递给进程进行处理,那么内核会将其阻塞,直到该信 号从信号掩码中移除,内核才会把该信号传递给进程从而得到处理。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

/*信号集
*sigset_t sig_set;定义信号集结构体变量
*sigemptyset(&sig_set);信号集初始化 ,将信号集清空*
*int sigfillset(sigset_t *set);使其包含所有信号(包括所有实时信号)
*int sigaddset(sigset_t *set, int signum);向信号集中添加一个信号
*int sigdelset(sigset_t *set, int signum);向信号集中移除一个信号
*int sigismember(const sigset_t *set, int signum);测试信号是否在信号集中,在返回1,不在返回0,失败返回-1,并设置errno
*int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);地向信号掩码中添加/移除信号。
*how参数:⚫ SIG_BLOCK:将参数 set 所指向的信号集内的所有信号添加到进程的信号掩码中。换言之,将信号掩码设置为当前值与 set 的并集。
*        ⚫ SIG_UNBLOCK:将参数 set 指向的信号集内的所有信号从进程信号掩码中移除。
*        ⚫ SIG_SETMASK:进程信号掩码直接设置为参数 set 指向的信号集。
*/

static void sig_handler(int sig)
{
    printf("执行信号处理函数...\n");
}

int main(void)
{
    struct sigaction sig = {0};//定义sigaction结构体变量

    sigset_t sig_set; //定义信号集结构体变量

    sigemptyset(&sig_set);/* 信号集初始化 ,将信号集清空*/
    sigaddset(&sig_set, SIGINT);//将中断信号添加到信号集中

    /* 向信号掩码中添加信号 */
    if (-1 == sigprocmask(SIG_BLOCK, &sig_set, NULL))
        exit(-1);

    /* 注册信号处理函数 */
    sig.sa_handler = sig_handler;
    sig.sa_flags = 0;

    if (-1 == sigaction(SIGINT, &sig, NULL))
        exit(-1);
    
    /* 向自己发送信号 */
    raise(SIGINT);

    /* 休眠 2 秒 */
    sleep(2);
    printf("休眠结束\n");

    /* 从信号掩码中移除添加的信号 */
    if (-1 == sigprocmask(SIG_UNBLOCK, &sig_set, NULL))
        exit(-1);

    exit(0);
}
5.3 发送实时信号
        等待信号集只是一个掩码,仅表明一个信号是否发生,而不能表示其发生的次数。换言之,如果一个同 一个信号在阻塞状态下产生了多次,那么会将该信号记录在等待信号集中,并在之后仅传递一次(仅当做发 生了一次),这是标准信号的缺点之一。
Linux 内核定义了 31 个不同的实时信号,信号编号范围为 34~64 ,使用 SIGRTMIN 表示编号最小的实时信号,使用 SIGRTMAX 表示编号最大的实时信号,其它信号编号可使用这两个宏加上一个整数或减去一个整数。
应用程序当中使用实时信号,需要有以下的两点要求:
发送进程使用 sigqueue() 系统调用向另一个进程发送实时信号以及伴随数据。
接收实时信号的进程要为该信号建立一个信号处理函数,使用 sigaction 函数为信号建立处理函数,并加入 SA_SIGINFO ,这样信号处理函数才能够接收到实时信号以及伴随数据,也就是要使用 sa_sigaction 指针指向的处理函数,而不是 sa_handler ,当然允许应用程序使用 sa_handler ,但这样就不能获取到实时信号的伴随数据了。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
/*
本函数的作用是向另一个进程发送实时信号
输入:接受进程的PID 、所需发送的实时信号(也可在程序中设置伴随数据)
输出:成功则打印输入的数据和显示发送成功
*/

int main(int argc, char *argv[])
{
    union sigval sig_val;//需发送的伴随数据
    int pid;
    int sig;

    /* 判断传参个数 */
    if (3 > argc)
        return -1;   

    /* 获取用户传递的参数并转换成整形数据 */
    pid = atoi(argv[1]);//PID  
    sig = atoi(argv[2]);//实时信号

    printf("pid: %d\nsignal: %d\n", pid, sig);

    /* 发送信号 */
    sig_val.sival_int = 10; //伴随数据
    if (-1 == sigqueue(pid, sig, sig_val)) {
        perror("sigqueue error");
        return -1;
    }

    puts("信号发送成功!");

    return 0;
}

配合下面这个函数可实现进程通信,用pa -aux获取到进程PID即可。

include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
/*
本函数为用户传入信号num,再接收另一个进程发过来的信号,与num比较,相等则
捕获该信号并输出其信息,不相等则结束该进程。
输入: 信号——num
输出:信号处理函数所打印的数据
*/



static void sig_handler(int sig, siginfo_t *info, void *context)
{
    sigval_t sig_val = info->si_value;

    printf("接收到实时信号: %d\n", sig);
    printf("伴随数据为: %d\n", sig_val.sival_int);
    printf("PID: %d\n", info->si_pid);
    printf("UID: %d\n", info->si_uid);
}
int main(int argc, char *argv[])
{
    struct sigaction sig = {0};
    int num;

    /* 判断传参个数 */
    if (2 > argc)
        return -1;

    /* 获取用户传递的参数 */
    num = atoi(argv[1]);

    /* 为实时信号绑定处理函数 */
    sig.sa_sigaction = sig_handler;
    sig.sa_flags = SA_SIGINFO;
    if (-1 == sigaction(num, &sig, NULL)) {
        perror("sigaction error");
        return -1;
    }

    /* 死循环 */
    for ( ; ; )
    sleep(1);

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

/*
本程序的作用是调用abort函数,发送SIGABRT信号到进程,从而终止程序的运行
*/

static void sig_handler(int sig)
{
    printf("接收到信号: %d\n", sig);
}

int main(int argc, char *argv[])
{
    struct sigaction sig = {0};

    sig.sa_handler = sig_handler;
    sig.sa_flags = 0;
    if (-1 == sigaction(SIGABRT, &sig, NULL)) {
        perror("sigaction error");
        return -1;
    }

    sleep(2);

    abort(); // 调用 abort

    for ( ; ; )
        sleep(1);
    
    return 0;
}

声明:本文仅为学习正点原子课程个人学习笔记,不做任何商业行为。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值