APUE读书笔记-第十章 信号 (二)

大概三个月前对信号这一章初步读了一下,当时看完了前5小节,今天开始余下内容的学习。

10.6 可重入函数

由于信号处理函数可能会中断正常执行的指令序列,对于我们自己编写的代码,可以预防这一问题,但对于库函数我们就无能为力了,因此10.6节给出了一些可重入函数,这些可重入函数就是可以保证安全调用的函数。

书中还给出了一个实例在此也就不详细研究了,基本功能比较明确就是在信号处理函数中调用不可重入函数,最后使程序崩溃。

10.7 SIGCILD语义

注意此处是SIGCILD信号而不是SIGCHILD信号,SIGCILD是System V的一个信号名。POSIX.1采用的是BSD的SIGCHILD信号。由于我的机器上没有SIGCILD这一信号,因此这一小节也就不深入研究了。

10.8 可靠信号术语与语义

其实有关于可靠信号的相关概念,在上一篇文章中已经进行过分析了,在此结合书中的内容再复习一下。

首先来看几个相关的术语。

信号的产生:当造成信号的事件发生时,为进程产生一个信号。

信号的递送:当信号产生时,内核在进程控制块中设置相应的标志。

未决信号:是一种状态,指处于信号产生到信号递送这一时间间隔内的信号。

阻塞信号递送:如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该函数,则为该进程将此信号保持未决状态(即不递送信号),直到该进程对此信号解除了阻塞,或者将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作。对于阻塞的信号,如果该信号发生多次,若是实时信号则排队,否则则不排队。

若多个信号要递送给一个进程,POSIX.1并没有规定这些信号的递送顺序。仅给出一个建议。

每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。

10.9 kill函数和raise函数

kill函数将信号发送给进程或进程组。函数原型如下:

#include <signal.h>
extern int kill (__pid_t __pid, int __sig) __THROW;

参数__pid的值不同,产生的效果不同

pid>0,将该信号发送给进程ID为pid的进程。

pid==0,将该信号发送给与发送进程属于同一进程组的所有进程。

pid<0,将该信号发送给其进程组ID等于pid绝对值(这一点我没看出来与上面提到的内容有什么不一样的)。

pid==-1,将该信号发送给发送进程有权限向他们发送信号的所有进程。

进程将信号发送给其他进程需要权限。超级用户可将信号发送给任一进程。对于非超级用户,其基本规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。kill函数返回前,信号的递送阶段已经完成。

raise函数则允许进程向自身发送信号。

extern int raise (int __sig) __THROW;

10.10 alarm函数与pause函数

使用alarm函数可以设置一个定时器。当定时器超时时,产生SIGALRM信号。该信号的默认动作是终止是调用该函数的进程。函数原型如下:

extern unsigned int alarm (unsigned int __seconds) __THROW;

参数second的值是产生SIGALRM信号需要经过的时钟描述。该函数返回0或以前设置的时钟时间的余留秒数。

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

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

以上内容有点混乱,让我们总结一下:如果调用alarm函数前,进程已经设置了闹钟时间,则返回上一个alarm函数的剩余时间,否则返回0。若出错则返回-1。alarm(0)的作用就是取消已有的闹钟,并返回剩余时间。

pause函数使调用进程挂起直至捕捉到一个信号。函数原型如下:

extern int pause (void);

这个函数的返回值比较奇怪,总是返回-1,所以对于pause函数的成功与否是通过检查errno,若函数运行成功则返回-1,同时设置errno为EINTR。

只有执行了一个信号处理程序并从其返回,pause才返回。

书中给出了两个有关的实例,不过其中涉及到了longjmp的有关内容,由于我对这个函数的特点还不是特别了解,所以就先把这两个实例放一下,先看后面的内容,等有机会了再回过头看看。

10.11 信号集

所谓信号集就是一个能表示多个信号的数据类型。由于总的信号数量可能超过整型数的位数,因此POSIX.1标准定义数据类型sigset_t数据类型,以包含一个信号集。并定义了以下5个函数:

#include <signal.h>
extern int sigemptyset (sigset_t *__set) __THROW __nonnull ((1)); //清空set指向的信号集
extern int sigfillset (sigset_t *__set) __THROW __nonnull ((1)); //初始化set指向的信号集,使其包括所有信号
extern int sigaddset (sigset_t *__set, int __signo) __THROW __nonnull ((1)); //将一个信号添加到已有信号集中
extern int sigdelset (sigset_t *__set, int __signo) __THROW __nonnull ((1)); //将一个信号从已有信号集中删除
上述四个函数若成功则返回0,失败返回-1
extern int sigismember (const sigset_t *__set, int __signo)
     __THROW __nonnull ((1));
判断某个信号是否是一个信号集的成员,若是则返回1,若不是则返回0

10.12 sigprocmask函数

sigprocmask函数可用于检测或更改进程的信号屏蔽字。

#include <signal.h>
extern int sigprocmask (int __how, const sigset_t *__restrict __set,sigset_t *__restrict __oset) __THROW;

 若__set非空,则根据__how的不同取值,修改当前屏蔽字;若__set为空,则当前信号屏蔽字通过__oset返回。 

10.13 sigpending函数

#include <signal.h>
extern int sigpending (sigset_t *__set) __THROW __nonnull ((1));

sigpending返回当前被阻塞的信号。在信号被屏蔽后如果产生了信号,那么此时该信号是未决的,但是不再受阻塞(根据APUE之前的描述,此处应该指已经在进程描述符中置了相关位),所以在sigprocmask返回之前,它被递送到调用进程。所以信号的阻塞通过上述描述可以理解为:信号的阻塞并不是指直接忽略该信号,而是指将相关的信号保存起来,若解除了信号的阻塞则理解执行信号处理程序,或者将此信号的动作改为忽略。之前谈到过信号经历销毁后才执行信号处理函数,所以我猜测此处在执行信号处理函数前,会判断是否是当前屏蔽的信号,若是则不进行任何处理,若不是则执行相应的信号处理函数。看来此处要研究一下相关的源码了

10.14 sigaction

sigaction函数的功能是检测或修改与制定信号相关联的动作。函数原型如下:

#include <signal.h>
extern int sigaction (int __sig, const struct sigaction *__restrict __act,
		      struct sigaction *__restrict __oact) __THROW;

这个函数执行功能的方式与sigprocmask相类似,若“__act”不为空则修改其动作,若“__oact”不为空则返回该信号的上一个动作。

struct sigaction定义如下,位于/usr/include/i386-linux-gnu/bits/sigaction.h中

struct sigaction
  {
    /* Signal handler.  */
#ifdef __USE_POSIX199309
    union
      {
	/* Used if SA_SIGINFO is not set.  */
	__sighandler_t sa_handler;
	/* Used if SA_SIGINFO is set.  */
	void (*sa_sigaction) (int, siginfo_t *, void *);
      }
    __sigaction_handler;
# define sa_handler	__sigaction_handler.sa_handler
# define sa_sigaction	__sigaction_handler.sa_sigaction
#else
    __sighandler_t sa_handler;
#endif

    /* Additional set of signals to be blocked.  */
    __sigset_t sa_mask;

    /* Special flags.  */
    int sa_flags;

    /* Restore handler.  */
    void (*sa_restorer) (void);
  };

其中sa_mask的功能与之前介绍的阻塞信号有些许不同,sa_mask的功能是在调用信号处理函数期间阻塞的信号。

通过上面的源码可以发现,若“SA_SIGINFO”标志被设置,则使用“void (*sa_sigaction) (int, siginfo_t *, void *);”这个版本的信号处理函数。其中siginfo_t包含了信号产生的详细信息,在我的机器上定义如下:

typedef struct
  {
    int si_signo;		/* Signal number.  */
    int si_errno;		/* If non-zero, an errno value associated with
				   this signal, as defined in <errno.h>.  */
    int si_code;		/* Signal code.  */

    union
      {
	int _pad[__SI_PAD_SIZE];

	 /* kill().  */
	struct
	  {
	    __pid_t si_pid;	/* Sending process ID.  */
	    __uid_t si_uid;	/* Real user ID of sending process.  */
	  } _kill;

	/* POSIX.1b timers.  */
	struct
	  {
	    int si_tid;		/* Timer ID.  */
	    int si_overrun;	/* Overrun count.  */
	    sigval_t si_sigval;	/* Signal value.  */
	  } _timer;

	/* POSIX.1b signals.  */
	struct
	  {
	    __pid_t si_pid;	/* Sending process ID.  */
	    __uid_t si_uid;	/* Real user ID of sending process.  */
	    sigval_t si_sigval;	/* Signal value.  */
	  } _rt;

	/* SIGCHLD.  */
	struct
	  {
	    __pid_t si_pid;	/* Which child.  */
	    __uid_t si_uid;	/* Real user ID of sending process.  */
	    int si_status;	/* Exit value or signal.  */
	    __sigchld_clock_t si_utime;
	    __sigchld_clock_t si_stime;
	  } _sigchld;

	/* SIGILL, SIGFPE, SIGSEGV, SIGBUS.  */
	struct
	  {
	    void *si_addr;	/* Faulting insn/memory ref.  */
	    short int si_addr_lsb;	/* Valid LSB of the reported address.  */
	  } _sigfault;

	/* SIGPOLL.  */
	struct
	  {
	    long int si_band;	/* Band event for SIGPOLL.  */
	    int si_fd;
	  } _sigpoll;

	/* SIGSYS.  */
	struct
	  {
	    void *_call_addr;	/* Calling user insn.  */
	    int _syscall;	/* Triggering system call number.  */
	    unsigned int _arch; /* AUDIT_ARCH_* of syscall.  */
	  } _sigsys;
      } _sifields;
  } siginfo_t __SI_ALIGNMENT;

与书中定义的形式不太一样。

上述信号处理函数中,最后一个void* 参数可被强制类型转换为ucontext_t结构类型,该结构标识信号传递时进程的上下文。不过对于这种结构类型的定义我并没有找到。

10.15 sigsetjmp与siglongjmp函数

由于我对于setjmp与longjmp函数的功能还不是特别了解,所以这一节就直接跳过。

10.16 sigsuspend函数

先来看看引入这一函数的原因,假设程序具有如下需求:“解除对一个信号的阻塞后,然后以pause等待以前被阻塞的信号发生”,但程序运行过程中可能存在以下情况:在阻塞解除后,pause调用前(注意此时并未进入pause,将进程挂起),发生了信号,则直接转入到信号处理函数,待信号处理函数运行结束后才进入到pause函数中,此时可能不会再出现相应信号,则造成了pause永远阻塞。

因此解决上述问题的一个思路就是原子操作,即先恢复信号屏蔽字,然后使进程休眠。函数原型如下:

#include <signal.h>
extern int sigsuspend (const sigset_t *__set) __nonnull ((1));

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

10.17 abort函数

abort函数的功能是使程序异常终止。该函数发送SIGABORT信号给调用进程。函数原型如下:

#include <stdlib.h>
extern void abort (void) __THROW __attribute__ ((__noreturn__));
abort不会返回到其调用者。如果捕捉到此信号,则信号处理程序不能返回的唯一方法是它调用exit、_exit、_Exit、longjmp或siglongjmp。

10.18 system函数

这一节主要讨论了system函数的实现,说实话其中的内容我也看的一知半解,就明确了一点:POSIX.1标准要求system的调用者在等待命令完成时应当忽略SIGINT与SIGCHLD信号。

10.19 sleep、nanosleep和clock_nanosleep函数

先来看看sleep函数的功能:

#include <unistd.h>
extern unsigned int sleep (unsigned int __seconds);

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

  1. 已经过了seconds所指定的墙上时钟时间。
  2. 调用进程捕捉到一个信号并从信号处理程序返回。

在第一种情况下返回值是0。在第二种情况下,返回值是未休眠完的秒数。

nanosleep函数与sleep函数功能相类似,但提供了纳秒级精度。

#include <time.h>
extern int nanosleep (const struct timespec *__requested_time,
		      struct timespec *__remaining);

10.21 作业控制信号

POSIX.1认为有以下6个与作业控制有关。

SIGCHLD 子进程已暂停或终止

SIGCONT 如果进程已暂停,则使其继续执行

SIGSTOP 暂停信号

SIGTSTP 交互式停止信号

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

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

10.22 信号名和编号

这一节没什么内容也不详细分析了。

这一节的确内容挺多的,写都懒得写了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值