我想创建两个执行线程,执行两个不同的函数,然后使用相同的调用堆栈返回。执行不需要并行执行,执行的线程可以一个接一个地执行。
我试图用setcontext / getcontext解决问题,但是他们不复制完整的调用堆栈,第二个线程不能使用第一个线程的调用堆栈:
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
ucontext_t fork_context, fork_context2;
char stack[SIGSTKSZ];
int end = 0;
void func2_1() {
printf("func2_1\n");
}
void func2_2() {
printf("func2_2\n");
end = 1;
}
void func2() {
printf("func2\n");
func2_1();
getcontext(&fork_context);
fork_context.uc_link = &fork_context2;
fork_context.uc_stack.ss_sp = stack;
fork_context.uc_stack.ss_size = sizeof(stack);
makecontext(&fork_context, (void (*)(void)) func2_2, 0);
}
void func1() {
printf("enter func1\n");
func2();
getcontext(&fork_context2);
printf("return func1\n");
}
int main(void) {
func1();
printf("back in main\n\n");
if (end != 1) {
setcontext(&fork_context);
}
return 0;
}
在func2()
中,我想执行func2_1()
,然后根据调用堆栈返回。在从main()
返回之前,我想执行func2_2()
并返回与从func2_1()
返回时相同的功能。上面的代码产生以下输出:
func2_1
return func1
back in main
func2_2
return func1
正确调用 func2_2()
然后在func1()
继续执行,但func1()
不会返回main()
,因为func1()
的堆栈帧}呼叫已被删除。我想要一个产生以下输出的程序:
func2_1
return func1
back in main
func2_2
return func1
back in main
一种可能性是使用POSIX fork()
复制整个过程(包括调用堆栈),但如果可能的话,我宁愿不使用fork()
。
答案 0 :(得分:3)
有没有办法在C
中保存和恢复调用堆栈
没有。堆栈的行为方式(是否以及哪些函数参数在寄存器或堆栈中传递,哪些寄存器必须保存/恢复等)是活动ABI和CPU的函数(例如,Linux ELF ABI有点不同)在amd64上比在x86上,并且任何操作系统可能同时支持多个ABI)并且 C 对此一无所知。可能通过某些特定于平台的API来完成,但这不是 C 问题。
我想创建两个执行线程,执行两个不同的函数
除了主线程之外,您是要创建两个新线程还是创建一个额外线程?如果在主线程中创建一个新线程,现在有两个执行线程,但是你只创建了一个(附加)线程。
新线程的“启动例程”调用的任何函数(或由这些函数调用的函数等)将在新线程中执行。由main()
调用的所有函数,或由main()
调用的函数调用的函数(依此类推)将在主线程中执行。
然后使用相同的调用堆栈返回。
咦?使用与堆栈相同的来从中返回 的内容?就它而言,该条款只是令人困惑!
执行不需要并行执行,执行的线程可以一个接一个地执行。
那么为什么要创建第二个线程而不只是按顺序调用函数?
我试图用setcontext / getcontext
解决问题
不要那样做。这些功能在2001年正式弃用,不再出现在POSIX / SUS中。新应用程序(或过去十年中编写的应用程序)应该使用POSIX线程。
这是一个POSIX线程程序,它粗略地反映了你在代码提取中试图做的事情,但老实说,我不确定我有多接近,因为我真的不清楚你是什么试图做。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#define bail(err, msg) { \
errno = err; \
perror(msg); \
exit(1); \
}
pthread_t tid;
// Executes in the main thread
void func2_1() {
printf("func2_1\n");
}
// Conforms to prototype expected by pthread_create(); executes in new
// thread.
void *func2_2(void *arg) {
(void)arg;
printf("func2_2\n");
// I suppose we just exit here, since there's nothing else for
// this thread to do:
pthread_exit(NULL);
}
// Executes in main thread
void func2() {
int rc; // Return code
printf("enter func2\n");
func2_1(); // Executes in main thread
rc = pthread_create(&tid, NULL, func2_2, NULL);
if(rc) {
bail(rc, "pthread_create() failed")
}
printf("return func2\n");
}
// Executes in main thread
void func1() {
printf("enter func1\n");
func2();
printf("return func1\n");
}
int main(void) {
int rc; // Return code
func1();
printf("back in main\n\n");
rc = pthread_join( tid, NULL );
if(rc) {
bail(rc, "pthread_join() failed");
}
printf("returning from main()\n");
return 0;
}
答案 1 :(得分:2)
我无法帮助认为fork()
是解决此问题的最简单方法,尽管您可能需要一些共享内存才能允许合并两个调用的结果。 fork()
复制整个流程结构并不完全正确;它通常是针对单个虚拟内存页面的copy-on-write实现的,因此它最终只会复制被修改状态的那部分;例如,它没有包含流程的代码。
如果您想自己动手,可以使用posix线程库的pthread_attr_setstack
创建一个具有特定堆栈的线程,您需要自己分配。由于您可以控制堆栈内存,因此您可以memcpy
释放它以保留它。理论上,至少,您可以使用复制的堆栈创建另一个线程,并使用现已弃用的getcontext
和朋友来保存和恢复不在堆栈中的调用状态部分。要有效地复制堆栈,您需要知道它的限制;有多种方法可以做到这一点,但它们都不是平台独立的。
如果您要提供call/cc
的C实现,您可能需要查看Chicken Scheme(http://www.call-cc.org)中的经典实现,这需要很多C堆栈中有趣的自由,显然是成功的。