Linux信号实践(2) --信号分类

简介: 信号分类 不可靠信号Linux信号机制基本上是从UNIX系统中继承过来的。早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:   1.进程每次处理信号后,就将对信号的响应设置为默认动作。

信号分类 

不可靠信号

Linux信号机制基本上是从UNIX系统中继承过来的。早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:

   1.进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。

   2.因此导致, 早期UNIX下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。 

Linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,Linux下的不可靠信号问题主要指的是信号可能丢失


可靠信号

随着时间的发展,实践证明,有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种UNIX版本分别在这方面进行了研究,力图实现"可靠信号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号(SIGRTMIN ~ SIGRTMAX),并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。

sigaction和signal函数都是调用内核服务do_signal函数;[内核服务函数,应用程序无法调用该函数]

早期UNIX系统只定义了31种信号,而Linux 3.x支持64种信号,编号1-64(SIGRTMIN=34,SIGRTMAX=64),将来可能进一步增加,这需要得到内核的支持。 前31种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL+C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。


信号API-信号发送(1)

1.kill

int kill(pid_t pid, int signo); 

kill既可以向自身发送信号,也可以向其他进程发送信号 

signo参数组合情况解释

   pid>0 将信号sig发给pid进程

   pid=0 将信号sig发给同组进程

   pid=-1 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身)

   pid<-1 将信号sig发送给进程pid(绝对值)的每一个进程

//示例
void onSignalAction(int signalNumber)
{
    switch(signalNumber)
    {
    case SIGUSR1:
        cout << "SIGUSR1 = " << signalNumber << endl;
        break;
    default:
        cout << "Other Signal ..." << endl;
        break;
    }
}

int main()
{
    if (signal(SIGUSR1,onSignalAction)== SIG_ERR)
    {
        perror("signal error");
        return -1;
    }

    pid_t pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        return -1;
    }
    else if (pid == 0)
    {
        /**向父进程发送信号
        pid_t ppid = getppid();
        kill(ppid,SIGUSR1);
        */

        /**向同组所有进程发送信号,子进程也会收到该信号
        kill(0,SIGUSR1);
        */

        //向本组所有进程发送信号,作用同上
//getpgrp()函数获取进程组pid
        pid_t pgid = getpgrp();
        killpg(pgid,SIGUSR1);
        exit(0);
    }

    int sleepTime = 3;
    while (sleepTime > 0)
    {
        write(STDOUT_FILENO,"Parent start Sleep...\n",
              sizeof("Parent start Sleep...\n"));
        sleepTime = sleep(sleepTime);
        write(STDOUT_FILENO,"Parent return from Sleep...\n",
              sizeof("Parent return from Sleep...\n"));
    }

    return 0;
}

注意:如果在fork之前安装信号,则子进程可以继承信号

 

Sleep遇上signal,子进程向父进程发送信号,sleep函数的几点说明

   1)sleep函数作用,让进程睡眠。

   2)能被信号打断,然后处理信号函数以后,就不再睡眠了。直接向下执行代码

   3)sleep函数的返回值,是剩余的秒数

Man手册显示:

RETURN VALUE

       Zero if the requested time has elapsed, or the number of  seconds  left to sleep, 

if the call was interrupted by a signal handler.

//示例:sleep遇上signal
void onSignalAction(int signalNumber)
{
    switch(signalNumber)
    {
    case SIGINT:
        cout << "SIGINT = " << signalNumber << endl;
        break;
    default:
        cout << "Other Signal ..." << endl;
        break;
    }
}

int main()
{
    if (signal(SIGINT,onSignalAction)== SIG_ERR)
    {
        perror("signal error");
        return -1;
    }
    cout << "Main Start Sleeping..." << endl;
    int returnValue = sleep(100); //可中断睡眠
    cout << "Main End Sleeping... returnValue = " << returnValue << endl;

    return 0;
}
//示例:sleep加强
int main()
{
//...同上
cout << "Main Start Sleeping..." << endl;
//sleep加强版^^
    int sleepTime = 20;
    do
    {
        sleepTime = sleep(sleepTime);
cout << "continue..." << endl;
    }
    while (sleepTime > 0);
    cout << "Main End Sleeping... sleepTime = " << sleepTime << endl;

    return 0;
}

2.raise

int raise(int sig);

给自己发送信号。raise(sig)等价于kill(getpid(), sig);


3.killpg

int killpg(int pgrp, int sig);

进程组发送信号。killpg(pgrp, sig)等价于kill(-pgrp, sig);


4.sigqueue

int sigqueue(pid_t pid, int sig, const union sigval value);

给进程发送信号,支持排队,可以附带信息。


信号API-pause

int pause(void);

将进程置为可中断睡眠状态。然后它调用内核函数schedule(),使Linux进程调度器找到另一个进程来运行

pause使调用者进程挂起,直到一个信号被捕获

//示例
int main()
{
    if (signal(SIGINT,handler)== SIG_ERR)
        err_exit("signal error");
    while(true)
    {
        pause();
        cout << "pause return..." << endl;
    }
}

信号API-信号发送(2)

unsigned int alarm(unsigned int seconds);

alarm函数,设置一个闹钟延迟发送SIGALRM信号(告诉Linux内核n秒中以后,发送SIGALRM信号);

手册描述-DESCRIPTION

       alarm() arranges for a SIGALRM signal to be delivered to the process in seconds seconds.

       If seconds is zero, no new alarm() is scheduled.

       In any event any previously set alarm() is cancelled.

//alarm 递归调用
void onSignalAction(int signalNumber)
{
    switch(signalNumber)
    {
    case SIGALRM:
        cout << "SIGALRM = " << signalNumber << endl;
        alarm(1);	//继续调用onSignalAction
        break;
    default:
        cout << "Other Signal ..." << endl;
        break;
    }
}

int main()
{
    if (signal(SIGALRM,onSignalAction)== SIG_ERR)
    {
        perror("signal error");
        return -1;
    }

    alarm(1);

    while(true)
    {
        pause();
        cout << "pause returned..." << endl;
    }

    return 0;
}

可重入/不可重入函数

  所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可重入函数在信号处理函数中被视为不安全函数。

  为了增强程序的稳定性,在信号处理函数中应使用可重入函数。 

//不可重入函数示例
struct Teacher
{
    int a;
    int b;
    int c;
    int d;
};

Teacher g_teacher;
void onSigAlarm(int signo)
{
    printf("%d %d", g_teacher.a, g_teacher.b);
    printf(" %d %d\n", g_teacher.c, g_teacher.d);
    alarm(1);
}

int main()
{
    if (signal(SIGALRM,onSigAlarm)== SIG_ERR)
        err_exit("signal error");

    Teacher zero = {0, 0, 0, 0};
    Teacher ones = {1, 1, 1, 1};

    alarm(1);
    g_teacher = zero;
    while(true)
    {
        g_teacher = zero;
        g_teacher = ones;
    }
}

输出结果演示:

 

原因分析:

可以将语句g_teacher = zero分解为:

        g_teacher.a = zero.a;

        g_teacher.b = zero.b;

        g_teacher.c = zero.c;

        g_teacher.d = zero.d;

因此, 在这四条语句执行的中间, 如果此时SIGALRM信号到达(中断到达), 则g_teacher中的一些数据会是新值, 而有些却是以前留下的脏值, 究其原始则是g_teacher = zero不是原子操作, 而信号处理函数onSigAlarm却又访问了全局变量g_teacher. 

如果将两条printf封装成一个函数

void unsafe_function()
{
    printf("%d %d", g_teacher.a, g_teacher.b);
    printf(" %d %d\n", g_teacher.c, g_teacher.d);
}

然后在onSigAlarm中调用, 则unsafe_function函数就成了不可重入函数(其实printf就是不可重入函数), 因此, 在信号响应函数中, 尽量不要调用不可重入函数;

 

不可重入函数

满足下列条件的函数多数是不可重入的:

  (1)使用静态数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;

  (2)函数实现时,调用了malloc()或者free()函数;

  (3)实现时使用了标准I/O函数

 

附-man 7 signal可以查看那些函数是可重入和不可重入的.

目录
相关文章
|
15天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
15天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
关系型数据库 MySQL Linux
Linux环境下MySQL数据库自动定时备份实践
数据库备份是确保数据安全的重要措施。在Linux环境下,实现MySQL数据库的自动定时备份可以通过多种方式完成。本文将介绍如何使用`cron`定时任务和`mysqldump`工具来实现MySQL数据库的每日自动备份。
92 3
|
2月前
|
监控 Linux 云计算
Linux操作系统在云计算环境中的实践与优化###
【10月更文挑战第16天】 本文探讨了Linux操作系统在云计算环境中的应用实践,重点分析了其在稳定性、安全性和高效性方面的优势。通过具体案例,阐述了Linux如何支持虚拟化技术、实现资源高效分配以及与其他开源技术的无缝集成。文章还提供了针对Linux系统在云计算中的优化建议,包括内核参数调整、文件系统选择和性能监控工具的应用,旨在帮助读者更好地理解和应用Linux于云计算场景。 ###
54 3
|
2月前
|
Ubuntu Linux
Linux实践|设置静态 IP 地址
Linux实践|设置静态 IP 地址
74 0
Linux实践|设置静态 IP 地址
|
4月前
|
存储 人工智能 数据管理
深入理解Linux操作系统之文件系统管理探索人工智能:从理论到实践的旅程
【8月更文挑战第30天】在探索Linux的无限可能时,我们不可避免地会遇到文件系统管理这一核心话题。本文将深入浅出地介绍Linux文件系统的基础知识、操作命令及高级技巧,帮助你更有效地管理和维护你的系统。从基础概念到实践应用,我们将一步步揭开Linux文件系统的神秘面纱。
|
4月前
|
Linux 调度
Linux0.11 信号(十二)(下)
Linux0.11 信号(十二)
36 1
|
4月前
|
存储 Linux 调度
|
4月前
|
存储 Unix Linux
Linux0.11 信号(十二)(上)
Linux0.11 信号(十二)
40 0
|
4月前
|
Linux
下一篇
DataWorks