本文引用图片均来自 李治军: 操作系统32讲
线程
内核级线程和用户级线程在概念上差不多,不过相比较于用户级线程的透明性,内核级线程是操作系统可见的。内核级线程由操作系统管理,操作系统负责内核级线程的创建、调度和销毁等
因为是由内核进行管理,所以内核级线程可以在多核CPU上并行(用户级线程只能并发),并且一个内核级线程阻塞操作系统可以调度进程的另一个内核级线程执行(一个用户级线程阻塞会导致进程的所有用户级线程阻塞)
内核级线程由ThreadCreate系统调用创建,操作系统会在内核段内存中创建线程表来管理内核级线程
简单概括两种级别线程的特点
| 用户级 | 内核级 |
|---|---|
| 线程的创建、消息传递等由线程库完成 | 线程创建由内核完成 |
| 线程调度可由用户控制灵活性高 | 线程调度由内核完成 |
| 线程切换无需内核介入代价小 | 切换代价高 |
| 无法并行 | 可以并行 |
| 一个线程阻塞导致进程内所有线程阻塞 | 线程阻塞后控制权会移交到其他线程不会阻塞整个进程 |
组合
操作系统中内核级线程和用户级线程是组合在一起使用的,在Linux中采用一对一的组合方式即一个用户线程固定搭配一个内核线程,此时应该将用户线程和内核线程看作一个整体,以下提到线程时都是指这个整体
顺便说一下,linux中创建进程的时候会自动创建一个线程作为主线程
在一对一模式中创建线程时会分别创建用户栈和内核栈分别对应线程在用户态运行时和内核态运行时的栈空间
内核栈会记录用户级线程(或者说线程在用户态运行时)的相关数据,例如SS和SP两项分别指向用户栈顶和栈尾,还记录用户线程执行的指令PC等
内核级线程(或者说线程在内核态运行时)其实是通过内核栈中记录的信息找到用户级线程的代码和数据后切换回用户态执行,个人理解操作系统是通过调度内核级线程间接实现了调度用户级线程
其他组合方式多对一和多对多本文不做介绍
调度
假设现在创建线程T执行C函数,ThreadCreate系统调用申请一段内存创建TCB,再分别申请内存创建用户栈和内核栈并把两个栈关联起来,内核栈中的PC指向C函数起始地址,最后TCB状态设为就绪并入队就完成了线程(包含用户级线程和内核级线程的整体)的创建
假设某线程执行过程如下
1. 线程执行A函数,A函数中调用B函数,104(B函数返回后的下一条指令地址)入栈
2. B函数调用read函数,204(read函数返回后的下一条指令地址)入栈
3. read函数中包含中断导致系统进入内核态并切换到内核栈,内核栈中记录中断返回后执行的下一条指令地址(304)
此时内核线程会因为读写磁盘而阻塞,内核负责调度下一个内核级线程执行
调度过程和进程调度类似,从就绪队列中找到下一个TCB,根据TCB记录的内核栈信息切换到下一个内核级线程,内核级线程再根据对应的内核栈中记录的用户级线程栈和指令地址等数据切到用户态中执行
线程切换是用户栈到内核栈,再由内核栈到用户栈
简单概括内核线程切换为以下5步:
1. 中断进入内核态
2. 中断处理并开始调度
3. 确定TCB
4. 根据TCB切换内核栈
5. 根据内核栈记录的信息切回用户态
// 6. 如果调度的线程不属于同一个进程还需切换地址映射表