[单刷APUE系列]第十章——信号[2]

目录

[单刷APUE系列]第一章——Unix基础知识[1]
[单刷APUE系列]第一章——Unix基础知识[2]
[单刷APUE系列]第二章——Unix标准及实现
[单刷APUE系列]第三章——文件I/O
[单刷APUE系列]第四章——文件和目录[1]
[单刷APUE系列]第四章——文件和目录[2]
[单刷APUE系列]第五章——标准I/O库
[单刷APUE系列]第六章——系统数据文件和信息
[单刷APUE系列]第七章——进程环境
[单刷APUE系列]第八章——进程控制[1]
[单刷APUE系列]第八章——进程控制[2]
[单刷APUE系列]第九章——进程关系
[单刷APUE系列]第十章——信号[1]
[单刷APUE系列]第十章——信号[2]shell

SIGCLD信号

SIGCLD和SIGCHLD是两个很类似的信号,SIGCLD是SystemV的一个信号名字,而SIGCHLD是BSD信号,可是POSIX.1标准使用了BSD的SIGCHLD信号名称。
BSD的SIGCHLD信号是很普通的意思,就是子进程状态改变就会产生这个信号,父进程则是调用wait函数查看子进程的状态,而SystemV的SIGCLD信号则不一样,基于SVR4的系统都会继承这个状况。segmentfault

  1. 若是进程明确配置SIGCLD信号为SIG_IGN,则调用进程的子进程不产生僵尸进程,在前面说过,若是子进程退出,可是没有父进程进行清理,则会产生僵尸进程。而若是配置成SIG_IGN,则子进程在终止的时候,就会丢弃其退出状态,那么父进程使用wait函数就不会接收到任何的信息,直到全部的子进程终止。app

  2. 若是SIGCLD被设置为捕捉,则内核就会马上检查是否有子进程准备好了等待,函数

基本就是这些,实际上这个信号可看可不看。由于在实际开发中是不可能使用这个信号的,很多平台都不支持此信号。ui

可靠信号术语和语义

信号是事件发生时,为进程产生一个信号或者发送一个信号,当信号产生时,内核会在进程表中设置一个标志。在信号产生和传递中间,信号是阻塞的(pending)。
进程能够设置阻塞一个信号传送,若是对这个进程发送了一个已经设置为阻塞的信号,而且该信号的动做是系统默认动做或者捕捉该信号,换言之,就是不忽略该信号的处理,则为该进程将此信号设置为pending状态,直到该进程对此信号接触阻塞,或者设置该信号的动做为忽略。this

int sigpending(sigset_t *set);

The sigpending function returns a mask of the signals pending for delivery to the calling process in the location indicated by set.  Signals may be pending because they are currently masked, or transiently before delivery (although the latter case is not normally detectable).

实际上,每一个进程都有一个信号屏蔽字(signal mask),就和权限屏蔽字同样,这是用于记录当前要阻塞传递的信号集。操作系统

发送信号

发送信号有两种函数指针

int kill(pid_t pid, int sig);
int raise(int sig);

两个函数的区别就是一个是系统函数库,一个是ISO C函数库,而且raise函数容许进程向自身发送信号。
kill函数很经常使用,因此在这里讲一下kill函数的参数rest

If pid is greater than zero:
        Sig is sent to the process whose ID is equal to pid.

If pid is zero:
        Sig is sent to all processes whose group ID is equal to the process group ID of the sender, and for which the process has permission; this is a variant of killpg(2).

If pid is -1:
        If the user has super-user privileges, the signal is sent to all processes excluding system processes and the process sending the signal.  If the user is not the super user, the signal is sent to all processes with the same uid as the user, excluding the process sending the signal.  No error is returned if any process could be signaled.
For compatibility with System V, if the process number is negative but not -1, the signal is sent to all processes whose process group ID is equal to the absolute value of the process number.  This is a variant of killpg(2).
  1. pid大于0,信号发送给当前为pid的进程code

  2. pid等于0,信号发送给当前进程同一进程组的全部进程

  3. pid等于-1,信号发送给发送进程有权限发送信号的全部进程

为了保持SystemV的兼容,当pid小于0且不等于-1的时候,信号发送给全部进程组ID等于当前进程绝对值的进程。

alarm和pause函数

unsigned alarm(unsigned seconds);

alarm函数就是设置一个定时器,当超时后会产生一个SIGALRM信号,若是忽略或者系统默认动做,就是进程终止,固然,通常状况下,进程都捕捉该信号。

int pause(void);

pause函数会强制进程暂停直到从kill函数或者setitimer函数收到一个信号。因此当看到这两个函数,基本想法就是这两个函数能让进程休眠

#include <signal.h>
#include <unistd.h>

static void sig_alrm(int signo)
{
}

unsigned int sleep1(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        return seconds;
    alarm(seconds);
    pause();
    return(alarm(0));
}

固然,这样子写确定是有问题的,好比

  1. 在sleep1以前,已经存在定时器了,那么sleep函数中第一次alarm调用将会擦除以前的闹钟,

  2. 程序修改了SIGALRM信号的配置

  3. 在第一课次调用alarm和apuse之间有一个竞争条件。

信号集

为了可以表示多个信号,系统提供了信号集的数据类型。在一般的开发中,常常会使用到二进制位来表示状态,二进制的每一位表明一种信号,可是实际上,因为信号的编号确定会超过一个整形量的位数,因此通常都不是用一个整形量表示信号集。POSIX.1定义了数据类型sigset_t用以表示一个信号集,苹果系统下其实是这么表示的

typedef __uint32_t    __darwin_sigset_t;
typedef __darwin_sigset_t sigset_t;

除此之外,系统还定一款了下列5个处理信号集的函数

int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigismember(const sigset_t *set, int signo);

sigemptyset函数初始化set指向的信号集,清除全部信号,sigfillset初始化set指向的信号集,被设置为包含全部信号,在使用以前,sigemptyset或者sigfillset必须被调用。sigaddset和sigdelset则是添加删除一个信号,sigismember函数则返回是否一个指定的signo信号被包含在这个信号集中。

sigprocmask函数

在前文中说起了信号屏蔽字指定了当前进程阻塞不能传递的信号集。而sigprocmask函数就是用来检测修改信号屏蔽字的函数

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

若是set不是null,sigprocmask函数的行为依赖how参数。

  1. SIG_BLOCK,进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的并集运算

  2. SIG_UNBLOCK,信号新的屏蔽字是当前信号屏蔽字和set指向信号集的补集的交集

  3. SIG_SETMASK,新的信号屏蔽字是set指向信号集

若是oset不是null,那么当前的信号屏蔽字将会被设置给oset,若是set参数为null,则不改变信号屏蔽字,how参数也没有意义。

sigpending和sigaction函数

int sigpending(sigset_t *set);

The sigpending function returns a mask of the signals pending for delivery to the calling process in the location indicated by set.  Signals may be pending because they are currently masked, or transiently before delivery (although the latter case is not normally detectable).

sigpending函数返回当前进程阻塞不能传递信号的信号集。

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

sigaction函数用于检查或修改与制定信号相关联的处理动做,若是act指针非空,则修改其动做,若是oact非空,则经过oact指针返回该信号的上一个动做,是否是以为很熟悉,这个函数很像sigprocmask函数,实际上这个函数就是替代了signal函数,在上面的参数中也能够看出实际上多了新结构体,手册上也写了,因此这里也列了出来。

struct  sigaction {
        union __sigaction_u __sigaction_u;  /* signal handler */
        sigset_t sa_mask;               /* signal mask to apply */
        int     sa_flags;               /* see signal options below */
};

union __sigaction_u {
        void    (*__sa_handler)(int);
        void    (*__sa_sigaction)(int, siginfo_t *,
                      void *);
};

#define sa_handler      __sigaction_u.__sa_handler
#define sa_sigaction    __sigaction_u.__sa_sigaction

看起来和原著上面不同啊,其实就是一个道理,原著中有两种handler,因此用了两个成员存储,而这里实际上使用一个union来存储,由于handler实际上只有一个的。当更改信号的时候,若是sa_handler包含一个信号捕捉函数地址,则sa_mask字段说明了一个信号集,在调用信号捕捉函数以前,信号集要加入到进程信号屏蔽字中。
siginfo_t结构体包含了信号产生缘由的有关信息,这里就不在继续列出。

sigsetjmp和siglongjmp函数

前面讲过setjmp和longjmp函数,主要用于非局部转移。信号处理程序常常会调用longjmp函数返回到main函数,可是,当使用longjmp函数的时候,信号会自动的加到进程屏蔽字中,若是使用longjmp跳出,则会在一些平台上致使信号屏蔽字没法恢复,因此Unix系统提供了两个新函数用于信号处理函数的非局部转移。这两个函数只在此介绍一下,不详细讲述了。

sigsuspend函数

从这个函数的名字中基本也能猜出是作什么的了,先来看看Unix系统手册是怎么讲的

int sigsuspend(const sigset_t *sigmask);

Sigsuspend() temporarily changes the blocked signal mask to the set to which sigmask points, and then waits for a signal to arrive; on return the previous set of masked signals is restored.  The signal mask set is usually empty to indicate that all signals are to be unblocked for the duration of the call.

In normal usage, a signal is blocked using sigprocmask(2) to begin a critical section, variables modified on the occurrence of the signal are examined to determine that there is no work to be done, and the process pauses awaiting work by using sigsuspend() with the previous mask returned by sigprocmask.

sigsuspend函数临时改变当前的阻塞信号屏蔽字为sigmask参数指定的信号集,而后等待一个信号到来,返回的时候,先前的信号屏蔽字将被恢复。可能有朋友想问,这个函数究竟是干吗的,若是按照咱们以前的知识,彻底可使用sig信号集函数去除被阻塞信号,而后使用pause等待信号发生。实际上,咱们须要考虑到这是两步操做,颇有可能在pause以前,就已经有信号传递了,因此这个函数只是执行了原子操做的封装。

abort函数

abort函数也没有什么好说的,这个函数从名字就知道是程序终止,而且是异常终止。

void abort(void);

这是一个ISO C库函数,函数就是发送了SIGABRT信号给调用进程。abort函数会致使不正常的程序终止,除非信号SIGABRT被捕捉而且信号处理函数没有返回。实际上,信号处理函数不能返回的惟一方法是它调用exit、_exit、_Exit、longjmp或者siglongjmp。当执行此函数的时候,全部的stream都会被冲洗而且被关闭。其实结合上面的信息,很容易就知道了,abort函数确定是会终止程序的,可是捕捉SIGABRT的惟一意图就是——在程序终止前由其执行所需的清理操做。

System函数

int system(const char *command);

system函数把command参数提交给命令解释器sh,调用的进程等待shell执行命令结束,忽略SIGINT、SIGQUIT,而且阻塞SIGCHLD信号,若是command参数是null指针,system函数将会返回非0当sh解释器可用,若是不可用,则返回0。
为何system函数须要考虑信号处理,实际上原著讲述足够详细了,一句话,system函数建立的子进程不该当使用wait函数得到退出状态而致使system函数阻塞。

sleep、nanosleep和clock_nanosleep函数

unsigned int sleep(unsigned int seconds);

sleep函数会致使调用进程挂起,当进程超过了seconds指定的时间或者收到一个信号而且从信号处理程序返回,那么进程将会恢复。实际上,很容易就把sleep函数和alarm函数对比,并且alarm函数的信号是否会致使sleep函数的失败,因此sleep函数并非那么好用。

int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);

nanosleep和sleep函数差很少,可是提供了纳秒级精度,并且很是尴尬的是,系统颇有可能不支持纳秒级精度,这就会致使时间取整。
因为现代化Unix操做系统有多个系统时钟,sleep函数也衍生出了clock_nanosleep函数用于相对特定时钟挂起,可是在苹果系统中,好像并无存在这个函数,手册和头文件都没有找到,因此这里就不在说起。

sigqueue函数

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

sigqueue在队列中向指定进程发送一个信号和数据。可是好像苹果系统也没有提供这个函数,或者说是提供了自有函数,因此很遗憾,没法讲解,只能请各位朋友自行阅读原著。

小结

其实本章最后应该还有两节,可是这两节只是将做业控制信号和信号名编号转换稍稍讲解一下,实际上没有什么价值,信号在大多数的应用程序中都是很是重要的手段,因此应当掌握。

相关文章
相关标签/搜索