用户级线程与内核级线程


前言


提示:以下是本篇文章正文内容

一、线程的定义

线程是操作系统能够调度和执行的基本单位,在 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)

猜你喜欢

转载自blog.csdn.net/qq_53144843/article/details/120461625