Kernel 进程相关

2015-06-02 Tuesday     linux , misc

实际上一个进程,包括了代码、数据和分配给进程的资源,这是一个动态资源。

这里简单介绍与进程相关的东西,例如进程创建、优先级、进程之间的关系、进程组和会话、进程状态等。

进程

一个进程,包括了代码、数据和分配给进程的资源。

初始进程

在 Linux 中,进程基本都是通过复制其它进程的结构来实现的,利用 slabs 来动态分配,系统没有提供用于创建进程的接口。

唯一的一个例外是第一个 task_struct,这是由一个静态或者说是固化的结构表示的 (init_task),该进程的 PID=0,可以参看 arch/x86/kernel/init_task.c,也可以称之为空闲进程。

当内核执行到 sched_init() 时,task_struct 的核心 TRLDT 就被手工设置好了,这时,0 号进程就有了,而在 sched_init() 之前,是没有 “进程” 这个概念的,而 init(PID=1) 进程是 0 号进程 fork() 出来的。

struct task_struct init_task = INIT_TASK(init_task);

查看 INIT_TASK() 宏定义时,会发现很多有意思的东西,如 init_task 的栈通过 .stack = &init_thread_info 指定,而该栈实际时通过如下的方式静态分配。

union thread_union init_thread_union __init_task_data = { INIT_THREAD_INFO(init_task) };

定义中的 __init_task_data 表明该内核栈所在的区域位于内核映像的 init data 区,可以通过编译完内核后所产生的 System.map 来看到该变量及其对应的逻辑地址。

$ cat System.map-`uname -r` | grep init_thread_union
ffffffff818fc000 D init_thread_union

而内核在无进程的情况下,将一直从初始化部分的代码执行到 start_kernel(),然后再到其最后一个函数调用 rest_init(),在该函数中将产生第一个进程 PID=1

start_kernel()
 |-rest_init()
   |-kernel_thread()        实际上调用kernel_init()创建第一个进程PID=1

kernel_init()
 |-run_init_process()
   |-do_execve()

最后 init_task 任务会变成 idle 进程。

在 Linux 系统中,可创建进程的最大值是由 max_threads@kernel/fork.c 变量确定的,也可以通过 /proc/sys/kernel/threads-max 查看/更改此值。

fork

通过 fork() 将创建一个与原来进程几乎完全相同的进程,系统给新的进程分配资源,例如存储数据和代码的空间,然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同,相当于克隆了一个自己。

#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    int count = 0;

    pid = fork();            // vfork(); error
    if (pid < 0) {
        printf("error in fork!");
    } else if (pid == 0) {   // child process
        count++;
        printf(" child: PID is %d, count is %d\n", getpid(), count);
    } else {                 // parent process
        count++;
        printf("parent: PID is %d, count is %d\n", getpid(), count);
    }

    return 0;
}

在语句 fork() 之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同。其中 fork() 的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

  1. 在父进程中,fork() 返回新创建子进程的进程 ID;
  2. 在子进程中,fork() 返回 0;
  3. 如果出现错误,fork() 返回一个负值;

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

执行流程

  1. 进程可以看做程序的一次执行过程。在 linux 下,每个进程有唯一的 PID 标识进程。 PID 是一个从 1 到 32768 的正整数,其中 1 一般是特殊进程 init ,其它进程从 2 开始依次编号。当用完 32768 后,从 2 重新开始。

  2. Linux 中有一个叫进程表的结构用来存储当前正在运行的进程。可以使用 ps aux 命令查看所有正在运行的进程。

  3. 进程在 linux 中呈树状结构, init 为根节点,其它进程均有父进程,某进程的父进程就是启动这个进程的进程,这个进程叫做父进程的子进程。

  4. fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

process fork

上图表示一个含有 fork 的程序,而 fork 语句可以看成将程序切为 A、B 两个部分。然后整个程序会如下运行:

  1. 设由 shell 直接执行程序,生成了进程 M 。 M 执行完 Part.A 的所有代码。

  2. 当执行到 pid = fork(); 时, M 启动一个进程 S ,S 是 M 的子进程,和 M 是同一个程序的进程。S 继承 M 的所有变量、环境变量、程序计数器的当前值。

  3. 在 M 进程中,fork() 将 S 的 PID 返回给变量 pid ,并继续执行 Part.B 的代码。

  4. 在进程 S 中,将 0 赋给 pid ,并继续执行 Part.B 的代码。

这里有三个点非常关键:

  • M 执行了所有程序,而 S 只执行了 Part.B ,即 fork() 后面的程序,(这是因为 S 继承了 M 的 PC-程序计数器)。

  • S 继承了 fork() 语句执行时当前的环境,而不是程序的初始环境。

  • M 中 fork() 语句启动子进程 S ,并将 S 的 PID 返回,而 S 中的 fork() 语句不启动新进程,仅将 0 返回。

举例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
    pid_t pid1;
    pid_t pid2;

    pid1 = fork();
    pid2 = fork();

    printf("pid1:%d, pid2:%d\n", pid1, pid2);
}

对于如上的程序,在执行之后将会生成 4 个进程,如果其中一个进程的输出结果是 pid1:1001, pid2:1002 ,那么其他的分别为。

pid1:1001, pid2:0
pid1:0, pid2:1003
pid1:0, pid2:0

实际的执行过程如下所示:

process fork

最后的实际执行过程为:

  1. 从 shell 中执行此程序,启动了一个进程,我们设这个进程为 P0 ,设其 PID 为 XXX (解题过程不需知道其 PID)。

  2. 当执行到 pid1 = fork(); 时, P0 启动一个子进程 P1 ,由题目知 P1 的 PID 为 1001 。我们暂且不管 P1 。

  3. P0 中的 fork 返回 1001 给 pid1 ,继续执行到 pid2 = fork(); ,此时启动另一个新进程,设为 P2 ,由题目知 P2 的 PID 为 1002 。同样暂且不管 P2 。

  4. P0 中的第二个 fork 返回 1002 给 pid2 ,继续执行完后续程序,结束。所以, P0 的结果为 “pid1:1001, pid2:1002” 。

  5. 再看 P2 , P2 生成时, P0 中 pid1=1001 ,所以 P2 中 pid1 继承 P0 的 1001 ,而作为子进程 pid2=0 。 P2 从第二个 fork 后开始执行,结束后输出 “pid1:1001, pid2:0” 。

  6. 接着看 P1 , P1 中第一条 fork 返回 0 给 pid1 ,然后接着执行后面的语句。而后面接着的语句是 pid2 = fork(); 执行到这里, P1 又产生了一个新进程,设为 P3 。先不管 P3 。

  7. P1 中第二条 fork 将 P3 的 PID 返回给 pid2 ,由预备知识知 P3 的 PID 为 1003 ,所以 P1 的 pid2=1003 。 P1 继续执行后续程序,结束,输出 “pid1:0, pid2:1003” 。

  8. P3 作为 P1 的子进程,继承 P1 中 pid1=0 ,并且第二条 fork 将 0 返回给 pid2 ,所以 P3 最后输出 “pid1:0, pid2:0” 。

  9. 至此,整个执行过程完毕。

进程创建

进程和线程相关的函数在内核中,最终都会调用 do_fork(),只是最终传入的参数不同。

其中在用户空间中与进程相关的接口有 fork()vfork()clone()exec();线程相关的有 pthread_create (glibc) 和 kernel_thread (Kernel内部函数),pthread_create() 是对 clone() 的封装,kernel_thread() 用于创建内核线程,两者最终同样会调用 do_fork()

区别

fork() 会创建新的进程;exec() 的原进程将会被新的进程替换;而 vfork() 其实就是 fork() 的部分过程,通过简化来提高效率。

fork() 是进程资源的完全复制,包括进程的 PCB(task_struct)、线程的系统堆栈、进程的用户空间、进程打开的设备等,而在 clone() 中其实只有前两项是被复制了的,后两项都与父进程共享,对共享数据的保护必须有上层应用来保证。

vfork()fork() 主要区别:

  • fork() 子进程拷贝父进程的数据段,堆栈段;vfork() 子进程与父进程共享数据段。
  • fork() 父子进程的执行次序不确定;vfork() 保证子进程先运行,在调用 exec()exit() 之前与父进程数据是共享的,在它调用 exec()exit() 之后父进程才可能被调度运行,否则会发生错误。
  • 如果在此之前子进程有依赖于父进程的进一步动作,如调用函数,则会导致错误。

fork() 在执行复制时,采用 “写时复制”,开始的时候内存并没有被复制,而是共享的,直到有一个进程去写某块内存时,它才被复制。实际操作时,内核先将这些内存设为只读,当它们被写时,CPU 出现访存异常,内核捕捉异常,复制空间,并改属性为可写。

但是,”写时复制” 其实还是有复制,进程的 mm 结构、页表都还是被复制了,而 vfork() 会忽略所有关于内存的东西,父子进程的内存是完全共享的。

不过此时,父子进程共用着栈,如果两个进程并行执行,那么可能会导致调用栈出错。所以,vfork() 有个限制,当子进程生成后,父进程在 vfork() 中被内核挂起,直到子进程有了自己的内存空间 (exec**) 或退出 (_exit)

并且,在此之前,子进程不能从调用 vfork() 的函数中返回,同时,不能修改栈上变量、不能继续调用除 _exit()exec() 系列之外的函数,否则父进程的数据可能被改写。

虽然 vfork() 的限制很多,但是对于 shell 来说却非常适合。

源码解析

如下是内核中的接口,do_fork() 返回的是子进程的 PID。

// kernel/fork.c
do_fork(SIGCHLD, 0, 0, NULL, NULL);                           // sys_fork
do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 0, NULL, NULL);  // sys_vfork
do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);  // sys_clone


do_fork() @ kernel/fork.c
  |-... ...                            // 做一些参数的检查工作,主要是针对flag
  |-copy_process()                     // 复制一个进程描述符,PID不同
  |  |-security_task_create()          // 安全模块的回调函数
  |  |-dup_task_struct()               // 新建task_struct和thread_info,并将当前进程相应的结构完全复制过去
  |  |-atomic_read()                   // 判断是否超过了进程数
  |  |-copy_creds()                    // 应该是安全相关的设置
  |  |-... ...                         // 判断创建的进程是否超了进程的总量等
  |  |-sched_fork()                    // 调度相关初始化
  |  |-copy_xxx()                      // 根据传入的flag复制相应的数据结构
  |  |-alloc_pid()                     // 为新进程获取一个有效的PID
  |  |-sched_fork()                    // 父子进程平分共享的时间片
  |
  |-wake_up_new_task()                 // 如果创建成功则执行
  |-wait_for_vfork_done()              // 如果是vfork()等待直到子进程exit()或者exec()

clone_flags 由 4 个字节组成,最低的一个字节为子进程结束时发送给父进程的信号代码,fork/vforkSIGCHLDclone 可以指定;剩余的三个字节则是各种 clone() 标志的组合,用于选择复制父进程那些资源。

内核有意选择子进程首先执行。因为一般子进程都会马上调用 exec 函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能开始向地址空间写入。

PID分配

pid 分配的范围是 0~32767,struct pidmap 用来标示 pid 是不是已经分配出去了,采用位图的方式,每个字节表示 8 个 PID,为了表示 32768 个进程号要用 32768/8=4096 个字节,也即一个 page,结构体中的 nr_free 表示剩余 PID 的数量。

PID 相关的一些操作实际上就是 bitmap 的操作,常见的如下。

  • int test_and_set_bit(int offset, void *addr);
    将 offset 在 pidmap 变量当中相应的位置为 1,并返回旧值;也就是申请到一个 pid 号之后,修改位标志,其中 addr 是 pidmap.page 变量的地址。

  • void clear_bit(int offset, void *addr);
    将 offset 在 pidmap 变量当中相应的位置为 0,也就是释放一个 pid 后,修改位标志,其中 addr 是 pidmap.page 变量的地址。

  • int find_next_zero_bit(void *addr, int size, int offset);
    从 offset 开始,找下一个是 0 (也就是可以分配) 的 pid 号,其中 addr 是 pidmap.page 变量的地址,size 是一个页的大小。

  • int alloc_pidmap();
    分配一个 pid 号。

  • void free_pidmap(int pid);
    回收一个 pid 号。

目前 Linux 通过 PID 命名空间进行隔离,其中有一个变量 last_pid 用于标示上一次分配出去的 pid 编号。

在内核中通过移位操作来实现根据 PID 查找地址,可以想象抽象出一张表,这个表有 32 列,1024行,这个刚好是一个页的大小,可以参考相应程序的示例。

系统进程遍历

在内核中,会通过双向链表保存任务列表,可以将 init_task 作为链表的开头,然后进行迭代。

可以通过内核模块 procsview 进行查看,通过 printk 将所有的进程打印出来,结果可以通过 tail -f /var/log/messages 查看。

可以通过宏定义 next_task()@include/linux/sched.h 简化任务列表的迭代,该宏会返回下一个任务的 task_struct 引用;current 标识当前正在运行的进程,这实际是一个宏,在 arch/x86/include/asm/current.h 中定义。

通过 Make 进行编译,用 insmod procsview.ko 插入模块对象,用 rmmod procsview 删除它。插入后,/var/log/messages 可显示输出,其中有一个空闲任务 (称为 swapper) 和 init 任务 (pid 1)。

进程关系

task_struct 结构中,保存了一些字段,用来维护各个进程之间的关系。

在 Linux 中,线程是通过轻量级进程 (LWP) 实现,会为每个进程和线程分配一个 PID,同时我们希望由一个进程产生的轻量级进程具有相同的 PID,这样当我们向进程发送信号时,此信号可以影响进程及进程产生的轻量级进程。

为此,采用线程组 (可以理解为轻量级进程组) 的概念,在线程组内,每个线程都使用此线程组内第一个线程 (thread group leader) 的 pid,并将此值存入tgid,当我们使用 getpid() 函数得到进程 ID 时,其实操作系统返回的是 task_struct 的 tgid 字段,而非 pid 字段。

struct task_struct {
    pid_t pid; pid_t tgid;            // 用于标示线程和线程组ID
    struct task_struct *real_parent;  // 实际父进程real parent process
    struct task_struct *parent;       // SIGCHLD的接受者,由wait4()报告
    struct list_head children;        // 子进程列表
    struct list_head sibling;         // 兄弟进程列表
    struct task_struct *group_leader; // 线程组的leader
};
$ ps -eo  uid,pid,ppid,pgid,sid,pidns,tty,comm

getpid()[tgid]、gettid()[pid]、getppid()[real_parent]、getsid()

getuid()、getgid()、geteuid()、getegid()、getgroups()、getresuid()、getresgid()、getpgid()、<br><br>

getpgrp()/getpgid()/setpgid()        // 获取或者设置进程组

gettid() 返回线程号,如果是单线程与 getpid() 相同,该值在整个 Linux 系统内是唯一的;pthread_self() 返回的线程号只能保证在进程中是唯一的。

process relations

下面是内核提供的系统调用,实现有点复杂,可以通过注释查看返回的 task_strcut 中的值。

Linux 中的进程组是为了方便对进程进行管理,假设要完成一个任务,需要同时并发 100 个进程,当用户处于某种原因要终止这个任务时,可以将这些进程设置备为同一个进程组,然后向进程组发送信号。

进程必定属于一个进程组,也只能属于一个进程组;一个进程组中可以包含多个进程,进程组的生命周期从被创建开始,到其内所有进程终止或离开该组。

由于 Linux 是多用户多任务的分时系统,所以必须要支持多个用户同时使用一个操作系统,当一个用户登录一次系统就形成一次会话。

一个或多个进程组可以组成会话,由其中一个进程建立,该进程叫做会话的领导进程 (session leader),会话领导进程的 PID 成为识别会话的 SID(session ID);一个会话可包含多个进程组,但只能有一个前台进程组。

会话中的每个进程组称为一个作业 (job),bash(Bourne-Again shell) 支持作业控制,而 sh(Bourne shell) 并不支持。

会话可以有一个进程组成为会话的前台工作 (foreground),而其他的进程组是后台工作 (background)。每个会话可以连接一个控制终端 (control terminal),当控制终端有输入输出时,都传递给该会话的前台进程组,由终端产生的信号,比如 CTRL+ZCTRL+\ 会传递到前台进程组。

会话的意义在于将多个工作囊括在一个终端,并取其中的一个工作作为前台,来直接接收该终端的输入输出以及终端信号,其他工作在后台运行。一般开始于用户登录,终止于用户退出,此期间所有进程都属于这个会话期。

一个工作可以通过 fg 从后台工作变为前台工作,如 fg %1

进程组和会话期

当通过 SSH 或者 telent 远程登录到 Linux 服务器时,如果执行一个长时间运行的任务,如果关掉窗口或者断开连接,这个任务就会被杀掉,一切都半途而废了,这主要是因为 SIGHUP 信号。

在 Linux/Unix 中,有这样几个概念:

  • 进程组 (process group): 一个或多个进程的集合,每一个进程组有唯一一个进程组 ID ,即进程组长进程的 ID 。
  • 会话期 (session): 一个或多个进程组的集合,有唯一一个会话期首进程 (session leader),会话期 ID 为首进程的 ID 。
  • 会话期可以有一个单独的控制终端(controlling terminal)。与控制终端连接的会话期首进程叫做控制进程(controlling process)。当前与终端交互的进程称为前台进程组,其余进程组称为后台进程组。

根据 POSIX.1 的定义,挂断信号 (SIGHUP) 默认的动作是终止程序;当终端接口检测到网络连接断开,将挂断信号发送给控制进程 (会话期首进程);如果会话期首进程终止,则该信号发送到该会话期前台进程组;一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于 STOP 状态,发送 SIGHUP 和 SIGCONT 信号到该进程组中所有进程。

因此当网络断开或终端窗口关闭后,控制进程收到 SIGHUP 信号退出,会导致该会话期内其他进程退出。

打开两个 SSH 终端窗口,或者两个 gnome-terminal,在其中一个运行 top 命令。在另一个终端窗口,找到 top 的进程 ID 为 24317 ,其父进程 ID 为 24230 ,即登录 shell 。使用 pstree 命令可以更清楚地看到这个关系。

# top

# ps -ef | grep top
UID        PID  PPID  C STIME    TTY       TIME CMD
andy     24317 24230  2 13:45 pts/16   00:00:05 top
andy     24526 24419  0 13:48 pts/17   00:00:00 grep --color=auto top

# pstree -H 24317 | grep top
|-sshd-+-sshd-+-sshd---bash---top

使用 ps -xj 命令可以看到,登录 shell (PID 24230) 和 top 在同一个会话期, shell 为会话期首进程,所在进程组 PGID 为 24230, top 所在进程组 PGID 为 24317 ,为前台进程组。

~$ ps -xj | grep 24230
 PPID   PID  PGID   SID    TTY   TPGID STAT   UID   TIME COMMAND
24229 24230 24230 24230 pts/16   24317 Ss    1000   0:00 -bash
24230 24317 24317 24230 pts/16   24317 S+    1000   0:18 top
24419 24543 24542 24419 pts/17   24542 S+    1000   0:00 grep --color=auto 2423

关闭第一个 SSH 窗口,在另一个窗口中可以看到 top 也被杀掉了,如果我们可以忽略 SIGHUP 信号,关掉窗口应该就不会影响程序的运行了。

nohup 命令可以达到这个目的,如果程序的标准输出/标准错误是终端, nohup 默认将其重定向到 nohup.out 文件。值得注意的是 nohup 命令只是使得程序忽略 SIGHUP 信号,还需要使用标记 & 把它放在后台运行。

其它

指定CPU

Affinity 是进程的一个属性,这个属性指明了进程调度器能够把这个进程调度到哪些 CPU 上,可以利用 CPU affinity 把一个或多个进程绑定到一个或多个 CPU 上。

CPU Affinity 分为 soft affinity 和 hard affinity; soft affinity 仅是一个建议,如果不可避免,调度器还是会把进程调度到其它的 CPU 上;hard affinity 是调度器必须遵守的规则。

增加 CPU 缓存的命中率。CPU 之间是不共享缓存的,如果进程频繁的在各个 CPU 间进行切换,需要不断的使旧 CPU 的 cache 失效。如果进程只在某个 CPU 上执行,则不会出现失效的情况。

另外,在多个线程操作的是相同的数据的情况下,如果把这些线程调度到一个处理器上,大大的增加了 CPU 缓存的命中率。但是可能会导致并发性能的降低。如果这些线程是串行的,则没有这个影响。

适合 time-sensitive 应用,在 real-time 或 time-sensitive 应用中,我们可以把系统进程绑定到某些 CPU 上,把应用进程绑定到剩余的 CPU 上。

CPU 集可以认为是一个掩码,每个设置的位都对应一个可以合法调度的 CPU,而未设置的位则对应一个不可调度的 CPU。换而言之,线程都被绑定了,只能在那些对应位被设置了的处理器上运行。通常,掩码中的所有位都被置位了,也就是可以在所有的 CPU 中调度。

另外可以通过 schedutils 或者 python-schedutils 进行设置,后者现在更加常见。

对于如何将进程和线程绑定到指定的 CPU 可以参考 github affinity_process.cgithub affinity_thread.c

杀死进程的N中方法

查看进程通常可以通过如下命令

$ ps -ef
$ ps -aux
......
smx    1827     1   4 11:38 ?        00:27:33 /usr/lib/firefox-3.6.18/firefox-bin
......

此时如果我想杀了火狐的进程就在终端输入:

$ kill -s 9 1827

其中 -s 9 制定了传递给进程的信号是 9,即强制、尽快终止进程。

无论是 ps -ef 还是 ps -aux,每次都要在一大串进程信息里面查找到要杀的进程,看的眼都花了。因此通过如下的方法进行改进。

使用grep

把 ps 的查询结果通过管道给 grep 查找包含特定字符串的进程。管道符 | 用来隔开两个命令,管道符左边命令的输出会作为管道符右边命令的输入。

$ ps -ef | grep firefox
smx    1827     1   4 11:38 ?        00:27:33 /usr/lib/firefox-3.6.18/firefox-bin
smx    12029  1824  0 21:54 pts/0    00:00:00 grep --color=auto firefox
$ kill -s 9 1827

使用pgrep

pgrep 的 p 表明了这个命令是专门用于进程查询的 grep 。

$ pgrep firefox
1827
$ kill -s 9 1827

使用pidof

实际就是 pid of xx,字面翻译过来就是 xx 的 PID ,和 pgrep 相比稍显不足的是,pidof 必须给出进程的全名。

$ pidof firefox-bin
1827
$kill -s 9 1827

无论是使用 ps 然后慢慢查找进程 PID 还是用 grep 查找包含相应字符串的进程,亦或者用 pgrep 直接查找包含相应字符串的进程 PID ,然后手动输入给 Kill 杀掉,都稍显麻烦。

一步完成

$ps -ef | grep firefox | grep -v grep | cut -c 9-15 | xargs kill -s 9

使用 pgrep/pidof

$ pgrep firefox | xargs kill -s 9

使用awk

$ ps -ef | grep firefox | awk '{print $2}' | xargs kill -9
kill: No such process

替换 xargs

$ kill -s 9 `ps -aux | grep firefox | awk '{print $2}'`

换成 pgrep

$ kill -s 9 `pgrep firefox`

使用 pkill

pkill=pgrep+kill。

$ pkill -9 firefox

说明:”-9” 即发送的信号是9,pkill与kill在这点的差别是:pkill无须 “s”,终止信号等级直接跟在 “-“ 后面。

使用 killall

killall和pkill是相似的,不过如果给出的进程名不完整,killall会报错。pkill或者pgrep只要给出进程名的一部分就可以终止进程。

$ killall -9 firefox

信号量

信号说明编号
SIGHUP Hangup1
SIGINT Interrupt2
SIGQUIT Quit and dump core3
SIGILL Illegal instruction4
SIGTRAP Trace/breakpoint trap5
SIGABRT Process aborted6
SIGBUS Bus error: "access to undefined portion of memory object"7
SIGFPE Floating point exception: "erroneous arithmetic operation"8
SIGKILL Kill (terminate immediately)9
SIGUSR1 User-defined 110
SIGSEGV Segmentation violation11
SIGUSR2 User-defined 212
SIGPIPE Write to pipe with no one reading13
SIGALRM Signal raised by alarm14
SIGTERM Termination (request to terminate)15
SIGCHLD Child process terminated, stopped (or continued*)17
SIGCONT Continue if stopped18
SIGSTOP Stop executing temporarily19
SIGTSTP Terminal stop signal20
SIGTTIN Background process attempting to read from tty ("in")21
SIGTTOU Background process attempting to write to tty ("out")22
SIGURG Urgent data available on socket23
SIGXCPU CPU time limit exceeded24
SIGXFSZ File size limit exceeded25
SIGVTALRM Signal raised by timer counting virtual time: "virtual timer expired"26
SIGPROF Profiling timer expired27
SIGPOLL Pollable event29
SIGSYS Bad syscall31

参考

神奇的vfork (local),一个针对错误使用 vfork() 时的场景分新。

进程描述和进程创建,用于描述进程,其中包括了进程创建以及退出,一篇不错的文章。



如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^


About This Blog

Recent Posts

Categories

Related Links

  • RTEMS
    RTEMS
  • GNU
  • Linux Kernel
  • Arduino

Search


This Site was built by Jin Yang, generated with Jekyll, and hosted on GitHub Pages
©2013-2019 – Jin Yang