zoukankan      html  css  js  c++  java
  • 《UNIX环境高级编程》(APUE) 笔记第十章

    10 - 信号

    GitHub 地址


    1. 信号

    信号是 软中断 ,信号提供了一种处理异步事件的方法。

    当造成信号的事件发生时,为进程 产生 一个信号(或向进程 发送 一个信号)。事件 可以是硬件异常(如除以 (0))、软件条件(如alarm定时器超时)、终端产生的信号或调用 kill 函数。

    每个信号都有一个名字,以 (3) 个字符 SIG 开头,定义在头文件 <signal.h> 中。信号名都被定义为 正整数常量(信号编号),不存在编号为 (0) 的信号(空信号)。

    产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量(如 errno)来判断是否发生了一个信号,而是必须告诉内核 “在此信号发生时,请执行以下操作” 。当对信号采取了这种操作时,称为向进程 递送 了一个信号。在信号产生和递送的时间间隔内,信号是 未决的

    内核进行信号处理(3) 种方式:

    1. 忽略此信号。大多数信号都可用这种方式进行处理,但是 SIGKILLSIGSTOP 不能被忽略,因为它们向内核和超级用户提供了使进程终止或停止的可靠方法。
    2. 捕捉信号。通知内核在某种信号发生时,调用一个用户函数,在用户函数中可执行用户希望对这种事件进程的处理。不能捕捉 SIGKILLSIGSTOP 信号。
    3. 执行系统默认动作。对于大多数信号的系统默认动作是终止该进程。终止+core 表示在进程当前工作目录的core文件中复制了该进程的内存镜像,UNIX系统调试程序使用core文件检查进程终止时的状态。

    2. 函数 signal

    signal 函数用于设置对应信号的处理方式:

    #include <signal.h>
    void (*signal(int signo, void (*func)(int)))(int);
    //返回值:若成功,返回 以前 的信号处理配置;若出错,返回 SIG_ERR
    

    (signo) 参数是信号名。

    (func) 的值:

    • SIG_IGN ,向内核表示忽略此信号
    • SIG_DFL ,表示接收到此信号后的动作是系统默认动作
    • (捕捉该信号)接到此信号后要调用的函数(信号处理程序 或叫 信号捕捉函数)的地址

    返回值 是一个函数指针,指向在此之前的信号处理程序的指针。

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

    进程创建 时,子进程继承父进程的信号处理方式,因为子进程在开始时复制了父进程内存映像。

    3. 中断的系统调用

    如果进程在执行一个 低速系统调用 而阻塞期间捕捉到一个信号,则该系统调用被中断。

    系统调用分成两类:低速系统调用其他系统调用 ,低速系统调用是可能会使进程永远阻塞的一类系统调用。

    sigaction 函数使用标志 SA_RESTART 允许应用程序请求重启动被中断的系统调用。

    Linux 系统中,当信号处理程序是用 signal 函数时,被中断的系统调用会重启。

    自动重启的系统调用包括:ioctlreadreadvwritewritevwaitwaitpid 。前五个函数只有对低俗设备进行操作时才会被信号中断。而 waitwaitpid 在捕捉到信号时总是被中断。

    4. 可重入函数

    进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉到信号时进程执行到何处,所以其结果是不可预知的。

    信号处理程序中保证调用安全的函数是 可重入的 并被称为是 异步信号安全的 ,在信号处理操作期间,它会阻塞任何会引起不一致的信号发送。

    不可重入函数 的原因:

    • 已知它们使用静态数据结构
    • 它们调用 mallocfree
    • 他们是标准 I/O 函数(标准I/O库的很多实现都以不可重入方式使用全局数据结构)

    5. SIGCLD 语义

    在 Linux 中,SIGCLD 等同于 SIGCHLD ,在一个进程终止或停止时,此信号将被送给其父进程。按系统默认,将忽略此信号。

    对此信号的 处理方式 是:

    • 按系统默认( SIG_DFL ),SIGCLD 被忽略,则子进程可能产生僵死进程,需父进程对其等待。
    • SIGCLD 的配置被设置 SIG_IGN ,则调用进程的子进程终止时丢弃状态,将不产生僵死程序。如果调用进程随后调用一个 wait 函数,那么它将阻塞直到所有子进程都终止,然后该 wait 会返回 (-1) ,并将其 errno 设置为 ECHILD
    • 如果将 SIGCLD 的配置设置为捕捉,则内核检查是否有子进程准备好被等待,如果有,则调用 SIGCLD 处理程序。(若在进程被安排捕捉 SIGCLD 之前已有子进程准备好被等待,此时系统不调用 SIGCLD 信号的处理程序)

    6. 函数 kill 和 raise

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

    #include <signal.h>
    int kill(pid_t pid, int signo);
    int raise(int signo);	//raise(signo) = kill(getpid(), signo)
    //两个函数返回值:若成功,返回 0;若出错,返回 -1
    

    (pid) 参数取值:

    • (pid>0):将该信号发送给进程 ID 为 (pid) 的进程
    • (pid==0):将信号发送给与发送进程属于同一进程组的所有进程(不包括系统进程集,即内核进程与init),且发送进程具有权限向这些进程发送信号
    • (pid<0):将该信号发送给其进程组ID等于 (pid) 绝对值的所有进程(不包括系统进程集),且发送进程具有权限向这些进程发送信号
    • (pid==-1):将信号发送给发送进程有权限向它们发送信号的所有进程(不包括系统进程集)

    权限

    • 超级用户可将信号发送给任一进程
    • 对于非超级用户,基本规则是发送者的实际用户ID或有效用户ID必须等于接受者的实际用户ID或有效用户ID。如果实现 _POSIX_SAVED_IDS ,则检查接收者的保存设置用户ID(而不是有效用户ID)

    如果调用 kill 为调用进程产生信号,而且此信号是不被阻塞的,那么在 kill 返回之前,(signo) 或者某个其他未决的、非阻塞信号被传送至该进程。

    7. 函数 alarm 和 pause

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

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    //返回值:0 或以前设置的闹钟时间的余留秒数
    

    参数 (seconds) 的值是产生信号 SIGALRM 需要经过的时钟秒数。当这一时刻到达时,信号由内核产生。

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

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

    #include <unistd.h>
    int pause(void);	//返回值:-1,errno设置为EINTR
    

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

    8. 信号集

    信号集 能表示多个信号,定义数据类型 sigset_t 以包含一个信号集,并有以下处理信号集的函数:

    #include <signal.h>
    int sigemptyset(sigset_t *set);	//初始化由set指向的信号集,清除其中所有信号
    int sigfillset(sigset_t *set);	//初始化由set指向的信号集,使其包括所有信号
    //所有应用程序使用信号集之前,要对该信号集调用sigemptyset或sigfillset一次
    int sigaddset(sigset_t *set, int signo);	//将一个信号添加到已有的信号集中
    int sigdelset(sigset_t *set, int signo);	//从信号集中删除一个信号
    //以上四个函数返回值:若成功,返回0;若出错,返回-1
    int sigimember(const sigset_t *set, int signo);	//判断是否已包含某信号。若真,返回1;若假,返回-1
    

    9. 函数 sigprocmask

    一个进程的 信号屏蔽字 ,规定了当前阻塞而不能递送给该进程的信号集。调用函数 sigprocmask 可以检测或更改进程的信号屏蔽字:

    #include <signal.h>
    int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
    //返回值:若成功,返回0;若出错,返回-1
    

    (oset) 是非空指针,那么进程的当前信号屏蔽字通过 (oset) 返回。

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

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

    sigprocmask 是仅为单线程进程定义的。

    10. sigpending

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

    #include <signal.h>
    int sigpending(sigset_t *set);	//返回值:若成功,返回0;若出错,返回-1
    

    11. 函数 sigaction

    sigaction 函数的功能是检查或修改与指定信号相关联的处理动作。很多平台都用 sigaction 实现 signal 函数。

    #include <signal.h>
    int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
    //返回值:若成功,返回0;若出错,返回-1
    

    (signo) 是要检测或修改其具体动作的信号编号。若 (act) 指针非空,则要修改其动作。如果 (oact) 指针非空,则系统经由 (oact) 指针返回该信号的上一个动作。

    关于连续发送同一信号的处理:

    若更改后的信号动作是 捕捉函数 而不是 SIG_IGN 或 SIG_DFL ,则调用该信号捕捉函数之前,此信号被添加到进程的信号屏蔽字中,仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们加入队列,所以如果某种信号在被阻塞时多次发生,其信号处理函数只会被调用一次。

    若更改后的信号动作是 捕捉函数 而不是 SIG_IGNSIG_DFL ,则调用该信号捕捉函数之前,此信号被添加到进程的信号屏蔽字中,仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。

    12. 函数 sigsetjmp 和 siglongjmp

    信号处理程序中调用 longjmp 有一个问题:当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信息中断该信号处理程序。如果用 longjmp 跳出信号处理程序,进程的信号屏蔽字可能无法恢复。

    信号处理程序使用 sigsetjmpsiglongjmp 进行非局部转移:

    #include <setjmp.h>
    int sigsetjmp(sigjmp_buf env, int savemask);	//返回值:若直接调用,返回0;若从siglongjmp调用返回,则返回非0
    void siglongjmp(sigjmp_buf env, int val);
    

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

    13. 函数 sigsuspend

    sigsuspend 函数在一个原子操作中设置信号屏蔽字,然后使进程休眠:

    #include <signal.h>
    int sigsuspend(const sigset_t *sigmask);
    

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

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

    sigsuspend 函数可用于:

    • 保护代码临界区,使其不被特定信号中断(在此功能中起到 pause 作用)
    • 等待一个信号处理程序设置一个全局变量
    • 实现父、子进程之间的同步

    14. 函数 abort

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

    #include <stdlib.h>
    void abort(void);
    

    abortSIGABRT 信号发送给调用进程(进程不应忽略此信号)。实质上,abort 函数向主机环境递送一个 未成功终止 的通知。

    让进程捕捉 SIGABRT 的意图是:在进程终止之前由其执行所需的清理操作。如果进程并不在信号处理程序中终止自己,当(SIGABRT)信号处理程序返回时,abort 终止该级才能拿,且对所有打开标准 I/O 流的效果应当与进程终止前对每个流调用 fclose 相同。

    15. 函数 sleep、nanosleep 和 clock_nanosleep

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

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

    • 已经过了 (seconds) 所指定的墙上时钟时间(返回值为 (0)
    • 调用进程捕捉到一个信号并从信号处理程序返回(返回值为未休眠完的秒数)

    nanosleep 函数提供了纳秒级的精度:

    #include <time.h>
    int nanosleep(const struct timespec *reqtp, struct timespec *remtp);
    

    (reqtp) 参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,(remtp) 参数指向的 timespec 结构就会被设置为未休眠完的时间长度。

    clock_nanosleep 函数可使用相对于特定时钟的延迟时间来挂起调用线程:

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

    (clock_id) 参数指定了计算延迟时间基于的时钟。

    (flags) 参数用于控制延迟是相对的还是绝对的:

    • (flags)(0) 时表示休眠时间是相对的
    • (flags) 值设置为 TIMER_ABSTIME ,表示休眠时间是绝对的(例如,希望休眠时间到达某个特定的时间)
  • 相关阅读:
    Java基础00-循环语句7
    Java基础00-分支语句6
    Java基础00-数据输入5
    Java基础00-运算符4
    Java基础00-基础语法3
    Java基础00-第一个程序2
    第十四题
    第十三题
    第十二题
    第十题
  • 原文地址:https://www.cnblogs.com/brianleelxt/p/13214731.html
Copyright © 2011-2022 走看看