zoukankan      html  css  js  c++  java
  • UNIX10-信号

    UNIX编程第10章

    信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法,例如,终端用户键入中断键,会通过信号机制停止一个程序,或及早终止管道中的下一个程序。

    每个信号都有一个名字,这些名字都以SIG开头,例如SIGABRT是夭折信号,当进程调用abort函数时产生这种信号。SIGALARM是闹钟信号,由alarm函数设置的定时器超时后将产生此信号。在头文件<signal.h>中,信号名都被定义为正整数常量(信号编号)。

    不存在编号为0的信号,空信号。

    很多条件可以产生信号:

    -当用户按某些终端键时,引发终端产生的信号。在终端上按Delete键(或CTRL+c键)通常产生中断信号(SIGINT)。这是停止一个已失去控制程序的方法。

    -硬件异常信号:除数为0、无效的内存引用等。这些条件通常由硬件检测到,并通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如对执行一个无效内存引用的进程产生SIGSEGV信号。

    -进程调用kill(2)函数可将任意信号发送给另一个进程或进程组。不过对此有所限制:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

    -进程调用kill(1)命令将信号发送给其它进程。此命令只是kill函数的接口。通常使用此命令终止一个失控的后台进程。

    -当检测到某些软件条件已经发生,并应将其通知有关进程时也产生信号。这里指的不是硬件产生条件(如除以0),而是软件条件。例如SIGURG(在网络连接上传来带外的数据)、SIGPIPE(在管道的读进程已终止后,一个进程写此管道)以及SIGALRM(进程所设置的定时器已经超时)。

    在某个信号出现时,可以告诉内核按以下3种方式之一进行处理,称之为信号的处理或与信号相关的动作:

    -忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号决不能被忽略。它们是SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的。

    -捕捉信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对此种事件进行的处理。但是不能捕捉SIGKILL和SIGSTOP信号。

    -执行系统默认动作。大多数系统默认动作是终止该进程。

    大多数UNIX系统使用core文件检查进程终止时的状态。

    在下列条件下不产生core文件:

    -进程是设置用户ID的,而且当前用户并非程序文件的所有者;

    -进程是设置组ID的,而且当前用户并非该程序文件的组所有者;

    -用户没有写当前工作目录的权限;

    -文件已存在,而且用户对该文件没有写权限;

    -文件太大(RLIMIT_CORE限制);

    core文件的权限(假定该文件在此之前并不存在)通常是用户读/写。

    信号和描述:

    SIGABRT  调用abort函数时产生此信号。进程异常终止。

    SIGLARM  当用alarm函数设置的定时器超时时,产生此信号。若由setitimer(2)函数设置的间隔时间已经超时,也产生此信号。

    SIGCHLD  在一个进程终止或停止时,SIGCHLD信号被送给其父进程。按系统默认,将忽略此信号,如果父进程希望被告知其子进程的这种状态改变,则应捕捉此信号。信号捕捉函数通常调用一种wait函数已取得子进程的ID和终止状态。

    SIGCONT  此作业控制信号发送给需要继续运行,但当前处于停止状态的进程。如果接收到此信号的进程处于停止状态,则系统默认动作是使该进程继续运行;否则默认动作是忽略此信号。

    SIGHUP  如果终端接口检测到一个连接断开,则将此信号送给与该终端相关的控制进程(会话首进程)。此信号送给session节后中s_leader字段所指向的进程。仅当终端的CLOALL标志没有设置时,在上述条件下才产生此信号。接到此信号的会话首进程可能在后台,这区别于由终端正常产生的几个信号(中断、退出和挂起),这些信号总是传递给前台进程组。如果会话首进程终止,也产生此信号,在这种情况下,此信号送给前台进程组中的每一个进程。通常用此信号通知守护进程再次读取它们的配置文件,选用SIGHUP的理由是,守护进程不会有控制终端,通常绝不会接收到这种信号。

    SIGINT  当用户按中断键(一般采用delete或ctrl+c)时,终端驱动程序产生此信号并发送至前台进程组的每一个进程。当一个进程在运行时失控,特别是它正在屏幕上产生大量不需要的输出时,常用此信号终止它。

    SIGIO  此信号指示一个异步I/O事件。

    SIGKILL  这是两个不能被捕捉或忽略的信号之一,它向系统管理员提供了一种可以杀死任一进程的可靠方法。

    SIGPIPE  如果在管道的读进程已经终止时写管道,则产生此信号。当类型为SOCK_STREAM的套接字已不再连接时,进程写该套接字也产生此信号。

    SIGQUIT  当用户在终端上按退出键(一般采用ctrl+)时,中断驱动程序产生此信号并发送给前台进程组中的所有进程。此信号不仅终止前台进程组(像SIGINT那样),同时产生一个core文件。

    SIGSEGV  指示进程进行了一次无效的内存引用(通常说明程序有错,比如访问了一个未经初始化的指针)。

    SIGSTOP  这是一个作业控制信号,它停止一个进程。它类似于交互停止信号(SIGTSTP),但是SIGSTOP不能被捕捉或忽略。

    SIGTERM  这是由kill(1)命令发送的系统默认终止信号。由于该信号是由应用程序捕获的,使用SIGTERM也让程序有机会在退出之前做好清理工作,从而优雅地终止(相对于SIGKILL而言,SIGKILL不能被忽略或捕捉)。

    SIGTSTP  交互停止信号,当用户在终端上按挂起键(一般ctrl+z)时,终端驱动程序产生此信号。该信号发送至前台进程组中的所有进程。

    SIGTTIN  当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。在下列例外的情形下不产生此信号:读进程忽略或阻塞此信号;读进程所属的进程组是孤儿进程组,此时读操作返回出错,error设置为EIO。

    SIGTTOU  当一个后台进程组试图写其控制终端时,终端驱动程序产生此信号。与上述的SIGTTIN不同,一个进程可以选者允许后台进程写控制终端。如果不允许写,则与SIGTTIN相似,也有两种特殊情况:写进程忽略或阻塞此信号;写进程所属的进程组是孤儿进程组,此时不产生信号,写操作返回出错,error设置为EIO。

    SIGURG  此信号通知进程已经发生一个紧急情况。在网络连接上接到带外数据时,可选择地产生此信号。

    SIGUSR1  这是一个用户定义的信号,可用于应用程序。

    SIGUSR2  这是另一个用户定义的信号,与SIGUSR1相似,可用于应用程序。

    函数signal:

    UNIX系统信号机制最简单的接口是signal函数。

    #include<signal.h>

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

      若成功返回以前的信号处理配置;若出错返回SIG_ERR;

    signo参数是信号名。func的值是常量SIG_IGN、常量SIG_DFL或当接收到此信号后要调用的函数的地址。

    如果指定的是SIG_IGN,则向内核表示忽略此信号(记住有两个信号SIGKILL和SIGSTOP不能忽略)。如果指定的SIG_DEL,则表示接收到此信号后的动作是系统默认动作。当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为捕捉该信号,称此函数为信号处理程序或信号捕捉函数。

    signal函数原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。第一个参数signo是一个整形数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。signal的返回值是一个函数地址,该函数有一个整型参数(即最后的(int))。 

    即signal的第二个参数是指向要设置的信号处理函数的指针,而返回值是指向之前的信号处理函数的指针。

    系统头文件<signal.h>种:

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

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

    #define SIG_IGN (void(*)())1

    这些常量可用于表示“指向函数的指针,该函数要求一个整型参数,而且无返回值”。signal的第二个参数就可以由它们表示。这些常量所使用的3个值不一定是-1、1和1,但它们必须是3个值而决不能是任一函数的地址。

    当执行一个程序时,所有的信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切地讲,exec函数将原先设置为要捕捉的信号都更改为默认动作,其它信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就不能捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。

    当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程开始时复制了父进程内存映像,所以信号捕捉函数的地址在子进程中是有意义的。

    不可靠的信号:

    在早期的UNIX系统中信号是不可靠的。信号可能会丢失,且对信号的控制能力很差。

    程序中设置了signal(SIGINT, func);设置了SIGINT的信号处理函数。在SIGINT信号发生之后到信号处理程序调用func之间有一个时间窗口,在这段时间中,可能发生另外一次中断信号。第二个信号的默认动作是终止该进程。

    在进程不希望某种信号发生时,它不能关闭该信号。

    中断的系统调用:

     早期UNIX系统的一个特性是:如果该进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再执行。该系统调用返回出错,其errno设置为EINTR,这样处理是因为一个信号发生了,进程捕捉到它,这意味着已经发生了某种事情,所以有机会应当唤醒阻塞的系统调用。

    为了支持这种特性,将系统调用分为两类:低速系统调用和其它系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:

    -如果某些类型文件(如读管道、终端设备和网络设备)的数据不存在,则读操作可能会使调用者永远阻塞;

    -如果这些数据不能被相同的类型文件立即接受,则写操作可能会使调用者永远阻塞;

    -在某种条件发生之前打开某些类型文件,可能会发生阻塞(例如要打开一个终端设备,需要先等待与之连接的调制解调器应答);

    -pause韩数(按照定义,它使调用进程休眠直至捕捉到一个信号)和wait函数;

    -某些ioctl函数;

    -某些进程间通信函数。

    在这些低速调用中,一个值得注意的例外是与磁盘I/O有关的系统调用。虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,I/O操作总会很快返回,并使调用者不再处于阻塞状态。

    可以用中断系统调用处理一些阻塞的系统调用,被中断的系统调用显示地处理出错返回,一些系统调用会自动重启,如ioctl、read、readv、write、writev、wait和waitpid。前5个函数只对低速设备进行操作时才会被信号中断。而wait和waitpid在捕捉到信号时总是被中断。

    可重入函数:

    进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时进程正在执行的正常指令序列。但是在信号处理程序中,不能判断捕捉到信号时进程执行到何处。如果进程正在执行malloc,在其堆中分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc,这时会发生什么?因为malloc通常为它所分配的存储区维护一个链表,而插入执行信号处理程序时,进程可能正在更改此链表。

    Single UNIX Specification说明了在信号处理程序中保证调用安全的函数。这些函数是可重入的并被称为是异步信号安全的(async-signal safe)。除了可重入外,在信号处理操作期间,它会阻塞任何会引起不一致的信号发送。

    一些不可重入函数的特征:

    -使用静态数据结构;

    -调用malloc或free;

    -是标准I/O函数,标准I/O库的很多实现都以不可重入方式使用全局数据结构。

    可靠信号术语和语义:

    当造成信号的事件发生时,为进程产生一个信号(或向进程发送一个信号)。事件可以是硬件异常(如除以0)、软件条件(如alarm定时器超时)、终端产生的信号或调用kill函数。当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。

    当对信号采取了这种动作时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。

    进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程对此信号解除了阻塞,或者将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作。进程调用sigpending函数来判断哪些信号是设置为阻塞并处于未决状态的。

    如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,那么将如何?POSIX.1允许系统递送该信号一次或多次。如果递送该信号多次,则称这些信号进行了排队。但是除非支持POSIX.1实时扩展,否则大多数UNIX并不对信号排队,而是只递送这种信号一次。

    如果有多个信号要递送给一个进程,那将如何呢?POSIX.1并没有规定这些信号的递送顺序,但是POSIX.1基础部分建议:在其它信号之前递送与进程当前状态有关的信号,如SIGSEGV。

    每个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞递送到该进程的信号集。对于每种可能的信号,该屏蔽字中都有一位与之对应。对于某种信号,若其对应位已设置,则它当前是被阻塞的。进程可以调用sigprocmask来检测和更改其当前信号屏蔽字。

    信号编号可能会超过一个整型所包含的二进制位数,因此POSIX.1定义了一个新数据类型sigset_t,它可以容纳一个信号集。例如,信号屏蔽字就存放在其中一个信号集中。

    函数kill和raise:

    kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

    #include<signal.h>

    int kill(pid_t pid, int signo);

    int raise(int signo);

      两个函数成功返回0,出错返回-1;

    raise(signo)等同于kill(getpid(), signo);

    kill的pid参数有以下4种情况:

    -pid>0  将该信号发送给进程ID为pid的进程;

    -pid==0  将该信号发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有权限向这些进程发送信号。这里的“所有进程”不包括实现定义的系统进程集。对于大多数UNIX系统,系统进程集包括内核进程和init(pid为1)。

    -pid<0  将该信号发送给其它进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程。这里的所有进程不包括系统进程集中的进程。

    -pid==-1  将该信号发送给发送进程有权限向它们发送信号的所有进程。不包括系统进程集中的进程。

    进程将信号发送给其它进程需要权限。超级用户可将信号发送给任一进程。对于非超级用户,其基本规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。在对权限进行测试时也有一个特例:如果被发送的信号是SIGCONT,则进程可将它发送给属于同一会话的任一其它进程。

    POSIX.1将信号编号0定义为空信号。如果signo参数是0,则kill仍执行正常的错误检查,但不发送信号。这常被用来确定一个特定进程是否仍然存在。如果向一个并不存在的进程发送空信号,则kill返回-1,errno被设置为ESRCH。但是,应当注意,UNIX系统在经过一定时间后会重新使用进程ID,所以一个现有的具有所给定进程ID的进程并不一定就是想要的进程。

    测试进程是否存在的操作不是原子操作。在kill向调用者返回测试结果时,原来已存在的被测试进程此时可能已经终止,所以这种测试并无多大价值。

    函数alarm和pause:

    使用alarm函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。

    #include<unistd.h>

    unsigned int alarm(unsigned int seconds);

      返回值:0或以前设置的闹钟时间的余留秒数。

    参数seconds的值是产生信号SIGALRM需要经过的时钟秒数。当这一时刻达到时,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而能够处理该信号还需要一个时间间隔。

    每个进程只能有一个闹钟时间。如果在调用alarm时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回,以前注册的闹钟时间则被新值代替。

    如果有以前注册的尚未超过的闹钟时间,而且本次调用的seconds值是0,则取消以前的闹钟时间,其余留值仍作为alarm函数的返回值。

    SIGALRM的默认动作是终止进程。如果想要捕捉SIGALRM信号,则必须在调用alarm函数之前安装该信号的处理程序。

    #include<unistd.h>

    int pause(void);

      返回值:-1,errno设置为EINTR;

    只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,errno设置为EINTR。

    信号集:

    我们需要一个能表示多个信号----信号集(signal set)的数据类型。我们将在sigprocmask类函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。不同的信号的编号可能超过一个整型量所包含的位数,所以POSIX.1定义数据集sigset_t以包含一个信号集,并且定义了下列5个处理信号集的函数。

    #include<signal.h>

    int sigemptyset(sigset_t *set);

    int sigfillset(sigset_t *set);

    int sigaddset(sigset_t *set, int signo);

    int sigdelset(sigset_t *set, int signo);

      4个函数返回值:成功返回0,出错返回-1;

    int sigismember(const sigset_t *set, int signo);

      若真返回1,若假返回0;

    函数sigemptyset初始化由set指向的信号集,清除其中所有信号。函数sigfillset初始化由set指向的信号集,使其包含所有信号。所有应用程序在使用信号集前,要对信号集调用sigemptyset或sigfillset一次。一旦初始化了一个信号集,以后就可以在该信号集中增、删特定的信号sigaddset添加信号到信号集,sigdelset删除信号集中指定信号。对于所有以信号集作为参数的函数,总是以信号集地址作为向其传送的参数。

    sigemptyset函数将整型设置为0,sigfillset将整型中的各位设置为1.这两个函数可以在<signal.h>中实现为宏:

    #define sigemptyset(ptr) (*(ptr)=0)

    #define sigfillset(ptr) (*(ptr)=~(sigset_t),0, 0)

    sigfillset必须返回0.使用C语言的逗号运算符将逗号运算符后的值作为表达式的值返回。

    函数sigprocmask:

    函数sigprocmask可以检测或更改,或同时进行检测和更改进程的信号屏蔽字。

    #include<signal.h>

    int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict aset);

      若成功返回0,若出错返回-1;

    首先,若aset是非空指针,那么进程的当前信号屏蔽字通过aset返回。

    其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

    how可选的值:

    -SIG_BLOCK  该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了希望阻塞的附加信号;

    -SIG_UNBLOCK  该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向的信号集补集的交集。set包含了希望解除阻塞的信号;

    -SIG_SETMASK  该进程新的信号屏蔽字是set指向的值。

    如果set是空指针,则不改变信号的屏蔽字,how也无意义了。

    在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少将其一递送给该进程。

    (sigprocmask是仅为单线程进程定义的。处理多线程中信号屏蔽字使用另一个函数。)

    函数sigpending:

    sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也是一组当前未决的。该信号集通过set参数返回。

    #include<signal.h>

    int sigpending(sigset_t *set);

      成功返回0,出错返回-1;

    函数sigaction:

    sigaction函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。

    #include<signal.h>

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

      成功时返回0,出错返回-1;

    参数signo是要检查或修改其具体动作的信号编号。若act指针非空,则要修改其动作。如果oact指针非空,则系统经由oact指针返回该信号的上一个动作。此函数使用下列结构:

    struct sigaction{

      void (*sa_handler)(int);  //指向信号处理函数的指针,或者SIG_IGN、SIG_DFL

      sigset_t sa_mask;  //额外的要阻塞的信号

      int sa_flags;  //信号的选项

      //可选的handler

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

    };

    当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址(不是常量SIG_IGN或SIG_DFL),则sa_mask字段说明了一个信号集,再调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。这样在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。

    如果系统不支持信号队列,那么当同一种信号多次发生时,通常不将它们加入队列,所以如果在某种信号被阻塞时,它发生了5次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。

    一旦对给定的信号设置了一个动作,那么在调用sigaction函数显示地改变它之前,改设置就一直有效。

    早期signal函数存在缺陷:在信号递送给进程后(将要执行信号处理函数),signal函数不会设置对该信号的阻塞,但是会将该信号的处理程序设置为默认行为。所以,如果同意信号多次发生,在第一个信号发生到信号处理程序调用signal函数之间会有一段时间间隔,如果这次期间再次发生了该信号,则第二次发生的信号会执行默认行为。

    act结构的sa_flags字段指定对信号进行处理的各个选项。

    sa_flags可取如下值:

    SA_INTERRUPT  由此信号中断的系统调用不自动重启动。

    SA_NOCLDSTOP  若signo是SIGCHLD,当子进程停止(作业控制),不产生此信号。当子进程终止时,仍旧产生此信号。若已设置此标志,则当停止的进程继续运行时,作为XSI扩展,不产生SIGCHLD信号。

    SA_NOCLDWAIT  若signo是SIGCHLD,则当调用进程的子进程终止时,不创建僵尸进程。若调用进程随后调用wait,则阻塞到它所有子进程都终止,此时返回-1,errno设置为ECHLD。

    SA_NODEFER  当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask包括了此信号)。注意,此种类型的操作对应于早期的不可靠信号。

    SA_ONSTACK  若用sigaltstack(2)已声明一个替换栈,则此信号递送给替换栈上的进程。

    SA_RESETHAND  在此信号捕捉函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除SA_SIGINFO标志。注意,此种类型的信号对应于早期的不可靠信号。但是不能自动重置SIGILL和SIGTRAP这两个信号的配置。设置此标志使sigaction的行为如同设置了SA_NODEFER标志。

    SA_RESTART  由此信号中断的系统调用自动重启动。

    SA_SIGINFO  此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针。

    sa_sigaction字段是一个替代的信号处理程序,在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。对于sa_sigaction字段和sa_handler字段两者,实现可能使用同一存储区,所以应用只能一次使用这两个字段中的一个。

    通常,按下列方式调用信号处理程序:

    void handler(int signo);

    但是,如果设置了SA_SIGINFO标志,那么按下列方式调用信号处理程序:

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

    siginfo结构包含了信号产生原因的有关信息。符合POSIX.1的所有实现必须包括si_signo和si_code成员。另外,符合XSI的实现至少包含下列字段:

    struct siginfo{

      int si_signo;  //信号序号

      int si_errno;  //如果非0,errno从<errno.h>中获得值

      int si_code;  //额外的信息(依据信号而定

      pid_t si_pid;  //发送进程ID

      uid_t si_uid;  //发送进程实际用户ID

      void *si_addr;  //造成fault的地址

      int si_status;  //退出值或者信号编号

      union sigval si_val;  //application-specific value

      //可能还有其它成员//

    };

    sigval联合包含下列字段:

    int sival_int;

    void *sival_ptr;

    应用程序在递送信号时,在si_value.sival_int中传递一个整型数或者在si_value.sival_ptr中传递一个指针值。

    若信号是SIGCHLD,则将设置si_pid、si_status和si_uid字段。若信号是SIGBUS、SIGILL、SIGFPE或SIGSEGV,则si_addr包含造成故障的根源地址,该地址可能并不准确。si_errno字段包含错误编号,它对应于造成信号产生的条件,并由实现定义。

    信号处理程序sa_sigaction的context参数是无类型指针,它可被强制类型转换为ucontext_t结构类型,该结构标识信号传递时进程的上下文。该结构至少包含下列字段:

    ucontext_t *uc_link;  //指向当前上下文返回后的重新开始的上下文

    sigset_t uc_sigmask;  //当前上下文active时的信号屏蔽字

    stack_t uc_stack;  //当前上下文使用的栈

    mcontext_t uc_mcontext;  //保留上下文的机器独特(machine-specific)表示

    uc_stack字段描述了当前上下文使用的栈,至少包含下列成员:

    void *ss_sp;  //栈base或指针

    size_t ss_size;  //栈size

    int ss_flags;  //flags

    函数sigsetjmp和siglongjmp:

    调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数。此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢?

    为了允许两种形式并存,POSIX.1并没有指定setjmp和longjmp对信号屏蔽字的作用,而是定义了两个新函数sigsetjmp和siglongjmo。在信号处理程序中进行非局部转移时应当使用这两个函数。

    #include<setjmp.h>

    int sigsetjmp(sigjmp_buf env, int savemask);

      返回值:若直接调用返回0,若从siglongjmp调用返回非0;

    void siglongjmp(sigjmp_buf env, int val);

    这两个函数和setjmp、longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带有非0的savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。

    函数sigsuspend:

    上面已经说明,更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞。使用这种技术可以保护不希望由信号中断的代码临界区。

    如果希望对一个信号解除阻塞,然后pause以等待以前被阻塞的信号发生,会如何呢?

    如果在信号阻塞时,产生了信号,那么该信号的传递就被推迟直到对它解除了阻塞。如果信号发生在解除阻塞到调用pause之间的时间窗口中,则pause之后不再有信号递送,pause将会一直阻塞。这是早期的不可靠信号机制的一个问题。

    为了纠正上面的问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠。这种功能由sigsuspend函数所提供。

    #include<signal.h>

    int sigsuspend(const sigset_t*sigmask);

      //返回值:-1,并将errno设置为EINTR;

    进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。

    此函数没有成功的返回值。如果它返回到调用者,则总是返回-1,并将errno设置为EINTR(表示一个被中断的系统调用)。

    sigsuspend的应用:可以保护代码临界区,使其不被特定信号中断;等待一个信号处理程序设置一个全局变量;可以用信号实现父、子进程之间的同步,这是信号应用的;另一个实例

    函数abort:

    abort函数的功能是使程序异常终止。

    #include<stdlib.h>

    void abort(void);

      不返回值。

    此函数将SIGABRT信号发送给调用进程(进程不应忽略此信号)。ISO C规定,调用abort将向主机环境递送一个未成功终止的通知,其方法是调用raise(SIGABRT)函数。

    ISO C要求捕捉到此信号而且相应信号处理程序返回,abort仍不会返回到其调用者。如果捕捉到此信号,则信号处理程序不能反悔的唯一方法是它调用exit、_exit、_Exit、longjmp或siglongjmp。POSIX.1也说明abort并不理会进程对此信号的阻塞和忽略。

    让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清理操作。如果进程并不在信号处理程序中终止自己,POSIX.1声明当前信号处理程序返回时,abort终止该进程。

    ISO C针对此函数的规范将下列问题留由实现决定:是否要冲洗输出流以及是否要删除临时文件。POSIX.1的要求则更进一步,它要求如果abort调用终止进程,则它对所有打开标准I/O流的效果应当与进程终止前对每个流调用fclose相同。

    函数system:

    POSIX.1要求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD。

    函数sleep、nanosleep和clock_nanosleep:

    #include<unistd.h>

    unsigned int sleep(unsigned int seconds);

      返回值:0或未休眠完的秒数;

    此函数使调用进程被挂起直到满足下面两个条件之一:

    -已经过了seconds所指定的墙上时间。

    -调用进程捕捉到一个信号并从信号处理程序返回。

    如同alarm一样,由于其它系统活动,实际返回时间比所要求的会迟一些。

    尽管sleep可以用alarm函数实现,但这并不是必需的。如果使用alarm则这两个韩式之间可能相互影响。(进程只会留下最新的alarm指定的时间)。(Linux内核中使用timer_list结构来表示定时器)

    nanosleep函数与sleep函数类似,但提供了纳秒级的精度:

    #include<time.h>

    int nanosleep(const struct timespec *reqtp, struct timespec *remtp);

      返回值:若休眠到要求的时间,返回0;若出错返回-1.

    这个函数挂起调用进程,直到要求的时间已经超过或者某个信号中断了该函数。reqtp参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,remtp参数指向的timespec结构就会被设置为未休眠的时间长度。如果对未休眠完的时间并不感兴趣,可以将该参数置为NULL。

    如果系统并不支持纳秒这一精度,要求的时间就会取整。因为nanosleep函数并不涉及产生任何信号,所以不需要担心与其它函数的交互。

    随着多个系统时钟的引入,需要使用相对于特定时钟的延迟时间来挂起调用线程。clock_nanosleep函数提供了这种功能。

    #include<time.h>

    int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *reqtp, struct timespec *remtp);

      返回值,若休眠要求的时间则返回0,出错则返回错误码;

    clock_id参数指定了计算延迟时间基于的时钟。flags参数用于控制延迟是相对的还是绝对的。flags为0表示休眠时间是相对的,如果flags值设置为TIMER_ABSTIME,表示休眠时间是绝对的。其它的参数reqtp和remtp,与nanosleep函数中的相同。但是使用绝对时间时,remtp参数未使用,因为没有必要。

    函数sigqueue:

    大部分UNIX系统不对信号排队。在POSIX.1的实时扩展中,有些系统开始增加对信号排队的支持。

    通常一个信号带有一个位信息:信号本身。除了对信号排队以外,这些扩展允许应用程序在递交信号时传递更多的信息。这些信息嵌入在siginfo结构中。除了系统提供的信息,应用程序还可以向信号处理程序传递整数或者指向包含更多信息的缓冲区指针。

    使用排队信号必须做以下几个操作:

    -使用sigaction函数安装信号处理程序时指定SA_SIGINFO标志。如果没有给出这个标志,信号会延迟,但信号是否进入队列要取决于具体实现;

    -在sigaction结构的sa_sigaction成员中提供信号处理程序。实现可能允许用户使用sa_handler字段,但不能获取sigqueue函数发送出来的额外信息。

    -使用sigqueue函数发送信号。

    #include<signal.h>

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

    sigqueue函数只能把信号发送给单个进程,可以使用value参数向信号处理程序传递整数和指针值,除此之外,sigqueue函数与kill函数类似。

    信号不能被无限排队。SIGQUEUE_MAX限制。到达相应的限制以后,sigqueue就会失败,将errno设置为EAGAIN。

    随着实时信号的增强,引入了用于应用程序的独立信号集。这些信号的编号在SIGRTMIN~SIGRTMAX之间,包括这两个限制值。注意这些信号的默认行为是终止进程。

    Linux支持sigqueue,并且调用者即使没使用SA_SIGINFO标志,也对信号排队。

    作业控制信号:

    POSIX.1认为以下6个信号月作业控制有关:

    -SIGCHLD  子进程已停止或终止;

    -SIGCONT  如果进程已停止,则使其继续运行;

    -SIGSTOP  停止信号;

    -SIGTSTP  交互式停止信号;

    -SIGTTIN  后台进程组成员读控制终端;

    -SIGTTOU  后台进程组成员写控制终端;

    除了SIGCHLD之外,大多数应用程序并不处理这些信号,交互式shell则通常会处理这些信号的所有工作。

    在作业控制信号间有某些交互。当对一个进程产生4种停止信号(SIGTSTP、SIGSTOP、SIGTTIN或SIGTTOU)中任意一种时,对该进程的任一未决SIGCONT信号就被丢弃。与此类似,当对一个进程产生SIGCONT信号时,对同一进程的任一未决停止信号被丢弃。

    注意,如果进程是停止的,则SIGCONT的默认动作是继续该进程;否则忽略此信号。通常,对该信号无需做任何事情。当对一个停止的进程产生一个SIGCONT信号时,该进程就继续,即使该信号是被阻塞或忽略的也是如此。

    信号名和编号:

    某些系统提供数组:

    extern char*sys_siglist[];

    数组下标是信号编号,数组中的元素是指向信号名字符串的指针。

    可以使用psignal函数可移植地打印与信号编号对应的字符串。

    #include<signal.h>

    void psignal(int signo, const char*msg);

    字符串msg输出到标准错误文件,后面跟随着一个冒号和一个空格,再后面对该信号的说明,最后是一个换行符。如果msg为NULL,只有信号说明部分输出到标准错误文件。

    如果在sigaction信号处理程序中有siginfo结构,可以使用psiginfo函数打印信号信息:

    #include<signal.h>

    void psiginfo(const siginfo_t *info, const char *msg);

    它的工作方式与psignal函数类似。虽然这个函数访问除信号编号以外的更多信息,但不同平台输出的这些额外信息可能有所不同。

    如果只需要信号的字符描述部分,也不需要把它写到标准错误文件中,可以使用strsignal函数,它类似于strerror:

    #include<string.h>

    char *strsignal(int signo);

      返回值:指向描述该信号的字符串的指针。

  • 相关阅读:
    AngularJS总结
    网页的颜色表示方法
    计算机中的字符编码
    计算机中的进制
    常用的HTML 标签二
    常用的HTML标签
    常用的字符实体标记
    一个请求的访问流程
    http请求访问过程
    codeforces 269C Flawed Flow(网络流)
  • 原文地址:https://www.cnblogs.com/cjj-ggboy/p/12292022.html
Copyright © 2011-2022 走看看