相关文章
容器技术回顾 - 使用 UDS 实现 Pod 间通信
容器技术回顾 - 多 Pod 间共享内存通信
Linux 基础知识 - 进程间通信与同步
今天我们来学习一下进程间通信相关的知识,今天会涵盖 1和 2.
1 进程间通信的一些基本概念2 信号2.1 信号的处理2.2 信号与系统调用的关系2.3 信号的复位2.4 在进程间发送信号2.5 系统调用alarm()和pause()2.6 系统调用setjmp()和longjmp()3 管道3.1 用C 来建立、使用管道3.2 需要注意的问题4 有名管道4.1 有名管道的创建4.2 有名管道的I/O 使用4.3 未提到的关于有名管道的一些注意4.5 文件和记录锁定5.1 实例程序及其说明5.2 锁定中的几个概念5.3 System V 的咨询锁定5.4 BSD 的咨询式锁定5.5 前面两种锁定方式的比较5.6 Linux 的其它上锁技术6 System V IPC 6.1 ipcs 命令6.2 ipcrm 命令7 消息队列(Message Queues)7.1 有关的数据结构7.2 有关的函数7.3 消息队列实例——msgtool,一个交互式的消息队列使用工具8 信号量(Semaphores)8.1 有关的数据结构8.2 有关的函数8.3 信号量的实例——semtool,交互式的信号量使用工具9 共享内存(Shared Memory)9.1 有关的数据结构9.2 有关的函数9.3 共享内存应用举例——shmtool,交互式的共享内存使用工具9.4 共享内存与信号量的结合使用
网络程序设计中通常包括两个或更多的需要互相对话(interprocess communications )的进程,因此进程通信的方法在网络程序设计中是极为重要的。网络程序设计在这个方面不同于一般程序设计通常所使用的方法。一个传统的程序可以通过全局变量或函数调用和不同的模块(甚至同一机器上的其它应用程序)对话,但是在网络上却不行。
网络程序设计的一个重要的目标是保证进程间不互相干涉,否则系统可能被挂起或自旋锁,因此,进程间必须使用简洁有效的方法进行通信,在此方面,Linux 具有非常显著的兼容性。因为Linux 的许多基本性能如管道,队列等都非常适合网络。
因此我们将详细的介绍各种进程间通信的方法及其使用方式。
1. 进程间通信的一些基本概念
下面是一些在学习进程间通信时会遇到的基本概念:
进程阻塞
当一个进程在执行某些操作的条件得不到满足时,就自动放弃CPU 资源而进入休眠状态,以等待条件的满足。当操作条件满足时,系统就将控制权返还给该进程继续进行未完的操作。
共享资源
因为计算机的内存、存储器等资源是有限的,无法为每一个进程都分配一份单独的资源。所以系统将这些资源在各个进程间协调使用,称为共享资源。
锁定
当某个进程在使用共享资源使用,可能需要防止别的进程对该资源的使用。比如,一个进程在对某个文件进行读操作时,如果别的进程也在此时向文件中写入了内容,就可能导致进程读入错误的数据。为此,Linux 提供一些方法来保证共享资源在被某个进程使用时,别的进程无法使用。这就叫做共享资源的锁定。
2. 信号
信号是UNIX 系统所使用的进程通信方法中,最古老的一种。系统使用它来通知一个或多个进程异步事件的发生,比如键盘上某个键被按下,或者计时器到达了某个特定的事件。系统也用信号来处理某种严重的错误。比如,一个进程试图向一块不存在的虚拟内存写入数据,或者某个进程试图执行一条非法指令。
信号不但能从内核发往一个进程,也能从一个进程发往另一个进程。例如,用户在后台启动了一个要运行较长时间的程序,如果想中断其执行,可以用kill 命令把 SIGTERM 信号发送给这个进程,SIGTERM 将终止此进程的执行。
信号还提供了向 UNIX 系统进程传送软中断的简单方法。信号可以中断一个进程,而不管它正在作什么工作。由于信号的特点,所以不用它来作进程间的直接数据传送,而把它用作对非正常情况的处理。
由于信号本身不能直接携带信息,这就限制了它作为一项通用的进程通信机制。但是,每种信号都有其特定的含义,并由其名字所指示。在Linux 系统库bits/signum.h 中对这些信号名作了定义,每个名字代表一个正整数。例如:
#define SIGHUP 1 /* Hangup (POSIX). */
定义了信号 SIGHUP。
Linux 提供的大多数信号类型是供内核使用的,只有少数的几种信号可以用作在进程之间传送。下面给出常用的信号和它们的意义:
SIGHUP
当终止一个终端时,内核就把这一种信号发送给该终端所控制的所有进程。通常情况下,一个进程组的控制终端是该用户拥有的终端,但不完全是如此。当进程组的首进程结束时,就会向该进程组的所有进程发送这种信号。这就可以保证当一个用户退出使用时,其后台进程被终止,除非有其它方面的安排。
SIGINT
当一个用户按了中断键(一般为Ctrl+C)后,内核就向与该终端有关联的所有进程发送这种信号。它提供了中止运行程序的简便方法。
SIGQUIT
这种信号与 SIGINT 非常相似,当用户按了退出键时(为 ASCII 码 FS,通常为Ctrl+\),内核就发送出这种信号。SIGQUIT 将形成 POSIX 标准所描述的非正常终止。我们称这种 UNIX 实现的实际操作为核心转贮(core dump),并用信息“Quit (core dump)”指出这一操作的发生。这时,该进程的映象被转贮到一个磁盘文件中,供调试之用。
SIGILL
当一个进程企图执行一条非法指令时,内核就发出这种信号。例如,在没有相应硬件支撑的条件下,企图执行一条浮点指令时,则会引起这种信号的发生。SIGILL 和 SIGQUIT一样,也形成非正常终止。
SIGTRAP
这是一种由调试程序使用的专用信号。由于他的专用行和特殊性,我们不再对它作进一步的讨论。SIGTRAP 也形成非正常终止。
SIGFPE
当产生浮点错误时(比如溢出),内核就发出这种信号,它导致非正常终止。
SIGKILL
这是一个相当特殊的信号,它从一个进程发送到另一个进程,使接收到该信号的进程终止。内核偶尔也会发出这种信号。SIGKILL 的特点是,它不能被忽略和捕捉,只能通过用户定义的相应中断处理程序而处理该信号。因为其它的所有信号都能被忽略和捕捉,所以只有这种信号能绝对保证终止一个进程。
SIGALRM
当一个定时器到时的时候,内核就向进程发送这个信号。定时器是由改进程自己用系统调用 alarm() 设定的。
SIGTERM
这种信号是由系统提供给普通程序使用的,按照规定,它被用来终止一个进程。
SIGSTOP
这个信号使进程暂时中止运行,系统将控制权转回正在等待运行的下一个进程。
SIGUSR1 和 SIGUSR2
和 SIGTERM 一样,这两种信号不是内核发送的,可以用于用户所希望的任何目的。
SIGCHLD
子进程结束信号。UNIX 中用它来实现系统调用 exit() 和 wait()。执行 exit()时,就向子进程的父进程发送 SIGCHLD 信号,如果这时父进程政在执行wait(),则它被唤醒;如果这时候父进程不是执行 wait(),则此父进程不会捕捉SIGCHLD 信号,因此该信号不起作用,子进程进入过渡状态(如果父进程忽略 SIGCHLD,子进程就结束而不会进入过渡状态)。这个机制对大多数UNIX 程序员来说是相当重要的。
对于大多数情况来说,当进程接收到一个信号时,它就被正常终止,相当于进程执行了一个临时加入的 exit() 调用。在这种情况下,父进程能从进程返回的退出状态中了解可能发生的事情,退出状态的低 8 位含有信号的号码,其高 8 位为 0。
信号 SIGQUIT、SIGILL、SIGTRAP、SIGSYS 和 SIGFPE 会导致一个非正常终止,它们将发生核心转贮,即把进程的内存映象写入进程当前目录的 core 文件之中。core 文件中以二进制的形式记录了终止时程序中全部变量之值、硬件寄存器之值和内核中的控制信息。非正常终止进程的退出状态除了其低端第7 位被置位外,其它均与通过信号正常终止时一样。
Linux 的调试程序 gdb 知道 core 文件的格式,可以用它们来观察进程在转贮点上的状态。这样,就可以用 gdb 正确的定出发生问题的位置。这里再介绍一下系统调用 abort(),它在Linux 系统库 stdlib.h 中定义:
void abort(void);
abort() 向调用进程发送一个信号,产生一个非正常终止,即核心转贮。由于它能够使一个进程在出错时记录进程的当前状态,所以可以用它来作为调试的辅助手段。这也说明了进程可以向自己发送信号这一事实。
2.1 信号的处理
几乎所有的信号都将终止接收到该信号的进程。对于一些简单的程序,这完全能满足要求。用户按了中断或者退出键,就可以停止一个有问题的程序的运行。但是在大型的程序中,一些意料之外的信号会导致大问题。例如,正当在对一个重要的数据库进行修改期间,由于不小心碰到了中断键,而使程序被意外的终止,从而产生严重的后果。
UNIX 的系统调用 signal() 用于接收一个指定类型的信号,并可以指定相应的方法。这就是说,signal()能够将指定的处理函数与信号向关联。它在Linux 系统库 signal.h 中的函数声明如下:
int signal (int sig, __sighandler_t handler);
第一个参数 sig 指明了所要处理的信号类型,它可以取除了 SIGKILL 和 SIGSTOP 外的任何一种信号。参数handler 描述了与信号关联的动作,它可以取以下三种值:
一个返回值为整数的函数地址。
此函数必须在 signal() 被调用前声明,handler 中为这个函数的名字。当接收到一个类型为 sig 的信号时,就执行 handler 所指定的函数。这个函数应有如下形式的定义:
int func(int sig);
sig 是传递给它的唯一参数。执行了 signal() 调用后,进程只要接收到类型为sig 的信号,不管其正在执行程序的哪一部分,就立即执行 func() 函数。当func() 函数执行结束后,控制权返回进程被中断的那一点继续执行。
SIG_IGN
这个符号表示忽略信号。执行了相应的 signal() 调用好,进程会忽略类型为 sig 的信号。
SIG_DFL
这个符号表示恢复系统对信号的默认处理。
函数如果执行成功,就返回信号在此次signal()调用之前的关联。
如果函数执行失败,就返回SIG_ERR。通常这种情况只有当sig 参数不是有效的信号时才会发生。函数不对handler 的有效性进行检查。
下面我们来看几个例子。
首先,下面的这段代码则将使进程忽略SIGINT 信号:
#include <stdlib.h>#include <stdio.h>#include <signal.h>int main(void){ signal(SIGINT,SIG_IGN); /*告诉进程将SIGINT 信号忽略*/ printf("xixi"); sleep(10); /*系统函数sleep()使进程休眠指定的时间(以秒为单位)*/ printf("end"); return;}
如果在程序中需要重新恢复系统对信号的缺省处理,就使用下面的语句:
signal(SIGINT,SIG_DFL);
在 Linux 程序中常常利用 SIG_IGN 和 SIG_DFL 屏蔽 SIGINT 和 SIGQUIT 来保证执行重要任务的程序不会被意外的中止。
在 shell 中也是利用这一技术来确保用户按中断键时,不中断后台程序的运行。因为被一个进程忽略的信号,在进程执行 exec() 调用后,仍然被忽略,所以 shell 能够调用 signal() 来保证 SIGQUIT 和 SIGINT 被忽略,然后用 exec 执行新程序。但是要注意到,在父进程中设定的信号和函数的关联关系会被 exec() 调用自动用 SIG_DFL 恢复成系统的缺省动作,这是因为在 exec 的子进程中没有父进程的函数映象。
再让我们来看看下面这段捕捉 SIGINT 的代码:
#include <stdlib.h>#include <stdio.h>#include <signal.h>int catch(int sig);int main(void){ signal(SIGINT,catch); /* 将SIGINT 信号与catch 函数关联 */ printf("xixi"); sleep(10); printf("end"); return;}int catch(int sig){ printf("Catch succeed!"); return 1;}
当程序运行时我们按下中断键(Ctrl+C),进程被中断,函数catch 就被执行。它执行完毕后,进程回到中断点继续执行。
如果我们希望一个进程被信号终止前能够完成一些处理工作,如删除工作中使用的临时文件等,就可以设计一个信号处理函数来完成工作。比如可以有这样的一个处理函数:
int catch(int sig){ printf("Catch succeed!"); exit(1);}
这个函数在最后调用 exit()函数来使进程结束运行。这样就保留了信号原有的中断进程的功能。
这里需要指出一点,当程序把一个信号处理函数与 SGINT 和 SIGQUIT 联系起来后,如果该程序在后台执行,那么由于 shell 的作用,会使得SIGINT 和SIGQUIT 被忽略。这样后台程序就不会被 SIGINT 和 SIGOUT 所中止。
前面曾经提过,signal() 调用返回原先与指定信号相关联的处理函数,这样,我们就可以保存和恢复原来对指定信号的处理动作。下面的代码说明这一技术:
int (*oldptr)(),newcatch();/* 设定SIGINT 的关联,同时保存原来的关联*/oldptr=singal(SIGINT,newcatch);/* 工作代码段 */… ../* 恢复原来的关联 */signal(SIGINT,oldptr);
2.2 信号与系统调用的关系
当一个进程正在执行一个系统调用时,如果向该进程发送一个信号,那么对于大多数系统调用来说,这个信号在系统调用完成之前将不起作用,因为这些系统调用不能被信号打断。但是有少数几个系统调用能被信号打断,例如:wait(),pause()以及对慢速设备(终端、打印机等)的read()、write()、open()等。如果一个系统调用被打断,它就返回-1,并将 errno 设为EINTR。可以用下列代码来处理这种情况:
if (wirte(tfd,buf,SIZE)<0) { if (errno==EINTR) { warn(“Write interrupted.”); … … }}
2.3 信号的复位
在 Linux 中,当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。下面的程序演示了这一点:
#include <signal.h>int interrupt(){ printf(“Interrupt called”); sleep(3); printf(“Interrupt Func Ended.”);}main(){ signal(SIGINT,interrupt); printf(“Interrupt set for SIGINT”); sleep(10); printf(“Program NORMAL ended.”); return;}执行它,结果如下:Interrupt set for SIGINT<ctrl+c>Interrupt called<ctrl+c>Func EndedInterrupt calledFunc EndedProgram NORMAL ended.
但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断:
#include <signal.h>int interrupt(){ printf(“Interrupt called”); sleep(3); printf(“Interrupt Func Ended.”);}int catchquit(){ printf(“Quit called”); sleep(3); printf(“Quit ended.”);}main(){ signal(SIGINT,interrupt); signal(SIGQUIT,catchquit); printf(“Interrupt set for SIGINT”); sleep(10); printf(“Program NORMAL ended.”); return;}
执行这个程序的结果如下:
Interrupt set for SIGINT<ctrl+c>Interrupt called<ctrl+\>Quit calledQuit ended.Interrupt Func Ended.Program NORMAL ended.
还要注意的是,在 Linux 系统中同种信号是不能积累的。比如我们执行上面的代码:
Interrupt set for SIGINT<ctrl+c>Interrupt called<ctrl+c><ctrl+c><ctrl+c>Func EndedInterrupt calledFunc EndedProgram NORMAL ended.
而且如果两个信号同时产生,系统并不保证进程接收它们的次序。以上的两个缺点影响了信号作为进程通信手段的可靠性,因为一个进程不能保证它发出的信号不被丢失。
当某个信号未被处理的时候,如果对该信号执行signal 调用,那么该信号将被注销。
2.4 在进程间发送信号
一个进程通过对 signal() 的调用来处理其它进程发送来的信号。同时,一个进程也可以向其它的进程发送信号。这一操作是由系统调用 kill() 来完成的。kill() 在 linux 系统库 signal.h 中的函数声明如下:
int kill(pid_t pid, int sig);
参数 pid 指定了信号发送的对象进程:它可以是某个进程的进程标识符(pid),也可以是以下的值:
如果 pid 为零,则信号被发送到当前进程所在的进程组的所有进程;
如果 pid 为 -1,则信号按进程标识符从高到低的顺序发送给全部的进程(这个过程受到当前进程本身权限的限制,请看后面的解释);
如果 pid 小于-1,则信号被发送给标识符为pid 绝对值的进程组里的所有进程。
需要说明的是,一个进程并不是向任何进程均能发送信号的,这里有一个限制,就是普通用户的进程只能向具有与其相同的用户标识符的进程发送信号。也就是说,一个用户的进程不能向另一个用户的进程发送信号。只有root 用户的进程能够给任何线程发送信号。
参数 sig 指定发送的信号类型。它可以是任何有效的信号。
由于调用 kill()的进程需要直到信号发往的进程的标识符,所以这种信号的发送通常只在关系密切的进程之间进行,比如父子进程之间。
下面是一个使用 kill()调用发送信号的例子。这个程序建立两个进程,并通过向对方发送信号 SIGUSR1 来实现它们之间的同步。这两个进程都处于一个死循环中,在接收对方发送的信号之前,都处于暂停等待中。这是通过系统调用pause()来实现的,它能够使一个程序暂停,直至一个信号到达,然后进程输出信息,并用 kill 发送一个信号给对方。当用户按了中断键,这两个进程都将终止。
#include <signal.h>int ntimes=0;main(){ int pid,ppid; int p_action(), c_action(); /* 设定父进程的SIGUSR1 */ signal(SIGUSR1,p_action); switch(pid=fork()) { case -1: /*fork 失败*/ perror("synchro"); exit(1); case 0: /*子进程模块*/ /* 设定子进程的SIGUSR1 */ signal(SIGUSR1,c_action); /* 获得父进程的标识符 */ ppid=getppid(); for(;;) { sleep(1); kill(ppid,SIGUSR1); pause(); } /*死循环*/ break; default: /*父进程模块*/ for (;;) { pause(); sleep(1); kill(pid,SIGUSR1); } /*死循环*/ }}p_action(){ printf("Patent caught signal #%d",++ntimes);}c_action(){ printf("Child caught signal #%d",++ntimes);}程序运行结果如下:Patent caught signal #1Child caught signal #1Patent caught signal #2Child caught signal #2Patent caught signal #3Child caught signal #3Patent caught signal #4Child caught signal #4<ctrl+c>
这里顺便介绍一下kill 命令,它是一个对系统调用kill()的命令层接口。kill 命令用于向一个运行进程发送信号,它发送的信号默认为SIGTERM,但是也可以指定为其它信号。我们可以直接用信号的号码来指定kill 命令所发送信号之类型,也可以用符号名指定。比如可以用下面的命令来完成向进程标识符为1234 的进程发送SIGINT 信号:
kill –s SIGINT 1234
2.5 系统调用 alarm() 和 pause()
1.系统调用 alarm()
alarm()是一个简单而有用的系统调用,它可以建立一个进程的报警时钟,在时钟定时器到时的时候,用信号向程序报告。alarm()系统调用在 Linux 系统函数库 unistd.h 中的函数声明如下:
unsigned int alarm(unsigned int seconds);
函数唯一的参数是seconds,其以秒为单位给出了定时器的时间。当时间到达的时候,就向系统发送一个SIGARLM 信号。例如:
alarm(60);
这一调用实现在 60 秒后发一个 SIGALRM 信号。alarm 不会象 sleep 那样暂停调用进程的执行,它能立即返回,并使进程继续执行,直至指定的延迟时间到达发出 SIGALRM 信号。事实上,一个由 alarm()调用设置好的报警时钟,在通过 exec() 调用后,仍将继续有效。但是,它在 fork()调用后中,在子进程中失效。
如果要使设置的报警时钟失效,只需要调用参数为零的 alarm():
alarm(0)
alarm() 调用也不能积累。如果调用 alarm 两次,则第二次调用就取代第一次调用。但是,alarm 的返回值柜橱了前一次设定的报警时钟的剩余时间。
当需要对某项工作设置时间限制时,可以使用 alarm()调用来实现。其基本方法为:先调用 alarm()按时间限制值设置报警时钟,然后进程作某一工作。如果进程在规定时间以内完成这一工作,就再调用 alarm(0)使报警时钟失效。如果在规定时间内未能完成这一工作,进程就会被报警时钟的 SIGALRM 信号中断,然后对它进行校正。
下面这个程序使用上述方法来强制用户作出回答。在其中包括一个 quickreply()函数,它有一个参数 prompt,它是一个指向提示字符串的指针。quickreply 的返回值也是一个指针。它指向含有输入行信息的字符串。这个例行程序在试作五次之后,如果仍未得到输入信息,就返回一个 null 指针。每当 quickreply 要提醒用户时,它就向终端发送 ASCII 码 007,这会使终端响铃。
quickreply 调用了标准 I/O 库中的例行程序 gets()。gets() 把标准输入上的下一行信息存入一个字符型数组,它返回一个指向该数组的指针。当到达文件末或出错时,gets 则返回一个 null 指针。函数 catch 是信号 SIGALRM 的关联函数,它完成对此信号的处理。catch 设置了一个 timed_out 标志,在quickreply 中对这个标志进行检查,看它是否超过了规定的时限。
#include <stdio.h>#include <signal.h>#define TIMEOUT 5#define MAXTRIES 5#define LINESIZE 100#define BELL '\007'#define TRUE 1#define FALSE 0/* 判断超时是否已经发生的标志 */static int time_out;static char inputline[LINESIZE];char* quickreply (char* prompt);main(){ printf("%s",quickreply("Input"));}char* quickreply (char* prompt){ int (*was)(),catch(),ntries; char* answer; /* 设定捕捉SIGALRM 的的关联并保存原有关联 */ was=signal(SIGALRM,catch); for (ntries=0;ntries<MAXTRIES;ntries++) { time_out=FALSE; printf("%s>",prompt); /* 设定定时器 */ alarm(TIMEOUT); /* 获取输入 */ answer=gets(inputline); /* 关闭定时器 */ alarm(0); if (!time_out) break; } /* 恢复原有的SIGALRM 关联 */ signal(SIGALRM,was); return (time_out?((char*) 0):answer);}/* SIGALRM 信号处理函数 */catch(){ /* 设定超时标志 */ time_out=TRUE; /* 响铃警告 */ putchar(BELL);}
2.系统调用pause()
系统调用 pause()能使调用进程暂停执行,直至接收到某种信号为止。pause() 在 Linux 系统函数库 unistd.h 中的函数声明如下:
int pause(void);
该调用没有任何的参数。它的返回始终是 -1 , 此时 errno 被设置为 ERESTARTNOHAND。
下面这个程序为了在规定时间显示一个消息,使用了alarm 和 pause。对它的调用方法如下:
$tml minutes message-text &
第一个参数为时间数,第二个参数为显示的消息。
#include <stdio.h>#include <signal.h>#define TRUE 1#define FALSE 0#define BELLS "\007\007\007"int alarm_flag=FALSE;/* SIGALRM 处理函数 */setflag(){ alarm_flag=TRUE;}main(int argc,char* argv[]){ int nsecs; int i; if (argc<2) { fprintf(stderr,"Usage:tml #minutes message"); exit(1); } if ((nsecs=atoi(argv[1])*60)<=0) { fprintf(stderr,"Invalid time"); exit(2); } /* 设定SIGALRM 的关联动作 */ signal(SIGALRM,setflag); /* 设定定时器 */ alarm(nsecs); /*使用pause()调用等待信号*/ pause(); if (alarm_flag) { printf(BELLS); for (i=2;i<argc;i++) { printf("%s",argv[i]); } } exit(0);}
2.6 系统调用 setjmp()和 longjmp()
有时候,当接收到一个信号时,希望能跳回程序中以前的一个位置执行。例如,在有的程序内,当用户按了中断键,则程序跳回到显示主菜单执行。我们可以用库系统调用 setjmp()和 longjmp()来完成这项工作。setjmp()能保存程序中的当前位置(是通过保存堆栈环境实现的),longjmp()能把控制转回到被保存的位置。在某种意义上,longjmp()是远程跳转,而不是局部区域内的跳转。我们必须注意到,由于堆栈已经回到被保存位置这一点,所以 longjmp()从来不返回。然而,与其对应的 setjmp()是要返回的。
setjmp()和 longjmp() 在 setjmp.h 中的定义分别如下:
int setjmp(jmp_buf env);void longjmp(jmp_buf env, int val);
setjmp()只有一个参数env,用来保存程序当前位置的堆栈环境。而longjmp()有两个参数:
参数 env 是由 setjmp()所保存的堆栈环境。
参数 val 设置 setjmp()的返回值。longjmp()本身是没有返回的,但其执行后跳转到保存 env 参数的setjmp()调用,并由setjmp()调用返回,就好像程序刚刚执行完setjmp()一样,此时setjmp()的返回值就是val。但是要注意的是,longjmp()调用不能使setjmp()调用返回0,如果val 为0,则setjmp()的返回为1。
下面的例子演示了setjmp()和longjmp()的使用:
#include <stdio.h>#include <setjmp.h>#include <signal.h>jmp_buf position;main(){ int goback(); … … /* 保存当前的堆栈环境 */ setjmp(position); signal(SIGINT,goback); domenu(); … …}goback(){ fprintf(stderr,”Interrupted”); /* 跳转回被保存的断点 */ longjmp(position,1);}