代码随想录每日一题:操作系统

进程和线程

进程和线程的区别

进程是系统进行资源分配和调度的基本单位。

线程(thread)是操作系统能够进行运算调度的最小单位,线程是进程的子任务,是进程内的执行单元。

一个进程至少有一个线程,一个进程可以运行多个线程,这些线程共享同一块内存。

资源开销:

进程:由于每个进程都有独立的内存空间,创建和销毁进程的开销较大。进程间切换需要保存和恢复整个进程的状态,因此上下文切换的开销较高。

线程:线程共享相同的内存空间,创建和销毁线程的开销较小。线程间切换只需要保存和恢复少量的线程上下文,因此上下文切换的开销较小。

通信与同步:

进程:由于进程间相互隔离,进程之间的通信需要使用一些特殊机制,如管道、消息队列、共享内存等。

线程:由于线程共享相同的内存空间,它们之间可以直接访问共享数据,线程间通信更加方便。

安全性:

进程:由于进程间相互隔离,一个进程的崩溃不会直接影响其他进程的稳定性。

线程:由于线程共享相同的内存空间,一个线程的错误可能会影响整个进程的稳定性。

进程调度算法

进程调度算法你了解多少

进程调度算法是操作系统中用来管理和调度进程(也称为任务或作业)执行的方法。这些算法决定了在多任务环境下,如何为各个进程分配CPU时间,以实现公平性、高吞吐量、低延迟等不同的调度目标。

1.先来先服务调度算法

按照进程到达的先后顺序进行调度,即最早到达的进程先执行,直到完成或阻塞。

2.最短作业优先调度算法

优先选择运行时间最短的进程来运行

3.高响应比优先调度算法

综合考虑等待时间和服务时间的比率,选择具有最高响应比的进程来执行

4.时间片轮转调度算法

将CPU时间划分为时间片(时间量),每个进程在一个时间片内运行,然后切换到下一个进程。

5.最高优先级调度算法

为每个进程分配一个优先级,优先级较高的进程先执行。这可能导致低优先级进程长时间等待,可能引发饥饿问题。

6.多级反馈队列调度算法

将进程划分为多个队列,每个队列具有不同的优先级,进程在队列之间移动。具有更高优先级的队列的进程会更早执行,而长时间等待的进程会被提升到更高优先级队列。

7.最短剩余时间优先:每次选择剩余执行时间最短的进程来执行。

8.最大吞吐量调度:旨在最大化单位时间内完成的进程数量

9.最大吞吐量调度:旨在最大化单位时间内完成的进程数量

进程间通信

进程间有哪些通信方式

1.管道(Pipe):

管道是一种半双工的通信方式,用于在父子进程或者同一用户下的两个进程之间进行通信。可以分为匿名管道和命名管道(FIFO)。匿名管道主要用于具有亲缘关系的进程,而命名管道可以用于不同用户之间的进程通信。

2.消息队列(Message Queue):

消息队列是一种消息传递机制,允许进程通过在消息队列中发送和接收消息来进行通信。

3.信号量(Semaphore):

信号量用于控制多个进程对共享资源的访问。通过信号量可以实现进程的同步和互斥,避免竞态条件。

4.共享内存(Shared Memory):

共享内存允许多个进程在它们之间共享同一块物理内存区域,从而实现高效的数据交换。

5.套接字(Socket):

套接字是一种用于网络通信的IPC方式,可以用于在不同计算机上的进程之间进行通信。它不仅可以用于同一主机上的进程通信,还可以用于跨网络的进程通信。

死锁

什么是死锁?如何避免死锁?

死锁是指两个或多个进程在争夺系统资源时,由于互相等待对方释放资源而无法继续执行的状态。

死锁只有同时满足以下四个条件才会发生:

  • 互斥条件:一个进程占用了某个资源时,其他进程无法同时占用该资源。
  • 持有并等待条件:一个进程可以在等待其他资源的同时持有一些资源。
  • 不可剥夺条件:资源不能被强制性地从一个进程中剥夺,只能由持有者自愿释放。
  • 环路等待条件:多个进程之间形成一个循环等待资源的链,每个进程都在等待下一个进程所占有的资源。

只需要破坏上面一个条件就可以避免死锁,最常见的并且可行的就是使用资源有序分配法(让所有进程按照相同的顺序请求资源),来破坏环路等待条件。

虚拟内存

什么是虚拟内存?为什么需要虚拟内存?

虚拟内存就是在进程创建的时候会为其分配一段连续的虚拟地址空间,他不是真实存在的,是通过映射与实际物理内存地址连接的。它可以让进程感觉在使用连续的地址空间,可以访问比实际物理内存更大的内存地址。

为什么需要虚拟内存?

1.内存扩展:可以让进程使用比实际物理空间更大的地址空间,可以实施更大的进程,做更多的运算。

2.内存隔离:虚拟内存可以让进程独享一段连续的虚拟地址空间,进程之间互不干扰。

3.物理内存管理:进程运行时,将数据和程序放入物理内存,如果物理内存满了,选择不常用的数据和程序,将其放进硬盘,来释放内存。

4.页面交换:当物理内存不足时,操作系统将一部分数据从物理内存移动到硬盘的虚拟内存中,这个过程被称为页面交换,再次需要时,可以从虚拟内存中加载到物理内存里,保证系统继续运行。

5.内存映射文件:虚拟内存可以把文件映射在内存中,可以使得访问文件和访问内存一样高效

内存分段和分页

什么是内存分段和分页?它们的作用是什么?

内存分段是将一个程序的内存空间分为不同的逻辑段(segments),每个段代表程序的一个功能模块或数据类型,如代码段、数据段、堆栈段等。每个段都有其自己的大小和权限。

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

作用:

逻辑隔离: 内存分段和分页都实现了程序的逻辑隔离,使不同的功能模块或数据类型能够被单独管理和保护,提高了程序的可靠性和安全性。

内存保护: 通过将不同的段或页面设置为只读、可读写、不可执行等权限,操作系统可以确保程序不会越界访问或修改其他段的内容,从而提高了系统的稳定性。

虚拟内存: 分段和分页都有助于实现虚拟内存的概念,允许应用程序认为它们在使用的是一个比实际物理内存更大的内存空间。

内存共享: 通过分页,操作系统可以实现内存页面的共享,从而节省内存空间,多个进程可以共享相同的代码或数据页面。

内存管理: 分页更加灵活,允许操作系统将不同进程的页面分散存放在物理内存中,从而提高内存利用率。分段则更适用于管理不同的逻辑模块。

用户态和内核态

用户态(User Mode)和内核态(Kernel Mode)是操作系统中两种不同的执行模式,用于控制进程或程序对计算机硬件资源的访问权限和操作范围。这两种模式的区别在于进程在不同模式下可以执行的指令和访问的资源不同。

用户态(User Mode):

处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。

内核态(Kernel Mode):

处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。

页面置换算法

页面置换算法,例如LRU(最近最少使用)、FIFO(先进先出)等

常见页面置换算法有:最佳置换算法(OPT)、先进先出(FIFO)、最近最久未使用算法(LRU)、时钟算法(Clock)

最佳置换算法: 该算法根据未来的页面访问情况,选择最长时间内不会被访问到的页面进行置换。那么就有一个问题了,未来要访问什么页面,操作系统怎么知道的呢?操作系统当然不会知道,所以这种算法只是一种理想情况下的置换算法,通常是无法实现的。

先进先出算法:也就是最先进入内存的页面最先被置换出去。这个算法比较简单明了,就不过多解释了。但是先进先出算法会存在一个问题,就是Belady问题,即随着分配给进程的空闲页面数增加,缺页的情次反而也会增加。

这和我们常识是相悖的,因为我们通常认为如果一个进程经常发生缺页,那么就应该应该为他多分配一点内存。然而使用FIFO算法时,反而可能导致更多缺页情况出现。这就是Belady问题,Belady问题只会在使用FIFO算法时出现。

最近最久未使用算法:LRU算法基于页面的使用历史,通过选择最长时间未被使用的页面进行置换。LRU算法的核心思想是,最近被访问的页面可能在未来被再次访问,而最长时间未被访问的页面可能是最不常用的,因此将其置换出去可以腾出空间给新的页面

LRU算法通常是使用一个数据结构去维护页面的使用历史,维护使用历史就是通过访问字段实现的。访问字段的位数和操作系统分配给该进程的页面数有关,比如分配4个页面,访问字段就是2位,16个页面,访问字段就是4位,依次类推。如此,每一个页面的访问字段都可以不同,通过访问字段的不同,我们就可以判断页面的使用历史。

时钟算法:Clock算法基于一个环形链表或者循环队列数据结构来管理页面的访问情况,用于选择被置换的页面。Clock算法的核心思想是通过使用一个指针(称为时钟指针)在环形链表上遍历,检查页面是否被访问过。这个访问过同样需要我们上面说到的访问字段来表示,此时访问字段只有一位。每个页面都与一个访问位相关联,标记该页面是否被访问过。

当需要进行页面置换时,Clock算法从时钟指针的位置开始遍历环形链表。

如果当前页面的访问位为0,表示该页面最久未被访问,可以选择进行置换。将访问位设置为1,继续遍历下一个页面。

如果当前页面的访问位为1,表示该页面最近被访问过,它仍然处于活跃状态。将访问位设置为0,并继续遍历下一个页面如果遍历过程中找到一个访问位为0的页面,那么选择该页面进行置换。

进程同步和互斥

解释一下进程同步和互斥,以及解决这些问题的方法。

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

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

解决进程同步和互斥问题的方法包括:

  1. 临界区(Critical Section): 将可能引发互斥问题的代码段称为临界区。为了实现互斥,每个进程在进入临界区前必须获取一个锁,退出临界区后释放该锁。这确保同一时间只有一个进程可以进入临界区。

  2. 互斥锁(Mutex): 互斥锁是一种同步机制,用于实现互斥。每个共享资源都关联一个互斥锁,进程在访问该资源前需要先获取互斥锁,使用完后释放锁。只有获得锁的进程才能访问共享资源。

  3. 信号量:信号量包括一个计数器和一组等待队列。进程可以尝试获取信号量,如果计数器大于零,则减少计数器并继续执行。否则,进程将进入等待队列,直到其他进程释放信号量。

  4. 条件变量(Condition Variable): 条件变量用于在进程之间传递信息,以便它们在特定条件下等待或唤醒。通常与互斥锁一起使用,以确保等待和唤醒的操作在正确的时机执行。

中断和异常

什么是中断和异常?它们有什么区别?

中断是指计算机处理器在执行程序过程中,由硬件或外部设备发出的信号,用于暂停当前程序的执行,以处理紧急事件或来自外部设备的请求。中断可以分为外部中断和内部中断。

外部中断: 由外部设备引发,如键盘输入、鼠标事件、硬件故障等。外部中断会导致处理器从当前任务中切换到中断处理程序,然后再回到原来的任务。

内部中断(时钟中断): 是由计算机内部的定时器或时钟引发的。它定期发生,用于确保操作系统的正常运行,例如进行时间片轮转调度。

而异常指在程序执行过程中发生的非预期事件或错误条件,如非法操作码、地址越界、算术溢出等。

区别:

触发原因:

  • 中断是由硬件或外部设备引发的,用于处理外部事件或请求。
  • 异常是由程序内部的错误或非预期事件引发的,如非法指令、除以零等。

处理方式:

  • 中断会暂停当前程序的执行,切换到中断处理程序,处理完成后再返回到原来的任务。
  • 异常会引发特定的异常处理机制,通常涉及中断当前指令的执行,跳转到异常处理程序,然后恢复执行。

频率:

  • 中断的发生频率取决于外部事件或设备的活动,可能不是定期发生的。
  • 异常通常是在程序执行过程中出现错误时触发的,发生频率相对不那么高。

典型的锁

介绍一下几种典型的锁

互斥锁:互斥锁是一种最常见的锁类型,用于实现互斥访问共享资源。在任何时刻,只有一个线程可以持有互斥锁,其他线程必须等待直到锁被释放。这确保了同一时间只有一个线程能够访问被保护的资源。

读写锁:读写锁允许多个线程同时“读取”共享资源,但在写入资源时需要独占访问,一旦有写者,则后续读者必须等待,唤醒时优先考虑写者,这对于读取频繁、写入较少的场景可以提高并发性能。

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

条件变量:条件变量是用于线程之间通信的一种同步机制,它与锁一起使用,用于等待某个条件满足。一个线程可以等待条件变量,并在满足条件时被唤醒。常用于生产者-消费者模型等场景。

线程同步方式

你知道的线程的同步方式有哪些?

多线程同时访问和修改某个数据的话,会造成数据的不一致和冲突问题,所以就需要线程同步,线程同步的方式有:

1.互斥锁

互斥锁就是,当一个资源被访问和操作时,会对这个资源加锁,把这个资源锁定,其他线程不能对其进行操作。直到上一个线程操作完成之后,会释放互斥锁,其他资源才可以进行操作。

2.信号量

信号量是互斥锁的扩展。允许多个线程同时访问同一个资源,当信号量大于1时,可以对资源进行访问;信号量为0时,其他线程阻塞。信号量为几,代表可以同时几个线程访问该资源。

3.条件变量

线程可以睡眠等待,而不是忙等待,这样可以节约CPU的资源。等某个条件成立的时候,线程再被唤醒。

4.读写锁

多个线程可以同时读取同一个数据,但是只允许一个线程对其进行写操作。读写锁适用于读多写少的场景。

5.自旋锁

类似于互斥锁。当资源被锁定之后,其他线程会在一个循环中等待,直到锁被释放。自旋锁适用于锁被持有时间较短的场景。

6.原子操作

确保某个操作是在单个步骤中完成,不会被其他线程所干扰。一般由硬件支持。

7.栅栏

就是允许多个线程等待,直到所有的线程都到达一个点之后,再同时执行。

8.事件

线程等待某个事件发生时,某个事件被触发了,等待的线程会被唤醒

0%