APUE 学习笔记——信号

1、产生信号的场景一般有哪些?

  • 当用户按某些终端键时,引发终端产生信号。如当用户在终端上按Delete键(通常是Ctrl+C)时,产生中断信号SIGINT
  • 硬件异常信号:除数为0、无效的内存引用等等

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

  • 进程调用kill()函数可将任意信号发送给另一个进程或者进程组

    要求接收信号的进程和发送信号的进程的所有者必须相同,或者发送信号的进程的所有者是超级用户

  • 用户可以用kill命令将任意信号发送给其他进程

    此命令只是kill()函数的接口。通常用于终止一个失控的后台进程

  • 当检测到某种软件条件已经发生并应将其通知有关进程时,也产生信号。如定时器超时的时候产生SIGALRM信号

2、信号处理的操作有哪三种?

  • 忽略此信号。大多数信号都可以使用这种方式进行处理,但是SIGKILLSIGSTOP信号决不能被忽略
    • SIGKILLSIGSTOP向内核和超级用户提供了使进程终止或者停止的可靠方法
    • 如果忽略某些由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的
  • 捕捉信号。为了做到这一点,进程需要通知内核当某种信号发生时,调用一个用户函数。
    • 在用户函数中,内核执行进程希望对这种事件进行的处理
    • 注意无法捕捉SIGKILLSIGSTOP信号
  • 执行系统默认动作。对大多数信号的系统默认动作是终止该进程。默认动作是忽略的常见的信号有:SIGCANCEL(线程库内部使用)、SIGCHILD(子进程状态改变)

3、forkexec 对信号处理的影响?

  • 调用fork之后,子进程继承父进程的信号处理方式,因为子进程在开始时复制了父进程的进程空间
  • 对于子进程,exec函数会将原先设置为要捕捉的信号都改为默认动作,非捕捉的信号则不变。这是因为信号捕捉函数的地址很可能在新程序中没有任何意义

4、什么是低速系统调用?

低速系统调用是可能使进程永远阻塞的一类系统调用,包括:

  • 如果某些类型文件(如读管道、终端设备、网络设备)的数据不存在,则读操作可能会使调用者永远阻塞
  • 如果写某些类型文件(如写管道、终端设备、网络设备),可能会使得调用者永远阻塞
  • pause函数(根据定义,它使调用进程休眠直到捕捉一个信号)和wait函数
  • 某些ioctl函数
  • 某些进程间通信函数

5、什么是中断的系统调用自动重启?

有时候用户根本不知道所使用的输入、输出设备是否是低速设备。如果不提供重启动功能,则对每次read、write系统调用就要进行是否出错返回的测试;如果是被中断的,则需要再调用read、write系统调用。POSIX要求:只有中断信号的SA_RESTART标志有效时,才重启动被该信号中断的系统调用。

6、什么是可重入函数?有什么特征?

对于某一类函数,如果在捕捉到信号的时候,进程正在执行这些函数,那么在信号处理程序中,可以安全的重复调用这些函数。这一类函数称作可重入函数

SUS规范说明了在信号处理程序中保证调用安全的函数。这些函数有以下特点:

  • 没有使用静态数据结构。使用了静态数据结构的函数不是可重入的
  • 没有调用malloc或者free。调用malloc或者free的函数不是可重入的
  • 没有使用标准IO函数。使用标准IO函数的函数不是可重入的。因为标准IO库很多都是用了全局数据结构

7、可靠信号有哪些术语?

  • 信号产生:当造成信号的事件发生时,内核为进程产生一个信号(或者说向进程发送一个信号)
  • 信号递送:当内核在进程表中以某种方式设置一个标志时,我们说向进程递送(delivery)了一个信号
  • 信号未决:在信号产生(generation)和递送之间的很短的时间间隔内,信号时未决的(pending)
  • 信号阻塞:进程可以阻塞信号的递送:
    • 如果为进程产生了一个信号,而这个信号时进程阻塞的,而且对该信号的动作是系统默认的或者捕捉该信号(即不是忽略),则内核为该进程将此信号一直保持为未决状态;直到该进程对此信号解除了阻塞或者将对此信号的动作更改为忽略
    • 进程可以调用sigpending()函数来判断哪些信号时设置为阻塞并且处于未决状态的
    • 如果在进程解除对某个信号的阻塞之前,这种信号发生了多次
      • POSIX.1运行系统递送该信号一次或者多次。如果递送该信号多次,则称这些信号进行了排队
      • 除非支持POSIX.1实时扩展,否则大多数UNIX并不对信号排队,而是只递送这种信号一次

内核在递送一个信号给进程时才决定对此信号的处理方式(而不是在产生该信号的时候决定处理方式)。于是进程在信号递送给它之前仍可以改变对该信号的动作(通常只有在信号被阻塞且处于未决状态下,我们才有机会改变对该信号的动作。如果该信号未被阻塞,则从信号产生到信号递送之间根本没有机会执行信号处理动作的改变)

8、sigprocmaskhow参数有哪些选项?

  • how=SIG_BLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set指向的信号集的并集。即set包含了希望阻塞的信号
  • how=SIG_UNBLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set指向的信号集补集的并集。即set包含了希望解除阻塞的附加信号
  • how=SIG_SETMASK:该进程新的信号屏蔽字是set指向的值

9、数据类型 sigactionsa_flags 的选项有哪些?

sa_flags字段指定对信号进行处理的各个选项:

  • SA_INTERRUPT:由此信号中断的系统调用不会自动重启动
  • SA_NOCLDSTOP:如果signoSIGCHLD,则当子进程停止时,不产生此信号。
  • SA_NOCLDWAIT:如果signoSIGCHLD,则当调用进程的子进程终止时,不创建僵死进程。

    若进程随后调用wait,则阻塞到它所有子进程都终止,此时返回 -1 ,errno设置为ECHILD

  • SA_NODEFER:当捕捉到信号时,在执行其信号捕捉函数时,系统并不自动阻塞此信号

    这意味着,如果在信号捕捉函数执行期间,该信号又发生了,则会执行新一轮的信号捕捉函数

  • SA_RESETHAND:在信号处理函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除SA_SIGINFO标志。

    这意味着所建立的信号处理函数只是一次性的。下一次信号递送时,采用默认的SIG_DFL

  • SA_RESTART:由此信号中断的系统调用自动重启动
  • SA_SIGINFO:此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针,以及一个指向进程上下文标志符的指针

    此时对应的是字段 void (*sa_sigaction)(int,siginfo_t *,void*);

10、当执行信号捕捉函数时,sa_mask 会对进程的信号屏蔽字有什么影响?

  • 在调用该信号捕捉函数之前,这 sa_mask 信号集要加到进程的信号屏蔽字中

    这是为了确保在信号处理函数执行过程中,额外的屏蔽某些信号

  • 仅当从信号捕捉函数返回时,再将进程的信号屏蔽字恢复为原先值。这样在调用信号处理程序时就能够阻塞某些信号
  • 在信号处理程序被调用时,操作系统建立的新的信号屏蔽字包括了正在被递送的信号。确保了在处理一个给定的信号时,如果这种信号多次发生,那么它会被阻塞到对当前信号的处理结束为止。同时多次发生的这种信号并不会被排队,而是只登记为一次。

    通常你并不需要在sa_mask中添加signo,因为这是操作系统默认帮你处理的

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

11、sa_sigactionsa_handler 有什么区别?

sa_sigaction:它是一个替代的信号处理程序,当sa_flags中设置了SA_SIGINFO标志时使用sa_sigaction

  • 对于sa_sigactionsa_handler,你只能选择使用这两个字段中的一个
  • 通常按照下列方式调用信号处理程序: void handler(int signo);
  • 但是如果设置了SA_SIGINFO标志,则按照下列方式调用信号处理程序: void handler(int signo,siginfo_t *info,void* context);。其中siginfo_t结构包含了信号产生原因的有关信息,结构大致如下:
struct siginfo{
    int si_signo; //信号编号
    int si_errno; //如果非零,则为 errno
    int si_code;  //额外的信息
    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; //退出状态或者信号编号
    union sigval si_value; //application-specific value
  • 如果信号是SIGCHLD,则将设置si_pidsi_statussi_uid字段
  • 如果信号是SIGBUSSIGILLSIGFPESIGSEGV,则si_addr包含了造成故障的根源地址,该地址可能并不准确。si_errno字段包含了错误编号,它对应了造成信号产生的条件,并由操作系统定义
  • 其中code给出了信号产生的详细信息,它有一些常量定义。如SIGCHLD信号中,code可以为:CLD_EXITEDCLD_KILLEDCLD_STOPPED....

12、sigsetjmp/siglongjmplongjmp/setjmp的区别?

在信号处理程序中直接使用 longjmp/setjmp 有个问题:当捕捉到一个信号并进入信号处理程序中时,此时当前信号自动的加到进程的信号屏蔽字中。如果 longjmp 跳出了信号处理程序,那么此时进程的信号屏蔽字并不恢复。

因此提供了 sigsetjmp/siglongjmp 函数。sigsetjmp/siglongjmp函数与 longjmp/setjmp 的唯一区别是:sigsetjmp 增加了一个参数 savemasksavemask 非 0 则 siglongjmp 跳转时会自动恢复保存的信号屏蔽字。

注意:savemask 非 0 时,调用 sigsetjmp 会在 env 中记录下当前的信号屏蔽字。如果后续siglongjmp跳转,则恢复的是sigsetjmp时的信号屏蔽字,而不是进入信号处理程序之前时刻的信号屏蔽字。

13、sigsuspend 函数的作用?

sigsuspend将进程的信号屏蔽字设置为sigmask指向的值,然后将进程投入睡眠,这两步是原子的。如果不是原子的,可能在修改屏蔽字之后,进程睡眠之前发生了信号递送,则进程的睡眠不会被该信号打断(信号发生在进程睡眠之前)。

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

14、clock_nanosleep 的意义?

  • clock_id
    • CLOCK_REALTIME:实时系统时间
    • CLOCK_MONOTONIC:不带负跳数的实时系统时间
    • CLOCK_PROCESS_CPUTIME_ID:调用进程的CPU时间
    • CLOCK_THREAD_CPUTIME_ID:调用线程的CPU时间
  • flags:用于控制延迟是相对的还是绝对的。
    • 0:表示休眠时间是相对的(如休眠的时间为 3秒)
    • TIMER_ABSTIME:休眠时间是绝对的(如休眠到时钟到达某个特定的刻度)

提供clock_nanosleep的原因是:由于多个系统时钟的引入,需要使用相对于特定始终的延迟时间挂起调用线程。除了出错返回之外,调用clock_nanosleep(CLOCK_REALTIME,0,reqtp,remtp)和调用nanosleep(reqtp,remtp)效果相同的。

要求提供绝对延迟是因为某些应用对休眠长度有精度要求。而相对休眠时间会导致实际休眠比要求的长(处理器调度和抢占可能会导致相对休眠时间超过实际需要的时间间隔)

API

void (*signal(int signo,void (*func)(int)))(int);
int kill(pid_t pid,int signo);
int raise(int signo);
unsigned int alarm(unsigned int seconds);
int pause(void);

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

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

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

struct sigaction{
    void (*sa_handler)(int); //可以为 SIG_IGN、SIG_DFL或者信号处理函数的地址
    sigset_t sa_mask; //额外需要屏蔽的信号
    int sa_flags; // 标志位
    void (*sa_sigaction)(int,siginfo_t *,void*); //可选的另一种信号处理函数
}

int sigsetjmp(sigjmp_buf env,int savemask);
void siglongjmp(sigjmp_buf env,int val);

int sigsuspend(const sigset_t *sigmask);

void abort(void);

unsigned int sleep(unsigned int seconds);
int nanosleep(const struct timespec*reqtp,struct timespec *remtp);
int clock_nanosleep(clockid_t clock_id,int flags,
        const struct timespec *reqtp,struct timespec *remtp);
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!