C语言 实现用户级线程库 - 从 makecontext 返回值



我在用户线程库上看到了一些问题,但似乎没有一个能回答我的问题。 我能够创建线程,运行它们,取消它们,然后退出它们。 由于某种原因,我无法做的是让一个线程返回数据。

初始化线程库时,我按如下方式设置退出线程上下文:

getcontext(&threadEnd);
threadEnd.uc_stack.ss_sp = (char *)malloc(SIGSTKSZ);
threadEnd.uc_stack.ss_size = SIGSTKSZ;
makecontext(&threadEnd, (void *) thread_exit, 1, &ReturnValue);

我创建一个线程并按如下方式分配它:

thread->myContext.uc_stack.ss_sp = (char *) malloc(SIGSTKSZ);
thread->myContext.uc_stack.ss_size = SIGSTKSZ;
thread->myContext.uc_link = &threadEnd;

当函数返回并调用 thread_exit() 时:

    void thread_exit(void* retval){
    int* test;
    if (retval != NULL)
    {
        test = (int*) retval;
        printf("Returned value: %in", *test);
        fflush(stdout);
    }

打印输出始终为"返回值:0"

被调用的函数返回指向整数的指针。

我做错了什么?

如果您在 GBD 中单步执行程序,则不会保存用于创建上下文的函数的返回。

我的实验示例:(观察 rax 寄存器):

返回语句:

thread1 (arg=0x1) at test_create_join.c:14
14      return (void *)11;
Value returned is $1 = 19
(gdb) info registers
rax            0x13 19
---

返回后:

(gdb) step
15  }
(gdb) info registers
rax            0xb  11

内部上下文切换:

__start_context () at ../sysdeps/unix/sysv/linux/x86_64/__start_context.S:32
32  ../sysdeps/unix/sysv/linux/x86_64/__start_context.S: No such file or directory.
(gdb) info registers
rax            0xb  11

您可以看到为几条指令保留了返回值,但经过几个步骤后,它变为 0。显然它特定于x86_64拱门,但我认为它可能与大多数拱门相同(即 makecontext 的行为)

现在,如果您需要线程函数的返回值,则可以采用另一种方式。只需创建一个线程处理程序来运行线程,并使用处理程序来创建新上下文。在这里,您可以获得要作为线程运行的函数的返回,并将其保存在线程控制块结构中以供以后使用。

typedef struct {
    thread_start_routine start;
    void *arg;
} entry_point;
static void thread_runner(void *args) {
    exit_critical_section();
    entry_point *entry = (entry_point *) args;
    // run the thread and get the exit code
    current->retcode = entry->start(entry->arg);
}

您需要一些简单的同步来等待一个线程。工作线程设置变量测试和信号。

// Pseudo code
// worker thread
retval = &VALUE; // retval is pointer?
SignalEvent( hEvent );

和主线程:

// Pseudo code
// main thread
int* test;
hEvent = CreateEvent();
WaitOnEvent( hEvent );
if (retval != NULL)
{
    test = (int*) retval;
    printf("Returned value: %in", *test);
    fflush(stdout);
}

对于许多线程,您需要决定是简单地等待其中任何一个,还是等待任何但直到它们都发出信号,或者您想等到所有线程都发出信号然后检查值。此外,我们通常会考虑等待事件的线程是否在等待时执行一些空闲循环,并可能执行其他工作(更复杂的情况)。

如果您使用C++我可以提供一个带有 11 个条件变量的示例C++但对于 C,它通常是 POSIX 或 Win32 API,但我们可以使用伪代码。Win32 SetEvent 和 WaitForSingleObject。这个 POSIX 主题几乎涵盖了您的条件变量。

请注意,线程

完成一个线程也会触发信号,因此在 Win32 中,您可以直接等待线程句柄,但这种方法可能不可移植。此外,线程及其同步不是 C 语言标准的一部分,因此只适用于提供两者的某些操作系统才有意义。

首先,当向makecontext提供参数时,最后一个应该始终NULL

makecontext(&threadEnd, (void *) thread_exit, 1, &ReturnValue, NULL);

遇到了同样的问题,我处理它的方式有点不同,即不使用我存储start_routine uc_link,即 funcarg thread结构内部并使用了包装函数:实际调用线程函数并存储返回值的thread_runner

makecontext(&thread->ctx, (void *) thread_runner, 1, thread, NULL);

其中线程流道是:

void thread_runner(thread_t *thread){
  void **retval = thread->func(thread->arg);
  if(retval != NULL){
    thread_exit(*retval);
  } else {
    thread_exit(NULL);
  }
}

我们不应该在 makecontext() 的参数列表中传递指针。它们必须是手册中指定的整数:

函数 func 被调用,并传递了序列遵循 argc 的整数 (int) 参数;调用方必须在 argc 中指定这些参数的数量。

在某些体系结构上,

整数与指针具有相同的大小(例如 32 位),但在其他体系结构上,指针为 64 位,而整数的长度为 32 位。在最近的 GLIBC/Linux x86_64 体系结构中,参数在上下文中存储为"长长"整数。因此,这可能会起作用,因为这会使参数存储为 64 位值(与指针的大小兼容),但这不是可移植的。因此,这可以解释为什么您没有通过"&ReturnValue"指针获得正确的值。

相关内容

  • 没有找到相关文章

最新更新