侧边栏壁纸
博主头像
一定会去到彩虹海的麦当

说什么呢?约定好的事就一定要做到啊!

  • 累计撰写 63 篇文章
  • 累计创建 16 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

[操作系统]——进程与线程

一定会去到彩虹海的麦当
2022-02-15 / 0 评论 / 0 点赞 / 199 阅读 / 4,697 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

📚笔记整理自小林coding的《图解系统》,作者写的很不错,我自己整理一下方便后期复习

@

进程、线程基础知识

进程和线程的基本概念

进程:就是正在执行程序的实例,比如说 Web 程序就是一个进程,shell 也是一个进程,文章编辑器 typora 也是一个进程。

线程:进程当中的⼀条执⾏流程。

同⼀个进程内多个线程之间可以共享代码段、数据段、打开的⽂件等资源,但每个线程各⾃都有⼀套独⽴ 的寄存器和栈,这样可以确保线程的控制流是相对独⽴的

进程与线程的区别

  • 进程是资源(包括内存、打开的⽂件等)分配的单位,线程是 CPU 调度的单位;

  • 进程拥有⼀个完整的资源平台,⽽线程只独享必不可少的资源,如寄存器和栈;

  • 线程同样具有就绪、阻塞、执⾏三种基本状态,同样具有状态之间的转换关系;

  • 线程能减少并发执⾏的时间和空间开销

    • 线程的创建时间⽐进程快,因为进程在创建的过程中,还需要资源管理信息
    • 线程的终⽌时间⽐进程快,因为线程释放的资源相⽐进程少很多;
    • 同⼀个进程内的线程切换⽐进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着 同⼀个进程的线程都具有同⼀个⻚表,那么在切换的时候不需要切换⻚表。
    • 由于同⼀进程的各线程间共享内存和⽂件资源,那么在线程之间数据传递的时候,就不需要经过内核 了

为什么需要线程

  • 进程在同一时间只能干一件事情

  • 进程在执行的过程中如果阻塞,整个进程就会被挂起,即使进程中有些工作不依赖与等待的资源,仍然不会执行。

进程的上下⽂切换

各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执⾏,那 么这个⼀个进程切换到另⼀个进程运⾏,称为进程的上下⽂切换

进程的上下⽂切换不仅包含了虚拟内存、栈、全局变量等⽤户空间的资源,还包括了内核堆栈、寄 存器等内核空间的资源

通常,会把交换的信息保存在进程的 PCB,当要运⾏另外⼀个进程的时候,我们需要从这个进程的 PCB 取出上下⽂,然后恢复到 CPU 中,这使得这个进程可以继续执⾏

进程的状态

1、进程的三态模型

image-20220215183344541

  • 运⾏状态(Runing):该时刻进程占⽤ CPU;

  • 就绪状态(Ready):可运⾏,由于其他进程处于运⾏状态⽽暂时停⽌运⾏;

  • 阻塞状态(Blocked):该进程正在等待某⼀事件发⽣(如等待输⼊/输出操作的完成)⽽暂时停⽌运 ⾏,这时,即使给它CPU控制权,它也⽆法运⾏

2、进程的五态模型

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

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

image-20220215183502142

  • NULL -> 创建状态:⼀个新进程被创建时的第⼀个状态;
  • 创建状态 -> 就绪状态:当进程被创建完成并初始化后,⼀切就绪准备运⾏时,变为就绪状态,这个 过程是很快的;
  • 就绪态 -> 运⾏状态:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运⾏ 该进程;
  • 运⾏状态 -> 结束状态:当进程已经运⾏完成或出错时,会被操作系统作结束状态处理;
  • 运⾏状态 -> 就绪状态:处于运⾏状态的进程在运⾏过程中,由于分配给它的运⾏时间⽚⽤完,操作 系统会把该进程变为就绪态,接着从就绪态选中另外⼀个进程运⾏;
  • 运⾏状态 -> 阻塞状态:当进程请求某个事件且必须等待时,例如请求 I/O 事件;
  • 阻塞状态 -> 就绪状态:当进程要等待的事件完成时,它从阻塞状态变到就绪状态;

3、进程的七态模型

在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次 运⾏的时候,再从硬盘换⼊到物理内存。

image-20220215183631856

挂起状态:描述进程没有占⽤实际的物理内存空间的情况

  • 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
  • 就绪挂起状态:进程在外存(硬盘),但只要进⼊内存,即刻⽴刻运⾏

进程的控制结构

PCB 是进程存在的唯⼀标识,包含有进程描述信息、进程控制和管理信息、资源分配清单、CPU相关信息(例如寄存器的值)等

PCB通常是通过链表的⽅式进⾏组织,把具有相同状态的进程链在⼀起,组成各种队列。

  • 将所有处于就绪状态的进程链在⼀起,称为就绪队列;
  • 把所有因等待某事件⽽处于等待状态的进程链在⼀起就组成各种阻塞队列

image-20220215184536966

进程间的通信方式

管道

1、匿名管道

  • 匿名管道顾名思义,它没有名字标识,匿名管道是特殊⽂件只存在于内存,没有存在于⽂件系统中,shell命令中的「 | 」竖线就是匿名管道

  • 通信的数据是⽆格式的流并且⼤⼩受限,通信的⽅式是单向的,数据 只能在⼀个⽅向上流动,如果要双向通信,需要创建两个管道

  • 匿名管道是只能⽤于存在⽗⼦关系的 进程间通信,匿名管道的⽣命周期随着进程创建⽽建⽴,随着进程终⽌⽽消失

    以使⽤ fork 创建⼦进程,创建的⼦进程会复制⽗进程的⽂件描述符,这样就做到了两个进程各有相同写入端描述符、读取端描述符,两个进程就可以通过各自的文件描述符写入和读取同一个管道文件实现跨进程通信。

2、命名管道

  • 命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使⽤命名管道的前提,需要在⽂件系统 创建⼀个类型为 p 的设备⽂件,那么毫⽆关系的进程就可以通过这个设备⽂件进⾏通信

不管是匿 名管道还是命名管道,进程写⼊的数据都是缓存在内核中,另⼀个进程读取数据时候⾃然也是从内核中获 取,同时通信数据都遵循先进先出原则,不⽀持 lseek 之类的⽂件定位操作

消息队列

  • 消息队列克服了管道通信的数据是⽆格式的字节流的问题,消息队列的消息体是可以⽤户⾃定义的数据类型
  • 消息队列通信的速度不是最及时的,毕竟每次数据的写⼊和读取都需要经过⽤户态与内核态之间的拷⻉过程。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除;
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

共享内存

  • 共享内存可以解决消息队列通信中⽤户态与内核态之间数据拷⻉过程带来的开销,它直接分配⼀个共享空 间,每个进程都可以直接访问,就像访问进程⾃⼰的空间⼀样快捷⽅便,不需要陷⼊内核态或者系统调⽤
  • 但是便捷⾼效的共享内存通信,带来新的 问题,多进程竞争同个共享资源会造成数据的错乱

信号量

  • 信号量用来保护共享资源,以确保任何时刻只能有⼀个进程访问共享资源,这种⽅式就是互斥 访问
  • 信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是⼀个计数器,表示的 是资源个数,其值可以通过两个原⼦操作来控制,分别是 P 操作和 V 操作。

信号

  • 信号是进程间通信机制中唯 ⼀的异步通信机制,信号可以在应⽤进程和内核之间直接交互,内核也可以利⽤信号来通知⽤户空间的进 程发⽣了哪些系统事件
  • 信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令)
  • ⼀旦有信号发⽣,进程有三种⽅式响应信号 1. 执⾏默认操作、2. 捕捉信号、3. 忽略信号

Socket通信

  • 为了解决与不同主机的进程间通信问题

  • 也可以⽤于本地主机进程间通信

  • 可根据创建 Socket 的类型不同,分为三种常⻅的通信⽅式,⼀个是基于 TCP 协议的通信⽅式,⼀个是基于 UDP 协议 的通信⽅式,⼀个是本地进程间通信⽅式

多线程同步

1、锁

使⽤加锁操作和解锁操作可以解决并发线程/进程的互斥问题。

任何想进⼊临界区的线程,必须先执⾏加锁操作。若加锁操作顺利通过,则线程可进⼊临界区;在完成对 临界资源的访问后再执⾏解锁操作,以释放该临界资源

2、信号量

通常信号量表示资源的数量,对应的变量是⼀个整型( sem )变量。

通过两个原⼦操作的系统调⽤函数来控制信号量:

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

信号量初始化为1:使得两个进程互斥访问共享内存

信号量初始化为0:实现多进程同步的方式

守护进程、僵尸进程和孤儿进程

守护进程

指在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,周期性地执行某种任务。
Linux的大多数服务器就是用守护进程的方式实现的,如web服务器进程http等

孤儿进程

如果父进程先退出,子进程还没退出,那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)
一个父进程退出,而它的一个或多个了进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程

如果了进程先退出,父进程还没退出,那么了进程必须等到父进程捕获到了了进程的退出状态才真正结束,否则这个时候了进程就成为僵尸进程。
设置僵尸进程的目的是维护了进程的信息,以便父进程在以后某个时候获取。

死锁

定义

当两个线程为了保护两个不同的共享资源⽽使⽤了两个互斥锁,那么这两个互斥锁应⽤不当的时 候,可能会造成两个线程都在等待对⽅释放锁,在没有外⼒的作⽤下,这些线程会⼀直相互等待,就没办 法继续运⾏,这种情况就是发⽣了死锁

死锁产生的必要条件

  • 互斥条件:每个资源都被分配给了一个进程或者资源是可用的

  • 保持和等待条件:已经获取资源的进程被认为能够获取新的资源

  • 不可抢占条件:分配给一个进程的资源不能强制的从其他进程抢占资源,它只能由占有它的进程显示释放

  • 循环等待:死锁发生时,系统中一定有两个或者两个以上的进程组成一个循环,循环中的每个进程都在等待下一个进程释放的资源。

避免死锁问题的发⽣

避免死锁问题就只需要破环必要条件的一个就可以,最常⻅的并且可⾏的就是使⽤资源有序分配法,来 破环循环等待条件

将所有的资源统一编号,如下图所示

img

进程可以在任何时间提出请求,但是所有的请求都必须按照资源的顺序提出。如果按照此分配规则的话,那么资源分配之间不会出现环。

几种典型锁

悲观锁

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

悲观锁有:

1、读写锁

  • 多个读者可以同时进行读
  • 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
  • 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

2、互斥锁

  • 一次只能一个线程拥有互斥锁,其他线程只有等待

  • 互斥锁加锁失败后,线程会释放 CPU ,给其他线程

  • 互斥锁加锁失败时,会从⽤户态陷⼊到内核态,让内核帮我们切换线程,虽然简化了使⽤锁的难 度,但是存在⼀定的性能开销成本。

    如果你能确定被锁住的代码执⾏时间很短,就不应该⽤互斥锁,⽽应该选⽤⾃旋锁,否则使⽤互斥 锁

3、自旋锁

  • 如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。

  • ⾃旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「⽤户态」完成加锁和解锁操作,不 会主动产⽣线程上下⽂切换,所以相⽐互斥锁来说,会快⼀些,开销也⼩⼀些。

乐观锁

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

0

评论区