操作系统的功能

操作系统位于硬件资源之上,管理硬件资源(CPU,内存,硬盘,io设备),位于应用程序之下,为其提供服务,并管理应用程序

  1. 资源分配和回收:如进程调度分配CPU,如内存分配与回收
  2. 为应用程序提供服务:将硬件资源的操作封装起来,提供相对统一的接口(系统调用)给开发者调用
  3. 管理应用程序:即控制进程的生命周期

进程,线程,协程

进程是资源分配和调度的基本单位

代码经编译形成可执行文件,可执行文件运行,会被装载到内存,由CPU执行程序中的每一条指令,这个运行的程序即进程

挂起:大量阻塞进程可能会占用物理内存空间,系统通常将其换出到硬盘,等需要再运行的时候,从硬盘换入到物理内存.这个没有占用实际物理空间的状态即挂起状态

线程是程序执行的最小单位,线程是进程的子任务,是进程的执行单元,一个进程至少有一个线程,一个进程可以有多个线程,这些线程共享同一块内存,但都有着独立的寄存器和栈以确保控制流相对独立.

无论是创建进程的fork还是创建线程的thread,底层都是内核函数clone,如果复制对方的地址空间,就产出进程;如果共享对方地址空间,就产生一个线程

资源开销:

  1. 线程创建更快.进程创建过程中还需要资源管理信息,如内存管理信息、文件管理信息;而线程创建过程中不需要,而是共享它们
  2. 线程终止得更快,因为释放的资源要少很多,仅是少量寄存器和私有的栈区
  3. 线程切换更快,一个进程的线程共享同一块内存,有着相同的页表,切换时不需要切换页表,只需保存和恢复少量上下文信息. 由于每个进程都有独立的内存空间,包括代码、数据和堆栈空间,创建和销毁的开销较大;进程间的切换需要保存和恢复整个进程的状态,因此上下文切换的开销也比较大

通信与同步:进程间相互隔离,所以需要使用管道,消息队列,共享内存等特殊通信方式; 线程间通信比较简单,可以直接访问共享数据

安全性:进程间相互隔离,一个进程的崩溃不会影响到其他进程;

线程一个错误可能影响整个进程,导致崩溃

协程是一种用户态的轻量级线程,它的调度完全由用户控制,不需要操作系统的干预。协程提供了一种避免非阻塞I/O操作的方法,可以在I/O操作等待时挂起当前任务,并在适当的时候恢复,这样可以在单个线程中高效地执行多个任务。协程的开销远小于线程,因为它避免了系统调用和上下文切换的成本。

为什么进程切换比线程切换更慢

每个进程都要自己的虚拟地址空间,线程共享所在进程的虚拟地址空间,所以同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换.

把虚拟地址空间转换为物理地址需要查找页表,这是一个很慢的过程,通常使用cache即快表缓存常用的地址映射,加速页表查找

进程切换后页表也要进行切换,页表切换后快表就失效了,导致cache命中率下降,虚拟地址空间转换为物理地址就会变慢,表现成程序运行就会变慢.而线程切换无需切换地址空间,也就不会导致快表失效,命中率下降

不同的线程

  1. 用户线程

在用户空间实现,不由内核管理.整个线程的管理调度由用户级线程库函数实现而非操作系统

优点:线程间切换不需要切换到内核空间中,节省开销

不同进程可对自己的线程选择不同的调度算法

对线程的管理是用户代码的一部分

缺点:不由操作系统调度,一旦用户线程发起系统调用而阻塞,此进程下用户线程都无法运行

用户线程运行无法被打断,除非由操作系统,但操作系统不直接参与调度

时间片分配给进程,线程越多,被分配到的时间片就越少

  1. 内核线程

由操作系统管理调度,创建管理都由操作系统负责

优点:一个内核线程发起系统调用而阻塞不会影响其他内核线程

多线程的进程会被分配更多的CPU时间

缺点:线程的创建终止切换均由系统调用进行,开销比较大

  1. 轻量级线程

内核支持的用户线程,一个进程可以有多个轻量级线程,每个对应一个内核线程,即每一个轻量级线程由一个内核线程支持.它由内核管理并像普通进程一样被调度

并行与并发

并行是指在同一时刻执行多个任务,这些任务可以同时进行,每个任务都在不同的处理单元(如多个CPU核心)上执行。在并行系统中,多个处理单元可以同时处理独立的子任务,从而加速整体任务的完成。

并发是指在相同的时间段内执行多个任务,这些任务可能不是同时发生的,而是交替执行,通过时间片轮转或者事件驱动的方式。并发通常与任务之间的交替执行和任务调度有关。

什么是中断,什么是异常

中断是由计算机外部事件触发的,通常与硬件设备相关;

异常是由计算机系统内部时间出发的,通常与正在执行的程序或者指令有关

他们都会导致处理器暂停当前正在执行的任务,并转向到一个特定的处理程序.处理完这些特殊情况后,处理器会返回到被打断的任务继续进行

用户态与核心态

  1. 用户态和内核态的区别

用户态和内核态是操作系统为了保护系统资源和实现权限控制而设计的两种不同的CPU运行级别,可以控制进程或程序对计算机硬件资源的访问权限和操作范围。

  • 用户态:在用户态下,进程或程序只能访问受限的资源和执行受限的指令集,不能直接访问操作系统的核心部分,也不能直接访问硬件资源。
  • 核心态:核心态是操作系统的特权级别,允许进程或程序执行特权指令和访问操作系统的核心部分。在核心态下,进程可以直接访问硬件资源,执行系统调用,管理内存、文件系统等操作。
  1. 在什么场景下,会发生内核态和用户态的切换
  • 系统调用:当用户程序需要请求操作系统提供的服务时,会通过系统调用进入内核态。
  • 异常:当程序执行过程中出现错误或异常情况时,CPU会自动切换到内核态,以便操作系统能够处理这些异常。
  • 中断:外部设备(如键盘、鼠标、磁盘等)产生的中断信号会使CPU从用户态切换到内核态。操作系统会处理这些中断,执行相应的中断处理程序,然后再将CPU切换回用户态。

进程调度算法

  1. 先来先服务:按照请求到来的顺序进行调度,这种调度算法的优势是非常简单,缺点是前面长作业用时过长可能导致后面短作业饥饿.适用于CPU繁忙型系统,不适用于IO繁忙型
  2. 短作业优先:预估作业的执行时间,按估计运行之间最短的顺序去调度,但是是非抢占式的,如果一直有短作业到那么会造成长作业阻塞
  3. 最短剩余时间优先:实在短作业优先上的改进,是一种抢占式调度算法,当有请求到来的时候,会将其预估运行时间和当前作业的剩余时间作比较,如果更小,那么当前进程挂起,运行新进程,否则新进程等待
  4. 优先级调度:是给作业分配不同优先级,按照优先级去调度,但如果一直有高优先级进程在前面的话也会导致低优先级进程饥饿,可以随着时间推移增加等待进程的优先级.适合根据紧急程度动态调整,适合实时操作系统
  5. 高响应比优先 将响应比优先级更高的投入运行.响应比=(等待时间+要求服务时间)/要求服务时间.等待时间相同则短作业优先,作业时间相同则等得久的优先.
  6. 时间片轮转:为每个进程分配一个时间片,进程轮流执行,时间片用完后就轮到下一个进程, 适合分时操作系统,但频繁的进程切换会带来不小的开销
  7. 多级队列:设置多个就绪队列,将不同类型或性质的进程分配到不同就绪队列,每个队列调度算法可以不同
  8. 多级反馈队列:是优先级和时间片的结合,设置多个队列,每个队列优先级从高到低,时间片从小到大.新进程到来时先被放到第一级队列里面按照先来先服务等待被分配时间片,如果时间片用完,进程还没有结束,那么就被放到下一级队列的队尾,如果已经在最下一级的队尾了,就放到该队列队尾. 如果是被抢占的进程就直接放到原先队列队尾. 优点是对各个类型都非常公平,每个新到达的进程都会比较快得到响应,不必预估进程的运行时间,可以防止用户作假

进程间通信方式

进程之间一般是独立的,但内核空间是每个进程都共享的,所以进程间通信必须通过内核

  1. 共享内存

进程之间存在一块可以直接访问的共享内存.通过对这块共享内存读写实现进程间消息交换

  1. 消息队列

若没有共享内存,需要利用操作系统提供的发送/接收消息两个原语进行数据交换.

发送进程将数据发送给接收进程的消息缓冲队列/中间实体,接收进程从缓冲队列/中间实体去的消息

内核中每个消息体存在最大长度限制,所以消息队列不适合较大数据的传输,通信也不是很即时

  1. 管道

管道是连接一个读进程和一个写进程以实现两者之间通信的共享文件,管道不属于文件系统,存在于内存当中.是半双工通信

匿名管道:没有名字用完就被销毁,主要用于有亲缘关系的进程之间的通信

命名管道:与路径名相关联,可以用在没有亲缘关系的进程间通信,只要访问该路径,就可以通过该管道通信.存在于文件系统中,但内容存在内存,使用后将继续保存在文件系统中以便后续使用

  1. 信号量

信号量主要用来同步与互斥,本身并不传递信息

P后>=0,则还有资源可用,V后<=0,表明有进程被阻塞需要唤醒

信号量设置为1则互斥,设置为0则同步

  1. 信号

信号是进程间通信机制中唯一的异步通信机制,可以在一个进程中通知另一个进程发生了某种时间从而实现进程通信

  1. socket通信

主要用不同主机的进程间通信,也可以用在同一台主机上的进程间通信

线程间同步方式

  1. 互斥锁:允许只有一个线程访问共享资源
  2. 条件变量:允许一个线程等待某个条件满足,而其他线程可以发出信号通知他
  3. 读写锁:读共享写排他
  4. 信号量:1为互斥,0为同步

线程分离状态

指的是新创建线程的情况,一般默认是非分离,也就是joinable可等待的,原先的线程需要等待创建的线程结束,当join()函数返回,创建的线程才算终止,才可以释放自己占用的资源

1
2
thread1{//直到2结束,收到其返回值,1的阻塞才会结束,通过join回收其资源.
pthread_join(thread2);}

分离状态则是指,创建的线程没有被其他线程锁等待,自己运行结束,也就终止了,马上释放资源.分离的线程执行完毕自动释放资源,无需其他线程来回收,

分离场景:有时候并不需要获取其返回值,故而分离以避免资源浪费和不必要的同步开销.比如服务器为每个客户端创建线程处理请求,处理完后就可以自动释放资源,无需主线程逐个等待.

进程同步和互斥

进程同步是指多个并发执行的进程之间协调和管理它们的执行顺序,以确保它们按照一定的顺序或时间间隔执行。

互斥指的是在某一时刻只允许一个进程访问某个共享资源。当一个进程正在使用共享资源时,其他进程不能同时访问该资源。

解决进程同步和互斥的问题有很多种方法,其中一种常见的方法是使用信号量和 PV 操作。信号量是一种特殊的变量,它表示系统中某种资源的数量或者状态。PV 操作是一种对信号量进行增加或者减少的操作,它们可以用来控制进程之间的同步或者互斥。

  • P操作:相当于“检查”信号量,如果资源可用,就减少计数,然后使用资源。
  • V操作:相当于“归还”资源,增加信号量的计数,并可能唤醒等待的进程。

除此之外,下面的方法也可以解决进程同步和互斥问题:

  • 临界区:将可能引发互斥问题的代码段称为临界区,里面包含了需要互斥访问的资源。进入这个区域前需要先获取锁,退出临界区后释放该锁。这确保同一时间只有一个进程可以进入临界区。
  • 互斥锁(Mutex):互斥锁是一种同步机制,用于实现互斥。每个共享资源都关联一个互斥锁,进程在访问该资源前需要先获取互斥锁,使用完后释放锁。只有获得锁的进程才能访问共享资源。
  • 条件变量:条件变量用于在进程之间传递信息,以便它们在特定条件下等待或唤醒。通常与互斥锁一起使用,以确保等待和唤醒的操作在正确的时机执行。

死锁的必要条件

  1. 互斥: 每个资源要么分配给一个进程,要么就是可用的
  2. 占有和等待:已经得到了某个资源的进程可以再次请求新的资源
  3. 不可抢占: 已经被分配给一个进程的资源不能被强制抢占,只能由占有它的进程主动释放
  4. 环路等待:多个进程组成一条环路,环路中每个进程都在等待着下一个进程所占有的资源

死锁的预防

主要是破坏上述四个必要条件

**一次性申请所有资源:**规定所有进程开始执行前请求所需要的全部资源;当一个进程请求资源时,先暂时释放其当前占用的所有资源,然后再尝试一次获得所需的全部资源[破环条件2]

申请不到就释放:保证每一个进程在任何时刻都只能占用一个资源,如果请求别的资源就要先释放第一个资源[破坏条件3]

按序申请资源:将所有的资源统一编号,所有的资源请求必须按照编号顺序提出[破环条件4]

死锁的避免:银行家算法

虚拟内存

虚拟内存地址:程序所使用的内存地址;

物理内存空间:实际存在硬件里面的空间地址

操作系统提供一种机制,在物理内存和虚拟内存之间建立一个地址映射表,进程持有的虚拟地址会通过CPU芯片中的内存管理单元的映射关系,来转换变成物理地址,然后再通过物理地址访问内存

操作系统主要通过内存分段和内存分页来管理虚拟内存和物理内存之间的关系

虚拟地址空间构成虚拟内存,他使得应用程序认为自己拥有连续的可用内存空间,但实际上十多个被分割的物理内存页、以及部分暂时存储在磁盘上的交换分区所构成的

为什么需要虚拟内存

虚拟内存的思想,整体来看就是:通过结合磁盘和内存各自的优势,利用中间层对资源进行更合理的调度,充分提高资源的利用率,并提供和谐以及统一的抽象

  1. 可以结合磁盘和内存的优势,为进程提供看起来速度足够快并且容量足够大的存储
  2. 为进程提供独立的内存空间并引入多层页表结构将虚拟内存翻译成物理内存,进程之间可以共享物理内存减小开销,也能简化程序的链接、装载以及内存分配的过程
  3. 可以控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统安全性

分段与分段

虚拟内存采用分页技术,将地址空间划分成固定大小的页,每一页再与内存进行映射

分段则是地址空间划分成多个拥有独立地址空间的段,每个段上的地址空间划分成大小相同的页.主要是为了使程序和数据可以被划分为逻辑上独立的地址空间,有助于共享和保护

页面置换算法

  1. OPT最佳页面置换:置换在未来最长时间不访问的页面,但无法实现
  2. FIFO先进先出置换:将页面以队列形式保存,先进入队列的页面先被置换进入磁盘
  3. LRU最近最久未使用:根据未被访问的时长升序排列页面,每次将最近最久未被使用页面置换出去
  4. 时钟页面置换:所有页面都保存在类似钟面的环形链表中,页面包含一个访问位,发生缺页中断时,顺时针遍历页面,若访问位为1则置0,若访问到0则置换
  5. 最不常用:记录每个页面的访问次数,发生缺页中断的时候,将访问次数最少的页面置换出去,需统计次数,有额外开销

磁盘调度算法

  1. FCFS先来先服务:按照最先来的请求,在寻道过程中可能跳过以后可能需要访问的磁道,导致访问磁道消耗时间过多
  2. SSTF:每次选择距离磁头最近的待处理请求,可能导致部分请求饥饿
  3. SCAN:先处理向上的请求,在处理反方向的请求

什么是IO多路复用,SELECT,POLL,EPOLL

指在单个进程或线程钟,处理多个IO事件.它允许单个进程同时监视多个文件描述符,当一个或者多个文件描述符就绪时,他就可以立即响应

io多路复用通常通过SELECT,POLL,EPOLL都是系统调用实现,目的都是监视多个文件描述符,以确定是否有数据可读可写或发生异常

  1. SELECT:

  2. select是最早的io多路复用机制,使用位图来存储监视的文件描述符,监视的文件描述符数量有限

  3. 每次调用都需要将待监视的文件描述符从用户态加载到内核态,性能开销随数量增加而增大

  4. 对监视的文件描述符集合的扫描是线性的,性能随数量增加而下降

  5. 就绪事件发生,返回对应的文件描述符集合

  6. POLL:

  7. POLL是对SELECT的改进,使用动态数组(以链表形式)来存储监视的文件描述符集合,数量上没有限制

  8. 每次调用也需要从用户态加载到内核态,但性能有所提升

  9. 扫描也是线性的,但只需要扫描实际使用的数组

  10. 就绪事件发生,返回对应的文件描述符集合

  11. EPOLL:

  12. 使用事件驱动的方式,用内核时间表记录要监视的文件描述符集合和事件,每次调用不需要从用户态加载到内核态,而是采用回调的方式,当文件描述符准备就绪时,内核直接通知应用程序

  13. 支持EPOLL IN/OUT/ET三种工作模式,满足不同需求

  14. 对大量文件描述符的管理有更好的性能和扩展性

  15. LT水平工作模式:文件描述符准备就绪时,epoll_wait函数会立即返回,并返回所有储在就绪状态的文件描述符,如果应用程序没有处理完所有的就绪事件,且该文件描述符上就绪状态没有发生改变,那么下次调用epool_wait时,该文件描述符仍会被返回

  16. ET边缘工作模式:只返回该文件描述符上自上次调用epoll_wait函数后发生的就绪事件.若应用程序没有处理完所有的就绪事件,且文件描述符上事件状态没有发生改变,那么下一次调用epoll_wait不会被返回,除非上面有新的就绪事件发生.该模式要求应用程序立即对就绪事件进行响应,否则可能丢失事件

IO复用

IO操作就是运行代码的过程中,可能需要对文件读写的操作.即将文件输入到内存 和将代码执行结果产生的文件输出到外设

涉及到数据交换的地方,就需要IO接口,如网络IO,磁盘IO

五种IO模型

输入阶段=等待数据准备好+从内核向进程拷贝数据

  1. 阻塞IO

  2. 进程或线程等待某个条件,条件不满足则一直等下去;应用进程通过recvfrom接收数据,但由于内核还没准备好数据报,进程就会被阻塞住

  3. 直到内核准备好数据报,recvfrom完成数据报复制工作,进程才会结束阻塞状态

  4. 非阻塞IO

  5. 进程通过轮询的方式不断去问内核有没有准备好,某一次轮询发现数据已经准备好了,就把数据从内核拷贝到用户空间,没准备好时,应用进程直接返回,不再一味去等

  6. IO复用

  7. 调用select/poll,阻塞在这两个调用的其中一个之上,比如阻塞在select调用上,等待数据报变为可读,

  8. 当select返回套接字可读这一条件时,调用recvfrom将所读数据报都复制到应用进程缓冲区.

  9. 多个进程IO注册到同一个select,当用户进程调用select,select监听所有注册好的IO,任意一个所需数据准备好,则返回条件,通过recvfrom进行数据拷贝

  10. 信号驱动IO

  11. 开启套接字信号驱动功能,通过sigaction系统调用安装一个信号处理函数,该函数立即返回,不阻塞;

  12. 数据报准备好后,内核为进程产生一个SIGIO信号递交给进程;

  13. 可以在信号处理函数中调用recvfrom读取数据报,通知主循环数据已准备好待处理

  14. 可以立即通知主循环,读取数据报

  15. 等待数据报到达期间,进程不被阻塞,主循环可以继续执行,等待来自信号处理函数的通知:既可以是数据已经准备好待处理,也可以是数据报已经准备好被读取

四种同步模型区别只在第一阶段等待数据的处理方式不同,第二阶段均为将数据从内核拷贝到用户空间,期间进程阻塞于recvfrom调用

  1. 异步IO

信号IO是内核通知我们何时可以启动一个IO操作;异步IO是告诉我们IO操作何时完成

  1. 进程告知内核启动某个操作,并由内核在整个操作完成后通知进程
  2. 比如用户进程调用aio_read函数,告诉内核整个操作完成时如何通知我们,然后就去做别的事
  3. 当内核收到aio_read后,立即返回,然后内核开始等待数据准备,之后把数据拷贝到用户空间,最后通知进程本次IO已经完成

孤儿进程和僵尸进程

孤儿进程:一个父进程退出,而他的一个或者多个子进程还在运行,那些子进程都将成为孤儿进程,将被INIT进程所收养,并由INIT进程对它们完成状态收集工作

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,被称为僵尸进程

介绍一下所知道的锁

互斥锁:任何时刻保证只有一个线程可以持有该锁,其他线程必须等待指导该锁被释放

自旋锁:基于忙则等待,线程在尝试获取锁时会不断轮询,直到锁被释放

其他的锁都是基于这两个的:读写锁,乐观锁(先修改共享资源,出问题再回退),悲观锁(访问资源要先上锁)