3.1.3 与信号处理有关的函数
(1)信号的发送
信号的发送,是指由发送进程把信号送到指定进程的信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将它唤醒,发送进程就此结束。一个进程可能在其信号域中有多个位被置位,代表有多种类型的信号到达,但对于一类信号,进程却只能记住其中的某一个。
发送信号的主要函数有:kill()、raise()、sigqueue()、alarm()、setitimer()以及abort()。
①kill()函数格式如下:
int kill(pid_t pid,int signo)其中,pid是接收信号的进程标识符,参数signo是要发送的信号值。
当pid〉0时,内核将信号发送给进程pid;
当pid=0时,内核将信号发送给与发送进程同组的所有进程;
当pid=-1时,内核将信号发送给除发送进程自身外,所有进程pid大于1的进程;
当pid〈0 pid!=-1时,内核将信号发送给进程标识符为-pid的所有进程。
signo是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限。root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号。
kill()最常用于pid〉0时的信号发送,调用成功返回0;否则,返回-1。注:对于pid〈0时的情况,对于哪些进程将接受信号,各种版本说法不一。
②raise()函数格式如下:
int raise(int signo)
向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。
③sigqueue()函数格式如下:
int sigqueue(pid_t pid, int signo, const union sigval val)
调用成功返回0;否则,返回-1。
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到参数信号处理函数的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数;sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。
④alarm()函数格式如下:
unsigned int alarm(unsigned int seconds)
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。
返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
⑤setitimer()函数格式如下:
int setitimer(int which, const struct itimerval *value, struct itimerval*ovalue));
setitimer()比alarm功能强大,支持3种类型的定时器:
ITIMER_REAL:设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程。
ITIMER_VIRTUAL:设定进程执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程。
ITIMER_PROF:设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程。
setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。
setitimer()调用成功返回0,否则返回-1。
itimerval结构如下:
struct itimerval
{
struct timeval it_interval;
struct timeval it_value;
}
⑥abort()函数格式如下:
void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收,该函数无返回值。
(2)信号的安装
如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。
Linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()在可靠信号系统调用的基础上实现,是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue()系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。
当一个进程要进入或退出一个低优先级睡眠状态时,或一个进程即将从核心态返回用户态时,核心都要检查该进程是否已收到软中断。当进程处于核心态时,即使收到软中断也不予理睬;只有当它返回到用户态后,才处理软中断信号。
①signal()函数格式
进程用signal()预置对信号的处理方式,允许调用进程控制软中断信号。
系统调用格式:signal(signo,function)
参数定义为:
signal(signo,function)
int signo;
void (*func) ( )
其中signo用于指定信号的类型,signo为0则表示没有收到任何信号。
function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGKILL,SIGTRAP和SIGPWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DFL,一个进程不能捕获SIGKILL信号。
function 的解释如下:
function=1时,进程对signo类信号不予理睬,亦即屏蔽了该类信号;
function=0时,缺省值,进程在收到signo信号后应终止自己;
function为非0,非1类整数时,function的值即作为信号处理程序的指针。
②sigaction()函数格式
int sigaction(int signum,const struct sigaction *act,struct sigaction*oldact));
sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数,等等。
sigaction结构定义如下:
struct sigaction {
union{
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int,struct siginfo *, void *);
}_u
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
}
其中,sa_restorer,已过时,POSIX不支持它,不应再被使用。
联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。
由_sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用(posix没有规范使用该参数的标准),第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:
siginfo_t {
int si_signo; /* 信号值,对所有信号有意义*/
int si_errno; /* errno值,对所有信号有意义*/
int si_code; /* 信号产生的原因,对所有信号有意义*/
union{ /* 联合数据结构,不同成员适应不同信号 */
//确保分配足够大的存储空间
int _pad[SI_PAD_SIZE];
//对SIGKILL有意义的结构
struct{
...
}...
... ...
... ...
//对SIGILL,SIGFPE, SIGSEGV,SIGBUS有意义的结构
struct{
...
}...
... ...
}
}
siginfo_t结构中的联合数据成员确保该结构适应所有的信号,比如对于实时信号来说,则实际采用下面的结构形式:
typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;
结构的第四个域同样为一个联合数据结构:
union sigval {
int sival_int;
void *sival_ptr;
}
采用联合数据结构,说明siginfo_t结构中的si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据。在信号的处理函数中,包含这样的信号相关数据指针,但没有规定具体如何对这些数据进行操作,操作方法应该由程序开发人员根据具体任务事先约定。
前面在讨论系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。
sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。
请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。
sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。
(3)信号集及信号集操作函数
信号集被定义为一种数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
信号集用来描述信号的集合,Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:
①int sigemptyset(sigset_t *set):初始化由set指定的信号集,信号集里面的所有信号被清空。
②int sigfillset(sigset_t *set):调用该函数后,set指向的信号集中将包含linux支持的64种信号。
③int sigaddset(sigset_t *set, int signum):在set指向的信号集中加入signum信号。
④int sigdelset(sigset_t *set, int signum):在set指向的信号集中删除signum信号。
⑤int sigismember(const sigset_t *set, int signum):判定信号signum是否在set指向的信号集中。
(4)信号阻塞与信号未决
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:
①int sigprocmask(int how, const sigset_t *set, sigset_t *oldset):能够根据参数how来实现对信号集的操作。操作主要分三种:
当how= SIG_BLOCK时,在进程当前阻塞信号集中添加set指向信号集中的信号。
当how= SIG_UNBLOCK时,如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞。
当how= SIG_SETMASK时,更新进程阻塞信号集为set指向的信号集。
②int sigpending(sigset_t *set):获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。
③int sigsuspend(const sigset_t *mask):用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。sigsuspend返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
(5)信号的接收
进程的task_struct中有一个signal域记录进程接收到的信号类型,共32位。当某位置为1时,表示收到了某类信号。Linux不提供处理多个同类信号的方式。即进程无法区分它是收到了1个还是4个SIGCONT信号。信号并非一产生就立刻处理,而是等到进程再次运行时才处理。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。