-1

我已经看到了这个:实现用户级线程包,它不适用。

在分配线程创建堆栈的 Thread_new(int func(void*)) 的实现过程中,如果我是正确的,我无法想出设置程序计数器 (%eip) 的方法,所以当线程由调度程序启动,它从给定函数的 (func) 入口点开始。

虽然我见过很多只使用 c(无汇编)的实现,但我们得到了以下代码(x86):

_thrstart:
    pushl  %edi
    call *%esi
    pushl %eax
    call Thread_exit

将 %edi 推入堆栈是否有特定原因?除了字节复制,我似乎找不到 esi/edi 的其他用途。

我意识到对 *%esi 的间接调用可能用于从新线程的上下文中调用函数,但除此之外,我似乎不明白 %esi 如何(或什么)指向一个有效函数从 Thread_new 调用 _thrstart 时的地址

笔记:

Thread_exit 是清理线程,在 c 中实现。

这是家庭作业

4

3 回答 3

3

一般来说; 您可以将“调度程序”分解为 4 个部分。

第一部分是从一个线程切换到另一个线程的机制。这主要涉及在某处存储前一个线程的状态并从某处加载下一个线程的状态。在这里,“某处”可能是某种线程控制块,也可能是线程的堆栈,或两者兼而有之,或其他东西。线程的状态可能包括通用寄存器的内容、它的栈顶 ( esp)、它的指令指针 ( eip) 以及其他任何内容(MMX/SSE/AVX 寄存器)。但是,对于协作调度,线程的状态可能要少得多(例如,线程的大部分状态都被线程切换破坏了,并且使用了协作调度,以便线程本身知道它的状态何时将被破坏并为此做好准备)。

第二部分是决定何时进行线程切换以及切换到哪个线程。这对于不同的调度程序有很大的不同。

第三部分是启动一个线程。这主要涉及构建将在线程切换期间加载的数据。但是,可以以“惰性”方式执行此操作,在第一次创建线程时只创建最少量的状态,然后在给定 CPU 时间后完成创建线程状态的其余部分。

第四部分是终止一个线程。这涉及销毁/释放线程切换期间将加载的数据;但也可能意味着清理线程未能释放的任何资源(例如文件句柄、网络连接、线程本地存储等),这样您就不会以“资源泄漏”告终。

于 2014-10-19T00:49:43.880 回答
2

通常,在简单的 RTOesses 中,线程不是通过调用或跳转来启动的——它们是通过返回或中断返回来启动的。

诀窍是在新堆栈的顶部组装数据,这样看起来就好像线程之前已经运行过,并且要么调用了调度程序,要么通过中断进入了它。在这个“框架”的底部应该是线程函数的地址。然后,您可以使用帧的地址加载堆栈指针,启用中断并执行 RET 或 IRET 以启动线程功能。

还可以方便地首先推动新线程可以检索的参数和对“TerminateThread”或“Thread_Exit”的调用,以便如果线程函数返回,调度程序可以终止它。

于 2014-10-19T07:00:16.523 回答
0

看来问题没有以前那么复杂了。

根据@Martin James 给出的答案,准备堆栈以便返回地址是 _thrstart 函数。根据用于执行上下文切换的程序集,寄存器ediesi存储在堆栈上的特定位置(当线程处于非活动状态时)。通过使用ediesi作为通用寄存器,edi 包含void*参数,而esi包含要从新线程调用的函数的地址。

_thrstart:
pushl  %edi        #pushes argument for function func to the stack
call *%esi         #indirect call to func
pushl %eax         #Expect return value in eax, push to stack
call Thread_exit   #Call thread cleanup
于 2014-10-19T15:51:22.170 回答