- 前言
- 一、线程的定义
- 二、用户级线程
- 三、核心级线程
- 总结
提示:以下是本篇文章正文内容
一、线程的定义线程是操作系统能够调度和执行的基本单位,在 Linux 中也被称之为轻量级进程(LWP:light weight process),在 Linux 系统中,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源比如 cpu、内存、文件,将线程分配到某个 cpu 上执行
一个进程可以拥有多个线程,它还可以同时使用多个cpu 来执行各个线程,以达到最大程度的并行,提高工作的效率。
线程的本质是一个进程内部的一个控制序列,它是进程里面的东西,一个进程可以拥有一个进程或者多个进程
每一个进程都包含一个映射表,如果进程切换了,那么程序选择的映射表肯定也不一样;进程的切换其实是包含两个部分的,**第一个指令的切换,第二个映射表的切换。**指令的切换就是从这段程序跳到另外一段程序执行,映射表切换就是执行不同的进程,所选择的映射表不一样。线程的切换只有指令的切换,同处于一个进程里面,不存在映射表的切换。进程的切换就是在线程切换的基础上加上映射表的切换。
进程 = 资源 + 指令执行序列 所以,对应线程来说只是切换pc,内存和表不用切。
在每个大的进程里,有很多小的线程,并行的时候只需要改每个小的线程的PC指针,而不需要切换映射表。
所以,学习好线程是学习好进程的关键。
二、用户级线程pthread_create函数用来创建一个线程,yield函数保证线程之间可以进行切换。
打开一个浏览器:
一个线程用来从服务器接收数据
一个线程用来处理图片(如解压缩)
一个线程用来显示文本
一个线程用来显示图片
这些线程要共享资源: 接收数据放在100处, 显示时要读… 所有的文本、 图片都显示在一个屏幕上
Yield进行线程间的调度: 下面程序就是用户级线程的应用,通过用户主动进行切换,不用内核帮助。用户级线程是可以独立于操作系统的
yield实现线程的切换 两个执行序列对应两个栈 线程一先执行A,执行到B,跳转到B并将A的下一条指令的压栈,Yield切换到线程二的C,同时将下一条指令压栈。到了线程二在C里面又跳转到D并压栈,D里面又切换线程,压栈,同时将B压入的栈弹出,执行B。B执行完又弹栈,跳到A里面执行…(大概就是这样吧) 注:线程一和线程二压入的栈不是同一个
两个线程的样子: 两个TCB、 两个栈、 切换的PC在栈中 创建一个线程就要为该线程创建相应的栈,并将sp指向栈顶
Yield是用户程序
用户级线程只能在用户态进行切换,进入内核后还是同一个进程,Yiled程序是用户程序,而核心级线程会进入内核进行切换,ThreadCreat是系统调用,内核也知道TCB,Yield程序也不是用户编写,而是内核程序,用户不可见,至于调度点,也是有操作系统决定
核心级线程与用户级线程区别:
1.核心级线程需要在用户态和核心态里面跑,在用户态里跑需要一个用户栈,在核心态里面跑需要一个核心栈。用户栈和核心栈合起来称为一套栈,这就是核心级线程与用户级线程一个很重要的区别,从一个栈变成了一套栈。
2.用户级线程用TCB切换栈的时候是在一个栈与另外一个栈之间切换,核心级线程就是在一套栈与另外一套栈之间的切换(核心级线程切换),核心级线程的TCB应该是在内核态里面。
内核级线程一套栈如图: 用户栈+内核栈
用户栈和内核栈之间的关联:
当线程进入内核的时候就应该建立一个属于这个线程的内核栈,通过INT中断进入内核。当线程下一次进入内核的时候,操作系统可以根据一些硬件寄存器来知道这个哪个线程,它对应的内核栈在哪里。**同时会将用户态的栈的位置(SS、SP)和程序执行到哪个地方了(CS、IP)都压入内核栈。**等线程在内核里面执行完(也就是IRET指令)之后就根据进入时存入的SS、SP的值找到用户态中对应栈的位置,根据存入的CS、IP的值找到程序执行到哪个地方。
内核级线程执行过程: 首先该线程调用B函数,将104压栈(用户栈),进入B函数之后调用read()这个系统调用,同时将204压栈(用户栈),进入read()系统调用通过int0x80这个中断号进入内核态,执行到sys_read()
sys_read()函数:
sys_read()
{
启动磁盘读;
将自己变成阻塞;
找到next;
switch_to(cur, next);}
switch_to的作用就是切换线程 switch_to仍然是通过TCB找到内核栈指针;然后通过ret切到某个内核程序;最后再用CS:PC切到用户程序
形参cur表示当前线程的TCB,next表示下一个执行线程的TCB。 这个函数首先将目前esp寄存器的值存入cur.TCB.esp,将next.TCB.esp放入esp寄存器里面;其实就是从当前线程的内核栈切换到next线程的内核栈。
内核级线程自己的代码还是在用户态的,只是进入内核态完成系统调用,也就是逛一圈之后还是要回去执行的。因此切换到next线程就是要根据next线程的内核栈找到这个线程阻塞前执行到的位置,并接着执行。所以切换到next线程的内核栈之后应该通过一条包含IRET指令的语句进入到用户态执行这个线程的代码。这样就从cur线程切换到next线程。
内核级线程的切换是在内核里面进行切换的,切换完成后,在根据next内核栈里面的数据,返回到next用户栈
所以,要想从next的内核栈,经过弹栈,返回到next的用户栈,在我们调用ThreadCreate()创建线程的时候,我们就要将线程的内核栈和用户栈创建好并且将相应的数据压栈。、 1、申请内存,作为TCB 2、申请内存,作为内核栈 3、内核栈和用户栈相关联 4、TCB关联内核栈 如图
内核线程switch_to的五段论(这里先了解一下) 下一篇笔记将详细介绍这个五段论
对比:
提示:这里对文章进行总结: 根据在用户空间还是在核心实现多线程机制,线程又被分为用户级线程(User Level Thread)和内核级线程(Kernel Level Thread)