Linux 中的进程:
 程序时一个预定义的指令序列,用来完成一个特定的任务。
 C 编译器可以把每个源文件翻译成一个目标文件,链接器将所有的目标文件与一些必要的库链接在一起,产生一个可执行文件。当程序被执行时,操作系统将可执行文件复制到内存中,这就是程序的映像。
 进程是一个程序正在执行的实例。每个这样的实例都有自己的地址空间与执行状态。进程必须有一个PID(Process
ID,进程标识),以便操作系统能够区分各个不同的进程。操作系统记录进程的 PID
与状态,并根据这些信息来分配系统资源。当操作系统产生一个新的PID,生成对应的用于管理的数据结构,并为运行程序代码分配了必要的资源,一个新的进程就产生了。
 可以认为进程就是一个执行的流程,在顺序执行时 CPU 的程序计数器总是指向下一条要执行的指令的地址,如果 CPU 或程序指令修改了程序计数器的内容,执行流程就发生了跳转。

一个进程具有如下核心要素:
 ◆ 程序映像:二进制指令序列。
 ◆ 地址空间:用于存放程序和执行程序。
 ◆ PCB(Process Control Block,进程控制块):内核中描述进程的主要数据结构。
 
 进程管理是操作系统的核心功能。

创建进程:
 Linux
系统上的进程间有父子关系。一个进程有且仅有一个父进程,但是可能有多个子进程。所有的进程都有一个共同的祖先,即 init 进程。init
是系统启动后创建的第一个用户态进程,它的 PID 为 1。init
进程对保持进程的正常运行十分重要,它会持续存在知道系统关闭,而且即使是超级用户也不能够通过信号使其终止。
 Linux
系统中可以使用三个系统调用创建进程:fork,vfork 和 clone。前两个是所以类 UNIX 系统都提供的传统的创建进程的系统调用,而
clone 是 Linux 系统独有的用于创建线程的系统调用方式,它可以用来创建进程或线程。从可移植的角度考虑,不鼓励直接使用 clone
系统调用,如果要创建线程,可以使用 POSIX 的线程 API。

fork 系统调用是创建进程最常用的方式,其接口头文件与函数原型如下:
 #include <unistd.h>
 pid_t fork(void);

当 fork
调用成功返回时,系统中将会出现一个新的进程。新的进程成为原进程的子进程,而原进程则是新进程的父进程。子进程几乎完全克隆了父进程的一切特征,包括虚拟地址空间和执行进度。fork
函数返回一个 pid_t 型的进程 ID,从程序员的角度看,父子进程的唯一差异在于 fork
函数的返回值是不同的:父进程中返回非零值,是其子进程的进程ID,如果是 -1,就表示创建进程失败;而在子进程中永远返回
0。这就是在程序中判断是父进程还是子进程的依据。
 
 创建进程的另外一个系统调用时 vfork,其接口头文件与函数原型如下:
 #include <sys/types.h>
 #include <unistd.h>
 pid_t vfork(void);

vfork 系统调用与 fork 基本是一样的,但是用它创建了子进程后并不完全复制父进程的虚拟地址空间。在早期的操作系统上,fork
系统调用会为新创建的进程分配新的物理内存以容纳从父进程复制过来的虚拟地址空间。考虑到很多时候,子进程将会立即调用 exec
函数装载新的程序执行,这使得之前的内存分配与线性地址空间复制毫无意义并且浪费资源,vfork
的思想就是消除这个新的物理内存分配与虚拟地址空间复制的过程,让新进程的创建更有效率。使用 vfork 时,父进程会一直阻塞,直到子进程退出或调用
exec 执行新程序,并且在子进程中最好不要修改任何全局变量,因为实际上操作系统并没有为这些变量分配物理内存。
 在现代的操作系统中,fork 调用都使用了所谓的“写时复制”(copy on write)技术,因此 fork 系统调用与 vfork 的工作效率几乎是一样的。“写时复制”的实现如下:
 ◆ 当一个进程创建时,它与父进程尽可能的共享同样的物理内存,内核仅仅复制进程的页表项并标明页的属性是“写时复制”。
 ◆ 当进程去修改内存时,就会引发一个页异常,在异常的处理过程中,内核会分配新的物理页并复制要修改的内存,然后重新进行内存映射。
 ◆ 当异常处理完毕返回后,进程修改的已经是新的物理内存,不会影响到原来与之共享内存的其他进程。
 
 
执行进程:
 在 Linux 系统中有一系列的函数可以讲一个进程的执行流程从一个可执行程序转移到另一个可执行程序,也就是装载并运行一个程序。这些函数通常被称为 exec 函数族,它们的接口头文件和原型如下:
 #include <unistd.h>
 int execl(const char *path, const char *arg, ...);
 int execlp(const char *file, const char *arg, ...);
 int execle(const char *path, const char *arg, ..., char *const envp[]);
 int execv(const char *path, char *const argv[]);
 int execvp(const char *file, char *const argv[]);

在解释这些函数的用法之前,我们先来了解一些变量 environ,它是一个每个程序都可以访问的全局变量,但访问前必须先进行声明:
 extern char **environ;
 实际上它是一个字符串数组的首地址,代表当前进程执行的环境变量。
 execl,execv
和 execle 函数会执行由 path 参数指定的可执行文件。execl 和 execle 执行新程序时的命令行参数由参数 arg
及随后的可变个数参数给出,而 execv 函数执行新程序时的命令行参数由字符串数组参数 argv 给出。使用 execl 和 execv
函数时,执行新程序的环境变量取自 environ,即与当前进程在相同环境中执行,而使用 execle 函数时,执行新的环境变量由参数 envp
给出。
 
 函数    可执行文件           参数形式       环境变量
 execl    给出全路径           可变参数列表     不提供,取自 environ 变量
 execlp   在 PATH 环境变量中查找   可变参数列表     不提供,取自 environ 变量
 execle   给出全路径           可变参数列表     要提供
 execv   给出全路径           字符串数组      不提供,取自 environ 变量
 execvp   在 PATH 环境变量中查找    字符串数组     不提供,取自 environ 变量

以上的函数实际上都是利用下面这个系统调用来实现的:
 int execve(const char *filename, char *const argv[], char *const envp[]);

◆ filename:要执行的程序文件。
 ◆ argv:以 NULL 结尾的字符串数组,表示命令行参数。
 ◆ envp:以 NULL 结尾的字符串数组,表示环境变量。
 ◆ 返回值:-1 表示执行失败,如果执行成功,则这个系统调用不会返回。

要理解 execve 函数的关键在于当它执行成功时是不会返回的,因为执行流程已经进入了一个新的程序。
 在 execve
系统调用中,内核首先会查看文件的权限。进程的所有者必须有执行这个文件的权限。如果测试失败,execve 函数会返回 -1,并且将变量 errno
设置为 EPERM。通过权限检测后,内核就会去查看文件内容,检查程序是否真的是一个可执行文件。一般来说,Linux
系统中的可执行文件分为两类:可执行目标文件与可执行脚本。
 ◆ 可执行目标文件
 经链接器链接后可直接执行的文件成为可执行目标文件。内核一般支持几种特定格式的可执行文件。ELF 格式是 Linux 系统中普遍使用的一种标准的可执行文件格式。
 ELF
格式的文件在开头有四个字节的标签,以 0x7f 开始,随后是字符 E,L,F。内核据此来判断一个文件是否是 ELF 格式的文件。并非使用的
ELF 文件都是可执行的。编译过程中产生的目标文件也是 ELF 格式的,但没有经过最终的链接就不是可执行文件。当内核确认一个文件是 ELF
格式的文件后,就会检查 ELF 文件头,以确认文件是否真正可执行以及获取执行时所需的各种信息,然后将程序加载并执行。
 ◆ 可执行脚本
 可执行脚本是一个特殊的文件,它能够指示内核启动一个解释器去执行后续的内容。这个解释器必须是可执行目标文件。如果没有合适的解释器,execve 将返回 -1 并且设置 errno 变量的值为 ENOEXEC。
 一般情况下,脚本的解释器是 Shell,但内核也会查看脚本文件第一行,如果前两个字符是 #!,它就会将第一行的剩余部分解析为启动解释器的命令。如:
 #!/bin/sh
 这样内核将会启动 /bin/sh 作为脚本的解释器。

进程的内存布局:
 进程可以认为是程序运行时的一个实例,程序中所使用的各种变量和内存在进程的虚拟地址空间中是有一定的分布规律的,如下面的例程:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4
5 int z = 0; /* 全局变量在数据段中 */
6
7 /* 函数在代码段中 */
8 int main()
9 {
10 int *a = 0; /* 非静态的局部变量在用户栈中 */
11 pid_t pid;
12 if((pid = fork()))
13 {
14 /* 父进程执行这里的代码 */
15 a = (int *)malloc(100*sizeof(int)); /* 所分配的内存在父进程的堆中 */
16 z = pid;
17 printf("z1 = %d\n", z);
18 }
19
20 else
21 {
22 /* 子进程执行这里的代码 */
23 a = &z;
24 *a = pid;
25 printf("z2 = %d\n", z);
26 }
27 printf("pid = %d\n", pid);
28
29 return 0;
30 }

在例程中通过 fork 函数产生了一个子进程,注意它和父进程的虚拟地址空间是相互独立的,故对全局变量的访问互补影响。程序中定义的全局变量和静态变量在运行时将放在数据段,而非静态的局部变量则放在用户栈上,通过 malloc 等函数分配得到的内存则位于进程的堆中。如下图所示是一个进程的虚拟堆中空间的布局:

程序包含代码与数据,在运行前,它们要被载入内存,这就是程序的内存映像,也可以认为是进程的内存布局,程序文件中的代码部分成为代码段,包含 CPU 要执行的指令序列;程序文件的数据部分成为数据段,包含各种全局变量和静态变量。栈是实现函数调用的基础,由内核进行分配,程序中的非静态局部变量将在栈中动态创建。堆是进行动态内存分配的场所,由内核根据需要进行分配。当创建一个进程时,子进程完成复制了父进程的代码、数据、堆和栈等。

进程的状态迁移:
 三个基本状态:就绪态、运行态、睡眠态。如下图:

图二。

进程的终止:
 自愿终止 和 被迫终止。
 自愿终止指的是应用程序中主动调用了执行退出过程的系统调用而终止,这个可以通过函数 exit 来做到,其

接口头文件与原型如下:
 #include <stdlib.h>
 void exit(int status); 
 
 被迫终止指的是应用程序中没有主动调用退出进程的系统调用而被内核强制终止的情形。

当一个进程终止时,内核会通知其父进程。在父进程进行处理前,这个进程成为所谓的僵尸进程,它所占的各

种资源已经被回收,但进程描述符仍然存在,以便父进程获取它的退出状态。父进程可以用 wait 函数或 waitpid 函

数获取子进程的退出状态,它们的接口头文件及原型如下:
 #include <sys/types.h>
 #inclde <sys/wait.h>
 pid_t wait(int *status);
 pid_t waitpid(pid_t pid, int *status, int options);

调用 wait 函数使当前进程阻塞直到它的某个子进程退出,这时返回值是退出的子进程的 PID,而参数

status 指向的整数被设为子进程的退出状态(或称返回值)。如果有错误发生则返回 -1。
 waitpid 函数则提供了比 wait 函数更为精细的控制,其各个参数及返回值含义如下:
 ◆ pid:指定要等待的子进程的 PID。
 ◆ status:用于获取子进程的退出状态。
 ◆ options:等待的参数,可以改变函数的行为。
 ◆ 返回值:退出的子进程的 PID,返回 -1 表示有错误发生。

其中 pid 参数有如下多种用法:
 ◆ pid > 0 ,表示某个特定的子进程。
 ◆ pid = 0 ,表示任何进程组 ID 等于当前进程的子进程。
 ◆ pid = -1 ,表示任何子进程。
 ◆ pid < -1 ,表示任何进程组 ID 等于 -pid 的子进程。

options 参数的一个常用值是 WNOHANG,它表示函数以非阻塞的方式执行,即如果没有子进程退出则对函数的

调用立刻以错误状态返回。

对 wait 函数的调用 wait(&status) 实际上等价于:
 waitpid(-1, &status, 0);

如果一个进程没有子进程,则对上述函数的调用会立刻以错误状态返回,并且变量 errno 的值被设为 ECHILD


 如果父进程在子进程退出前就已经退出,这时称子进程为孤儿进程。在进程退出时,内核会检视它的子进程并

使这些子进程成为 1 号进程的子进程,也就是说,子进程被 init 进程“收养”。

进程与信号:
  
 信号是内核提供的一种异步消息机制,主要用于内核对进程发送异步通知事件,可以理解为对进程的执行流程

的一个“软中断”。
 信号总是由内核递交给进程,但从应用程序的角度讲,信号的来源是多种多样的,如:
 ◆ 当进程在一个没有打开的管道上等待时,内核发出 SIGPIPE 信号。
 ◆ 进程在 Shell 中前台执行时,用户按下 Ctrl+C 组合键,将向进程发送 SIGINT 信号。
 ◆ 用户使用 kill 命令向某个进程发送信号。
 ◆ 进程访问非法的内存地址时内核向其发送“段错误”信号 SIGSEGV。
 ◆ 一个进程使用系统调用向另一个进程发送信号。
 ◆ 发生各种运行异常时,内核将向进程发送 SIGFPE 信号。

Linux 中的信号处理机制:
 在内核对进程管理的 PCB信息块中有若干个字节,其中每个比特位用于表示某个信号是否发生。当需要向某个

进程发送一个特定的信号时,就将其 PCB 信息中对应的比特位置为 1。但是对信号的处理并不立刻发生,内核会在从

内核态返回用户态时对当前进程的 PCB 中表示信号的数据进行检查,如果有信号发生,则内核会修改当前进程栈中的

信息,使得返回用户态后首先执行与信号绑定的处理函数,然后再从当前进程被中断或进行系统调用的地方继续执行。
 
关于信号的一些概念:
◆ 发送信号
 给某个进程发送信号实质上就是讲其 PCB 中的对应比特位置 1.当然,这个操作只能由内核来进行,但应用程

序可以通过系统调用间接的完成这个操作。
◆ 信号屏蔽
 每个进程都有一个用来描述哪些信号将被屏蔽的信号集,成为信号掩码,如果某个信号在进程的信号掩码中,

则发生到进程的这种信号将会被屏蔽。屏蔽只是延迟信号的到达,信号会在解除屏蔽后继续传递。在信号处理函数和程

序的其他部分共享全局变量时,一般会在访问变量前屏蔽信号,以免在访问过程中发生信号而导致同步问题,访问完毕

后再解除对信号的屏蔽。
◆ 忽略信号
 忽略信号就是指当进程收到某个信号时直接丢弃,对进程的执行没有影响。但有些信号是不能忽略的,如

SIGKILL 和 SIGSTOP 信号。
◆ 捕捉信号
 对每个信号来说,系统都有默认的信号处理函数。如果程序中用自己定义的函数取代了默认的信号处理函数,

则称为捕捉了这个信号。

发送信号:
 信号实际上是由一个正整数代表的,但为了使用方便,每个信号都定义了一个名称,代表一种特殊的含义。这

些信号的定义在系统头文件 signal.h 中。

向一个进程发送喜欢可以使用 kill 命令,默认情况下 kill 命令发送 SIGTERM 信号以试图终止一个进程。
 #include <sys/types.h>
 #include <signal.h>
 int kill(pid_t pid, int sig);
  
 ◆ pid > 0:表示目标进程的 PID。
 ◆ pid = 0:表示与当前进程同组的所有进程。
 ◆ pid = -1:表示所有当前进程有发送信号权限的其他进程。
 ◆ pid < -1:表示所有处于进程组 -pid 中的进程。

sig 参数则是要发送的信号。函数返回 0 则表示信号发送成功,如果信号发送失败,则函数返回 -1。

注意的是,发送信号的进程需要有必要的权限,一般情况下,发送信号的进程与接收信号的进程应属于相同的

用户。root 可以发送信号到任何进程。

进程可以使用 raise 函数给自己发送信号:
 #include <signal.h>
 int raise(int sig);

使用 alarm 函数可以在过一段时间后向进程自己发送 SIGALRM 信号,其接口头文件及原型如下:
 #include <unistd.h>
 unsigned int alarm(unsigned int seconds);
 seconds 参数表示一个以秒为单位的超时时间。超过这段时间后,内核将自动向进程自己发送 SIGALRM 信号

。如果在指定时间超时前再次调用 alarm 函数,则新的时间值将覆盖旧的,并且从当前时间开始重新计算。如果指定

的时间值为 0,则表示取消信号的发送。
 alarm 函数的返回值表示前一次对 alarm 函数的调用还剩多少秒就要超时,如果返回 0 则说明没有调用过

alarm 函数或前一次调用 alarm 函数后已超时。
 利用 alarm 函数可以实现定时操作:
 #include <unistd.h>

int main()
 {
  alarm(1);
  for(; ;);
 }

使用 alarm 函数设置了 1 秒后发送 SIGALRM 信号,而 SIGALRM 信号的默认操作是退出进程,所以尽管随后

是一个死循环,进程仍然能够退出

捕捉信号:
  
 
使用 signal 函数
 signal 函数是 Linux 系统上传统的信号处理接口:
 #include <signal.h>
 sighandler_t signal(int signum, sighandler_t handler);

其中 sighandler_t 类型是一个函数指针类型,定义如下:
 typedef void (*sighandler_t)(int);

这个类型表示一个信号处理函数。signal 函数的作用就是讲 handler 参数所指向的函数注册成为参数

signum 所代表的信号的处理函数,它的返回值是这个信号原来的处理函数,如果返回 SIG_ERR,则说明有错误发生,
注册失败。
 注册成功后,所注册的函数就会在信号被处理时调用,代替了默认的行为,或者成为信号被捕捉。 
 
 使用 signal 函数时应注意以下两点:
 ◆ handler 参数的值可以是 SIG_IGN 或者 SIG_DFL,SIG_IGN 表示忽略这个信号,SIG_DFL 表示对信号的处

理重设为系统的默认方式。
 ◆ 有些信号是不可以忽略或捕获的,如 SIGKILL 和 SIGSTOP。

下面给出一个例程来说明信号的产生、忽略与捕获的编程,例程代码如下:

  1 /* 文件名:sigtest.c */
2 /* 说明:信号处理例程 */
3
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <signal.h>
7 #include <unistd.h>
8 #include <sys/wait.h>
9
10 static pid_t pid;
11
12 /* 子进程 1 SIGALRM 信号处理函数 */
13 static void wakeup(int dummy)
14 {
15 printf("I (pid = %d) am up now\n",pid);
16 }
17
18 /* 子进程 1 SIGINT 信号处理函数 */
19 static void handler(int dummy)
20 {
21 printf("I (pid = %d) got an interrupt, will exit\n",pid);
22 exit(0);
23 }
24
25 /* 子进程 2 信号处理函数 */
26 static void trapper(int i)
27 {
28 if(i == SIGUSR1)
29 {
30 printf("I (pid = %d) got SIGUSR1,will exit\n", pid);
31 exit(0);
32 }
33 else
34 {
35 printf("I (pid = %d) got signal %d, will continue\n", pid, i);
36 }
37 }
38
39
40 /* 父进程信号处理函数 */
41 void parent(int sig)
42 {
43 printf("Signal (%d) received by parent (%d)\n", sig, pid);
44 }
45
46 int main(int argc, char *argv[])
47 {
48 int i, cpid1, cpid2;
49
50 printf("Number of signal is %d\n", NSIG); //输出系统中信号的个数
51
52 if(!(cpid1 = fork())) //创建第一个子进程
53 {
54 pid = cpid1 = getpid(); //获得子进程的进程号
55 printf("CPID1 = %d\n", cpid1);
56
57 for(i=1; i<NSIG; i++)
58 {
59 signal(i, SIG_IGN); //忽略所以的信号
60 }
61
62 signal(SIGINT, handler); //捕获信号 SIGINT
63 signal(SIGALRM, wakeup); //捕获超时信号
64 alarm(2); //启动定时器,设置 2 秒后超时
65
66 for(; ;)
67 {
68 pause(); //等待信号
69 }
70
71 printf(" -- CPID1 (%d) terminates\n", cpid1);
72
73 exit(0);
74 }
75 else if(!(cpid2 = fork())) //创建第二个子进程
76 {
77 pid = cpid2 = getpid();
78 printf("CPID2 = %d\n", cpid2);
79
80 for(i=1; i<NSIG; i++)
81 {
82 signal(i, trapper); //捕获所有的信号
83 }
84
85 for(; ;)
86 {
87 pause(); //等待信号
88 }
89
90 printf(" -- CPID2 (%d) terminates\n", cpid2);
91
92 exit(0);
93 }
94
95 /* 下面是父进程执行的代码 */
96 pid = getpid(); //取得 PID
97 sleep(3); //睡眠,让子进程先运行
98 printf("This is parent process (pid = %d)\n", pid);
99
100 for(i=1; i<NSIG; i++)
101 {
102 signal(i, parent); //捕获所以信号
103 }
104
105 printf("Send SIGUSR1(%d) to CPID1 (%d)\n", SIGUSR1, cpid1);
106 kill(cpid1, SIGUSR1);
107 printf("Send SIGINT(%d) to CPID1 (%d)\n", SIGINT, cpid1);
108 kill(cpid1, SIGINT);
109 printf("Send SIGINT(%d) to CPID2 (%d)\n", SIGBUS, cpid2);
110 kill(cpid2, SIGINT);
111 printf("Send SIGUSR1(%d) to CPID2 (%d)\n", SIGUSR1, cpid2);
112 kill(cpid2, SIGUSR1);
113
114 for(; wait((int *)0) > 0; ); //等待子进程结束
115
116 return 0;
117 }

在这个例程中,父进程又创建了两个子进程,这三个进程分别注册或忽略了相关的信号,然后通过 alarm 函数设置超

时信号或通过 kill 函数发送信号。还用到了 pause 和 sleep 函数,如下:
 #include <unistd.h>
 int pause(void);
 unsigned int sleep(unsigned int seconds);

pause 函数将使当前进程进入睡眠态,直到有信号发送。它的返回值永远是 -1,同时变量 errno 的值被设为

EINTR 以表示有信号发生。
 sleep 函数将使当前进程进入睡眠态,并在 seconds 参数指定的秒数后被唤醒继续执行。注意当有未忽略的

信号发生时 sleep 函数会提前返回,返回值是剩余的秒数。如果返回 0 则说明是正常被唤醒的,而不是因信号的发生

提前返回的。

 

使用 sigaction 函数:
 signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受

到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数,其接口头文件及原型如下:
 #include <signal.h>
 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

◆ signum:要操作的信号。
 ◆ act:要设置的对信号的新处理方式。
 ◆ oldact:原来对信号的处理方式。
 ◆ 返回值:0 表示成功,-1 表示有错误发生。

struct sigaction 类型用来描述对信号的处理,定义如下:
 struct sigaction
 {
  void     (*sa_handler)(int);
  void     (*sa_sigaction)(int, siginfo_t *, void *);
  sigset_t  sa_mask;
  int       sa_flags;
  void     (*sa_restorer)(void);
 };

在这个结构体中,成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员

sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值

包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理

函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
 sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被

自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
 sa_flags 成员用于指定信号处理的行为,它可以是一下值的“按位或”组合。
 
 ◆ SA_RESTART:使被信号打断的系统调用自动重新发起。
 ◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
 ◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵

尸进程。
 ◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
 ◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
 ◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

re_restorer 成员则是一个已经废弃的数据域,不要使用。

下面用一个例程来说明 sigaction 函数的使用,代码如下:

 1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4 #include <errno.h>
5
6 static void sig_usr(int signum)
7 {
8 if(signum == SIGUSR1)
9 {
10 printf("SIGUSR1 received\n");
11 }
12 else if(signum == SIGUSR2)
13 {
14 printf("SIGUSR2 received\n");
15 }
16 else
17 {
18 printf("signal %d received\n", signum);
19 }
20 }
21
22 int main(void)
23 {
24 char buf[512];
25 int n;
26 struct sigaction sa_usr;
27 sa_usr.sa_flags = 0;
28 sa_usr.sa_handler = sig_usr; //信号处理函数
29
30 sigaction(SIGUSR1, &sa_usr, NULL);
31 sigaction(SIGUSR2, &sa_usr, NULL);
32
33 printf("My PID is %d\n", getpid());
34
35 while(1)
36 {
37 if((n = read(STDIN_FILENO, buf, 511)) == -1)
38 {
39 if(errno == EINTR)
40 {
41 printf("read is interrupted by signal\n");
42 }
43 }
44 else
45 {
46 buf[n] = '\0';
47 printf("%d bytes read: %s\n", n, buf);
48 }
49 }
50
51 return 0;
52 }

在这个例程中使用 sigaction 函数为 SIGUSR1 和 SIGUSR2 信号注册了处理函数,然后从标准输入读入字符

。程序运行后首先输出自己的 PID,如:
 My PID is 5904
 
 这时如果从另外一个终端向进程发送 SIGUSR1 或 SIGUSR2 信号,用类似如下的命令:
 kill -USR1 5904

则程序将继续输出如下内容:
 SIGUSR1 received
 read is interrupted by signal
 
 这说明用 sigaction 注册信号处理函数时,不会自动重新发起被信号打断的系统调用。如果需要自动重新发

起,则要设置 SA_RESTART 标志,比如在上述例程中可以进行类似一下的设置:
 sa_usr.sa_flags = SA_RESTART;

转载链接:http://www.cnblogs.com/wblyuyang/archive/2012/11/13/2768923.html

进程-信号相关 函数-(转自wblyuyang)的更多相关文章

  1. 三十四、Linux 进程与信号——信号特点、信号集和信号屏蔽函数

    34.1 信号特点 信号的发生是随机的,但信号在何种条件下发生是可预测的 进程杠开始启动时,所有信号的处理方式要么默认,要么忽略:忽略是 SIGUSR1 和 SIGUSR2 两个信号,其他都采取默认方 ...

  2. 二十四、Linux 进程与信号---wait 函数

    24.1 wait 函数说明 24.1.1 waitpid---等待子进程中断或结束 waitpid(等待子进程中断或结束) 相关函数 wait,fork #include <sys/types ...

  3. [置顶] Linux信号相关笔记

    最近又温习了一遍Linux中的信号知识,发现有很多东西以前没有注意到,就通过这篇博客记录一下,巩固一下知识点. 一,信号基础: 信号是什么?为了回答这个问题,首先要从异常说起,这里的异常不是指c++/ ...

  4. linux系统编程之信号(六):信号发送函数sigqueue和信号安装函数sigaction

    一,sigaction() #include <signal.h> int sigaction(int signum,const struct sigaction *act,struct ...

  5. linux系统编程之信号:信号发送函数sigqueue和信号安装函数sigaction

    信号发送函数sigqueue和信号安装函数sigaction sigaction函数用于改变进程接收到特定信号后的行为. sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然 ...

  6. APUE学习笔记——10信号——信号接口函数 signal 和 sigaction

    signal函数     signal函数是早起Unix系统的信号接口,早期系统中提供不可靠的信号机制.在后来的分支中,部分系统使用原来的不可靠机制定义signal函数,如 Solaris 10 .而 ...

  7. APUE学习笔记5——信号、信号集和进程信号屏蔽字

    1 信号传递过程 当引发信号的事件发生时(如软硬件异常.软件定时.终端产生信号或调用kill函数等等),会产生信号,内核会发送给目标进程. 在信号产生到信号传递给目标进程之间的时间间隔内,称该信号为未 ...

  8. [Linux]信号集和sigprocmask信号屏蔽函数

    一.概述 系统提供这样一种能力,就是创建一个信号集,然后传递给信号屏蔽函数,从而屏蔽向该进程发送的信号. 有一点需要注意的是,不能屏蔽SIGKILL和SIGSTOP信号. 信号集是sigset_t类型 ...

  9. Linux 两组信号对比(关闭和停止进程信号)

    之前看信号的时候,没有太注意不同信号的对比.今天再次看到的时候,突然感觉对一些信号,非常相似,乃至非常容易混淆.今天周末就抽空总结一下. 一.关闭进程信号 常见的4中关闭进程信号是SIGKILL,SI ...

随机推荐

  1. Java高级【Junit、反射、注解】

    1.Junit单元测试 * 测试分类:     1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值.     2. 白盒测试:需要写代码的.关注程序具体的执行流程. * Junit使用 ...

  2. shell脚本 4 函数与正则

    shell函数 shell中允许将一组命令集合或语句形成一段可用代码,这些代码块称为shell函数.给这段代码起个名字称为函数名,后续可以直接调用该段代码. 格式 func() {   #指定函数名 ...

  3. 动态扩展磁盘(LVM)

    使用gtp格式磁盘为lvm类型 [root@elk-log-srv01 ~]# parted /dev/vdd GNU Parted 3.1 Using /dev/vdd Welcome to GNU ...

  4. 【SpringBoot】Spring Boot

    Spring Boot是由Pribotal团队提供,设计用来简化新Spring应用的初始搭建和开发过程的开源框架. 随着Spring体系越来越庞大,各种配置也是越来越复杂,Spring Boot就是解 ...

  5. HTML / CSS技巧 – 可滚动的 tbody(漂亮表格)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. 缓冲区溢出分析第09课:MS06-040漏洞研究——深入挖掘

    前言 经过前两次的分析,我们已经对Netapi32.dll文件中所包含的漏洞成功地实现了利用.在系统未打补丁之前,这确实是一个非常严重的漏洞,那么打了补丁之后,这个动态链接库是不是就安全了呢?答案是否 ...

  7. R3获取kernel32地址

    获取Kernel32地址 如果是搞PE变形或者PE重构,再或者代码注入,很多时候我们要动态获取Loadlibrary()以及GetPeocAddress()两个函数的地址,通过这两个函数再动态获取其他 ...

  8. 如何在jQuery的Ajax调用后管理一个重定向请求

    1 success:function(data){ 2 if(data.xx == "xx") 3 { 4 //code... 5 window.location.href =&q ...

  9. 栈(Stack) --- C# 自定义和微软官方的区别

    最近在学习算法基础,本篇文章作为一个记录,也算是一次实践和总结.(顺便也深入C#运行时学习一下) 目录 1. 栈是什么 2. Stack 自定义实现 3. Stack C#官方实现 4. 区别 5. ...

  10. 通过CRM系统改变传统工作模式

    在现在这个互联网时代,同行业的竞争越发激烈,因此许多企业都选择使用CRM来提高企业的销售业绩.CRM客户关系管理系统是能够优化企业的销售流程.维护良好的客户关系.对销售流程进行管理的强大工具.但是很多 ...