Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Comments
    • Chapter 2 - 微型计算机组成结构

      • Chapter 2 - 微型计算机组成结构
    • Chapter 3 - 内核编程语言和环境

      • Chapter 3 - 内核编程语言和环境
    • Chapter 4 - 80X86 保护模式及其编程

      • Chapter 4.1-4.2 - 80X86 系统寄存器和系统指令 & 保护模式内存管理
      • Chapter 4.3 - 分段机制
      • Chapter 4.4 - 分页机制
      • Chapter 4.5 - 保护
      • Chapter 4.6 - 中断和异常处理
      • Chapter 4.7 - 任务管理
      • Chapter 4.8 - 保护模式编程初始化
      • Chapter 4.9 - 一个简单的多任务内核实例
    • Chapter 5 - Linux 内核体系结构

      • Chapter 5.1-5.2 - Linux 内核模式 & 体系结构
      • Chapter 5.3 - Linux 内核对内存的管理和使用
      • Chapter 5.4-5.6 - 中断机制 & 系统调用 & 系统时间和定时
      • Chapter 5.7-5.9 - Linux 进程控制 & 堆栈使用 & 文件系统
    • Chapter 6 - 引导启动程序 (boot)

      • Chapter 6 - 引导启动程序 (boot)
    • Chapter 7 - 初始化程序 (init)

      • Chapter 7 - 初始化程序 (init)
    • Chapter 8 - 内核代码

      • Chapter 8.1 - 内核代码总体功能
      • Chapter 8.2 - asm.s 程序
      • Chapter 8.3 - traps.c 程序
      • Chapter 8.4 - sys_call.s 程序
      • Chapter 8.5 - mktime.c 程序
      • Chapter 8.6 - sched.c 程序
      • Chapter 8.7 - signal.c 程序
      • Chapter 8.8 - exit.c 程序
      • Chapter 8.9 - fork.c 程序
      • Chapter 8.10 - sys.c 程序
    • Chapter 9 - 块设备驱动程序

      • Chapter 9.1 - 块设备驱动程序 总体功能
      • Chapter 9.2 - blk.h 文件
      • Chapter 9.3 - hd.c 程序
      • Chapter 9.4 - ll_rw_blk.c 程序
      • Chapter 9.5 - ramdisk.c 程序
    • Chapter 10 - 字符设备驱动程序

      • Chapter 10.1 - 字符设备驱动程序 总体功能
      • Chapter 10.2 - keyboard.S 程序
      • Chapter 10.3 - console.c 程序
      • Chapter 10.4 - serial.c 程序
      • Chapter 10.5 - rs_io.s 程序
      • Chapter 10.6 - tty_io.c 程序
      • Chapter 10.7 - tty_ioctl.c 程序
    • Chapter 12 - 文件系统

      • Chapter 12.1 - 文件系统 总体功能
      • Chapter 12.2 - buffer.c 程序
      • Chapter 12.3 - bitmap.c 程序
      • Chapter 12.4 - truncate.c 程序
      • Chapter 12.5 - inode.c 程序
      • Chapter 12.6 - super.c 程序
      • Chapter 12.7 - namei.c 程序
      • Chapter 12.9 - block_dev.c 程序
      • Chapter 12.10 - file_dev.c 程序
      • Chapter 12.11 - pipe.c 程序
      • Chapter 12.12 - char_dev.c 程序
      • Chapter 12.13 - read_write.c 程序
      • Chapter 12.14 - open.c 程序
      • Chapter 12.15 - exec.c 程序
      • Chapter 12.16 - stat.c 程序
      • Chapter 12.17 - fcntl.c 程序
      • Chapter 12.18 - ioctl.c 程序
      • Chapter 12.19 - select.c 程序

Chapter 8.7 - signal.c 程序

Created by : Mr Dk.

2019 / 08 / 20 11:58

Ningbo, Zhejiang, China


8.7 signal.c 程序

8.7.1 功能描述

涉及内核中所有有关信号处理的函数。

UNIX 类系统中,信号是一种软件中断处理机制,异步处理事件。信号是一个整数值,除了指明信号类别,不携带任何信息。早期 UNIX 内核中信号处理方法并不是那么的可靠,可能导致信号丢失。POSIX 提供了一种可靠处理信号的方法。为保持兼容性,Linux 内核同时提供了两种处理信号的方法。

8.7.1.1 Linux 中的信号

用一个 unsigned long (32-bit) 的 bitmap 来表示不同的信号

  • 系统中最多可以有 32 个不同的信号
  • 其中 20 种是 POSIX.1 标准中规定的信号
  • 另两种是 Linux 专用信号 SIGUNUSED (未定义) 和 SIGSTKFLT (堆栈错)

进程收到信号时,可以有三种处理方式:

  • 忽略信号:但 SIGKILL 和 SIGSTOP 忽略不掉
  • 捕获信号:告诉内核,在指定的信号发生时,调用自定义的信号处理函数
  • 默认操作:由系统响应的默认信号处理程序进行处理

8.7.1.2 信号处理的实现

一大堆相关的系统调用:

  • sys_ssetmake():设置信号屏蔽码
  • sys_sgetmask():获取信号屏蔽码
  • sys_signal():信号处理,即传统的 signal()
  • sys_sigaction():可靠的信号处理函数 sigaction()

系统调用中断处理程序中,处理信号的程序 do_signal()。以上,signal() 和 sigaction() 的功能比较类似,都用于更改信号的原处理句柄。signal() 是内核处理信号的传统方式,在某些特殊时刻会导致信号丢失。这两个系统调用会在进程 PCB 的 sigaction[] 结构体数组中,将自定义信号处理函数的指针和相关属性记录到结构体中。当内核 退出 系统调用和某些中断过程时,会检测当前进程是否收到信号。若收到了信号,那么就会根据 sigaction[] 中对应的结构项执行信号处理服务程序。

signal() 函数

两个参数:

  • 要捕获的信号值:int signr
  • 新的信号处理函数指针:void (*handler)(int)

函数指针可以是用户定义的信号处理函数,也可以是内核提供的特定函数指针。

#define SIG_DFL ((void (*)(int))0) // 默认操作
#define SIG_IGN ((void (*)(int))1) // 忽略

0 和 1 是实际程序中不可能出现的函数地址,所以 signal() 函数就可以根据这两个值来决定对信号进行默认处理或忽略。SIGKILL 和 SIGSTOP 不能被忽略。

程序刚开始时,系统设置所有的信号处理方式为 SIG_DFL 或 SIG_IGN。另外,当程序 fork 一个子进程时,子进程会 继承 父进程的信号处理方式 (屏蔽码)。在父进程对信号的设置和处理方式,在子进程中同样有效。

signal() 函数不可靠的原因:信号发生后,系统会将信号处理函数重新设置为默认值。因此,在自定义的信号处理函数中,首先需要再一次设置信号处理函数。在此之前,如果又来了一个信号,将会被丢失。

void sig_handler(int signr)
{
    // 信号发生,调用信号处理函数
    // 系统将该信号的处理函数重置为默认
    // 如果想要继续捕获该信号,需要再一次设置处理函数
    signal(SIGINT, sig_handler); // 在此语句之前,若再来一个信号,将会被丢失
}

main()
{
    signal(SIGINT, sig_handler); // 第一次设置信号处理函数
    ...
}
sigaction() 函数

可靠地内核处理信号机制。

int sigaction(int sig, struct sigaction *act, struct sigaction *oldact);

很显然,就是将 act 对应的结构放置到进程 PCB 的 sigaction[] 中,并返回数组中原来的 sigaction 结构。该结构定义如下:

struct sigaction {
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}

其中,sa_mask 是一个暂时性的信号屏蔽集合?

  • 在 sa_handler 被调用前,sa_mask 被加入进程 PCB 的信号屏蔽集合中
  • 在 sa_handler 被调用后,系统会恢复进程原来的信号屏蔽 bitmap

在处理一指定信号期间,能确保阻塞同一个信号而不让其丢失,直到此次处理完毕???

一个信号被阻塞期间而多次发生时,通常只保存一个样例,即对于阻塞的多个同一信号,只会再调用一次信号处理句柄。修改了一个信号处理句柄后,除非再次更改,否则一直使用该处理句柄;而 signal() 函数会在处理句柄结束后,恢复成信号的默认处理句柄。sa_flags 用于指定处理信号的选项,改变信号处理的默认流程。

#define SA_NOCLDSTOP 1
#define SA_INTERRUPT 0x20000000
#define SA_NO_MASK   0x40000000
#define SA_ONESHOT   0x80000000

sa_restorer 是一个函数指针,在编译链接时由 Libc 函数库提供。在信号处理函数结束后清理用户态堆栈,恢复系统调用返回值。

do_signal() 函数

在进程每次从系统调用中断、时钟中断等退出时,若进程已收到信号,则把信号的处理句柄插入到 用户程序堆栈 中。这样,中断返回后会立刻执行信号处理程序,再返回用户程序。处理过程如图所示:

8-10

如何把信号处理句柄塞进用户态堆栈呢?

  • 函数首先处理两个默认信号句柄
  • 若用户自定义了处理句柄
    • 将内核态堆栈中返回到原用户程序的 eip 指针保存为 old_eip
    • 将 eip 替换为自定义句柄的入口指针
    • 将内核态堆栈中的 esp 指针减小 - 使用户态堆栈向下扩展
    • 将内核态堆栈上的一些寄存器内容复制到用户态堆栈上
      • old_eip:使信号句柄执行完后可以返回用户程序继续执行
      • eax:本来存放着系统调用返回值,但在信号处理句柄中可能会使用到 eax - 所以将 eax 保存在堆栈上
      • edx、ecx、eflags 也需要被恢复
      • 可能有进程的阻塞码
      • 此外,信号处理函数还需要信号值 signr 作为参数

8-11

这样,从中断返回后,程序首先转移到信号处理句柄中执行。执行完毕后再返回到用户程序。

sa_restorer() 函数

由函数库提供,负责清理信号处理程序执行完毕后,恢复用户程序的寄存器值和系统调用返回值。

.globl ____sig_restore
.globl ____masksig_restore
# 没有 blocked
____sig_restore:
    addl $4, %esp # 丢弃堆栈上的信号值 signr
    popl %eax
    popl %ecx
    popl %edx
    popfl
    ret
# 有 blocked
____masksig_restore:
    addl $4, %esp # 丢弃堆栈上的信号值 signr
    call ____ssetmask
    addl $4, %esp # 丢弃堆栈上的 blocked
    popl %eax
    popl $ecx
    popl %edx
    popfl
    ret

综上,do_signal() 执行完后,sys_call.s 将进程内核态堆栈上 eip 以下的值全部弹出。执行 IRET 指令后,弹出用户态堆栈信息,返回用户特权级执行程序。此时,eip 已经被改为信号处理句柄,所以立即执行信号处理程序。处理完后,通过 RET 指令,CPU 将控制权移交 sa_restorer(),该函数清理用户态堆栈,使堆栈看起来像没有运行过信号处理程序一样。完成后,再通过 RET 指令,弹出 old_eip,恢复原用户程序的执行。用户态堆栈的数据如下图所示:

8-12

8.7.1.3 进程挂起

该程序中还包含一个 sys_sigsuspend() 的系统调用实现函数

  • 临时把进程的信号屏蔽码替换成参数中给定的集合
  • 然后挂起进程,直到收到一个信号为止
int sys_sigsuspend(int restart, unsigned long old_mask, unsigned long set);

restart 为重启标志:

  • 0 - 当前的屏蔽码保存在 oldmask 中,阻塞信号,直到收到任何一个信号
  • 非 0 - 从保存的 oldmask 中恢复进程的原屏蔽码

实际调用时,通过函数库中的形式调用:

int sigsuspend(unsigned long set);

由库函数来维护前两个参数:

  • 第一次调用系统调用时,restart 为 0,保存原来的 blocked 为 old_mask
  • 第二次调用时,恢复进程保存在 old_mask 中的屏蔽码

8.7.1.4 被信号中断的系统调用的重新启动

慢速系统调用。被阻塞期间,收到了一个信号。在处理信号时,引入了对某些中断系统调用重新启动的功能:

  • ioctl、read、write 对低速设备操作时,才会被信号中断
  • wait 和 waitpid 在捕捉到信号时总是会被中断

在 Linux 0.12 内核中,在 sigaction 结构体中可以设置是否重新启动被中断的系统调用:

  • 如果设置 SA_INTERRUPT 标志
  • 且信号不是 SIGCONT、SIGSTOP、SIGTSTP、SIGTTIN 和 SIGTTOU

那么在系统调用执行时,收到信号就会被中断,否则内核自动重新执行被中断的系统调用。在目前的 Linux 系统中,SA_INTERRUPT 已经弃置不用,使用意义相反的 SA_RESTART。

8.7.2 代码注释

与信号屏蔽相关的系统调用

// 获取当前任务的信号屏蔽 bitmap (signal-get-mask)
int sys_sgetmask()
{
    return current->blocked
}
// 设置新的信号屏蔽 bitmap,返回原信号屏蔽 bitmap
// SIGKILL 和 SIGSTOP 不能被屏蔽
int sys_ssetmask(int newmask)
{
    int old = current->blocked;
    
    currrent->blocked = newmask & ~(1<<(SIGKILL-1)) & ~(1<<(SIGSTOP-1));
    return old;
}
// 取得进程收到的但被屏蔽的信号
// fill in "set" with signals pending but blocked
// set 应当是一个用户空间指针,所以需要验证存储空间
int sys_sigpending(sigset_t *set)
{
    verify_area(set, 4); // 4B
    put_fs_long(current->blocked & current->signal, (unsigned long *)set);
    return 0;
}
// 更换新的信号屏蔽码,并等待信号到来
// restart 不为 0,则重新运行程序 - 恢复 old_mask 的原进程屏蔽码
// restart 为 0,则是第一次调用 - 保存进程当前屏蔽码到 old_mask,替换新的屏蔽码
int sys_sigsuspend(int restart, unsigned long old_mask, unsigned long set)
{
    extern int sys_pause(void);
    
    if (restart) {
        // restarting system call
        current->blocked = old_mask;
        return -EINTR; // 系统调用被信号中断
    }
    
    *(&restart) = 1;
    *(&old_mask) = current->blocked;
    current->blocked = set;
    (void) sys_pause(); // 等待信号到来
    return -ERESTARTNOINTR; // 处理信号,并返回本系统调用继续运行 - 不中断
}

sigaction 数据在内核空间和用户空间之间的互相拷贝

fs 数据段表示用户空间。

static inline void save_old(char *from, char *to)
{
    int i;
    
    verify_area(to, sizeof(struct sigaction)); // 验证用户空间内存是否够大
    for (i = 0; i < sizeof(struct sigaction); i++) {
        put_fs_byte(*from, to);
        from++;
        to++;
    }
}
static inline void get_new(char *from, char *to)
{
    int i;
    
    for (i = 0; i < sizeof(struct sigaction); i++)
        *(to++) = get_fs_byte(from++);
}

signal() 系统调用

为指定的信号安装新的信号句柄:

  • 可以是用户指定的函数
  • 也可以是 SIG_DFL 或 SIG_IGN
// 参数 restorer 由 Libc 库提供
// 返回原信号句柄
int sys_signal(int signum, long handler, long restorer)
{
    struct sigaction tmp;
    
    // 验证信号在有效范围内 (1-32)
    // 不能是信号 SIGKILL 和 SIGSTOP - 这两个信号不能被进程捕获
    if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)
        return -EINVAL;
    
    // 构造 sigaction 结构体内容
    tmp.sa_handler = (void (*)(int)) handler;
    tmp.sa_mask = 0;
    tmp.sa_flags = SA_ONESHOT | SA_NOMASK; // 句柄使用一次后恢复默认
    tmp.sa_restorer = (void (*)(void)) restorer;
    
    // 替换 sigaction 结构体
    handler = (long) current->sigaction[signum-1].sa_handler;
    current->sigaction[signum-1] = tmp;
    return handler;
}

sigaction() 系统调用

改变进程在收到一个信号时的操作 - SIGKILL 除外

  • 新 action 不为空,则新 action 被安装
  • oldaction 不为空,则原 action 被保存到 oldaction 中
int sys_sigaction(int signum, const struct sigaction *action, struct sigaction *oldaction)
{
    struct sigaction tmp;
    
    // 验证信号值在有效范围内 (1-32)
    // 信号不得是 SIGKILL 和 SIGSTOP
    if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)
        return -EINVAL;
    
    // 旧的 sigaction 结构体
    tmp = current->sigaction[signum-1];
    // 从用户空间中拷贝新的 sigaction 结构
    get_new((char *)action, (char *) (signum - 1 + current->sigaction)); 
    if (oldaction)
        save_old((char *) &tmp, (char *) oldaction);
    
    if (current->sigaction[signum-1].sa_flags & SA_NOMASK)
        current->sigaction[signum-1].sa_mask = 0;
    else
        current->sigaction[signum-1].sa_mask |= (1 << (signum-1)); // ?
    
    return 0;
}

core dump

目前暂未实现。

do_signal() 函数

系统调用中断处理程序中,真正的信号预处理程序:

  • 将信号处理句柄插入到用户态堆栈中
  • 使系统调用结束后立刻去执行信号处理句柄
  • 然后继续执行用户程序

函数的参数在 sys_call.s 中被依次压入内核态堆栈:

  • CPU 执行中断指令后,保存的用户态堆栈的 SS、ESP、EFLAGS、CS、EIP
  • DS、ES、FS 段寄存器;EAX 寄存器
  • EDX、ECX、EBX
  • 系统调用返回值 EAX
  • 当前信号值 signr
int do_signal(long signr, long eax, long ebx, long ecx, long edx,long orig_eax,
             long fs, long es, long ds,
             long eip, long cs, long eflags,
             unsigned long * esp, long ss)
{
    unsigned long sa_handler;
    long old_eip = eip; // 用户程序返回地址
    struct sigaction * sa = current->sigaction + signr - 1; // current->sigaction[signr-1]
    int longs; // 用户态堆栈扩展的长度
    
    unsigned long * tmp_esp;
    
#ifdef notdef
    // printk ...
    // debug info
#endif
    
    // 功能号不为 -1,是系统调用
    if ((orig_eax != -1) &&
        ((eax == -ERESTARTSYS) || (eax == -ERESTARTNOINTR))) {
        if ((eax == -ERESTARTSYS) &&
            ((sa->saflags & SA_INTERRUPT) || signr < SIGCONT || signr > SIGTTOU))
            // 系统调用返回码是 -ERESTARTSYS (重新启动系统调用)
            // 并且 sigaction 中含有标志 SA_INTERRUPT
            // 信号不是 SIGCONT、SIGSTOP、SIGTSTP、SIGTSTP、SIGTTIN、SIGTTOU
            *(&eax) = -EINTR; // 返回 被信号中断的系统调用
        else {
            // 返回用户程序时,让程序重新启动被信号中断的系统调用
            *(&eax) = orig_eax; // 恢复 eax 中的系统调用功能号
            *(&eip) = old_eip -= 2; // 指令指针回调
        }
    }
    
    sa_handler = (unsigned long) sa->sa_handler;
    
    if (sa_handler == 1) // 信号句柄为 SIG_IGN - 忽略
        return (1);
    if (!sa_handler) { // 信号句柄为 SIG_DFL - 默认处理
        // 根据具体的信号进行分别处理
        switch (signr) {
            case SIGCONT:
            case SIGCHLD:
                // 默认忽略
                return (1);
            case SIGSTOP:
            case SIGTSTP:
            case SIGTTIN:
            case SIGTTOU:
                // 当前进程状态置为停止状态
                // 若父进程对该进程的 SIGCHLD 信号没有设置处理标志 SA_NOCLDSTOP
                // 向父进程发送 SIGCHLD 信号
                current->state = TASK_STOPPED;
                current->exit_code = signr;
                if (!(current->p_pptr->sigaction[SIGCHLD-1].sa_flags & SA_NOCLDSTOP))
                    current->p_pptr->signal |= (1<<(SIGCHLD-1));
                return (1);
            case SIGQUIT:
            case SIGILL:
            case SIGTRAP:
            case SIGIOT:
            case SIGFPE:
            case SIGSEGV:
                if (core_dump(signr))
                    do_exit(signr | 0x80);
            default:
                do_exit(signr);
        }
    }
    
    // 句柄只使用一次,则置空
    // 目前句柄已经保存在 sa_handler 中
    if (sa->sa_flags & SA_ONESHOT)
        sa->sa_handler = NULL;
    
    *(&eip) = sa_handler; // 修改 eip 为信号处理句柄
    longs = (sa>sa_flags & SA_NOMASK) ? 7 : 8;
    *(&esp) -= longs; // 堆栈向下扩展
    verify_area(esp, longs * 4);
    
    tmp_esp = esp; // tmp_esp 用作暂时的栈顶指针
    put_fs_long((long) sa->sa_restorer, tmp_esp++);
    put_fs_long(signr, tmp_esp++);
    if (!(sa->sa_flags) & SA_NOMASK)
        put_fs_long(current->blocked, tmp_esp++);
    put_fs_long(eax, tmp_esp++);
    put_fs_long(ecx, tmp_esp++);
    put_fs_long(edx, tmp_esp++);
    put_fs_long(eflags, tmp_esp++);
    put_fs_long(old_eip, tmp_esp++);
    current->blocked |= sa->sa_mask;
    return (0);
}

8.7.3 其它信息

8.7.3.1 进程信号说明

IndexNameDescriptionDefault Operation
1SIGHUP当不再有控制终端时产生挂断控制终端或进程
2SIGINT键盘中断 - ^C终止程序
3SIGQUIT键盘退出中断 - ^\程序被终止,产生 core dump 文件
4SIGILL非法指令程序被终止,产生 core dump 文件
5SIGTRAP调试,跟踪断点
6SIGABRT放弃执行,异常结束程序被终止,产生 core dump 文件
6SIGIOT同 SIGABRT程序被终止,产生 core dump 文件
7SIGUNUSED没有使用
8SIGFPE浮点异常程序被终止,产生 core dump 文件
9SIGKILL终止 - 该信号不能被捕获或者忽略程序被终止
10SIGUSR1用户定义的信号进程被终止
11SIGSEGV程序引用无效内存程序被终止,产生 core dump 文件
12SIGUSR2用于定义的信号进程被终止
13SIGPIPE程序向一个 socket 写时没有读者进程被终止
14SIGALRM设定的 alarm 延迟时间到进程被终止
15SIGTERM要求程序终止,可以被捕获进程被终止
16SIGSTKFLT协处理器堆栈错误进程被终止
17SIGCHLD子进程发出,子进程已停止或终止忽略
18SIGCONT致使被 SIGSTOP 停止的进程恢复运行恢复进程执行
19SIGSTOP停止进程的运行 - 不可被捕获或忽略停止进程运行
20SIGTSTP向终端发送停止键序列停止进程运行
21SIGTTIN后台进程试图从一个不再被控制的终端上读取数据停止进程运行
22SIGTTOU后台进程试图向一个不再被控制的终端上输出数据停止进程运行
Edit this page on GitHub
Prev
Chapter 8.6 - sched.c 程序
Next
Chapter 8.8 - exit.c 程序