理解signal()

        
        今天,朋友问我关于signal()的问题,又不记得细节了。靠着以前的记忆,和查书《Unix环境高级编程》,临时大线条忽悠了过去。然后感觉空荡荡的,非常心虚,赶紧恶补一下,记录之,以供后续参考。
       
        1. 概念
        信号是程序员必须熟悉的概念,与进程密切相关。个人理解,程序如同我们的计划/规划,在理想上下文中设计,静态分析,可以只关注核心功能,预期逻辑。程序一旦运行起来,就变成了进程。进程在实际环境中执行,自身、其他进程、环境,都在动态变化,会遇到各种异常,必须考虑如何处理这些异常。信号,就是在实际运行的进程中,通知各种异常事件的机制。(如果不深究细节,还有其他称谓:中断、消息)
        部分应用程序员可能认为,信号常在硬件,或内核,或操作系统使用,应用程序不用太关心。事实上,信号在重要的应用程序中不可或缺。以下是信号在应用程序中的常用场景:
        1)遇到一个死循环程序,使用“Ctrl+c”,终止程序运行。
        2)程序运行中异常退出,我们查看输出内容,是“Segmentationfault”,或“Aborted”,或“Floating point exception”,或其他?
        3)程序中,创建一个定时器,刷新显示图像(GUI中间件通常已提供),或定时收集事件。
        4)程序在“前台”还是“后台”运行,进程状态是“阻塞”,还是“执行”?
       
        2. 信号实现
        信号是一种机制,向程序传递异常信息。在Unix中大放异彩,主要包括信号编号(名字)、信号处理函数。由于与硬件、应用紧密相关,类Unix的各种系统中,支持的信号类型和个数都不尽相同。遇到信号方面的疑问,查看系统头文件signal.h定义,是最真实有效的手段。
        头文件signal.h中(各个系统的路径也不一样,linux,常规真实定义在/usr/include/bits/signum.h中),宏定义了信号的名字和对应编号。
        以前的信号处理函数,定义为:void (*signal(int signo, void (*func)(int)))(int)。一开始理解了很久,使用《C程序设计语言》介绍的方法,分解:

//void (*signal(int signo, void (*func)(int)))(int)

1)void (*handler)(int);
    handler,一个指向函数的指针,该函数有一个int参数,返回void。
    
2)T *signal(int signo, P), P = void (*handler)(int);
    signal,一个函数,该函数有2个参数(signo, P),返回类型T的指针。
    
3)void (T) (int),T = signal(int signo, P)
    T,一个函数,该函数有一个int参数,返回void。
    
4)最终,signal,一个函数,该函数有2个参数(第1个是signo, 第2个是“一个指向函数的指针,该函数有一个int参数,返回void”);返回一个函数的指针,该函数有一个int参数,返回void。

//最后,看了书以及头文件signal.h,都采用typedef重新定义,才理解得更容易:
typedef void (* func)(int);
func signal(int, func);

        信号处理函数,有一个int参数,返回void。signal,是一个函数,该函数有2个参数(第1个是signo, 第2个是signo关联的信号函数指针),返回原来关联的信号函数指针。由此给出SIG_ERR,SIG_DFL,SIG_IGN,三个函数指针的宏定义。
       
        3. 函数
        书中,对产生信号的常用函数,进行了说明和示例,非常重要。在终端,我们也经常使用着这些与函数同名的程序。此处列出我曾经用过的:
        1)kill/raise。向进程发送信号。
        linux中的常用进程命令:kill -n pid(需要ps -a查询出进程号);killall pname(直接发送TERM到进程名,常用于终止后台进程)。
        2)alarm/pause/sleep。闹钟与挂起。
        linux中的常用进程命令:sleep n(睡眠n秒);usleep n(睡眠n微妙)。
        3)abort。异常终止。
        程序中的断言assert()。
        4)system。执行第3方程序命令。
        程序中调用,以子进程方式运行第3方程序,阻塞式。曾经在项目中遇到“在线程中,再创建进程,导致系统有时异常”问题,和同事一起,用“后台进程 + 文件 + 定时器”,搞了一个system的替代方案,实现阻塞和非阻塞地调用第3方程序。
        5)CHLD/CONT/STOP/TSTP/TTIN/TTOU。作业控制信号。
        linux中,有几个命令,在调试和执行命令时非常有用(特别是只有一个串口终端时,这是法宝):

// 终端进程/作业控制

Ctrl+z; // 把当前进程移到后台,并挂起。
jobs;   // 查看后台进程(本终端中运行的应用程序)
    输出如下:
    [1]-  Stopped                 ./a.out
    [2]   Stopped                 ./b.out
    [3]+  Stopped                 ./b.out
fg [n]; // 将后台的进程,移动前台执行。n可选,是jobs输出的编号,默认操作最近进入后台进程。
bg [n]; // 将后台的阻塞进程,在后台变成执行。
./xx &; // 后台执行xx程序。

       
        4. 总结
        信号,很好很强大。是现实编程中必须掌握的工具。
        如果你是程序员,还没有关注过信号,必须马上行动了。
        如果你还没有看《Unix高级环境编程》第10章,必须马上行动了。
        如果你还没有买《Unix高级环境编程》,必须马上行动了。Richard Stevens的所有书,一直都在程序员的必买书单中。

       
        参考资料:
        1. 《Unix高级环境编程》,第10章,信号

你可能感兴趣的:(理解signal())