APUE学习笔记——10信号——信号接口函数 signal 和 sigaction

signal函数

signal函数是早起Unix系统的信号接口,早期系统中提供不可靠的信号机制。在后来的分支中,部分系统使用原来的不可靠机制定有signal函数,如 Solaris 10 。而更多的系统采用新语义 可靠信号机制,如4.4BSD。

出于signal函数不同系统的不统一性,我们一般使用sigaction函数取代它。关于sigaction函数,我们在本文后面做详细介绍。

函数原型:


#include <signal.h>

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

                     Returns: previous disposition of signal (see following) if OK,SIG_ERR on error

参数:

signo:信号的名字。

func:可以看出是一个函数指针,用于指定针对信号的处理方式。

信号有三种处理方式,1)忽略,此时func赋值为SIG_IGN; 2)使用默认动作,此时func赋值为SIG_DFL; 3)自定义动作,此时func赋值为我们自定义函数的函数指针,会调用到信号处理程序(signal handler)或信号捕捉函数(signal-catching
function)。


#define SIG_ERR (void (*)())-1

#define SIG_DFL (void (*)())0

#define SIG_IGN (void (*)())

函数类型分析

返回值:   返回的是一个函数指针,该指针指向的函数是类似void fun(int )型,带一个int参数,返回值void的类型。其实跟signal的第二个参数func是一样类型的。

可以对func或者函数返回类型进行typedef以下:


typedef void Sigfunc(int);

即使sigfunc就是一个 返回值为void,带一个int型参数的函数。

signal函数可以变形为:


Sigfunc *signal(int, Sigfunc *)

使用signal的一个例子:


#include "apue.h"

#include "myerr.h"



static void sig_usr(int); /* one handler for both signals */



int

main(void)

{

        if (signal(SIGUSR1, sig_usr) == SIG_ERR)

                err_sys("can’t catch SIGUSR1");

        if (signal(SIGUSR2, sig_usr) == SIG_ERR)

                err_sys("can’t catch SIGUSR2");

        for ( ; ; )

                pause();

}



static void

sig_usr(int signo) /* argument is signal number */

{

        if (signo == SIGUSR1)

                printf("received SIGUSR1\n");

        else if (signo == SIGUSR2)

                printf("received SIGUSR2\n");

        else

                err_dump("received signal %d\n", signo);

}

~    

运行结果:


[email protected]:~/Windeal/apue$ ./exe &

[1] 2982

[email protected]:~/Windeal/apue$ kill -USR1 2982

[email protected]:~/Windeal/apue$ received SIGUSR1

kill -USR2 2982

received SIGUSR2

[email protected]:~/Windeal/apue$ kill 2982

[1]+  Terminated              ./exe

[email protected]:~/Windeal/apue$ 

新程序的启动 与signal的缺陷

来看下,运行一个程序时,信号的状态。

使用fork床键子进程时,子进程会继承父进程的信号状态。Note:用户定义的信号捕捉函数的的地址在子进程时有效的。

使用exec运行新程序时,exec调用者要捕捉的信号,在子进程中会被置为默认动作(因为调用者定义的信号捕捉函数的地址在新程序中已经失效了),而其他信号不变(设置忽略的信号,新进程也会忽略)。

Example:

shell启动一个后台进程,会设置该后台进程自动屏蔽掉中断和退出信号。这样前台退出时,后台进程才能继续进行。很多捕捉这两个信号的交互式程序使用下列形式代码:


void sig_int(int), sig_quit(int);



if (signal(SIGINT, SIG_IGN) != SIG_IGN)

    signal(SIGINT, sig_int);

if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)

    signal(SIGQUIT, sig_quit);

该代码反应了一个signal的一个缺陷,就是它必须通过改变系统信号的处理方式才能获得系统当前的处理方式(好像很绕口,下面继续解释)。

分析这段代码,首先我们明确代码的目的:判断信号当前的处理方式,如果设置被忽略,那就不做处理,如果不是处理被忽略的状态,就捕获它。根据前面后台进程的例子,shell(前台)设置了新要开启的后台进程的处理方式为忽略,那么if
(signal(SIGINT,
SIG_IGN)
!=
SIG_IGN) 就为假
signal(SIGINT,
sig_int);就不执行。而判断句中的signal(SIGQUIT,
SIG_IGN)只是继续把信号处理方式重复设置为忽略(相当于没做处理)

下面针对中断信号的状态做一个通俗点的解释。


        /* STATUS_1 */

if (signal(SIGINT, SIG_IGN) != SIG_IGN)

        /* STATUS_2 */

    signal(SIGINT, sig_int);

        /* STATUS_3 */

假设在STATUS_1信号处理方式为忽略,那么在进程执行了signal(SIGINT,
SIG_IGN),然后进入STATUS_3,仍然为忽略状态。

假设在STATUS_1中信号处理方式不为忽略,则signal(SIGINT,
SIG_IGN) 将信号处理方式也设为忽略(也就是说进入STATUS_2时,STATUS_2中信号的处理方式为忽略),但是
signal(SIGINT,
sig_int)又将信号处理方式设置为捕获,且捕获函数为sig_int。因此STATUS_3时,信号的处理方式为sig_int捕获。

  signal缺陷:从上面的代码可以看出,signal需要通过改变信号的处理方式(上面的例子是一直讲信号处理方式设置为SIG_IGN)来获得当前信号处理方式; 简单地说就是它没有提供测试当前信号处理方式的功能,而需要在用户代码中来实现。sigaction提供了可以确定信号处理方式的方法,下面将给予介绍。

sigaction函数

sigaction提供了测试、改变某个信号处理方式的方法(测试盒改变可以一起进行),

函数原型:


#include <signal.h>

int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);

               Returns: 0 if OK,?1 on error

signo:信号处理方式

act:为空表示不改变信号动作,非空表示改变信号动作(处理方式)

oact:非空时,此函数执行前信号的响应动作(处理方式)

结构体struct sigaction


struct sigaction {

    void  (*sa_handler)(int); /* addr of signal handler, */

    /* or SIG_IGN, or SIG_DFL */

   sigset_t sa_mask; /* additional signals to block */

   int  sa_flags; /* signal options, Figure 10.16 */

   /* alternate handler */

   void  (*sa_sigaction)(int, siginfo_t *, void *);

};

当更改信号动作时,act->sa_handler包含信号处理函数的地址指针(与SIG_IGN or SIG_DFL相对).act->sa_mask说明一个信号集,在调用信号捕获函数前,该信号集加入到进程的信号屏蔽字中(sa_mask只是起一个说明的作用,在sigaction函数中,没有真正的操作,真正的关于屏蔽自的操作,在sigaction之前就应该先完成)。当信号捕获函数返回时,进程的信号屏蔽字复位。
这样,调用信号处理函数时,就能阻塞信号。如下所示:


   //设置新的信号屏蔽字, 阻塞信号

sigaction(signo, act, oact);//调用sigaction函数

   //复位信号屏蔽字

从上面说明可以看出,在调用信号处理函数时,操作系统建立的新屏蔽字阻塞该信号(包括正在被传送的信号)。这也保证了在处理信号过程中,如果信号在此发生,也会被阻塞。但是,大多数实现没有信号阻塞排队功能。也就是说,在处理信号时,不管信号在发生几次,均被阻塞,但是信号处理函数只会被调用一次。

一旦给信号被安装了一个动作,那么在sigaction调用显示改变它之前,这个信号动作将一直有效。这种处理方式不同于早期系统的信号不可靠的信号处理方式。可以说现在系统的信号处理方式是可靠的。

其它参数:

act->sa_flags指定了信号处理的一些选项。各个选项如下所示:

act->sa_sigaction是act->sa_handler的一个可替代的方案。如果SA_SIGINFO选项被设置,则使用


 sa_sigaction(int signo, siginfo_t* info,void *context);

否则默认使用:


sa_handler(signo);

siginfo_t包含了信号产生原因的相关信息。


struct siginfo {

    int  si_signo; /* signal number */

    int  si_errno; /* if nonzero, errno value from errno.h */

    int  si_code; /* additional info (depends on signal) */

    pid_t  si_pid; /* sending process ID */

    uid_t  si_uid; /* sending process real user ID */

    void  *si_addr; /* address that caused the fault */

    int  si_status; /* exit value or signal number */

    union sigval si_value; /* application-specific value */

    /* possibly other fields also */

};

union sigval:


union sigval{

    int  sival_int;

    void *sival_ptr;

}

Example

用sigaction函数实现signal


#include "apue.h"

/* Reliable version of signal(), using POSIX sigaction(). */

Sigfunc *

signal(int signo, Sigfunc *func)

{

    struct sigaction act, oact;

    act.sa_handler = func;

    sigemptyset(&act.sa_mask);

    act.sa_flags = 0;

    if (signo == SIGALRM) {

#ifdef  SA_INTERRUPT

        act.sa_flags |= SA_INTERRUPT;

#endif

    }else {

        act.sa_flags |= SA_RESTART;

    }

    if (sigaction(signo, &act, &oact) < 0)

        return(SIG_ERR);

    return(oact.sa_handler);

}
时间: 2024-10-13 17:09:47

APUE学习笔记——10信号——信号接口函数 signal 和 sigaction的相关文章

APUE学习笔记——10.9 信号发送函数kill、 raise、alarm、pause

转载注明出处:Windeal学习笔记 kil和raise kill()用来向进程或进程组发送信号 raise()用来向自身进程发送信号. #include <signal.h> int kill(pid_t pid,int signo); int raise(int signo); Both return: 0 if OK,?1 on error kill向进程号为pid的进程发送signo信号 能够看出 以下两行代码是等价的: kill(getpid(), signo); raise(sig

APUE学习笔记——10.可靠信号与不可靠信号

首先说明:现在大部分Unix系系统如Linux都已经实现可靠信号. 1~31信号与SIGRTMIN-SIGRTMAX之间并不是可靠信号与不可靠信号的区别,在大多数系统下他们都是可靠信号. 只不过: 1~31信号                              --不支持排队,为普通信号. SIGRTMIN-SIGRTMAX信号 --支持排队,实时信号 不可靠信号 什么是不可靠信号: 不可靠的意思是信号可能丢失或者被错误处理. 在早起系统中,信号存在两大缺陷,导致了信号不可靠. 缺陷一:

APUE学习笔记——10.11~10.13 信号集、信号屏蔽字、未决信号

如有转载,请注明出处:Windeal专栏 首先简述下几个概念的关系: 我们通过信号集建立信号屏蔽字,使得信号发生阻塞,被阻塞的信号即未决信号. 信号集: 信号集:其实就是一系列的信号.用sigset_t set表示. 数据类型:sigset_t 类似于整型(位数可能超过整型,因而不能用整型表示). 我们一般在sigprocmask()等函数中使用信号集,用于创建一系列进程要阻塞的信号,告诉内核不允许这些信号发生. 几个关于信号集的函数: #include <signal.h> int sige

APUE学习笔记——10.18 system函数 与waitpid

system函数 system函数用方便在一个进程中执行命令行(一行shell命令). 用法如下: #include <stdio.h> #include <stdlib.h> int main() { printf("Hello\n"); system("sleep 5"); return 0; } 在程序中通过system调用了命令行 sleep 5.(这里知识举一个例子,当然可以执行一个类似" bash test.sh&quo

APUE学习笔记——10.15 sigsetjmp和siglongjmp

转载自:sigsetjmp使用方法 如侵犯您的权益,请联系:[email protected] sigsetjmp使用方法 分类: c/c++ linux2012-02-03 12:33 1252人阅读 评论(0) 收藏 举报 signal 相关函数:longjmp, siglongjmp, setjmp 表头文件:#include <setjmp.h> 函数定义:int sigsetjmp(sigjmp_buf env, int savesigs) 函数说明:sigsetjmp()会保存眼下

APUE学习笔记——10 信号(一)——信号介绍

信号的基本概念 信号是软件中断,信号提供了解决异步时间的方法. 每一中信号都有一个名字,信号名以SIG开头. 产生信号的几种方式 很多条件可以产生信号: 终端交互:用户按下某一些按键,如ctl+c,会产生信号. 硬件异常:如除数为0,内存引用错误.    kill(2)函数:将信号发送到一个进程或者进程组   kill(1)命令:该命令为kill(2)函数的接口.用于终止失控的后台in成.  检测到某软件条件发生:如网络连接上传来外数据(产生SIGURG信号),闹钟超时(产生SIGALRM信号)

APUE学习笔记——3.10文件共享

基本概念 内核使用3个数据结构描述一个打开的文件:进程表.文件表.V节点表 首先了解3种数据结构的概念 1 进程表 每一个进程有一个进程表.进程表里是一组打开的文件描述符,如标准输入0,标准输出1,标准错误2... 2 文件表 进程打开一个文件时,内核就为该文件创建一个文件表. 进程表对文件表是 一对多的关系    文件表对文件描述符也是一对多的关系(可能多个文件描述符指向同一文件表) 文件表中包含了文件状态标志.当前偏移量.和V-node pointer表项 文件状态标志如:read,  wr

MySQL学习笔记10(MySQL函数)

MySQL学习笔记10 MySQL函数 MySQL数据库中提供了很丰富的函数.MySQL函数包括数学函数.字符串函数.日期和时间函数.条件判断函数.系统信息函数.加密函数.格式化函数等.通过这些函数,可以简化用户的操作.SELECT语句及其条件表达式都可以使用这些函数.同时,INSERT.UPDATE.DELECT语句及其条件表达式也可以使用这些函数. 1:数学函数 数学函数是M有SQL中常用的一类函数.主要用于处理数字,包括整型.浮点数等.数学函数包括绝对值函数.正弦函数.余弦函数.获取随机数

APUE学习笔记:第九章 进程关系

9.1 引言 本章将更详尽地说明进程组以及POSIX.1引入的会话的概念.还将介绍登陆shell(登录时所调用的)和所有从登陆shell启动的进程之间的关系. 9.1 终端登陆 系统管理员创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名传递给getty程序的参数.当系统自举时,内核创建进程ID为1的进程,依旧是init进程.init进程使系统进入多用户状态.init进程读文件/etc/ttys,对每一个允许登陆的终端设备,init调用一次fork,所生成的子进程则