操作系统

操作系统

资源来自:图解网络介绍 | 小林coding (xiaolincoding.com)

磁盘比内存慢几万倍?

CPU 可以比喻成我们的大脑,我们当前正在思考和处理的知识的过程,就好比 CPU 中的寄存器处理数据的过程,速度极快,但是容量很小。而 CPU 中的 L1-L3 Cache 好比我们大脑中的短期记忆和长期记忆,需要小小花费点时间来调取数据并处理。

从 寄存器、CPU Cache,到内存、硬盘,这样一层层下来的存储器,访问速度越来越慢,存储容量越来越大,价格也越来越便宜,而且每个存储器只和相邻的一层存储器设备打交道,于是这样就形成了存储器的层次结构。

内存分段

程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(*Segmentation*)的形式把这些段分离出来。

  • 段选择子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。
  • 虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。

内存分页

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫Page)。在 Linux 下,每一页的大小为 4KB

采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。

多级页表

TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。

段页式内存管理

段页式内存管理实现的方式:

  • 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制;
  • 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页

段页式地址变换中要得到物理地址须经过三次内存访问:

  • 第一次访问段表,得到页表起始地址;
  • 第二次访问页表,得到物理页号;
  • 第三次将物理页号与页内位移组合,得到物理地址。

可用软、硬件相结合的方法实现段页式地址变换,这样虽然增加了硬件成本和系统开销,但提高了内存的利用率。

回收内存

在前面我们知道了回收内存有两种方式。

  • 一种是后台内存回收,也就是唤醒 kswapd 内核线程,这种方式是异步回收的,不会阻塞进程。
  • 一种是直接内存回收,这种方式是同步回收的,会阻塞进程,这样就会造成很长时间的延迟,以及系统的 CPU 利用率会升高,最终引起系统负荷飙高。

可被回收的内存类型有文件页和匿名页:

  • 文件页的回收:对于干净页是直接释放内存,这个操作不会影响性能,而对于脏页会先写回到磁盘再释放内存,这个操作会发生磁盘 I/O 的,这个操作是会影响系统性能的。
  • 匿名页的回收:如果开启了 Swap 机制,那么 Swap 机制会将不常访问的匿名页换出到磁盘中,下次访问时,再从磁盘换入到内存中,这个操作是会影响系统性能的。

进程、线程基础知识

进程的状态

进程三个基本状态.jpg

  • 运行状态(Running):该时刻进程占用 CPU;
  • 就绪状态(Ready):可运行,由于其他进程处于运行状态而暂时停止运行;
  • 阻塞状态(Blocked):该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行;

当然,进程还有另外两个基本状态:

  • 创建状态(new):进程正在被创建时的状态;
  • 结束状态(Exit):进程正在从系统中消失时的状态;

PCB

进程描述信息:

  • 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符;
  • 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务

进程控制和管理信息:

  • 进程当前状态,如 new、ready、running、waiting 或 blocked 等;
  • 进程优先级:进程抢占 CPU 时的优先级;

资源分配清单:

  • 有关内存地址空间或虚拟地址空间的信息,所打开文件的列表和所使用的 I/O 设备信息。

CPU 相关信息:

  • CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程重新执行时,能从断点处继续执行。

相同状态的进程链在一起,组成各种队列

线程

线程是进程当中的一条执行流程。

线程的优点:

  • 一个进程中可以同时存在多个线程;
  • 各个线程之间可以并发执行;
  • 各个线程之间可以共享地址空间和文件等资源;

线程的缺点:

  • 当进程中的一个线程崩溃时,会导致其所属进程的所有线程崩溃(这里是针对 C/C++ 语言,Java语言中的线程奔溃不会造成进程崩溃)

线程与进程的比较

  • 进程是资源(包括内存、打开的文件等)分配的单位,线程是 CPU 调度的单位;
  • 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈;
  • 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;
  • 线程能减少并发执行的时间和空间开销;

线程的实现

线程控制块(Thread Control Block, TCB

  • 用户线程(*User Thread*):在用户空间实现的线程,不是由内核管理的线程,是由用户态的线程库来完成线程的管理;
  • 内核线程(*Kernel Thread*):在内核中实现的线程,是由内核管理的线程;
  • 轻量级进程(*LightWeight Process*):在内核中来支持用户线程;

调度原则

  • CPU 利用率:调度程序应确保 CPU 是始终匆忙的状态,这可提高 CPU 的利用率;
  • 系统吞吐量:吞吐量表示的是单位时间内 CPU 完成进程的数量,长作业的进程会占用较长的 CPU 资源,因此会降低吞吐量,相反,短作业的进程会提升系统吞吐量;
  • 周转时间:周转时间是进程运行+阻塞时间+等待时间的总和,一个进程的周转时间越小越好;
  • 等待时间:这个等待时间不是阻塞状态的时间,而是进程处于就绪队列的时间,等待的时间越长,用户越不满意;
  • 响应时间:用户提交请求到系统第一次产生响应所花费的时间,在交互式系统中,响应时间是衡量调度算法好坏的主要标准。

调度算法

  • 先来先服务(First Come First Serve, FCFS)算法
  • 最短作业优先(Shortest Job First, SJF
  • 高响应比优先 (Highest Response Ratio Next, HRRN
    响应比公式.jpg
  • 时间片轮转(Round Robin, RR

    最古老、最简单、最公平且使用最广的算法

    每个进程被分配一个时间段,称为时间片(Quantum),即允许该进程在该时间段中运行。

    • 如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配给另外一个进程;
    • 如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;

    另外,时间片的长度就是一个很关键的点:

    一般来说,时间片设为 20ms~50ms 通常是一个比较合理的折中值。

  • 最高优先级(Highest Priority First,HPF

    • 静态优先级:创建进程时候,就已经确定了优先级了,然后整个运行时间优先级都不会变化;
    • 动态优先级:根据进程的动态变化调整优先级,比如如果进程运行时间增加,则降低其优先级,如果进程等待时间(就绪队列的等待时间)增加,则升高其优先级,也就是随着时间的推移增加等待进程的优先级
  • 多级反馈队列(Multilevel Feedback Queue

    「时间片轮转算法」和「最高优先级算法」的综合和发展。

多级队列.jpg

进程间有哪些通信方式?

  • 管道:管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。
  • 消息队列:消息这种模型,两个进程之间的通信就像平时发邮件一样,你来一封,我回一封,可以频繁沟通了。
  • 共享内存:共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中
  • 信号量:信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据

    信号量表示资源的数量,控制信号量的方式有两种原子操作:

    • 一个是 P 操作,这个操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
    • 另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程;
  • 信号:

    • Ctrl+C 产生 SIGINT 信号,表示终止该进程;
    • Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束。
  • Socket:跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。

死锁

产生条件:互斥条件、持有并等待条件、不可剥夺条件、环路等待条件

怎么避免:

Java 程序是否死锁,则可以使用 jstack

避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。

  1. 互斥锁与自旋锁

    • 互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
    • 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;
  2. 读写锁:读写锁适用于能明确区分读操作和写操作的场景

    • 当「写锁」没有被线程持有时,多个线程能够并发地持有读锁,这大大提高了共享资源的访问效率,因为「读锁」是用于读取共享资源的场景,所以多个线程同时持有读锁也不会破坏共享资源的数据。
    • 但是,一旦「写锁」被线程持有后,读线程的获取读锁的操作会被阻塞,而且其他写线程的获取写锁的操作也会被阻塞。
  3. 乐观锁与悲观锁

    悲观锁做事比较悲观,它认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁

乐观锁做事比较乐观,它假定冲突的概率很低,它的工作方式是:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作

一个进程最多可以创建多少个线程

  • 32 位系统,用户态的虚拟空间只有 3G,如果创建线程时分配的栈空间是 10M,那么一个进程最多只能创建 300 个左右的线程。
  • 64 位系统,用户态的虚拟空间大到有 128T,理论上不会受虚拟内存大小的限制,而会受系统的参数或性能限制。

内存页面置换算法

  1. 最佳页面置换算法基本思路是,置换在「未来」最长时间不访问的页面
  2. 先进先出置换算法

    既然我们无法预知页面在下一次访问前所需的等待时间,那我们可以选择在内存驻留时间很长的页面进行中置换

  3. 最近最久未使用的置换算法,选择最长时间没有被访问的页面进行置换
  4. 时钟页面置换算法

    • 如果它的访问位位是 0 就淘汰该页面,并把新的页面插入这个位置,然后把表针前移一个位置;
    • 如果访问位是 1 就清除访问位,并把表针前移一个位置,重复这个过程直到找到了一个访问位为 0 的页面为止;
  5. 最不常用算法:当发生缺页中断时,选择「访问次数」最少的那个页面,并将其淘汰

磁盘调度算法

  1. 先来先服务
  2. 最短寻道时间优先
  3. 扫描算法:磁头在一个方向上移动,访问所有未完成的请求,直到磁头到达该方向上的最后的磁道,才调换方向,这就是扫描(Scan)算法

    还是以这个序列为例子,磁头的初始位置是 53:

    98,183,37,122,14,124,65,67

    那么,假设扫描调度算先朝磁道号减少的方向移动,具体请求则会是下列从左到右的顺序:

    37,14,0,65,67,98,122,124,183

  4. 循环扫描算法

    那么,假设循环扫描调度算先朝磁道增加的方向移动,具体请求会是下列从左到右的顺序:

    65,67,98,122,124,183,1990,14,37

文件系统

空闲空间管理

  • 空闲表法

    空闲表法.png

  • 空闲链表法

    空闲块链表.png

  • 位图法

    位图是利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应

软链接和硬链接

硬链接是多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。

软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。

进程写文件时,进程发生了崩溃,已写入的数据会丢失吗

  • Write 操作:由于其不使用 page cache,所以其进行写文件,如果返回成功,数据就真的落盘了(不考虑磁盘自带的缓存);
  • Read 操作:由于其不使用 page cache,每次读操作是真的从磁盘中读取,不会从文件系统的缓存中读取。

LINUX命令

  1. 网络配置
    ipconfig
  2. socket 信息
    netstat 或者 ss
  3. 网络吞吐率和 PPS

    sar -n DEV,显示网口的统计数据;
    sar -n EDEV,显示关于网络错误的统计数据;
    sar -n TCP,显示 TCP 的统计数据

该文章的评论已关闭