目录
Uthread: switching between threads
线程
线程出现的原因
之前提到随着时代的发展,cpu逐渐发展成多核的形式,这就导致需要进行不同进程共享资源的问题。但进程对应的是一个任务,每个cpu核上都运行一个单独的进程可以使得不同任务并行运行。但对于同一个任务,多核能否进行加速呢?这就出现了线程的概念,具体的可以把线程理解为执行单位,或是单个串行执行代码的单元。类比的话就是进程代表一个建筑公司,那么线程就可以是其中的施工队,每个施工队负责具体的部分建造,整个公司负责一个整体的建造任务。
线程和进程的关系
进程中可以有多个线程,也可以只有一个线程,具体根据操作系统的不同而异。在XV6中,一个进程只包括两个线程,一个用户级线程,用来负责用户级任务的执行,一个是内核级线程,运行系统调用等内核态才可运行的操作,由操作系统进行管理。其中进程可以理解为资源调度的基本单位,就是所有的资源归进程所有,一个进程的所有线程共享该进程的地址空间(当出现竞争时需要用锁来同步),而线程是具体执行的基本单位。
线程的上下文
线程的上下文指的是线程在cpu上运行时的状态,因为要进行线程的切换和恢复,所以保存线程在cpu上运行的具体状态(PC,寄存器的值,栈等)时必要的。
线程的调度
多核cpu加快计算机效率一方面可以让单个进程的多个线程同时运行在多个cpu上,但实际情况中更多的是另一方面就是cpu核是有限的,但线程的数量则是成百上千的,这时需要的就是在单个cpu上进行线程的切换,这也叫做线程的调度。
线程的调度分为抢占式调度和自愿型调度,其中抢占式调度通常是由时间中断之后,内核中的中断处理程序获得cpu的控制权,之后主动出让cpu给线程调度程序,随后由线程调度程序来决定由哪些线程继续运行。其中线程的状态分为“正在运行”、“等待运行”、和“等待”三个状态。具体操作是线程调度器首先保存正在运行线程的上下文并将其置为“等待运行”,随后根据策略恢复某个“等待运行”的线程,仅需执行。
xv6中大致的线程调度流程如下:

大致可以理解为CC用户线程在用户空间运行,随后定时器中断,该线程将用户运行的状态保存在trapframe中,随后内核级线程保存其上下文,线程调度器从cc的内核级线程切换到cs的内核级线程,将cs内核级线程的上下文的信息加载到cpu上,随后恢复cs的trapframe,返回cs的用户空间,运行cs的用户级线程。所以xv6从不直接进行进程的切换,而是将调度权放在内核中,通过内核级线程的切换实现进程的切换。
线程调度的具体实施
在XV6中,线程调度的具体实施如下所示:

大致的流程可以理解为:P1用户进程首先由于时间中断,保存用户寄存器到trapframe中,之后进入内核态,内核级线程拿到cpu控制权,随后在中断处理程序中会有switch函数,该函数会保存当前P1的内核级线程的context(上下文),随后将cpu的控制权出让给该cpu的调度器线程(每个cpu都有独属于它的调度器线程)该调度器线程从所有内核级线程中按照某种策略选出合适的内核级线程P2唤醒,具体就是将当前的cpu信息修改成该内核级线程P2保存的context,注意该context也是该内核级线程P2之前在switch函数中保存的信息,故恢复信息后,就又跳转到该内核级线程P2的switch函数,随后恢复P2的trapframe信息,返回P2的用户级线程。
接下来结合代码进行讲解:
首先观察proc的结构体:
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual add

最低0.47元/天 解锁文章
1362

被折叠的 条评论
为什么被折叠?



