Linux中如何实现用户级线程的抢占式调度? [英] How is preemptive scheduling implemented for user-level threads in Linux?

查看:37
本文介绍了Linux中如何实现用户级线程的抢占式调度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于用户级线程,有 N 个用户级线程运行在单个内核线程之上.这与 pthreads 形成对比,后者只有一个用户线程在内核线程上运行.

With user-level threads there are N user-level threads running on top of a single kernel thread. This is in contrast to pthreads where only one user thread runs on a kernel thread.

N 个用户级线程被抢占地调度在单个内核线程上.但具体如何做到这一点.

The N user-level threads are preemptively scheduled on the single kernel thread. But what are the details of how that is done.

我听说线程库设置了一些东西,以便内核发送信号,这是将执行从单个用户级线程拉到信号处理程序然后可以进行抢占式调度的机制.

I heard something that suggested that the threading library sets things up so that a signal is sent by the kernel and that is the mechanism to yank execution from an individual user-level thread to a signal handler that can then do the preemptive scheduling.

但是如何保存和/或改变状态(例如寄存器和线程结构)以使这一切正常工作的细节是什么?是否有一个非常简单的用户级线程对学习细节很有用?

But what are the details of how state such as registers and thread structs are saved and/or mutated to make this all work? Is there maybe a very simple of user-level threads that is useful for learning the details?

推荐答案

要获得正确的详细信息,请使用源代码!但这就是我读它时的记忆......

To get the details right, use the source! But this is what I remember from when I read it...

可以通过两种方式调度用户级线程:自愿和抢占.

There are two ways user-level threads can be scheduled: voluntarily and preemptively.

  • 自愿调度:线程必须定期调用一个函数,将 CPU 的使用权交给另一个线程.这个函数被称为 yield()schedule() 或类似的东西.
  • 抢占式调度:库强行从一个线程中移除 CPU 并将其传递给另一个线程.这通常通过定时器信号完成,例如 SIGALARM(有关详细信息,请参阅 man ualarm).
  • Voluntary scheduling: threads must call a function periodically to pass the use of the CPU to another thread. This function is called yield() or schedule() or something like that.
  • Preemptive scheduling: the library forcefully removes the CPU from one thread and passes it to another. This is usually done with timer signals, such as SIGALARM (see man ualarm for the details).

关于如何做真正的切换,如果你的操作系统友好并且提供了必要的功能,那就很容易了.在 Linux 中,您有 makecontext()/swapcontext() 函数,可以轻松地从一项任务切换到另一项任务.同样,请参阅手册页了解详细信息.

About how to do the real switch, if your OS is friendly and provides the necessary functions, that is easy. In Linux you have the makecontext() / swapcontext() functions that make swapping from one task to another easy. Again, see the man pages for details.

不幸的是,这些函数已从 POSIX 中删除,因此其他 UNIX 可能没有它们.如果是这种情况,还有其他技巧可以完成.最流行的是调用 sigaltstack() 来设置一个备用堆栈来管理信号,然后 kill() 本身到达备用堆栈,然后 longjmp() 从信号函数到您想要运行的实际用户模式线程.聪明,嗯?

Unfortunately, these functions are removed from POSIX, so other UNIX may not have them. If that's the case, there are other tricks that can be done. The most popular was the one calling sigaltstack() to set up an alternate stack for managing the signals, then kill() itself to get to the alternate stack, and longjmp() from the signal function to the actual user-mode-thread you want to run. Clever, uh?

附带说明一下,在 Windows 中,用户模式线程被称为 fibers 并且也完全受支持(请参阅 CreateFiber() 的文档).

As a side note, in Windows user-mode threads are called fibers and are fully supported also (see the docs of CreateFiber()).

最后的手段是使用汇编程序,它几乎可以在任何地方工作,但它完全是特定于系统的.创建 UMT 的步骤是:

The last resort is using assembler, that can be made to work almost everywhere, but it is totally system specific. The steps to create a UMT would be:

  • 分配一个堆栈.
  • 分配并初始化一个 UMT 上下文:一个用于保存相关 CPU 寄存器值的结构.

并从一个 UMT 切换到另一个:

And to switch from one UMT to another:

  • 保存当前上下文.
  • 切换堆栈.
  • 在 CPU 中恢复下一个上下文并跳转到下一条指令.

这些步骤在汇编程序中相对容易完成,但如果没有上述任何技巧的支持,在普通 C 语言中就完全不可能.

These steps are relatively easy to do in assembler, but quite impossible in plain C without support from any of the tricks cited above.

这篇关于Linux中如何实现用户级线程的抢占式调度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆