操作系统 | 进程/线程切换问题
一. 底层逻辑:
1. 一个芯片上通常会运行多个独立的逻辑流,于是就有了并发。
2. 为了解决并发所带来的上下文切换问题,所以引入了进程。
3. 进程就这样抽象出一个概念,搭配虚拟内存、进程表之类的东西,用来管理独立的程序运行、切换。
4. 程序的运行涉及大量的计算机资源配置,出于安全性考虑,这些资源的分配,需要陷入内核,切换到操作系统,由操作系统来进行资源的配置。
5. 由于进程的切换需要反复进入内核,置换掉一大堆的状态,进程数一高,大部分的系统资源都被进程切换吃掉了。
6. 为了解决这个问题,搞出线程,这个地方阻塞了,但还有其它地方的逻辑流可以计算,这些逻辑流是共享一个地址空间的,不需要特别麻烦的切换页表、刷新TLB,只需要将寄存器刷新一遍就行,比进程切换开销少。
7. 如果连时钟阻塞、 线程切换这些功能我们都不需要了,自己在进程里面写一个逻辑流调度的东西。那么我们即可以利用到并发优势,又可以避免反复系统调用,还有进程切换造成的开销,分分钟给你上几千个逻辑流不费力。这就是用户态线程。
8. 实现一个用户态线程有两个必须要处理的问题:一是碰着阻塞式I\O会导致整个进程被挂起;二是由于缺乏时钟阻塞,进程需要自己拥有调度线程的能力。如果一种实现使得每个线程需要自己通过调用某个方法,主动交出控制权。那么我们就称这种用户态线程是协作式的,即是协程。
9. 本质上,协程就是用户空间下的线程。协程的优点是,让原来要使用异步+回掉的方式写的代码,可以用看似同步的方式写出来。
总结:
1. 由于并发的关系引入了进程,让不同的逻辑流可以并行执行。但是进程的切换需要陷入内核态,置换状态信息,造成极大的系统开销。
2. 为了减少这种系统开销,从而引入线程,线程可以解决并发的问题,同时也减少了系统的开销资源,由于线程间可以共享地址空间,所以在线程切换的时候,可以不用切换页表全局目录。
3. 自己在进程里面写一个逻辑流调度的东西。那么我们即可以利用到并发优势,又可以避免反复系统调用,还有进程切换造成的开销,分分钟给你上几千个逻辑流不费力。这就是用户态线程。(协程)
二. 虚拟内存知识复习
虚拟内存是什么?解决什么问题?如何映射?请看这篇博客:
操作系统面试题:虚拟内存是什么,解决了什么问题,如何映射?_我是方小磊的博客-CSDN博客_虚拟内存面试题
虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理内存上,那么必须有某种机制能记住虚拟地址空间中的某个数据被放到了哪个物理内存地址上,这就是所谓的地址空间映射,也就是虚拟内存地址与物理内存地址的映射关系,那么操作系统是如何记住这种映射关系的呢,答案就是页表,页表中记录了虚拟内存地址到物理内存地址的映射关系。有了页表就可以将虚拟地址转换为物理内存地址了,这种机制就是虚拟内存。
每个进程都有自己的虚拟地址空间,进程内的所有线程共享进程的虚拟地址空间。
三. 进程与线程
线程
- 多线程有多个控制序列,单线程只有一个控制序列
- 在一个程序里的一个执行路线就叫做线程(thread)。
更准确的定义是:线程是一个进程内部的控制序列(指令序列) - 一切进程至少都有一个执行线程
进程
进程的相关概念如下:
- (1)程序: 完成特定功能的一系列有序指令的集合,通过编译链接成可执行文件:
- (2)可执行文件: 称之为程序,代码段(指令)+数据段(指令操作的程序),保存在磁盘上
- (3)进程: 程序的一次动态执行过程,代码段+数据段+堆栈段+PCB(进程控制块:进程运行状态(包括:就绪状态,运行状态,等待状态),进程上下文,进程执行的CPU状态,当前运行到哪个地址:IP指令指针+SP堆栈指针+寄存器状态)
进程与程序的对比如下:
-
进程 程序 动态的(在不断的推进的过程中,会更改数据段,产生一些临时的数据保存在堆栈段当中,且PCB的的状态也是不断发生改变的) 静态的(保存在磁盘上,程序文件信息是不会改变的) 短暂的(只是程序的一次动态执行过程) 永久的 代码段+数据段+堆栈段+PCB 代码段+数据段 -
一个进程只能对应一个程序(一个进程是一个程序的动态执行实例,一个程序可以运行多个实例,一个程序可以对应多个进程)
-
进程数据结构:多个线程共享下面的信息
(1)进程ID,uid,gid,有效uid,有效gid,cwd当前工作状态,
(2)地址空间Memory Map每个进程都有2^32=4GB 的地址空间(对于32位的机器而言),
(3)信号分发表Signal Dispatch Table处理信号,
(4)文件描述表File Descriptor(维护当前打开的文件), -
进程数据结构:不共享的信息如下:
(1)CPU state(单线程只有一个,多线程有多个,保存执行程序所必要的信息):优先级Poriority,
(2)信号屏蔽字Signal Mask,
(3)寄存器Registers,
(4)内核堆栈Kernel Stack -
进程是资源竞争的基本单位
-
线程是程序执行的最小单位
-
线程共享进程数据,但也拥有自己的一部分数据
(1)线程ID
(2)一组寄存器:IP指令指针+SP堆栈指针+通用寄存器等
(3)栈:线程的局部变量
(4)errno:每个线程都有一个errno
(5)信号状态:每个线程都有一个对信号的处理状态
(6)优先级 -
fork和创建新线程的区别
(1)当一个进程执行一个fork调用的时候,会创建出进程的一个新拷贝,新进程将拥有它自己的变量和它自己的PID。
这个新进程的运行时间是独立的,他在执行时,几乎完全独立于创建他的进程
(2)在进程里面创建一个新线程的时候,新的执行线程会拥有自己的堆栈(因此,也就拥有自己的局部变量),但要与他的创建者共享全局变量,文件描述符,信号处理器和当前的工作目录状态
四. 进程切换在切换什么
1. 切换内核态堆栈(由于进程切换需要陷入内核态,用户态到内核态的切换,必然会涉及到内核态堆栈的切换)
2. PCB的切换(重新引起调度时,操作系统会找到新的进程的PCB,并完成该进程与新进程PCB的切换,寄存器的值,硬件上下文)
—ip(instruction pointer):指向当前执行指令的下一条指令
—bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
—sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
—cr3:页目录基址寄存器,保存页目录表的物理地址
3. 切换页表全局目录(由于进程是独享内存空间的,进程对内存的操作涉及到虚拟内存,页表是实现方式,所以切进程内存,就是切换页表)
4、刷新TLB(TLB本质是一种cache,用于完成虚拟地址和物理地址间的转换)
总结:
1. 进程切换必然陷入内核态,那就必然涉及到内核态堆栈切换。
2. 每一个进程都有一个PCB用于描述控制进程的运行。进程切换必然切换PCB。
3. 每个进程独享内存空间,虚拟内存到物理内存的映射,通过页表实现,所以要切换页表。
4. 虚拟地址和物理地址间的转换,还涉及到TLB(cache),所以需要刷TLB。
五. 用户级线程如何切换?
进程切换:包括指令切换,和内存的切换。(分治思想hhh 老师说的好好)
[线程]操作系统线程是怎么切换的(用户态的内部切换)_pmdream的博客-CSDN博客_线程切换
六. 进程切换和线程切换
其实进程和线程之间本质的区别在于对内存资源的占有
—进程独享资源,所以切进程需要切页表(虚拟地址空间的切换)
—线程共享资源,所以不需要切页表(虚拟地址空间的切换)
线程的切换本质上还是进程的切换,只要进程切换,必然会进入内核态,只有内核才有权力进行进程调度,而进程的调度也会涉及到一些数据结构,比如调度进程的红黑树,或者是阻塞进程的队列集,也只有内核才有资格访问。
进程切换与线程切换的一个最主要区别就在于进程切换涉及到虚拟地址空间的切换而线程切换则不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
举一个不太恰当的例子,线程切换就好比你从主卧走到次卧,反正主卧和次卧都在同一个房子中(虚拟地址空间),因此你无需换鞋子、换衣服等等。但是进程切换就不一样了,进程切换就好比从你家到别人家,这是两个不同的房子(不同的虚拟地址空间),出发时要换好衣服、鞋子等等,到别人家后还要再换鞋子等等。
因此我们可以形象的认为线程是处在同一个屋檐下的,这里的屋檐就是虚拟地址空间,因此线程间切换无需虚拟地址空间的切换;而进程则不同,两个不同进程位于不同的屋檐下,即进程位于不同的虚拟地址空间,因此进程切换涉及到虚拟地址空间的切换,这也是为什么进程切换要比线程切换慢的原因。
当每个进程创建的时候,内核会为每个进程分配虚拟内存,这个时候数据和代码还在磁盘上,当运行到对应的程序时,进程去寻找页表,如果发现页表中地址没有存放在物理内存上,而是在磁盘上,于是发生缺页异常,于是将磁盘上的数据拷贝到物理内存中并更新页表,下次再访问该虚拟地址时就能命中了。
七. 为什么虚拟地址切换很慢
现在我们已经知道了进程都有自己的虚拟地址空间,把虚拟地址转化为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个Cache就是TLB,Translation Lookaside Buffer,我们不需要关心这个名字,只需要知道TLB本质上就是一个cache,是用来加速页表查找的。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换不会导致TLB失效,因为线程无需切换地址空间,因此我们通常说线程切换比进程切换快,原因就在这里。
相关文章
- 【nodejs原理&源码赏析(6)】深度剖析cluster模块源码与node.js多进程
- 内存池、进程池、线程池
- 进程和线程的区别
- 进程和线程
- 对比python的进程和线程:多线程是假的
- 26、进程和线程之间的关系
- 【python】进程与线程
- SAP ABAP 守护进程的实现方式
- 常见Java面试题 线程和进程的区别?
- Qt进程间通信
- 【Linux】进程调度概述
- 【Linux 内核】Linux 内核特性 ( 组织形式 | 进程调度 | 内核线程 | 多平台虚拟内存管理 | 虚拟文件系统 | 内核模块机制 | 定制系统调用 | 网络模块架构 )
- 说说无效的线程和进程ID
- 线程插入技术——不就是DLL进程注入嘛,干嘛整这么多幺蛾子!造词这么多。。。
- 进程与线程
- Linux 操作系统中进程和线程的概念
- 进程和线程:进程的开销比线程大在了哪里?