💻 Computer Basics
1、计算机网络
1.1 传输层:TCP和UDP
1.1.1 三次握手
1.1.2 四次挥手
1.1.3 流量控制
1.1.4 拥塞控制
1.1.5 TCP和UDP的区别
1.1.6 TCP如何保证传输的可靠性
1.1.7 TCP长连接和短连接
1.1.8 应用层提高UDP协议可靠性的方法
1.1.9 UDP和IP的首部结构
1.2 应用层:HTTP和HTTPS
1.2.1 HTTP和HTTPS的区别
1.2.2 GET和POST的区别
1.2.3 Session与Cookie的区别
1.2.4 从输入网址到获得页面的过程(越详细越好)
1.2.5 HTTP请求有哪些常见的状态码
1.2.6 什么是RIP,算法是什么
1.2.7 HTTP1.1和HTTP2.0的主要区别
1.2.8 DNS
1.2.9 HTTPS加密和认证过程
1.2.10 常见网络攻击
1.2.11 REST
1.3 计算机网络体系结构
1.4 网络层协议
1.4.1 IP地址的分类
1.4.2 划分子网
1.4.3 什么是ARP协议
1.4.4 NAT协议
2、操作系统
2.1 进程和线程
2.1.1 进程和线程的区别
2.1.2 进程间通信方式
2.1.3 同步原语
2.1.4 进程状态
2.1.5 进程调度策略
2.1.6 僵尸进程和孤儿进程
2.1.7 协程
2.1.8 异常控制流
2.1.9 IO多路复用
2.1.10 用户态和内核态
2.2 死锁
2.3 内存管理
2.3.1 分段与分页
2.3.2 虚拟内存
2.3.3 页面置换算法
2.3.4 局部性原理
2.3.5 缓冲区溢出
2.4 磁盘调度
-
+
游客
注册
登录
进程和线程的区别
## 1 进程和线程有哪些区别 ### 1.1 资源占用 1. 进程是**系统进行资源分配和调度的基本单位**,具有**独立的虚拟内存空间**,典型的进程内存空间布局如下图所示: ![](https://notebook.ricear.com/media/202206/2022-06-24_171937_030717.png) 1. **用户栈**: 1. 栈保存了进程需要使用的各种**临时数据**(如临时变量的值)。 2. 栈是一种可以伸缩的数据结构,其扩展方向是**自顶向下**,**栈底在高地址上**,**栈底在低地址上**,当**临时数据被压入栈**时,**栈顶会向低地址扩展**。 2. **代码库**: 1. 进程的执行有时需要依赖**共享的代码库**(比如 `libc`),这些代码库会被**映射到用户栈下方的虚拟地址处**,并被**标记为只读**。 3. **用户堆**: 1. 堆管理的是**进程动态分配的内存**。 2. 与栈相反,堆的扩展方向是**自底向上**,**堆顶在高地址上**,**堆底在低地址上**,当**进程需要更多内存时**,**堆顶会向高地址扩展**。 4. **数据与代码段**: 1. 处于**较低地址**的是**数据段与代码段**。 2. 他们原本都保存在**进程需要执行的二进制文件中**,**在进程执行前**,**操作系统会将他们载入虚拟地址空间中**,其中,**数据段主要保存的是全局变量的值**,而**代码段保存的是进程执行所需的代码**。 5. **内核部分**: 1. 处于进程地址空间最顶端的是**内核内存**,**每个进程的虚拟地址空间里都映射了相同的内核内存**。 2. 当进程在**用户态**运行时,内核内存对其**不可见**,只有当进程进入**内核态**时,才能**访问内核内存**。 3. 与用户态相似,内核部分也有**内核需要的数据和代码段**,当进程**由于中断或系统调用进入内核后**,**会使用内核的栈**。 2. 线程是**进程内部可独立执行的单元**,他们之间**共享进程的地址空间**,但又**各自保存运行时所需的状态**(即上下文),是**操作系统调度和管理程序的最小单位**,下图展示包含三个线程的进程地址空间,多线程的地址空间主要有两个重要特征: ![](https://notebook.ricear.com/media/202206/2022-06-24_200944_913680.png) 1. **分离的内核栈与用户栈**: 1. 由于**每个线程的执行相对独立**,进程**为每个线程都准备了不同的栈**,**供他们存放临时数据**。 2. 在内核中,**每个线程也有对应的内核栈**,当**线程切换到内核中执行**时,他的**栈指针就会切换到对应的内核栈**。 2. **共享的其他区域**: 1. **进程除栈以外的其他区域由该进程的所有线程共享**,包括堆、数据段、代码段等。 2. 当**同一个进程多个线程**需要**动态分配更多内存**时(在C语言中可通过调用 `malloc`函数实现),他们的内存分配操作都是在**同一个堆**上完成的。 > 用户态线程和内核态线程的区别? > > 1. 根据线程是**由用户态应用还是由内核创建与管理**,可将线程分为两类,分别为**用户态线程**与**内核态线程**。 > 2. 内核态线程**由内核创建**,**受操作系统调度器直接管理**。 > 3. 用户态线程**由应用自己创建**,**内核不可见**,因此也**不直接受系统调度器管理**,与内核态线程相比,用户态线程更加**轻量级**,**创建开销更小**,但**功能也较为受限**,**与内核态相关的操作**(如系统调用)**需要内核态线程协助才能完成**。 > 4. > > 多线程模型有哪几种? > > 1. 为了**实现用户态线程与内核态线程的协作**,操作系统会建立两类线程之间的关系,这种关系称为**多线程模型**。 > 2. 一般来说,多线程模型主要有三种,分别是**多对一模型**、**一对一模型**以及**多对多模型**,这三种多线程模型的具体结构如下图所示。 > ![](https://notebook.ricear.com/media/202206/2022-06-24_203322_567465.png) > 3. 上面三种多线程模型的联系及区别如下: > 4. **多对一模型**: > 1. 多对一模型**将多个用户态线程映射给单一的内核态线程**。 > 2. 这种模型**较为简单**,但由于只有一个内核态线程,因此**每次只能有一个用户态线程可以进入内核**,**其他需要内核服务的用户态线程会被阻塞**。 > 3. 随着多核机器的逐步普及,用户态线程的数量也在不断增加多对一模型难以适应这种变化,因为目前在主流操作系统中已经不再使用了,不过,随着近年来应用变得越发复杂,应用内部的调度也开始变得重要,多对一模型又开始得到了应用。 > 5. **一对一模型**: > 1. 一对一模型**为每个用户态线程映射单独的内核态线程**。 > 2. 相比于多对一模型,一对一模型提供了**更好的扩展性**,因为**每个用户态线程可以使用自己的内核态线程执行与内核相关的逻辑**,**无须担心阻塞其他用户态线程**。 > 3. 一对一模型存在的一大缺点是**由于每个用户态线程都对应于一个内核态线程**,**创建内核态线程的开销会随着用户态线程数量的增加而不断增大**,因此,**实现一对一模型的操作系统往往都对用户态线程的总数量有限制**,**防止因内核态线程数量过多对应用性能造成不良影响**,Linux和Windows系列的操作系统都采用的是一对一模型。 > 6. **多对多模型**: > 1. 多对多模型可以**将**$N$**个用户态线程映射到**$M$**个内核态线程中**,其中$N \gt M$,比如,在多核机器中,可以**将内核态线程的数量**$M$**设定为核心数**,而**用户态线程的数量不做限制**。 > 2. 多对多模型可以看做**多对一模型和一对一模型的结合**,既**减轻了多对一模型中因为内核线程过少而导致的阻塞问题**,也**解决了一对一模型中因为内核态线程过多而导致的性能开销过大的问题**,不过,多对多模型也会**让内核态线程的管理变得复杂**,Solaris操作系统在版本9之前都提供多对多的线程模型,但在版本9之后改为一对一模型,此外,macOS和iOS使用的面向用户体验的调度器GCD也采用了多对多模型。 > ### 1.2 切换开销 1. 在进程切换时,涉及到**整个当前进程CPU环境的设置**以及**新被调度运行的CPU环境的设置**: 1. **在内核中**,**每个进程都通过一个数据结构来保存他的相关状态**,如他的**进程标识符**(Process IDentifier, PID)、**进程状态**、**虚拟内存状态**和**打开的文件**等,这个数据结构称为**进程控制块**(Process Control Block, PCB)。 2. 进程的上下文(context)包括**进程运行时的寄存器状态**,其能够**用于保存和恢复一个进程在处理器上运行的状态**。 3. 当操作系统需要**切换当前执行的进程**时,就会使用**上下文切换**(context switch)机制,该机制会**将前一个进程的寄存器状态保存到PCB中**,然后**将下一个进程先前保存的状态写入寄存器**,从而**切换到该进程执行**,进程的上下文切换过程如下图所示: ![](https://notebook.ricear.com/media/202206/2022-06-24_211730_410202.png) 1. 当进程1由于中断或者系统调用进入内核之后,操作系统就可以进行上下文切换: 1. 首先,操作系统会将进程1的上下文(寄存器信息)保存在其对应的PCB中。 2. 之后,如果调度器选择进程2作为下一个执行的进程,操作系统会取出进程2对应的PCB中的上下文,将其中的寄存器值恢复到对应的寄存器中。 3. 最后,操作系统会回到用户态,继续进程2的执行。 2. 在线程切换时,只需**保存和设置少量的寄存器的内容**,并不涉及存储器管理方面的操作: > 下文将会结合ChCore(AArch64架构)来对线程上下文切换的内容进行阐述。 > 1. 线程的上下文(context)是上下文切换的基础,为了实现不同线程间的切换,首先需要**保存处理器运行一个线程时的所有状态信息**,**这些状态信息就是线程的上下文**。 2. 在**实际的硬件**中,线程的上下文主要指的是**当前处理器中大部分寄存器的值**,这其中包括: 1. **程序计数器**(PC),**存储CPU当前所执行指令的地址**。 2. **通用寄存器**,**存储CPU当前正在处理的一些数据**。 3. **特殊寄存器**,**存储CPU当前的一些硬件状态和配置**,如页表地址等。 > 在ChCore中,线程的上下文即为AArch64架构下CPU中部分寄存器的值,包括X0 ~ X30共31个通用寄存器及一些特殊寄存器的值,在AArch64中,ELR_EL1(程序计数器)、SP_EL0(线程运行时的栈指针)、SPSR_EL1(线程执行时的CPU状态,如条件码、中断状态、是否处于调试模式等)、TTBR0_EL1(线程对应的进程的页表)这些特殊寄存器的值需要被保存。 > 3. 与进程类似,线程也有自己的**线程控制块**(Thread Control Block, TCB),用于**保存与自身相关的信息**: 1. 在目前主流的一对一线程模型中,**内核态线程和用户态线程会各自保存自己的TCB**,其中: 1. **内核态的TCB结构与前面介绍的PCB相似**,**会存储线程的运行状态**、**内存映射**、**标识符等信息**。 2. **用户态TCB的结构则主要由线程库决定**,例如,对于Linux平台上使用pthread线程库的应用来说,pthread结构体就是用户态的TCB,用户态的TCB可以认为是**内核态的扩展**,可以用来**存储更多与用户态相关的信息**。 > 什么是线程本地存储? > > 1. 线程本地存储(Thread Local Storage, TLS)是TCB的一项重要功能。 > 2. 在多线程编程中,可以通过TLS实现「**一个名字**,**多份拷贝**(与线程数量相同)」,的全局变量,这样,**当不同的线程在使用该变量时**,**虽然从代码层次看访问的是同一个变量**,**但实际上访问的是该变量的不同拷贝**,**于是可以很方便地实现线程内部**(而不是线程)**的全局变量**: > 3. 例如,一个多线程的应用程序可以通过 `__thread int count;`为每个线程定义变量 `count`。 > 4. 当某个线程对 `count`赋值时,只会修改自己的拷贝,并不会对其他线程产生影响。 > 5. 如下图所示,在运行过程中,线程库会为每个线程创建结构完全相同的TLS,保存在内存的不同地址上,在每个线程的TLS中,`count`都处于相同的位置,即每份 `count`的拷贝相对于TLS起始位置的偏移量都相等(下图中 `count`的偏移量为8)。 > ![](https://notebook.ricear.com/media/202206/2022-06-26_160445_018021.png) > > 3. 由于TLS结构的相似性,对TLS中变量寻址的实现方式也较为特殊: > 1. x86_64使用了**FS段寄存器**来实现TLS中变量的寻址,具体来说,当一个线程被**调度**时,`pthread`线程库会找到**该线程TLS的起始地址**,并**存入段寄存器FS中**,当线程**访问TLS中的变量**时,会**用FS中的值加上偏移量**的方式获取变量,**不同线程的FS寄存器中保存的TLS起始地址不同**,所以**不同的线程访问同名的TLS变量**时,最终其实访问了**不同的地址**。 > 2. 类似地,在AArch64中,TLS的起始地址将被存放在**寄存器TPIDR_EL0**中,当需要访问TLS中的变量时,线程会首先**读取寄存器TPIDR_EL0中的数值**,再**加上偏移量**以获取变量的值。 > 1. **在一对一的线程模型中**,**操作系统会将线程的上下文保存在该线程对应的TCB里**,TCB中除了包含**线程状态**、**调度优先级**等信息,还专门**预留了存储线程上下文的空间**,线程对应的内核栈紧接在TCB下面,供线程进入内核之后使用,ChCore的TCB结构如下图所示。 ![](https://notebook.ricear.com/media/202206/2022-06-26_165310_383555.png) > AArch64提供了寄存器SP_EL1,当线程进入内核态之后,会自动将栈指针从SP_EL0切换到SP_EL1,由于ChCore保证了内核栈在用户态线程切入内核态时为空,因此SP_EL1一定指向TCB的起始地址。 > 4. 由于线程的上下文切换涉及了如**页表切换**、**特殊寄存器的保存与恢复**等许多特权操作,还需要**使用一些内核数据结构以寻找到合适的目标线程进行切换**,因此对线程进行上下文切换的核心操作都是在**内核态**完成的,在ChCore中,这个过程主要包括三个部分,分别为**进入内核态与上下文保存**、**页表与内核栈的切换**,以及**上下文恢复与返回用户态**: ![](https://notebook.ricear.com/media/202206/2022-06-27_201638_676449.png) 1. **进入内核态与上下文保存**: 1. 线程的上下文切换通常是由**时钟中断**所触发的,操作系统的**调度器会对时钟进行设置**,当他**达到设定好的时间**时,就会**发出一个硬件中断**,在中断触发后,CPU的硬件会自动执行好一系列操作,保存好线程的上下文。 > ChCore为发生时钟中断所对应的处理函数配置了如下图所示 `exception_enter`,并**在内核态使用内核栈对用户线程的上下文进行了保存**,值得注意的是,这里并**没有对页表相关的寄存器进行保存**,这是由于**线程的顶级页表指针通常不会改变**,因此**该值被存储在了TCB中**,**不用每次进入内核态时都需要重新保存**。 > ![](https://notebook.ricear.com/media/202206/2022-06-27_173924_002279.jpg) > 2. **页表与内核栈的切换**: 1. 进入内核态后,在高特权级别下,我们才能够对页表进行切换,与此同时,由于ChCore采用一对一的线程模型,因此这一环节还涉及**内核线程之间的内核栈切换**。 2. 在ChCore中,这一环节是由 `switch_context`和 `eret_to_thread`这两个函数共同实现的: 1. `switch_context`:主要负责**切换页表和找到目标线程的内核栈**。 2. AArch64在地址翻译时会使用两个页表基地址寄存器TTBR0_EL1和TTBR1_EL1,分别对应于**进程虚拟地址空间的内核部分和应用部分**,由于每个线程映射的内核部分都是相同的,因此ChCore不需要改变TTBR0_EL1中的值,只需要**将TTBR1_EL1修改为目标线程的应用空间页表基地址**即可。 3. 另外,`switch_context`还会**找到目标线程的TCB的起始地址**,并**将改地址作为目标线程的内核栈顶**,之后,`eret_to_thread`**通过简单的 `mov`指令**,**切换到目标线程的内核栈**。 4. 由于所有的用户态线程在进入内核态之前都将线程的上下文保存在了该线程的TCB中,因此**当切换到另一个线程的内核栈之后**,**处理器的内核栈顶即为另一个线程的上下文**,由于在 `mov`指令之后,处理器的当前工作栈已经从原来的线程切换到了目标线程,由此可以认为 `mov`**是两个线程执行的分界点**。 ![](https://notebook.ricear.com/media/202206/2022-06-27_195701_277835.png) 3. **上下文恢复与返回用户态**: 1. `eret_to_thread`已经切换到了目标线程的内核栈,且栈顶上方就是目标线程的上下文,因此,内核只需要使用 `exception_enter`的反向操作 `exception_exit`,**将目标线程的上下文从内核栈的栈顶依次弹出**,**恢复到寄存器中**,**并最终返回用户态**,**继续目标线程的执行**即可。 ![](https://notebook.ricear.com/media/202206/2022-06-27_201001_735285.jpg) 2. 在恢复了用户上下文后,ChCore利用AArch64架构下的 `eret`这一指令完成一系列的操作,最终**将处理器的状态恢复为另一个用户态线程进入内核态执行上下文切换之前的状态**,从而完成整个上下文切换的过程。 ### 1.3 通信方式 1. 进程间的通信需要以 [进程间通信](https://notebook.ricear.com/project-26/doc-324) 的方式进行。 2. 同一进程的线程共享进程的地址空间,因此没有通信的必要,但需要做好 [同步和互斥](https://notebook.ricear.com/project-26/doc-325) ,保护共享的全局变量。 ## 参考文献 1. 《现代操作系统:原理与实现》 2. [进程间通信和线程间通信的几种方式](https://www.cnblogs.com/fanguangdexiaoyuer/p/10834737.html)。
ricear
2022年7月5日 21:21
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码