程序员不得不学的操作系统知识(二)

进程

进程的组成

进程是操作系统中分配资源的最小单位。进程由 3 个部分组成,分别是程序代码数据集、栈进程控制块(PCB)

各自的作用如下:

  1. 程序代码:描述了进程需要完成的功能。
  2. 数据集、栈:程序在执行时所需要的数据和工作区。
  3. 进程控制块:包含进程的描述信息和控制信息,它是进程存在的唯一标识。
  4. PCB:用来描述和控制进程运行的通用数据结构,是进程能够独立运行的基本单位。(常驻内存,存在系统专门开放的PCB块)

    程序员不得不学的操作系统知识(二)_第1张图片

进程的状态

程序员不得不学的操作系统知识(二)_第2张图片

  1. 创建态,分配PCB块,插入就绪队列,还未申请其他资源
  2. 就绪态,拥有除了CPU以外的资源
  3. 运行态,进程获得CPU执行权,正在执行
  4. 阻塞态,由于某种原因,位于阻塞状态,放弃CPU
  5. 终止态,进程结束后由系统清理并归还PCB块

进程通信

程序员不得不学的操作系统知识(二)_第3张图片

信号signal:通过向一个或多个进程发送异步事件信号来实现,如:SIGSTOP、SIGKILL等信号。

管道pipe:在两个进程之间,可以建立一个通道,一个进程向通道写入字节流,另一个进程从管道读取字节流。管道是同步的,当进程尝试从空管道读取数据时,该进程会被阻塞,直到有可用数据为止。如linux的 | 管线

共享内存:通过共享内存进行进程间通信,一个进程所作的修改对另一个进程可见。

先入先出队列FIFO:也称命名管道,具有支持文件和独特 API ,命名管道在文件系统中作为设备的专用文件存在。而非命名管道在结束后缓冲区会被系统回收。

消息队列:描述内核寻址空间内的内部链接列表。可以按几种不同的方式将消息按顺序发送到队列并从队列中检索消息。每个消息队列由 IPC 标识符唯一标识。

套接字Socket:提供端到端的双向通信,可有TCP、UDP的支持。

进程同步

临界资源:指的是一些虽作为共享资源却又无法同时被多个进程或线程共同访问的共享资源。为了对临界资源进行有效的约束,就提出了进程间同步的四个原则

  • 空闲让进:资源无占用,允许使用
  • 忙则等待:资源被占用,请求进程等待
  • 有限等待:保证有限等待时间能够使用资源,避免其它等待的进程僵死
  • 让权等待:等待时,进程需让出CPU,也就是进程由执行状态变为阻塞状态,这也是保证CPU可以高效使用的前提

死锁

死锁定义:如果一组进程中的每个进程都在等待一个事件,而这个事件只能由该组中的另一个进程触发,这种情况会导致死锁

死锁的条件

  1. 互斥条件:资源是排他性使用的
  2. 保持和等待条件:已拥有资源的进程不释放自己的资源,去申请新的资源
  3. 不可抢占条件:进程未使用完的资源不能被其他进程剥夺
  4. 循环等待:死锁发生时,必存在资源环形链路

处理死锁策略

  1. 鸵鸟算法(忽略死锁带来的影响)
  2. 检测死锁并恢复死锁,死锁发生时对其进行检测,一旦发生死锁后,采取行动解决问题(分配时监测是否会发生死锁,可以通过回滚、抢占、杀死进程恢复死锁)
  3. 通过合理分配资源来避免死锁(银行家算法,根据空闲资源表和资源需求表合理分配资源)
  4. 通过破坏死锁产生的四个条件之一来避免死锁

    • 破坏互斥条件
    • 破坏保持等待条件
    • 破坏不可抢占条件
    • 破坏循环等待条件

两阶段加锁

一种解决方式是使用 两阶段提交(two-phase locking)。顾名思义分为两个阶段,一阶段是进程尝试一次锁定它需要的所有记录。如果成功后,才会开始第二阶段,第二阶段是执行更新并释放锁。第一阶段并不做真正有意义的工作。

如果在第一阶段某个进程所需要的记录已经被加锁,那么该进程会释放所有锁定的记录并重新开始第一阶段。从某种意义上来说,这种方法类似于预先请求所有必需的资源或者是在进行一些不可逆的操作之前请求所有的资源。

通信死锁

进程 A 给进程 B 发了一条消息,然后进程 A 阻塞直到进程 B 返回响应。假设请求消息丢失了,那么进程 A 在一直等着回复,进程 B 也会阻塞等待请求消息到来,这时候就产生死锁

解决方法:超时重传

**进程间同步的方法:**消息队列、共享存储、信号量。会在后边的文章中详细介绍这些进程间同步的方法

fork进程

  • fork系统调用是用于创建进程
  • 对于虚拟空间地址来说,子进程会拷贝父进程的虚拟地址空间。所以,fork后子进程的用户区与父进程的用户区相同,也会拷贝内核区内容,仅仅是进程的 pid不同。
  • 在父进程中返回子进程的ID,在子进程中返回0。所以可以通过fork 的返回值来区分父进程与子进程
  • fork系统调用无参数

运用了读时共享、写时拷贝的原则,fork后,父子进程共享父进程的地址空间(只读),在父进程或者子进程进行写指令时,子进程才会复制一份地址空间,从而使得虚拟地址空间独立,在自己的地址空间进行写操作。也就是说,资源的复制是在需要写入时才会进行,在此之前,只会以只读方式进行共享

进程类型

前台进程:具有终端,可以和用户进行交互的进程

后台进程:不与用户进行交互,优先级比前台进程低

守护进程:特殊的后台进程

孤儿进程:父进程退出后,子进程即成为孤儿进程,将由**init进程(pid为1)**收养

僵尸进程:子进程的进程描述符在子进程退出后不会释放,只有当父进程调用**wait()、waitpid()**获取子进程信息才释放

线程

线程是操作系统进行运行调度的最小单位,线程除了拥有自己的栈、程序计数器等资源外,共享进程的资源。

通信,对于进程来说是进程间的通信(IPC),而对于线程,它是通过读写同一个进程的数据进行通信。

线程同步

互斥量

  • 本质上是资源排他性使用,效果相当于原子性。拥有两种状态:加锁和解锁。(会带来相关损耗,阻塞锁

    • 自旋锁:等待获取资源的时候CPU不会释放,优点:如果线程占用锁时间不长,就能避免上下文切换代价;缺点:耗费CPU时间
    • 读写锁:读不进行加锁,写的时候进行加锁,对于多读少写的情况,性能能有很好的提升

信号量:表示同时允许访问资源的最大线程数量,它是一个全局变量。(Java的semaphore)

条件变量:利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程设置条件为真,并通知等待的线程继续。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

  • 基本动作:wait、signal、notify

调度算法

  • 调度算法的目标:
  • 程序员不得不学的操作系统知识(二)_第4张图片
  • 先来先服务:按照FIFO的原则,将作业加入到就绪队列中,按照顺序调度作业。
  • 最短作业优先:按照线程作业的CPU时间片,优先调度所需最短CPU时间片的作业。
  • 最短剩余时间:最短作业优先的抢占式版本,总是调度剩余运行时间最短的作业。
  • 轮询调度:每个作业都会被分配一个CPU时间片,在这个时间片内允许进程运行。如果时间片结束时进程还在运行的话,则抢占一个 CPU 并将其分配给另一个进程。
  • 优先级调度:按照作业优先级进行调度。
  • 多级反馈队列:设置多级队列,设置不同优先级,并且分配不同的时间片。

POSIX线程

即线程标准。

线程调用 描述
pthread\_create 创建一个新线程
pthread\_exit 结束调用的线程
pthread\_join 等待一个特定的线程退出
pthread\_yield 释放CPU来运行另外一个线程
pthread\_attr\_init 创建并初始化一个线程的属性结构
pthread\_attr\_destory 删除一个线程的属性结构

线程实现

在用户空间实现线程

内核并不知道线程的存在,以进程为单位分配CPU时间片,每个进程要有专用的线程表。

优点:允许每个进程有自己定制的调度算法、调度效率比内核调用高(无需陷入内核,即上下文切换,无需刷新内存高速缓存)

缺点:会因为阻塞调用/缺页中断阻塞整个进程直到完成

程序员不得不学的操作系统知识(二)_第5张图片

在内核空间实现线程

内核中会有用来记录系统中所有线程的线程表,当进行系统调度的时候,会通过对线程表的更新进行调度。线程表拥有每个线程的寄存器、状态和其他信息。

优点:不会因某个线程阻塞而导致进程阻塞

缺点:系统调用代价大,上下文切换开销大,以及需要切换系统状态

程序员不得不学的操作系统知识(二)_第6张图片

混合实现:将用户级线程与某些或者全部内核线程多路复用起来。编程人员可以自由控制用户线程和内核线程的数量,具有很大的灵活度。内核只识别内核级线程,并对其进行调度。其中一些内核级线程会被多个用户级线程多路复用。

程序员不得不学的操作系统知识(二)_第7张图片

了解更多文章,‍♂️关注公众号:学编程的文若

你可能感兴趣的:(后端计算机基础操作系统前端)