我已经看到了这个:Implementing a User-Level Threads Package并且它不适用。
在执行Thread_new(int func(void *))期间,分配一个线程而创建一个堆栈,我无法想到一种设置方法程序计数器(%eip),如果我是正确的,所以当调度程序启动线程时,它从给定函数的(func)入口点开始。
虽然我已经看过许多c-only(无汇编)实现,但我们已经获得了以下代码(x86):
_thrstart:
pushl %edi
call *%esi
pushl %eax
call Thread_exit
是否有特定的理由将%edi推入堆栈?除了字节复制之外,我似乎无法找到esi / edi的另一种用途。
我意识到对*%esi的间接调用可能用于从新线程的上下文中调用函数,但除此之外,我似乎并不了解如何(或者什么)当从Thread_new
调用_thrstart时,%esi指向有效的函数地址注意:
Thread_exit是清理线程,在c。
中实现这是家庭作业
答案 0 :(得分:3)
总的来说;你可以将“调度程序”分成4个部分。
第一部分是从一个线程切换到另一个线程的机制。这主要涉及将先前线程的状态存储在某处并从某处加载下一个线程的状态。在这里,“某处”可能是某种线程控制块,或者它可能是线程的堆栈,或两者,或其他东西。线程的状态可能包括通用寄存器的内容,它的堆栈顶部(esp
),它的指令指针(eip
),以及其他任何东西(MMX / SSE / AVX寄存器)。但是,对于协作调度,线程的状态可能会少得多(例如,线程切换会破坏大部分线程的状态,并且使用协作调度,以便线程本身知道其状态何时将被删除并且可以为此做好准备)。
第二部分是决定何时进行线程切换以及切换到哪个线程。对于不同的调度程序,这种情况差别很大。
第三部分是开始一个线程。这主要涉及构造将在线程切换期间加载的数据。但是,可以以“懒惰”的方式执行此操作,在第一次创建线程时只创建最小量的状态,然后在给予CPU时间后完成创建线程状态的剩余部分。
第四部分是终止一个线程。这涉及销毁/释放在线程切换期间将加载的数据;但也可能意味着清理线程无法释放的任何资源(例如文件句柄,网络连接,线程本地存储等等),这样就不会导致“资源泄漏”。
答案 1 :(得分:2)
通常,在简单的RTOessess中,线程不是通过被调用或跳转来启动的 - 它们是通过返回或中断返回来启动的。
诀窍是在新堆栈的顶部汇编数据,这样看起来好像线程之前一直在运行,并且已经调用了调度程序或通过中断输入了它。在这个框架的底部'应该是线程函数的地址。然后,您可以使用帧的地址加载堆栈指针,启用中断并执行RET或IRET以启动线程函数。
首先推送新线程可以检索的参数以及调用“终止线程”'或者' Thread_Exit',以便如果线程函数返回,则调度程序可以终止它。
答案 2 :(得分:0)
似乎问题并不像以前那么复杂。
基于@Martin James给出的答案, Stack 已准备好,以便返回地址为_thrstart函数。 根据用于执行上下文切换的程序集,寄存器 edi 和 esi 存储在堆栈上的特定位置(当线程处于非活动状态时)。通过将 edi 和 esi 用作通用寄存器,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