Unix 信号大全

本文深入探讨Unix系统的信号机制,从概念出发,详细解释了不同类型的信号及其在进程管理中的作用,帮助读者理解这一核心的进程通信方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

信号的概念:

  首先,每个信号都有一个名字。这些名字都以三个字符 S I G开头。例如,S I G A B RT是夭折
信号,当进程调用a b o r t函数时产生这种信号。S I G A L R M是闹钟信号,当由a l a r m函数设置的时
间已经超过后产生此信号。 V 7有1 5种不同的信号, S V R 4和4 . 3 + B S D均有3 1种不同的信号。
在头文件< s i g n a l . h >中,这些信号都被定义为正整数(信号编号) 。没有一个信号其编号为
0。在1 0 . 9节中将会看到 k i l l函数,对信号编号0有特殊的应用。P O S I X . 1将此种信号编号值称为
空信号。

信号的分类:
在信号表中我们可以看到1-31 的信号为传统Unix支持的信号,是不可靠的信号(非实时的),编号32-64为后来扩充的信号,是可靠的信号(实时的)。不可靠和可靠的信号的区别在于前者不支持排队,可能造成信号的丢失,而后者不会。
  很多条件可以产生一个信号。
• 当用户按某些终端键时,产生信号。在终端上按D E L E T E键通常产生中断信号(S I G I N T) 。
这是停止一个已失去控制程序的方法。 (第11章将说明此信号可被映射为终端上的任一字符。 )
• 硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将
其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无
效存储访问的进程产生一个 S I G S E G V。
• 进程用k i l l ( 2 )函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号进程
和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
• 用户可用 k i l l ( 1 )命令将信号发送给其他进程。此程序是 k i l l函数的界面。常用此命令终止
一个失控的后台进程。
• 当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并不是指硬
件产生条件(如被 0除) ,而是软件条件。例如 S I G U R G (在网络连接上传来非规定波特率的数
据) 、S I G P I P E (在管道的读进程已终止后一个进程写此管道 ),以及S I G A L R M (进程所设置的闹
钟时间已经超时 )。
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能只是测
试一个变量 (例如e r r n o )来判别是否发生了一个信号,而是必须告诉内核“在此信号发生时,请
执行下列操作” 。
可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。
(1) 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。
它们是:S I G K I L L和S I G S TO P。这两种信号不能被忽略的原因是:它们向超级用户提供一种使
进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问
或除以0) ,则进程的行为是未定义的。
(2) 捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用
户函数中,可执行用户希望对这种事件进行的处理。例如,若编写一个命令解释器,当用户用
键盘产生中断信号时,很可能希望返回到程序的主循环,终止系统正在为该用户执行的命令。
如果捕捉到S I G C H L D信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用 w a i t p i d
以取得该子进程的进程I D以及它的终止状态。又例如,如果进程创建了临时文件,那么可能要
为S I G T E R M信号编写一个信号捕捉函数以清除临时文件( k i l l命令传送的系统默认信号是终止
信号) 。
(3) 执行系统默认动作。表 1 0 - 1给出了对每一种信号的系统默认动作。注意,对大多数信
号的系统默认动作是终止该进程。

  信号的处理机制
内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应与该信号(内核通过在进程的struct task_struct 结构中的信号域中设置对应位其来实现相一个进程发送信号)。
如果信号发送给一个正在谁秒的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不是唤醒进程。
内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时,所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完信号。内核处理一个进程收到的软中断是在该进程的上下文中,因此,进程必须处于运行状态。处理信号有三种类型:进程接收到信号后推出:进程忽略该信号;进程收到信号后执行用户自定义的使用系统调用signal()注册函数。当进程接收到一个它忽略的信号时,进程丢去该信号,就像从来没有收到该信号似的,而继续运行。如果进程收到一个要捕获的信号,那么进程返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回再弹出栈顶时,才返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核状态下运行的话,用户就可以获取任何权限)
信号的处理
signal function :
要对一个信号进行处理,就需要给出信号发生时系统所调用的处理函数。可以为一个特定的信号(除去无法捕捉的SIGKILL和SIGSTOP信号)注册相应的处理函数。如果正在运行的程序源代码里注册了针对某一个特定信号的处理程序,不论当时程序执行到何处,一旦进程接到收到该信号,相应的调用就会发生。所以说信号的发送和处理是异步的。
通过调用signal()函数来注册某个特定信号的处理程序,它的函数原型如图下:
#include
void ( *signal(int signum,void (*handler)(int))) (int)
返回:如果成功则返回以前的信号处理配置,若出错则为SIG_ERR。
参数signum表示所注册函数针对的信号,其取值为上面的信号宏定义。handler的取值是:(a)常数SIG_IGN,或者(b)常数SIG_DEL,或(c)当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则向内核表示忽略此信号。(注意有两个信号SIGKILL和SIGSTOP不能忽略)如果指定SIG_DEL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,我们称此为捕捉此信号,并称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)。
如果查看系统的头文件,则可能会找到下列形式的说明:
#define SIG_ERR (void (*) () ) -1
#define SIG_DEL (void(*)())0
#define SIG_IGN (void(*)()) 1
这些常数可以表示指向函数的指针,该函数需要一个整形参数,而且无返回值。signal的第二个参数及其返回值就可用它们表示。这些常数所使用的三个值不一定要是-1,0,和1,但他们必须要是三个值而决不能是任何一可说明函数的地址。大多数UNIX系统使用上面所示的值。
程序:
EG1:捕捉终端键入“Ctrl+c”时产生的SIGINT信号。
source code :
#include
#include
#include
void SignHandler(int iSignNum)
{
printf("Capture signal number : %d \n", iSignNum);
}
int main(void)
{
signal(SIGINT,SignHandler);
while(1)
sleep(1);
return 0 ;
}
EG 2:忽略终端键入“Ctrl+c”时产生的SIGINT信号

#include
#include
int main(void)
{
int i =0;
signal (SIGINT,SIG_IGN);
while(1)
{
printf("i = %d\n",i++);
sleep(1);
}
exit(0);
}


EG2 :接收信号的默认处理方,接收默认处理就相当于没有写信号处理程序。
#include
#include
int main(void)
{
signal(SIGINT,SIG_DEF);
while(1)
sleep(1);
return 0 ;
}

EG3 :一个程序中注册多个新号
#include
#include
#include
#include
void sigroutine(int dunno )
{

switch (dunno)
{
case 1: printf("Capture SIGHUP signal,the signal number is %d\n",dunno); break ;
case 2: printf("Capture SIGINT signal,the signal number is %d\n",dunno); break ;
case 3 :printf("Capture SIGQUIT signal,the signal number is %d\n",dunno); break;
}
return ;
}

int main(int argc ,char *argv[])
{
printf("process ID is %d \n",getpid());
if(signal(SIGHUP,sigroutine) == SIG_ERR)
{
printf("Could't register signal handler for SIGHUP!\n");
}

if(signal(SIGINT,sigroutine) == SIG_ERR)
{
printf("Couldn't register signal handler for SIGINT!\n");
}

if(signal(SIGQUIT,sigroutine)==SIG_ERR)
{

printf("Couldn't register signal handler for SIGQUIT!\n");
}
while(1)
sleep(1);
return 0 ;

}
EG4

#include
#include
#include
static void sig_usr(int) ;//one handler for both signals
int main(void)
{
if(signal(SIGUSR1, sig_usr)==SIG_ERR)
printf("can't catch SIGUSR1");
if(signal(SIGUSR2, sig_usr) == SIG_ERR )
printf("can't catch SIGUSR2");
for (;;)
pause() ;

}

static void sig_usr(int signo)
{
if(signo == SIGUSR1)
printf("received SIGUSR1\n");
else if (signo == SIGUSR2)
printf("received SIGUSR2\n");
else
printf("received signal %d \n",signal);
return 0 ;
}

Program Analysis:
  我们使该程序在后台运行,并且用 k i l l ( 1 )命令将信号送给它。注意,在 U N I X中,杀死( k i l l )这个术语是不恰当的。k i l l ( 1 )命令和k i l l ( 2 )函数只是将一个信号送给一个进程或进程组。该信号是否终止该进程则取决于该信号的类型,以及该进程是否安排了捕捉该信号。
$ a.out & 在后台启动进程
[1] 4720 作业控制s h e l l打印作业号和进程I D
$ kill -USR1 4720 向该进程发送 S I G U S R 1
received SIGUSR1
$ kill -USR2 4720 向该进程发送 S I G U S R 2
received SIGUSR2
$ kill 4720 向该进程发送 S I G T E R M
[1] + Terminated a.out &
当向该进程发送 S I G T E R M信号后,该进程就终止,因为它不捕捉此信号,而对此信号的系统
默认动作是终止。

sigaction function:

Linux还提供了另外一个功能更为强大的信号处理机制--sigaction系统调用。sigaction函数的功能是检查或者修改(或两者)与指定信号相关联的处理动作,此函数可以完全替代signal函数。sigaction函数原型如下:
#include
int sigaction(int signum ,const struct sigaction *act,struct sigaction *old);
返回:若成功则返回0 ,若出错返回-1.
参数signum为需要捕捉的信号。参数act是一个结构体,里面包含信号处理函数的地址,处理方式等信息。参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum信号的处理方式的信息。
结构体struct sigaction(注意,名称与函数sigaction相同)的原型为:
struct sigaction{
void (*sa_handler)(int) ; // 老类型的信号处理函数指针
void (*sa_sigaction)(int,siginfo_t *,void *);//新类型的信号处理函数指针

sigset_t sa_mask ; //将要被阻塞的信号
int sa_flags ; //信号处理方式掩码
void (*sa_restore)(void);//保留,不使用
}

sigaction函数不但可以实现signal函数的功能,而且还可以提供更详细的信息,确切了解进程接收到信号时所发生的具体细节。程序EG 5 显示了sigaction函数的功能,当终端没有产生SIGINT或SIGQUIT(ctrl+\)信号时,程序能很好地执行read()函数,即读入终端输入的字符串;当SIGINT或SIGQUIT信号产生时,进程被信号中断,read出错退出了。

EG 6:
#inclu
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值