0%

操作系统-学习笔记

基础

数据在线路上的传送方式

  1. 单工定义:单意思就是A只能发信号,B只能接收信号,通信是单向的。类比于灯塔发发出光信号,航船只能接收信号。

  2. 半双工定义:半双工数据传输允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信。举例:指A能发信号给B,B也能发信号给A,但这两个过程不能同时进行。

可以想象一下对讲机,你收到的回复并不是都马上就有的。而且前提是双方不能在同一个状态,如果双方同时处于收状态,或同时处于发状态,便不能正常通信了。

  1. 全双工定义:全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。

在A给B发信号的同时,B也可以给A发信号。典型的例子就是打电话,双方都能说,对方也能听到。网卡的全双工是指网卡在发送数据的同时也能够接收数据,两者同步进行。网卡一般都支持全双工。对于全双工以太。

1.1 什么是操作系统

可以这么说,操作系统是在硬件资源之上,应用程序之下,运行在内核态的软件。
它是应用程序和硬件之间的媒介,向应用程序提供硬件的抽象,以及管理硬件资源。

1.2 操作系统四大功能模块

内存管理、进程管理、⽂件系统管理、输⼊输出设备管理

  1. 进程调度能力:管理进程、线程,决定哪个进程、线程使用CPU。
  2. 内存管理能力:决定内存的分配和回收。
  3. 硬件通信能力:管理硬件,为进程和硬件之间提供通信。
  4. 系统调用能力:应用程序进行更高限权运行的服务,需要系统调用,用户程序和操作系统之间的接口。

1.3 用户态、内核态

  1. 权限:对硬件操作的权限:IO读写、内存分配等。

操作系统把一切对硬件的操作都对用户态的应用程序进行隐藏,应用程序是无法直接执行硬件使用和内存申请等操作。需要通过操作系统提供的接口(函数库)(系统调用)切换成内核态, 来完成上述需要高级权限的操作。

  1. 空间:用户空间、内核空间。

每个进程创建都会分配「虚拟空间地址」,一部分为内核空间,另一部分为用户空间。

用户态进程:只能操作用户空间的低位虚拟空间地址。

内核态进程:用户空间和内核空间的虚拟空间地址都可以操作。

补充:所有进程的内核空间地址是共享的(指所有进程的内核态逻辑地址是共享同一块内存地址),是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据

每个进程的 4G 虚拟空间地址,高位 1G 都是一样的,即内核空间。只有剩余的 3G 才归进程自己使用,换句话说就是, 高位 1G 的内核空间是被所有进程共享的!

最后做个小结,我们通过指令集权限区分用户态和内核态,还限制了内存资源的使用,操作系统为用户态与内核态划分了两块内存空间,给它们对应的指令集使用

从根上理解用户态与内核态

中断:主要是用来打断cpu执行顺序的一种机制,CPU默认是按照代码的逻辑顺序来一条条执行的,运行起来后是属于不可控的(如果没有中断,你不能在执行的时候强行打断它),如果有键盘输入或者啥的紧急信号,输进来,你得去立刻处理吧,处理的话,要cpu吧,那中断就提供了一个乱序执行的机制,允许你暂停当前的执行,然后到中断中处理,处理完了,再接着执行,无缝衔接。中断是一个专门做这种事的系统。假如没这个系统,cpu正在处理某个数据,要10s才能处理完成,完成后还需要执行到键盘这部分的代码,才能处理键盘的数据,是不是觉得会很卡呢?

两者切换

运行的代码权限高低, 运行在内核的代码权限高操作硬件、io、内存, 运行在用户态的权限低通过shell、systemcall系统调用操作io

每个进程都有两个栈,分别是用户栈与内核栈,对应用户态与内核态的使用
https://www.cnblogs.com/gtarcoder/articles/5278074.html
权限、 空间、

内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个
栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内 容是内核栈空间地址,使用内核栈。

本质:

内核在创建进程的时候,为进程创建相应的堆栈作为进程运行的内存空间地址。

寻址空间范围是 4G , 虚拟空间地址划分为两部分 一部分为内核空间,另一部分为用户空间,高位的 1G 由内核使用,而低位的 3G 由各个进程使用。

  • 用户态: 进程在用户空间运行,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;
  • 内核态: 进程在内核空间运行,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。

切换

if pid == fork() // systemcall

用户态切换到内核态

3种方式:

其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

  1. 系统调用:用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作fork()
  2. 异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
  3. 中断: 当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
过程
  1. 保留用户态现场(上下文, cpu堆栈寄存器当前运行的用户态地址)
  2. 执行该进程的cpu,堆栈寄存器地址指向该进程的内核空间,进入内核态
  3. 额外的检查(因为内核代码对用户不信任)
  4. 执行内核态代码
  5. 复制内核态代码执行结果,回到用户态
  6. 恢复用户态现场(上下文、寄存器、用户栈等)

当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。
进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。

具体地址相关:保存了用户态的现场地址,如何知道需要切换到的内核运行的具体地址?直接使用内核栈顶地址

关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信心,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以 在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。

程序、进程、线程

程序、进程、线程的区别与概念

  • 程序:是一个静态的概念,一般对应于操作系统中的可执行的文件。当程序被加载到内存中执行时,这个运行的程序就被称为进程。

  • 进程:是一个动态的概念,系统运行一个程序即是一个进程从创建,运行到消亡的过程,操作系统进行资源调度和分配的基本单位。

  • 线程: 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。

2.1 进程

进程基本概念、 PCB组成、 进程状态、 进程切换、 进程间通信、 父进程;子进程;僵尸进程;孤儿进程

1.2.1 PCB组成

PCB : 进程控制块

  • 操作系统对进程的感知,是通过进程控制块 PCB数据结构 来描述的。它是进程存在的唯一标识

系统利用PCB来控制和管理进程,所以PCB是系统感知进程存在的唯一标志。进程与PCB是一一对应的。

通常PCB应包含如下一些信息:

  1. 进程标识符 name
    每个进程都必须有一个唯一的标识符,可以是字符串,也可以是一个数字。

  2. 进程当前状态 status
    说明进程当前所处的状态。为了管理的方便,系统设计时会将相同的状态的进程组成一个链表(队列),如就绪进程队列,等待进程则要根据等待的事件组成多个等待队列,如等待打印机队列。

  1. 进程资源清单
    列出所拥有的除CPU外的资源记录,如拥有的I/O设备, 打开的文件列表等。

  2. 进程优先级 priority
    进程的优先级反映进程的紧迫程度,通常由用户指定和系统设置。

  3. CPU现场保护区 cpustatus
    当进程因某种原因不能继续占用CPU时(如等待打印机),释放CPU,这时就要将CPU的各种状态信息保护起来,为将来再次得到处理机恢复CPU的各种状态,继续运行。

  4. 进程同步与通信机制
    用于实现进程间互斥、同步和通信所需的信号量等。

  5. 进程所在队列PCB的链接字(当前PCB所在队列的下一个PCB地址)
    根据进程所处的现行状态,进程相应的PCB参加到不同队列中。PCB链接字指出该进程所在队列中下一个进程PCB的首地址。

  6. 与进程有关的其他信息
    如进程记账信息,进程占用CPU的时间等。

1.2.2 进程状态

创建 就绪 运行 阻塞

某个进程在某个时刻所处的状态分为以下几种,运行态running、就绪态ready、阻塞态wait。

对于阻塞状态;比如read系统调用阻塞,进程会占用内存空间,这是一种浪费行为,于是操作系统会有跟内存管理中物理页置换到磁盘一样的行为,把阻塞的进程置换到磁盘中,此时进程未占用物理内存。

我们称之为挂起;挂起不仅仅可能是物理内存不足,比如sleep系统调用过着用户执行Ctrl+Z也可能导致挂起。

除了创建和结束一般有三个状态:

运行态run:该时刻进程占用CPU

就绪态ready:可运行,由于其他进程处于运行状态而暂时停止运行

阻塞态wait:该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行

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

挂起态:新的状态,描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现

就绪挂起状态:进程在外存(硬盘),但只要进入内存,马上运行

特点:

1.就绪态和运行态可以相互转换,其它的都是单向转换。就绪态的进程通过调度算法从而获得CPU时间,转为运行状态;

2.运行态的进程,在分配给它的CPU时间片用完之后就会转为就绪状态,等待下一次调度。

3.阻塞态是缺少需要的资源从而由运行态转换而来,但是该资源不包括CPU时间,缺少CPU时间会从运行态转换为就绪态。

进程(线程)上下文切换

https://blog.csdn.net/five_years_old/article/details/121515401

进程切换:

切换时机:
进程切换指的是操作系统在多任务环境下,将正在执行的进程(也称为当前进程)从 CPU 中暂停,然后选择另一个进程来运行的过程。进程切换的时机通常有以下几种情况:

  1. 当前进程执行完毕:当当前进程完成它的任务时,操作系统就会将 CPU 分配给下一个就绪的进程。

  2. 当前进程请求等待:如果当前进程需要等待某个事件(比如 I/O 操作完成),则操作系统会将当前进程置为阻塞状态,并切换到另一个就绪的进程。

  3. 时间片耗尽:在多道程序设计中,为了公平地分配 CPU 时间片,操作系统会为每个进程分配一定的时间片。当当前进程的时间片用完时,操作系统会将 CPU 分配给下一个就绪的进程。

  4. 优先级调度:操作系统会根据进程的优先级来确定下一个要运行的进程。如果高优先级的进程处于就绪状态,操作系统可能会中断当前进程并切换到高优先级进程。

需要注意的是,进程切换的时机是由操作系统内核来决定的,具体实现可能会因操作系统和硬件平台而异。

切换调度算法:
进程调度算法是操作系统中非常重要的一部分,它用于决定在多个进程之间分配CPU时间的方式。常见的进程调度算法包括以下几种:

  1. 先来先服务(First-Come, First-Served, FCFS): 公平,非抢占式,不利于短作业 出现饥饿现象

根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业

优点:公平(非抢占式调度),能保证每个进程都会得到一定的CPU时间, 实现简单。
缺点:不利于短作业,会出现饥饿现象。

  1. 短作业优先(Shortest Job First, SJF):
    按照进程需要的CPU时间长短来分配CPU时间,即需要时间短的进程先分配CPU时间。

优点:平均等待时间较短,能提高系统的吞吐量和响应时间。
缺点:长作业出现饥饿现象,而且由于无法准确预测每个进程的运行时间,可能导致长作业得不到CPU时间。

  1. 优先级调度(Priority Scheduling):
    根据进程的优先级来决定调度顺序。优先级可以是静态的,也可以是动态的。静态优先级由用户或管理员指定,而动态优先级可能会随着时间的推移而改变。

优点:能够为高优先级的进程提供保障,能迅速处理紧急任务。
缺点:低优先级的进程可能会出现饥饿现象。

  1. 时间片轮转(Round Robin, RR):

将CPU时间分成一个个固定的时间片,按照时间片的顺序轮流分配给各个进程。时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。

优点:兼顾长短作业,能够保证每个进程都能得到一定的CPU时间
缺点:平均等待时间较长,无法适应长作业和紧急任务,上下文切换较费时,而且需要设置合适的时间片长度。

  1. 多级反馈队列(Multilevel Feedback Queue):
    将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。每个队列的时间片长度不同,高优先级队列的时间片长度较短,低优先级队列的时间片长度较长。

反馈:如果进程在一个队列中占用时间过长,就会降低优先级并移到下一个队列。

优点:兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。
缺点:需要复杂的调度逻辑和实现。

在实际应用中,需要根据具体的应用场景和性能要求选择合适的进程调度算法。例如,对于实时应用需要保证任务的响应时间和可靠性,可以采用优先级调度算法;对于长时间运行的大型系统,可以采用多级反馈队列算法来平衡各种任务的性能要求。

切换流程:

进程切换是操作系统内核中一个非常重要的功能,它允许操作系统在多个进程之间切换控制权,以实现多任务处理, 达到并发执行的目的。下面是进程切换的大致流程:

  1. 上下文保存(Save Context):当操作系统决定要切换到一个新的进程时,它会首先保存当前进程的上下文信息,包括程序计数器、寄存器内容、栈指针等等。这些信息都会被保存在该进程的进程控制块(Process Control Block, PCB)中,以便在以后重新执行该进程时能够恢复它的状态。

  2. 切换到内核态(Switch to Kernel Mode):进程切换通常是由操作系统内核完成的,因此需要将 CPU 的执行模式从用户态切换到内核态,这需要硬件支持。一旦 CPU 进入内核态,操作系统就可以执行它的进程调度算法来选择下一个要执行的进程。

  3. 选择新进程(Select New Process):在内核态中,操作系统会根据其进程调度算法选择一个新的进程来运行。这通常涉及到进程的优先级、进程状态和其他因素的考虑。一旦操作系统决定了下一个要运行的进程,它就会加载该进程的 PCB 并准备运行它。

  4. 上下文恢复(Restore Context):当新进程被选择后,操作系统会从该进程的 PCB 中恢复它的上下文信息,包括程序计数器、寄存器内容、栈指针等等。这些信息将被加载到 CPU 中,以便该进程可以继续执行。

  5. 切换到用户态(Switch to User Mode):一旦新进程的上下文信息被加载到 CPU 中,操作系统会将 CPU 的执行模式从内核态切换回用户态。此时,新进程就可以从上次停止的地方继续执行了。

将当前进程的程序计数器pc、寄存器、栈指针等上下文现场信息保存到进程的pcb中,以便之后重新执行该进程能恢复切换前的状态。 cpu 陷入内核态,由操作系统执行进程调度算法,选择下一个要执行的进程,加载下一个进程的pcb信息到cpu的程序计数器,寄存器,堆栈指针中,恢复该进程保存的现场信息,cpu切换回用户态,继续从断点处执行切换来的进程。

这是进程切换的大致流程,不同操作系统和不同硬件平台可能会有一些细微的差别。

线程切换流程:

进程内的线程切换 -> 普通线程切换
进程间的线程切换 -> 升级为进程切换
一般而言线程切换是在一个进程内多个线程的切换,如果是两个进程间的线程切换就会升级为进程切换。

用户级线程(协程)

严格来说是,内核级线程(os最小调度单位)的切换

过程步骤流程:

  1. 共享的空间不用切换,记录当前线程自己的寄存器值、堆栈指针等现场信息,
  2. 线程是os最小调度单位,由用户态陷入内核态,os调度算法选择下一个线程执行。
  3. 加载下一个要执行的线程信息
  4. 恢复用户态,执行新线程。

为什么进程切换比线程切换开销大? 虚拟空间的切换,虚拟内存 页表 Cache TLB

https://www.cnblogs.com/lfri/p/12597297.html
每个进程都有独立的虚拟内存空间:用户空间和内核空间, 因此进程的切换需要虚拟内存空间的切换, 虚拟内存技术中每个进程内部都保存自己的页表,用作虚拟地址到物理地址的转换, 涉及到的页表、TLB需要更新,

对进程/线程进行上下文切换关键的比较重要的寄存器:

PC:程序计数器,存放下一条要执行的指令地址;
IR:指令寄存器,存放指令寄存器当前执行中的指令;
GR:通用寄存器,比如 系统堆栈指针寄存器(SP),存放进程堆栈空间的地址
PSW:程序状态字,标记进程状态(是否中断);记录指令结果的状态信息,如算数执行结果是否进位、是否为零;以及控制器所需信息,是否允许中断,当前是管态还是目态;

寄存器记录的是一些二进制位,有的是状态,有的是内存地址

调度主体处于活跃状态时,这些二进制位是存储在硬件层面的寄存器上,控制器从 pc 取出下一条指令地址,解码指令,利用运算器执行,并记录运算的结果,存放到 psw 上,并递增 pc。(看起来是线性的,真实的情况是取址、译址、执行流水线地执行。

取址、译址、执行流程结束时,中断装置会检查当前是否存在中断,如果当前系统存在中断,调出中断处理程序,内核程序的 pc、psw 被加载到寄存器,开始以内核态执行,前一个用户态的进程的 pc、psw 寄存器的值会被记录到堆栈,并记录当前栈顶指针到该进程的 pcb。这时,如果中断是时间片中断,调度程序选出下一个待执行的 pcb,取出 sp 值,读取出 pc 和 psw,恢复到寄存器上,再次取址、译址、执行,周而复始。

至于 pcb 的读取,涉及到 cache,虚拟内存置换,内存的分段、分页存储,逻辑地址到物理地址到重定向的逻辑,主要依赖 mmu,tlb 等装置,会引入 mar,mdr 等寄存器。描述同一个进程的不同状态的二进制位,在某个时刻下,有的在寄存器中,有的在各种cache中,有的在内存中…

https://blog.csdn.net/alex_xfboy/article/details/90722654

进程间通信 (IPC,InterProcess Communication)|| 进程间通信

操作系统下不同的进程的操作是互不干扰的( 独立性,这是因为每个进程都有自己的独特的虚拟地址,这个虚拟地址是由物理内存映射而来的,对于不同的进程,相同的虚拟地址位置会映射到不同的物理内存上,所以进程具有独立性),那么我们难免会遇到一个进程需要另一个进程的东西,那么对于进程间通信,在这些方面就非常重要。

进程间通信的目的:

  • 数据传输:一个进程将自己的数据传输给另一个进程。
  • 资源共享:多个进程共享同一个资源。
  • 通知事件:一个进程要给另一个进程去发送消息。
  • 进程控制:一个进程想要去控制另一个进程,此时的控制想要拦截另一个进程的所有陷入和异常,并能够及时知道其状态的改变。

管道 (匿名管道、命名管道):是否通过管道文件 channel

  1. 管道可以分为两类:匿名管道和命名管道。匿名管道是单向的,只能在有父子进程间通信;命名管道是双向的,可以实现本机任意两个进程通信。

  2. 半双工通信:个管道有两个端口,每个端口都可以进行写入,或者读取资源,但是同一实际,相同的端口只能写入或者删除,不可同时进行。

  3. 管道的本质:本质为内核的一个缓冲区(内核的一块物理内存),通过多个进程访问同一个缓冲区来实现通信(由于内核对所有的进程来说都是一样的,所以在内核中开辟一块内存用于交流)。

  4. 匿名管道: 在内核中开辟这块内存,但是没有标识符,无法被其他进程找到。(所以匿名管道是针对于有亲缘关系的进程使用,例如:父子进程,由于子进程在通过父进程创建的时候,复制了父进程的文件描述信息,所以子进程也就有这个文件描述符去操作这个管道)
    所以说,对于匿名管道,只有通过子进程去复制父进程的方式,才能获取同一个管道的操作句柄(变量/内存地址)。

  5. 命名管道: 对于进程间的通信不可能只有父子间的通信,肯定还有没有亲缘关系的进程间通信,而对于这些进程,是通过命名管道进行通信的。

内核中开辟这一块缓冲区,并具有标识符,可以被其他进程找到。(适用于同一主机中没有亲缘关系的进程间通信)
对于一个进程创建了命名管道,这个命名管道会在文件系统中创建出一个管道文件(实际上是管道的名称),多个进程通过打开同一个管道文件,访问内核中的同一个缓冲区实现通信。

  1. 管道的特性:
  • 如果管道中没有数据,则read堵塞,如果管道中数据满了,则write堵塞。
  • 管道中的读端关闭了(没有人去写入数据了),那么write会出发异常,进程退出。
  • 在管道中,数据都是先入先出的。
  • 进程退出,管道释放,所有管道生命周期是跟随其进程的。
  • 内核会对管道的操作进行同步和互斥。
    1
    2
    3
    4
    5
    6
    7
    对于管道自带的同步和互斥

    ①:同步:按照一定的顺序去进行(写入数据的时候才能有数据被读取,没有数据,则read堵塞,数据满了,则write堵塞,读取了继续写)。

    ②:互斥:操作是安全可靠的(对于两个进程对一个管道文件写入的时候必须有数据,防止交叉写入,破坏数据原本的样子);并且读写大小不能超过PIPE_BUF(也就是4096字节),大小保证原子操作。

    ③:原子操作:一次性完成,中间不能被打断。

共享内存

  1. 原理:因为对于共享内存,他是在物理内存上开辟一个内存块,然后对于有需要的的进程,会连接这个内存块,将其映射到自己的虚拟地址上,这样对于不同的进程对于这个内存的操作就不会涉及内核了,所以大大的减少了内核和用户直接沟通的那段时间,提高了效率,所以是最快的IPC。

  2. 写入覆盖
    (1)写入共享内存后,数据不会消失,可以一直读取。
    (2)再次向共享内存写入数据后,会覆盖共享内存之前的内容,也就是每次向共享内存写入数据,都是从相同位置写入的,这点和管道不同。

  3. 共享内存的生命周期:

进程退出以后,共享内存依然存在,换到我们之前的匿名管道,管道的生命周期是随进程的,进程退出以后,管道也就不存在了。

进程可以调用函数接口与共享内存断开连接, 使共享内存的连接数减一, 共享内存即使连接数为0也不会去释放。因为共享内存不属于任何进程,不归进程管理,除非调用释放函数,否则要不要释放是OS决定的,因此共享内存的生命周期是随内核的!!

释放的方式有三种:

  • 关机
  • 调用释放共享内存的函数 shmctl
  • 命令行释放
  1. 共享内存中没有互斥同步关系。(通过信号量、信号等同步机制实现)

消息队列

  1. 消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。 可以通过发送消息来避免命名管道的同步和阻塞问题(命名管道要读端和写端都存在,否则出现阻塞).

消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

基本原理:A进程要给B进程发送消息,A进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据就可以了。

  1. 特点:

1.消息队列是保存在内核中的消息链表,每个消息体都是固定大小的存储块。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

2.如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在。

  1. 缺点:

1.通信不及时,附件也有大小限制。
2.消息队列不适合比较大数据的传输,每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限.
3.消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销

  1. 消息队列与命名管道的比较

消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。

与命名管道相比,消息队列的优势在于
1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

信号(软件中断)

信号可以理解成一种电报,发送方发送内容,指定接收进程,然后发出特定的软件中断操作系统接到中断请求后,找到接收进程,通知接收进程处理信号。

信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件;

信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件.

  1. 信号的特点
  • 简单

  • 不能携带大量信息

  • 满足某个特定条件才发送

  1. 一个完整的信号周期
  • 信号的产生

  • 信号在进程中的注册,信号在进程中的注销

  • 执行信号处理函数

  1. 信号的(产生动作)和状态

(1)产生动作 ctrlz kill

1.当用户按某些终端键时,将产生信号

2.硬件异常将产生信号

3.软件异常将产生信号

4.调用系统函数(如:kill、raise、abort)将发送信号

5.运行kill/killall命令将发送信号

(2)未决状态:没有被处理

(3)递达状态:信号被处理了

信号量(PV操作)

1.本质:是内核中的一个计数器

2.作用:实现进程间的同步与互斥(包含进程间对临界资源的访问操作)。主要作为进程间以及同一进程内不同线程之间的同步手段。
其中:临界资源就是大家都能访问到的资源,也就是我们需要传输的资源。

3.对于保护操作的方式也就是我们上面所说的同步和互斥:
①:同步:通过一些条件让资源访问有序。
②:互斥:通过让进程同一时间对资源进行唯一访问来保证资源安全。

4.信号量对同步和互斥的操作原理:
会通过计数器进行计数,若计数器大于0则表示可以访问资源,如果小于或者等于0,则不能去访问资源,进程堵塞。
其操作方式如下:

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

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

P 操作是⽤在进⼊共享资源之前,V 操作是⽤在离开共享资源之后,这两个操作是必须成对出现的

对于互斥和同步就像是在停车场停车一样,同步是在停车场门口进行的操作,排队驶入,而互斥就像是停车场内部对一个停车位进行的操作。

socket

进程调度策略

先来先服务、短作业优先、最短剩余时间优先

时间片轮转、优先级调度、多级队列

父进程;子进程;僵尸进程;孤儿进程

父进程、子进程

pid = fork()

fork 返回值 - 0 +
error child father

僵尸进程

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵尸进程(Zombie)的数据结构(pid、status、time)。如果“尸体”僵尸进程没有得到处理,进程号pid被一直占用。 一般通过其父进程来处理这个僵尸进程。

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源
我们都知道进程的工作原理。我们启动一个程序,开始我们的任务,然后等任务结束了,我们就停止这个进程。 进程停止后, 该进程就会从进程表中移除。
你可以通过 System-Monitor 查看当前进程。

在UNIX系统中,一个进程结束了,但是它的父进程没有等待(调用wait / waitpid)它, 那么它将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init来接管他,成为他的父进程

处理方法:

  1. 父进程通过wait()和waitpid()等函数等待子进程结束,但是,这会导致父进程挂起;

  2. 如果父进程要处理的事情很多,不能够挂起,通过signal()函数人为处理信号 SIGCHLD:

只要有子进程退出自动调用指定好的回调函数,因为子进程结束后,父进程会收到该信号SIGCHLD,可以在其回调函数里调用 wait()或waitpid()回收;

  1. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN)通知内核:

自己对子进程的结束不感兴趣,父进程忽略此信号,那么子进程结束后,内核会回收,并不再给父进程发送信号:

孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程 (进程 ID 为 1 的进程) 所收养,并由 init 进程对它们完成状态收集工作。因为孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。

线程

三种线程

  1. 用户级线程(协程) os无法感知,由用户程序自定义创建管理调度。

对于内核来说稳定安全,影响小,这些线程使用的是内核分配给进程的资源和空间,其状态对内核透明。
由用户程序自己定义创建管理,不被内核感知的线程, 内核眼中是进程在运行,实际在进程中某个线程在运行。
因此如果进程内部没有对线程阻塞进行处理的话,整个进程就会阻塞。
进程内只能有一个线程在运行,因此不能发挥cpu多核性能。

  1. 内核级线程 (用户进程、系统进程)os能感知,最小调度单位。

通过系统调用由内核创建管理,占用内存大,但是能真正发挥cpu多核性能。
但是切换时状态变化用户态、内核态、用户态切换,切换开销大,但综合多线程并行速度还是有提高的。
线程产生阻塞时,其同一个进程内的线程也能继续运行。

  1. 混合线程 gmp 大量用户级线程映射到少量的内核级线程

用户程序在内核中创建少量内核级线程,用户程序将大量的用户级线程与这些少量内核级线程进行绑定,组成混合线程。

组合方式的多线程实现, 线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行. 一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。

用户线程必须与内核线程关联的原因是,用户线程本身仅是用户程序中的一堆数据。 内核线程是系统中的实际线程,因此要使用户线程取得进展,用户程序必须让其调度程序获取用户线程,然后在内核线程上运行它。

特点 优点 缺点

通信 同步 互斥

上下文切换 占用资源情况 共享资源与独立资源

内存管理

操作系统内存管理
物理内存 虚拟内存

内存空间 cpu cache

分页 分段 段页式

物理内存阶段:
给每个进程预先分配独立的物理内存空间(由代码块、堆、栈组成),尽管空闲仍然占有着内存空间。
在进程中,该内存空间地址从0开始,但在操作系统中地址是实际的物理内存地址。
CPU 执行该进程时,需要使用叫做「内存管理单元 MMU」(Memory Management Unit)对进程的内存地址翻译。

虚拟内存阶段:解决进程占用大量物理内存空间却空闲的浪费问题。

虚拟内存的基本思想是,每个进程有用独立的逻辑地址空间,内存被分为大小相等的多个块,称为页(Page).每个页都是一段连续的地址。对于进程来看,逻辑上貌似有很多内存空间,其中一部分对应物理内存上的一块(称为页框,通常页和页框大小相等),还有一些没加载在内存中的对应在硬盘上。

物理内存存储管理(进程逻辑内存区地址与物理内存地址的映射,是否连续,如何管理)

  1. 连续

  2. 非连续

分页管理

分⻚是把整个虚拟和物理内存空间切成⼀段段固定尺⼨的⼤⼩。这样⼀个连续并且尺⼨固定的内存空间,我们叫⻚(Page)。在 Linux 下,每⼀⻚的⼤⼩为 4KB。

  1. 基本原理
    将程序的逻辑地址空间划分为固定大小的页(page),而物理内存划分为同样大小的页框(page frame)。程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。该方法需要CPU的硬件支持,来实现逻辑地址和物理地址之间的映射。在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内地址w(位移量)。

页式管理方式的优点是:

1)没有外碎片,每个内碎片不超过页大,是比前面所讨论的几种管理方式的最大进步。

2)一个程序不必连续存放。

3)便于改变程序占用空间的大小(主要指随着程序运行,动态生成的数据增多,所要求的地址空间相应增长)。

缺点是:要求程序全部装入内存,没有足够的内存,程序就不能执行。

  1. 页式管理的数据结构
    在页式系统中进程建立时,操作系统为进程中所有的页分配页框。当进程撤销时收回所有分配给它的页框。在程序的运行期间,如果允许进程动态地申请空间,操作系统还要为进程申请的空间分配物理页框。操作系统为了完成这些功能,必须记录系统内存中实际的页框使用情况。操作系统还要在进程切换时,正确地切换两个不同的进程地址空间到物理内存空间的映射。这就要求操作系统要记录每个进程页表的相关信息。为了完成上述的功能,

页式系统中,一般要采用如下的数据结构。进程页表、物理页面表、请求表

  • 进程页表:完成逻辑页号(本进程的地址空间)到物理页面号(实际内存空间,也叫块号)的映射。

每个进程有一个页表,描述该进程占用的物理页面及逻辑排列顺序,

  • 物理页面表:整个系统有一个物理页面表,描述物理内存空间的分配使用状况,其数据结构可采用位示图和空闲页链表。
    对于位示图法,即如果该页面已被分配,则对应比特位置1,否置0.

  • 请求表:整个系统有一个请求表,描述系统内各个进程页表的位置和大小,用于地址转换也可以结合到各进程的PCB(进程控制块)里。

  1. 页式管理地址变换

在页式系统中,指令所给出的地址分为两部分:逻辑页号和页内偏移地址。

原理:CPU中的内存管理单元(MMU)按逻辑页号通过查进程页表得到物理页框号,将物理页框号与页内偏移地址相加形成物理地址(见图4-4)。

逻辑页号,页内偏移地址->查进程页表,得物理页号,加上页内偏移地址->物理地址

上述过程通常由处理器的硬件直接完成,不需要软件参与。通常,操作系统只需在进程切换时,把进程页表的首地址装入处理器特定的寄存器中即可。

一般来说,页表存储在主存之中。这样处理器每访问一个在内存中的操作数,就要访问两次内存:

第一次 cpu访问进程页表 查找页表 将操作数的 逻辑地址 变换为 物理地址;

第二次 cpu 从内存物理地址读写数据 完成真正的读写操作。

  1. 快表TLB:在cpu内部,访问命中直接完成逻辑地址到物理地址转换。 局部性原理

这样做时间上耗费严重。为缩短查找时间,可以将页表从内存装入CPU内部的关联存储器(例如,快表) 中,实现按内容查找。

此时的地址变换过程是:在CPU给出有效地址后,由地址变换机构自动将页号送入快表,并将此页号与快表中的所有页号进行比较,若要访问的物理页记录在快表中。于是可直接读出该页所对应的物理页号,这样就无需访问内存中的页表。由于关联存储器的访问速度比内存的访问速度快得多。

  1. 多级页表
    操作系统可能会有非常多进程,如果只是使用简单分页,可能导致的后果就是页表变得非常庞大。所以,引入了多级页表的解决方案。

所谓的多级页表,就是把我们原来的单级页表再次分页,这里利用了 局部性原理 ,除了顶级页表,其它级别的页表一来可以在需要的时候才被创建,二来内存紧张的时候还可以被置换到磁盘中。

分段管理

分段存储管理是面向程序员提出的,目的是方便其编程、信息保护和共享及动态链接等各方面需求。
进程是根据自然段划分的,例如将一个进程划分为主程序、子程序、栈和一段数据,划分后的段都从0开始编制,进程被划分后自然需要建立逻辑地址结构来管理各个段。同时每个进程都有一张逻辑空间与内存空间映射的段表。

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

分段机制下的虚拟地址由两部分组成,段号和段内偏移量。

虚拟地址和物理地址通过段表映射,段表主要包括段号、 段的界限 。

分页、分段区别

分页和分段有什么区别?

  • 段是信息的逻辑单位,它是根据用户的需要划分的,因此段对用户是可见的 ;
  • 页是信息的物理单位,是为了管理主存的方便而划分的,对用户是透明的。
  • 段的大小不固定,有它所完成的功能决定;页的大小固定,由系统决定。
  • 段向用户提供二维地址空间;页向用户提供的是一维地址空间。
  • 段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制。

怎么理解分页存储管理是一维的,分段式是二维的?

分页的页空间是固定大小组合在一起的, 分段的段空间大小不一,各个段内有自己的空间。

段表存放在内存里,cpu在访问物理地址块的内容的时候,先要访问内存中的段表,进行特定的地址转换,得到物理地址,然后在去内存中访问物理地址对应的物理块。

页式存储是一维的,因为各个模块在链接时必须组织在同一地址空间;而分段式二维的,各个模块在链接时可以把每个段组织成一个地址空间。

也就是说,在编译时,如果是分页存储,你只需要给定一个虚拟地址,然后操作系统会自己去把虚拟地址划分成虚页号和页内偏移,所以是一维的。而如果是段式存储的话,你需要给定的虚拟地址必须包括虚段号和段内偏移量,因为分段式从程序员的角度来分的,操作系统并不知道,所以段式存储是二维的。

段页式管理

段页式存储管理方式将页式存储管理提供内存利用率的方式和分段管理中对段的共享结合起来。
实现原理:
(1)将作业分段;
(2)每段分页;
(3)内存分块,进程仍以块装入内存中;

虚拟内存

为什么要使用虚拟内存?

我们先回顾一下程序执行的原理,首先程序是运行在内存中的,程序运行时会将保存在硬盘上的程复制到RAM内存(载入内存),然后CPU执行内存中的程序代码。

如果执行的程序占用内存很大或很多,或同时执行多个程序,就会导致内存消耗殆尽。从而导致程序执行异常或崩溃。

虚拟内存工作原理

当进程开始运行时,先将一部分程序装入内存,另一部分暂时留在外存;当要执行的指令不在内存时,由系统自动完成将它们调入内存的工作;当没有足够的内存时,系统自动选择部分内存(暂不执行的程序)空间,将其中原有的内容交换到磁盘上,并释放这些内存空间供其他进程使用。

这样做的结果使程序的运行丝毫不受影响,使程序在运行中感觉到拥有一个不受内存容量约束的、虚拟的、能够满足自己需求的存储器。

缺页中断 页面置换 交换swap

虚拟内存技术使得不同进程在运行过程中,它所看到的是自己独自占有了当前系统的4G内存。所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。 事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好(叫做存储器映射),等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。

还有进程运行过程中,要动态分配内存,比如malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。

缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存时,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。

缺页本身是一种中断,与一般的中断一样,需要经过4个处理步骤:
1、保护CPU现场

2、分析中断原因

3、转入缺页中断处理程序进行处理

4、恢复CPU现场,继续执行

但是缺页中断是由于所要访问的页面不存在于内存时,由硬件所产生的一种特殊的中断,因此,与一般的中断存在区别:

1、在指令执行期间产生和处理缺页中断信号

2、一条指令在执行期间,可能产生多次缺页中断

3、处理完缺页中断返回时,执行的是产生中断的那条指令,而一般的中断返回是,执行下一条指令。

页面置换算法

https://developer.aliyun.com/article/999170

  1. opt 最有页面置换算法
    是一种最理想情况,实际系统无法实现的算法。该算法会根据将来的页面空闲状态,对于保存在内存当中的每一个逻辑页面, 计算在它的下一次访问之前, 还需等待多长时间, 从中选择等待时间最长的那个, 作为被置换的页面. 所计算出来的最优页面置换策略,来进行页面置换的。

但实际系统是无法获知将来内存中该页面的具体状态的,因此该算法是无法实现的。

  1. FIFO 先进先出 链表队列 淘汰驻留时间最长的页面

选择在内存中驻留时间最长的页面淘汰. 具体来说, 系统维护着一个链表, 记录了所有位于内存当中的逻辑页面. 从链表的排列顺序来看, 链首页面的驻留时间最长, 链尾页面的驻留时间最短. 当发生一个缺页中断时, 把链首页面淘汰出去, 并把新的页面添加到链表的末尾.

性能较差, 调出的页面有可能是经常要访问的页面. 并且有 belady现象. FIFO算法很少单独使用.

Belay现象:当分配给一个运行程序更多的物理页后,按理是缺页的现象变少了,但是如果使用FIFO算法,可能会导致缺页现象变多

FIFO实现简单,只需要链表,但是产生的缺页次数多

  1. LRU least Recently used 最近最久未使用 最近最少使用

淘汰长时间不使用的页面, 该算法开销大,实际使用需要硬件优化支持

基本思路
当一个缺页中断发生时, 选择最久未使用的那个页面, 并淘汰.
当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。

LRU与最优页面置换算法

LRU是根据过去,来推测具体换出哪一页(根据历史推测未来)
最优页面置换算法,替换将来最远都没有使用的页面(根据未来推测未来)

依据原理
它是对最优页面置换算法的一个近似, 其依据是程序的局部性原理, 即在最近一小段时间(最近几条指令)内, 如果某些页面被频繁地访问, 那么再将来的一小段时间内, 他们还可能会再一次被频繁地访问. 反过来说, 如果过去某些页面长时间未被访问, 那么在将来它们还可能会长时间地得不到访问.

实现实例
系统维护一个页面链表, 最近刚刚使用过的页面作为首节点, 最久未使用的作为尾结点. 再一次访问内存时, 找出相应的页面, 把它从链表中摘下来, 再移动到链表首. 每次缺页中断发生时, 淘汰链表末尾的页面.

  1. 时钟页面置换 clock
    Clock 页面置换算法,LRU的近似,对FIFO的一种改进

基本思路
需要用到页表项的访问位, 当一个页面被装入内存时, 把该位初始化为0. 然后如果这个页面被访问, 则把该位置设为1;

把各个页面组织成环形链表(类似钟表面), 把指针指向最老的页面(最先进来);

当发生一个缺页中断时, 考察指针所指向的最老页面, 若它的访问位为0, 立即淘汰; 若访问位为0, 然后指针往下移动一格. 如此下去, 直到找到被淘汰的页面, 然后把指针移动到下一格.

实例
时钟置换相比LRU差一些,与FIFO差不多,实际上,时钟置换与LRU产生中断缺页次数接近的,它达不到LRU最佳的效果,但是可以接近

二次机会法
因为考虑到时钟页面置换算法, 有时候会把一些 dirty bit 为1(有过写操作)的页面进行置换, 这样的话, 代价会比较大. 因此, 可以结合访问位和脏位一起来决定应该置换哪一页.

相当于说, 替换的优先级, 没有读写也没写过, 那么直接走, 如果写过或者访问过, 那么给你一次机会, 如果又写过, 又访问过, 那么久给你两次机会.

实例
和之前相比,区分出某一页是读还是写
优先换出只读的页,对于可写页多给一次机会,减少硬盘的读写次数

  1. 最不常用算法(Least Frequently used, LFU)计算访问次数,淘汰访问最少的页面
    基本思路
    当一个缺页中断发生时, 选择访问次数最少的那个页面, 并淘汰.

实现方法
对每一个页面设置一个访问计数器, 每当一个页面被访问时, 该页面的访问计数器加1. 当发生缺页中断时, 淘汰计数值最小的那个页面.

LFU和LRU区别
LRU考察的是多久未访问, 时间越短越好. 而LFU考察的是访问的次数和频度, 访问次数越多越好.

问题
一个页面在进程开始时使用得很多,但以后就不使用了。实现也费时费力

解决办法
定期把次数寄存器右移一位

  1. LRU、FIFO、Clock比较

FIFO和LRU都可用链表和栈来表示访问的次序

LRU考虑驻留时间和最近访问时间,考虑页的位置时,如果该页被访问到了,则从栈或者链表中取出来,放到头部去,但是FIFO没有这个过程

Clock只是对LRU算法的一个近似,Clock只用了一个bit或者俩个bit来表示访问时间,很明显用一个bit不可能表示一段时间的不同页面的访问顺序,它只是近似(Clock使用了硬件的一些bit,模拟访问时间和先后顺序)

注意
为了有效的减少缺页次数,除了算法本身,还有一个最重要的是本身的访问序列有一定要求,它最好具有局部性特征,才会使得这些算法能够发挥效果

其他

计算机设备组成及其作用: https://zhuanlan.zhihu.com/p/495709005

计算机是一种数据处理设备,它由CPU、内存、硬盘以及外部设备(键盘鼠标显示器)组成。

CPU负责数据处理,内存负责存储,外部设备负责数据的输入和输出,它们之间通过总线连接在一起。

CPU内部主要由控制器、运算器和寄存器组成。
控制器负责指令的读取和调度,
运算器负责指令的运算执行,
寄存器负责数据的存储,

它们之间通过CPU内的总线连接在一起。

每个外部设备(例如:显示器、硬盘、键盘、鼠标、网卡等等)则是由外设控制器、I/O端口、和输入输出硬件组成。外设控制器负责设备的控制和操作,I/O端口负责数据的临时存储,输入输出硬件则负责具体的输入输出,它们间也通过外部设备内的总线连接在一起。

在这套设计思想(冯.诺依曼体系架构)里面: 总是有一部分负责控制、一部分负责执行、一部分则负责存储,
它之间进行交互以及接口通信则总是通过总线来完成。

这种设计思路一样的可以应用在我们的软件设计体系里面:组件和组件之间通信通过事件的方式来进行解耦处理,而一个组件内部同样也需要明确好各个部分的职责(一部分负责调度控制、一部分负责执行实现、一部分负责数据存储)。

堆栈有什么区别?

1、堆栈空间分配不同。栈由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等,栈有着很高的效率;堆一般由程序员分配释放,堆的效率比栈要低的多。

2、堆栈缓存方式不同。栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,速度要慢些。

3、空间大小: 栈的空间大小并不大,一般最多为2M,超过之后会报Overflow错误。堆的空间非常大,理论上可以接近3G。(针对32位程序来说,可以看到内存分布,1G用于内核空间,用户空间中栈、BSS、data又要占一部分,所以堆理论上可以接近3G,实际上在2G-3G之间)。

4、能否产生碎片: 栈的操作与数据结构中的栈用法是类似的。‘后进先出’的原则,以至于不可能有一个空的内存块从栈被弹出。因为在它弹出之前,在它上面的后进栈的数据已经被弹出。它是严格按照栈的规则来执行。但是堆是通过new/malloc随机申请的空间,频繁的调用它们,则会产生大量的内存碎片。这是不可避免地。

io 数据在不同实体间(设备之间、进程内核之间)的流动

IO (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作,

用户进程: io调用

内核os: io执行

数据缓冲区:内核缓冲区 进程缓冲区

IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

数据流动的区域

用户空间 <–> 内核缓存区(磁盘缓存、socket缓存) <–> io设备(磁盘、缓存)

LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区。

对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。

所以,对于一个网络输入操作通常包括两个不同阶段:

(1) 进程选择io调用

(1)等待网络数据到达网卡→读取到内核缓冲区,数据准备好;

(2)从内核缓冲区复制数据到进程空间。

io种类

  1. 同步和异步强调的是发起io请求后,是否需要等到数据传入再继续执行。
    针对当前执行线程、或进程而言,发起IO调用后,当前线程或进程是否挂起等待操作系统的IO执行完成。
  • 同步io io请求后,一直等待

  • 异步io

  1. 阻塞和非阻塞强调的是进程处理io的状态,是否进入阻塞状态。
    对于操作系统IO是否处于就绪状态的处理方式。
  • 阻塞io
  • 非阻塞io

阻塞io: 发送请求后,等到回复再继续执行。

非阻塞io: 发送请求后,不需要等到回复,就可以执行,等回复响应后再去处理。

io复用:

select poll epoll
https://blog.csdn.net/m0_74787523/article/details/128139257?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EAD_ESQUERY%7Eyljh-1-128139257-blog-126186345.pc_relevant_landingrelevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EAD_ESQUERY%7Eyljh-1-128139257-blog-126186345.pc_relevant_landingrelevant&utm_relevant_index=2

https://blog.csdn.net/weixin_59253379/article/details/126186345

https://zhuanlan.zhihu.com/p/465249826

https://www.51cto.com/article/607937.html