# 【NO.71】linux操作系统中线程是怎么切换的（用户态的内部切换）

多线程，会减少内核态，用户态的切换，从而提高性能。

## 1.简述

为什么要有线程，大概是这样的？

因为一个进程，要进行多个指令，如果访问地址，需要有地址，要回到映射表中。

执行不下去了，进程要切换，那么一个执行指令序列，要切换到另外一个执行指令序列，

比如A函数 从b函数执行， 那么我们只切换指令，不切换映射表，那么就是线程了。

所以，期望是操作系统减少切换映射表，避免了进程切换的代价（切换映射表比较消耗资源）

大概，面试打到这里，很浅可能也够了。

但是求知的角度，一定要好好学习一下。

**（tips：进程切换其实是一样的。只不过从pcb变成了tcb）**

## 2.用户级线程-user threads

![img](https://pic1.zhimg.com/80/v2-c0475a2ae93b625169c271f5c5fac40c_720w.webp)

![img](https://pic3.zhimg.com/80/v2-f0b71a689bb5b614dc197f4abb6dc9c2_720w.webp)

什么是线程：

一个执行指令序列，切换到另外一个指令序列，只切指令。

映射表对应的是资源，也就是内存。

我们期望，资源和指令分开，资源不变，内存不用跟着切，多个指令多个程序交替进行，切起来还快。

并发的特点，还保证了切换特别快。

在一个资源下面，启动了多个轻巧的指令序列，可以来回切，也是程序执行起来的，就是函数呗~Thread

线程保留并发有点，还不用切换消耗特别大。

但是依然要一段程序切换到另一端程序。（线程切换）

**进程切换：包括指令切换，和内存的切换**。



## 3.指令是怎么切换的？（线程的切换）

我们要切换线程，并发，也就是交替执行，cpu的利用率才会上去。

如果读写磁盘，我们就释放cpu去。

![img](https://pic1.zhimg.com/80/v2-846455226351c296f0c87ec189e05e20_720w.webp)

yield的目的就是能切换出去，也能切换回来。

注意这边，跟接下来要讲到的细节指令有关系。

第一遍我都看懵了，我们期望就是下图，100的指令，切到300，让它去show，之后我们再回到GetData函数，进行往下走。

![img](https://pic2.zhimg.com/80/v2-b073b43f8fd7bc91160b87fb93bae795_720w.webp)

![img](https://pic2.zhimg.com/80/v2-9ee06221f72c848164ccfdc98ad9d76d_720w.webp)

（注意上图： 主要是两个执行序列，一个栈，但是这样有问题的，后面会说 ）

执行的切换，操作系统的核心！！

A到B执行的话，我们需要跳到b执行，还需要跳回来的，

所以需要把函数返回的地址压栈，用于跳回A方法！

当b执行的时候，yield是一个用户函数，我们自己定义的。

![img](https://pic4.zhimg.com/80/v2-d4cbf3ffd15e834e49f1c6e639b18d33_720w.webp)

所以我们需要跳到300执行，304压栈，继续往下执行跳转到了D，执行yield，把404压栈。

我们下一个指令序列的话，会进行pc指针，因为400处的yield执行完之后，要回到204

我觉得因为这边Dyield，让出了cpu，所以回到了200；

右大括号} 这个会有汇编指令，弹栈，此时栈顶的地址是404，B函数应该是返回到104处，而不是404处；

这样就会出现问题，

因为ret 结束要返回一个线程内部的函数调用，只能在一个线程内部绕来绕去，

所以应该在左边的框框折腾，为什么会跑到右边去呢，因为两个线程共用了一个栈，

ret不允许到另一个栈里面去，一个函数调用是在一个指令序列内部发生的事，所以每一个指令序列，函数调用应该用自己的栈。所以应该把共用的栈拆开来。

总结：上部分 就是用yield ，进行跳转的手段，然后强行让204之后的返回结果，绕到了另外一个指令序列里面去了，也就是到404弹出栈了，但是这样是不对滴。

那么两个栈：

![img](https://pic1.zhimg.com/80/v2-d4042bc8f966a188d6a2d23997b3c284_720w.webp)

所以栈是这样滴：

![img](https://pic3.zhimg.com/80/v2-5f3df287b39a30cc9b689571509e049a_720w.webp)

两个tcb对应两个栈。

所以yield，就会栈切回去，所以栈要存放起来，栈的指针，要存放在tcb，全局数据结构

将来切回去，要从tcb找到指针，tcb是cpu里面的寄存器，有点像大脑里面寄了一下，存放了一下地址。

（tips：进程切换其实是一样的。只不过从pcb变成了tcb）

用户级线程，也可以实现切换，由用户主动做的。（完成了一个栈到两个栈，支持了线程的切换，没有进入到内核！）

![img](https://pic3.zhimg.com/80/v2-163aaa9d9377b3eae5f83350cd135be2_720w.webp)

注意这个东西，大概就是thread 创建的一个过程，

过程：（用户自己的线程，在用户态上来回切来切去，操作系统感知不到它的存在。）

\1. 申请内存做tcb

2.申请内存作为栈 图上是1000

\3. 内存的起止地址放进去

\4. 栈和tcb关联

所以切换的时候首先找到tcb 找到esp，取出值，赋值给真的esp，所以这样弹栈就ok了~

问题：要是一个线程卡了，那么久进程阻塞了。内核需要切换到别的进程，那么这时候，

假如浏览器加载东西的时候，等待网卡IO ，一旦一个用户级的线程卡了，那么就切换到了别的进程，所以有的时候浏览器，切换到了别的标签了，那段程序就不知行了，所以浏览器可能就一块一块的。如果卡了，没有别的进程，那么cpu就会空转了。

核心级的话，需要进入到内核，tcb在内核里面。如果获取数据在内核阻塞，那么缓冲区已经下载了一部分数据，那部分就可以进行执行。

内核级线程就是schedule！调度的特性！内核接管线程，我要调度。

听起来还蛮酷的！

## 4.cpu中的MMU的作用

虚拟内存与物理内存之间的映射

用户空间映射到物理内存是独立的，提高安全性

修改内存访问级别 （0是最高级）

MMU是个硬件，每当cpu访问一个地址的时候，MMU从内存里面查table，把cpu想访问的那个虚拟地址转换成物理地址。因为MMU每次查table都要读内存，比较慢，就在内存和MMU之间弄了个缓存，这个缓存就是tlb，里面存着MMU最近访问过的table的内容，如果下次还访问table的这些项的话，就不用读内存里面的table，而是从tlb里面读，这样比较快。

TLB - translation lookaside buffer

快表，直译为旁路快表缓冲，也可以理解为页表缓冲，地址变换高速缓存。

其实就是个缓存；

## 5.TCB是什么？

**TCB(transmission control block)传输控制块**

**可以认为是寄存器吧~如果有不对的可以指正。**

当任务重新得到CPU使用权时，TCB能确保任务从被中断的点丝毫不差地继续执行

具体的就不细细研究了

原文地址：https://zhuanlan.zhihu.com/p/576178810

作者：linux