目录
【操作系统篇】
PS:本文不适合完全没有OS基础的阅读
什么是操作系统
- 管理计算机硬件和软件资源
- 本质是运行在计算机上的软件程序
- 屏蔽了硬件层的复杂性
- 内核是操作系统的核心部分,负责内存管理、文件系统管理、硬件设备管理、应用程序管理
系统调用
- 调用操作系统提供的内核态的功能–>系统调用
- 用户态与系统级别资源相关的操作–>系统调用向操作系统提出,由操作系统完成
- 系统调用有设备管理、文件管理、进程管理、进程通信、内存管理。
进程
进程和线程
- 对操作系统来说线程是最小的执行单元,进程是最小的资源管理单元。
- 无论进程还是线程都是操作系统管理的。
- 线程是进程中的一条执行流程,一个进程可以拥有多个线程。
- 同一进程内多个线程之间可以共享地址空间、代码段、数据段、打开的文件等资源;但每个线程都有独立一套寄存器和栈,这样可以确保线程的控制流是相对独立的。
- 线程同样拥有就绪、阻塞、执行三种基本状态。
- 线程能够减少并发执行的时间和空间开销。线程创建比进程快、终止也快,切换也快、线程之间传递数据不需要经过内核,交互效率更高。
- Java中线程状态转换是需要操作系统内核中的TCB(Tread Control Block)模块来改变线程的状态,这一过程需要耗费一定的CPU资源。
线程有自己的 stack,但是没有单独的 heap,也没有单独的 address space。只有进程有自己的 address space,而这个 space 中经过合法申请的部分叫做 process space。Process space 之外的地址都是非法地址。当一个线程向非法地址读取或者写入,无法确认这个操作是否会影响同一进程中的其它线程,所以只能是整个进程一起崩溃。
当一个线程崩溃时,很可能会导致其所属进程的所有线程崩溃。
协程
英文Coroutines是一种比线程更加轻量级的存在。
正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。
这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。Java的原生语法中并没有实现协程,Python、Go等语言有。
但是有开源框架模拟了协程的功能。
进程的状态和转化
进程的状态
- 创建状态
- 就绪状态:进程已经分配到处理器外的所有必要资源,放入就绪队列
- 运行状态:处于就绪状态的进程已经获得处理器,程序正在执行
- 阻塞状态:正在执行的进行因等待某事件的发生而暂停执行的状态,等待状态,睡眠状态;放入阻塞队列
状态之间的切换
- 就绪to运行:处于就绪的进程,进程调度程序分配处理器给它
- 运行to就绪:分时系统时间片用完;抢占调度方式,更高优先级到达。迫使当前进程让出CPU,进入就绪队列排队,等待下一次CPU调度
- 运行to阻塞:进程需要等待某事件的发生而无法继续运行,如等待IO操作;未能申请到所需的系统资源;等待其他进程发来信号。让出CPU,转变为阻塞状态。
- 阻塞to就绪:所等待的事件已经发生
进程的上下文切换
什么是上下文?
上下文就是运行任务时所依赖的环境,比如寄存器和程序计数器。
CPU把当前任务的状态保存到PCB,然后服务下一个任务。
任务的状态保存及再加载就叫作线程的上下文切换。
上下文切换指的是内核在CPU上对进程或线程进行切换。上下文切换过程中的信息被保存在进程控制块PCB中。
上下文切换的流程如下:
- 挂起一个进程,将这个进程在CPU中的状态(上下文信息)存储于内存的PCB中。
- 在PCB中检索下一个进程的上下文并将其在CPU的寄存器中恢复。
- 跳转到程序计数器所指向的位置(即跳转到进程中被中断时的代码行)并恢复该进程。
引起线程上下文切换的原因
- 当前正在执行的任务完成,系统的CPU正常调度下一个任务
- 当前正在执行的任务遇到I/O等阻塞操作,调度器挂起此任务,继续调度下一个任务
- 多个任务并发抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续调度下一个任务。
- 用户的代码挂起当前任务,比如线程执行sleep方法,让出CPU
- 硬件中断
进程调度算法
- 先来先服务 FCFS
- 短作业优先 SJF
-
高响应比优先
HRRF 响应比 =(等待时间+要求服务时间)/ 要求服务时间 - 优先级调度算法
- 时间片轮转调度
-
多级队列调度
不同的队列采用不同的调度算法 -
多级反馈队列调度
在系统中设置多个就绪队列,并为每个队列赋予不同的优先。第一个队列的优先级最高,第二个次之,其余队列的优先级逐个降低。该算法为不同列中的进程所赋予的执行时间片的大小也各不相同,在优先级愈高的队列中,其时间片愈小。
线程进程通信的方式
进程通信
-
管道/无名管道(Pipes)
:用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。(其实是内存里的文件)
管道传输数据是单向的,想互相通信得建两个管道才行。
-
有名管道
:有名管道以文件(磁盘文件\文件系统)的方式存在,可以实现本机任意两个进程通信。(严格遵循先进先出)
管道这种通信方式效率低,不适合进程间频繁地交换数据。但是简单,很容易知道管道里的数据被另一个进程读取了。
-
信号
:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
用于异常情况下的工作模式,唯一的异步通信机制。
-
消息队列
:(先进先出)消息队列是消息的链表,存放在内核中并由消息队列标识符标识。
消息这种模型让两个进程之间的通信就像发邮件一样,来一封,回一封。
但是存在通信不及时、附件大小有限制的问题。
在内核中每个消息体都有一个最大长度的限制。
消息队列通信过程中,存在用户态和内核态之间的数据拷贝开销。
-
信号量
:Semaphores 信号量是一个计数器,用于多线程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
信号量表示资源的数量,控制信号量的方式有两种原子操作。
P(-1) V(+1)
-
共享内存
:使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享数据的更新。
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另一个进程马上就能看到了,不需要拷贝来拷贝去。
-
套接字
:用于客户端和服务器之间通过网络进行通信。用套接字中的相关函数来完成通信过程。
线程通信
volatile,等待、通知机制(Wait/Notify),join方式,threadLocal
进程如何内配内存
- 程序文本段 包含了进程执行的程序机器语言指令
- 初始化数据段 包含了显式初始化的全局变量和静态变量
- 未初始化数据段 未显式初始化的全局变量和静态变量,内存初始化为0
- 堆段 可在运行时动态进程内存分配的一块区域
- 文件映射段 包括动态库、共享内存等
- 栈段 动态增长和收缩的段,由栈帧组成
堆和文件映射段的内存是动态分配的,比如使用C标准库的malloc()或者mmap(),就可以分别在堆和文件映射段动态分配内存。
Linux最先启动的三个进程
0 1 2号线程(PID)
idle init kthread
内存管理
操作系统的内存管理主要是负责分配与回收,地址转换将逻辑地址转换成相应的物理地址也是操作系统内存管理做的事。
内存管理机制
连续分配内存方式
- 固定分区方式:将物理内存空间划分为大小和数量都固定的若干分区,按照进程的请求分配分区,每个分区只能装入一个程序。固定分区还可以分为分区大小相同和大小不同两种方式。
- 可变分区方式:分区大小和数量都是可以变化的,分配的时候根据进程需求分配大小合适的空间;回收的时候相邻空闲分区可以合并成为较大的分区。
离散内存分配方式
- 分页存储管理:将内存物理空间和程序逻辑空间分成大小相等的块,逻辑空间中的块称为页面(page),物理空间的块称为物理块,或者页框和帧。在装载的时候,进程的各个页面装入物理块中,不要求物理块构成连续空间。
- 分段存储管理:将进程的虚拟地址空间按逻辑信息划分为若干段,存储有逻辑关系和意义的部分,相对完整的一组逻辑信息组织成一个段,能够更容易地实现程序的更新和升级、数据的共享和保护等。
- 段页式存储管理:将进程按照逻辑信息分为若干段,每个段再按系统规定的页面大小划分为若干页面。
分段和分页的共同点和区别
- 共同点:都是为了提高内存利用率,减少内存碎片;离散分配。
- 区别:页的大小是固定的,段的大小不固定;分页仅仅是满足操作系统内存管理的需求,段是逻辑信息的单位,在程序中可以体系为代码段,数据段,能够更好满足用户的需要。
虚拟存储系统
虚拟存储器
指具有请求调入功能和置换功能,能够利用外存储器的空余空间从逻辑上对内存容量进行扩充的一种存储器系统。从用户角度看,该系统所具有的内存容量可能比实际内存容量大的多,但这并非真实地增加了物理内存。
请求分页存储管理方式
请求分页存储管理方式是实现虚拟存储器的方式之一,在基本分页存储管理系统基础之上,增加了请求调页功能和页面置换功能而实现的。进程在内外存之间进行换入换出的基本单位都是页面。
页面置换算法
- 最佳置换算法,选择将来永远不再访问的页面或最长时间内不会访问的页面淘汰
- 先进先出置换算法,选择淘汰最先调入内存的页面
- 最近最久未使用置换算法
- 最近最少使用置换算法
- 时钟置换算法
虚拟地址到物理地址的转换
-
分页
虚拟地址:页号+页内偏移
页号是页表的索引,根据页号就能找到对应的物理基地址
物理基地址+页内偏移–>物理内存地址
PS: 页表存储在内存里 -
多级页表
假设虚拟地址:一级页号+二级页号+页内偏移
一级页号根据一级页表找到二级页表
二级页表+二级页号找到物理页号
物理页号+页内偏移–>物理地址 -
TLB
解决多级页表时间上的开销,将最常用的几个页表存储到访问速度更快的硬件中 Cache。所以会先去查TLB,没查到再查页表。 -
段页式
虚拟地址:段号+段内页号+页内偏移
根据段号从段表中找到页表起始地址
访问页表,得到物理页号
物理页号和页内偏移得到物理地址