C编程
一个鲜为人知的事实
大多数 C 语言实现都内置了可以用于协作式多任务处理(协程)的原语,它们是 setcontext
和 setjmp
。
setjmp
setjmp
函数与 longjmp
配对使用,用于将执行控制转移到代码中的另一个位置。它依赖于现有的 jmp_buf
声明。
#include <setjmp.h>
int main (void)
{
jmp_buf buf1;
if (setjmp(buf1) == 0)
{
/* 这段代码在第一次调用 setjmp 时执行。 */
longjmp(buf1, 1);
} else {
/* 一旦调用 longjmp,下面的代码将执行。 */
}
return 0;
}
setjmp()
将当前执行点存储在内存中,并且只要包含该函数的函数未返回,该执行点就保持有效。它最初返回 0。控制会在调用 longjmp
时返回到 setjmp
,并传入原始的 jmp_buf
以及替代的返回值。
注意,jmp_buf
被传递给 setjmp
时,并没有使用取地址操作符。
如何理解 setjmp 和 longjmp
最简单的理解方式是:setjmp
存储了 CPU 的状态,这包括程序计数器、堆栈指针、所有寄存器,以及标志寄存器中的标志位,这些状态会被存储在 jmp_buf
所指向的内存位置。jmp_buf
定义的大小足够存储涉及 CPU 的所有寄存器。longjmp(buf)
不会返回,因为它会从 jmp_buf
结构体的内容中恢复 CPU 的状态,恢复到先前通过 setjmp
设置的状态。所以,执行会从 setjmp
被调用后的位置继续,但 setjmp
的返回值不再是 0,而是 longjmp
第二个参数的值。这类似于 fork()
系统调用,它返回 0 给子进程,返回子进程的 PID 给父进程。
协程的应用
互联网建议,协程对于实现软件作为协作状态机非常有用,例如词法分析器处理输入文本并发出令牌(tokens),这样解析器可以决定是存储令牌并请求下一个,还是对当前的令牌集进行处理。这与多线程程序在数据上同步的情况不同,因为多线程可能会因忘记获取锁而发生竞争条件,但使用 setjmp
和 longjmp
,似乎是一种协作式进程,这保证了一次只有一个进程在运行,不必担心上下文切换和唤醒处于睡眠状态的进程(通过使用单独的 jmp_buf
静态位置,每个进程可以为自己的 jmp_buf
调用 setjmp
,在返回值为 0 时稍后调用 longjmp
,或者在非零返回值时继续循环处理共享数据)。