操作系统-

用户态和内核态的区别(20260319)

1.用户态和内核态

内核态和用户态是操作系统中的两种运行模式。它们的主要区别在于权限和可执行的操作:

  • 内核态(Kernel Mode):在内核态下,CPU可以执行所有的指令 和访问所有的硬件资源 。这种模式下的操作具有更高的权限,主要用于操作系统内核的运行。
  • 用户态(User Mode):在用户态下,CPU只能执行部分指令集无法直接访问硬件资源 。这种模式下的操作权限较低,主要用于运行用户程序

进程管理

1.进程,线程,协程的区别

  • 进程是操作系统中进行资源分配(虚拟内存,文件描述符,信号量)的基本单位,它拥有自己的独立内存空间和系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消息队列、信号量等。由于进程拥有独立的内存空间,因此其稳定性和安全性相对较高,但同时上下文切换的开销也较大,因为需要保存和恢复整个进程的状态。
  • 线程是进程内的一个执行单元,是CPU调度的基本单位。与进程不同,线程共享进程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享内存空间,因此存在数据竞争和线程安全的问题,需要通过同步和互斥机制来解决。
  • 协程是一种用户态的轻量级线程,其调度完全由用户程序控制,而不需要内核的参与。协程拥有自己的寄存器上下文和栈,但与其他协程共享堆内存。协程的切换开销非常小,因为只需要保存和恢复协程的上下文,而无需进行内核级的上下文切换。这使得协程在处理大量并发任务时具有非常高的效率。然而,协程需要程序员显式地进行调度和管理,相对于线程和进程来说,其编程模型更为复杂。

2.线程切换的流程

  • 上下文保存:当操作系统决定切换到另一个线程时,它首先会保存当前线程的上下文信息。上下文信息包括寄存器状态、程序计数器、栈指针、标志位等,用于保存线程的执行状态。(保存在线程控制块中 TCB)
  • 切换到调度器:操作系统将执行权切换到调度器(Scheduler)。调度器负责选择下一个要执行的线程,并根据调度算法做出决策。
  • 上下文恢复:调度器选择了下一个要执行的线程后,它会从该线程保存的上下文信息中恢复线程的执行状态。
  • 切换到新线程:调度器将执行权切换到新线程,使其开始执行。

3.进程的状态

创建状态 -> 就绪状态:初始化PCB,分配虚拟内存,内核栈,文件描述符表,信号和定时器,初始化执行上下文

4.进程的上下文切换

CPU的上下文切换:前一个任务的 CPU 上下文(CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

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

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

5.进程间的通讯

管道:管道本质上可以理解为是一个文件,上一个进程写,下一个进程读,管道是在内存里的。

匿名管道:一种在父子进程或者兄弟进程之间进行通信的机制,只能用于具有亲缘关系的进程间通信,通常通过pipe系统调用创建。

命名管道:一种允许无关的进程间进行通信的机制,基于文件系统,可以在不相关的进程之间进行通信

消息队列:保存在内核的消息列表

共享内存:分配一个共享内存,每个进程都可以访问(拿出一块虚拟空间映射到相同的物理内存上) 不需要陷入内核态

信号:异步通信机制,用于用户进程和内核的交互,来源包括:硬件(ctrl+C)和软件(kill)

信号量:锁

socket:不同主机的进程间通信,tcp/udp

6.线程间的通信

互斥锁

读写锁

自旋锁(CAS)

信号量:表示资源的数量

条件变量

7.进程调度算法

非抢占式的先来先服务 (适用于CPU密集型作业)

最短作业优先 (长任务被饿死)

高响应比优先 优先权 = (等待时间 + 要求服务时间)/ 要求服务时间

时间片轮转 非抢占式

最高优先级调度 包括抢占式和非抢占式

多级反馈队列调度:「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列;

内存管理

1.虚拟内存和物理内存

虚拟内存:是操作系统提供给每个运行中程序的一种地址空间,每个程序在运行时认为自己拥有的内存空间就是虚拟内存,其大小可以远远大于物理内存的大小。虚拟内存通过将程序的地址空间划分成若干个固定大小的页或段,并将这些页或者段映射到物理内存中的不同位置,从而使得程序在运行时可以更高效地利用物理内存。

物理内存:物理内存是计算机实际存在的内存,是计算机中的实际硬件部件。

虚拟内存的好处:

  • 第一,虚拟内存可以使得进程的运行内存超过物理内存大小,因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。
  • 第二,由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的。进程也没有办法访问其他进程的页表,所以这些页表是私有的,这就解决了多进程之间地址冲突的问题。
  • 第三,页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。

2.操作系统的内存管理

Linux 是通过对内存分页的方式来管理内存,分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫页(Page)。在 Linux 下,每一页的大小为 4KB。

虚拟地址与物理地址之间通过页表来映射,页表是存储在内存里的,内存管理单元 (MMU)就做将虚拟内存地址转换成物理地址的工作。

而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行

把虚拟内存地址,切分成页号和偏移量;根据页号,从页表里面,查询对应的物理页号;直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

3.页表

页表是存储在内存里的,内存管理单元 (MMU)就做将虚拟内存地址转换成物理地址的工作。

而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行

采用分页机制,页与页之间是紧密排列的,不会有外部碎片,但是会存在内部内存碎片

把虚拟内存地址,切分成页号和偏移量;根据页号,从页表里面,查询对应的物理页号;直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

4.段表

虚拟地址也可以通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段(代码,数据,堆,栈),每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址

5.多级页表和TLB的作用

多级页表:简单页表会导致页表本身占用的内存空间很大,多级页表通过设置页目录表,指向二级页表;二级页表(普通页表)指向物理页面

  • 解决了连续存放问题:现在,只有那个小小的"页目录表"需要连续存放(通常它也很小,能放在一个页框里)。而大量的"二级页表"可以被离散地存放在内存的任何空闲页框中。这完美地贯彻了分页的思想。

  • 为解决"全部在场"问题提供了可能:我们可以只把"页目录表"和当前需要的少数几个"二级页表"放入内存。如果访问到一个逻辑地址,其对应的二级页表不在内存中,就会触发一个缺页中断(这属于虚拟存储的范畴)。操作系统再把需要的那个二级页表从硬盘调入内存。这样就无需让整个页表体系常驻内存了。

TLB:缓存了虚拟页号与物理页号

6.程序的内存布局

  • 代码段,包括二进制可执行代码;
  • 数据段,包括已初始化的静态常量和全局变量;
  • BSS 段,包括未初始化的静态变量和全局变量;
  • 堆段,包括动态分配的内存,从低地址开始向上增长;
  • 文件映射段,包括动态库、共享内存等;
  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;

7.堆、栈的区别

分配方式:堆是动态分配内存,由程序员手动申请和释放内存,通常用于存储动态数据结构和对象。栈是静态分配内存,由编译器自动分配和释放内存,用于存储函数的局部变量和函数调用信息。

内存管理:堆需要程序员手动管理内存的分配和释放,如果管理不当可能会导致内存泄漏或内存溢出。栈由编译器自动管理内存,遵循后进先出的原则,变量的生命周期由其作用域决定,函数调用时分配内存,函数返回时释放内存。

大小和速度:堆通常比栈大,内存空间较大,动态分配和释放内存需要时间开销。栈大小有限,通常比较小,内存分配和释放速度较快,因为是编译器自动管理。

8.写时复制

主进程在执行 fork 的时候,操作系统会把主进程的「页表」复制一份给子进程,这个页表记录着虚拟地址和物理地址映射关系,而不会复制物理内存,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。

在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题

9.操作系统内存不足的时候会发生些什么?

应用程序通过 malloc 函数申请内存的时候,实际上申请的是虚拟内存,此时并不会分配物理内存。

当应用程序读写了这块虚拟内存,CPU 就会去访问这个虚拟内存, 这时会发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler (缺页中断函数)处理。

缺页中断处理函数会看是否有空闲的物理内存,如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。

如果没有空闲的物理内存,那么内核就会开始进行回收内存的工作,回收的方式主要是两种:直接内存回收和后台内存回收。

  • 后台内存回收(kswapd):在物理内存紧张的时候,会唤醒 kswapd 内核线程来回收内存,这个回收内存的过程异步的,不会阻塞进程的执行。
  • 直接内存回收(direct reclaim):如果后台异步回收跟不上进程内存申请的速度,就会开始直接回收,这个回收内存的过程是同步的,会阻塞进程的执行。

如果直接内存回收后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会放最后的大招了 ------触发 OOM (Out of Memory)机制。O

OM Killer 机制会根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存依然不足,OOM Killer 会继续杀死占用物理内存较高的进程,直到释放足够的内存位置。

主要有两类内存可以被回收,而且它们的回收方式也不同。

  • 文件页(File-backed Page):内核缓存的磁盘数据(Buffer)和内核缓存的文件数据(Cache)都叫作文件页。大部分文件页,都可以直接释放内存,以后有需要时,再从磁盘重新读取就可以了。而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放。所以,回收干净页的方式是直接释放内存,回收脏页的方式是先写回磁盘后再释放内存。
  • 匿名页(Anonymous Page):这部分内存没有实际载体,不像文件缓存有硬盘文件这样一个载体,比如堆、栈数据等。这部分内存很可能还要再次被访问,所以不能直接释放内存,它们回收的方式是通过 Linux 的 Swap 机制,Swap 会把不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。

10.页面置换有哪些算法?

页面置换算法的功能是,当出现缺页异常,需调入新页面而内存已满时,选择被置换的物理页面,也就是说选择一个物理页面换出到磁盘,然后把需要访问的页面换入到物理页。

最佳页面置换算法(OPT

置换在「未来」最长时间不访问的页面。

理想情况下的算法

先进先出置换算法(FIFO

选择在内存驻留时间很长的页面进行中置换

最近最久未使用的置换算法(LRU

选择最长时间没有被访问的页面进行置换

时钟页面置换算法(Lock

该算法的思路是,把所有的页面都保存在一个类似钟面的「环形链表」中,一个表针指向最老的页面。

当发生缺页中断时,算法首先检查表针指向的页面:

  • 如果它的访问位位是 0 就淘汰该页面,并把新的页面插入这个位置,然后把表针前移一个位置;
  • 如果访问位是 1 就清除访问位,并把表针前移一个位置,重复这个过程直到找到了一个访问位为 0 的页面为止;

最不常用置换算法(LFU

当发生缺页中断时,选择「访问次数」最少的那个页面,并将其淘汰。

中断

1.什么是中断?

CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,这一过程便是中断。

中断分为外部中断和内部中断:

  • 外部中断分为可屏蔽中断和不可屏蔽中断:
    • 可屏蔽中断通过INTR线向CPU请求的中断,主要来自外部设备如硬盘,打印机,网卡等。此类中断并不会影响系统运行,可随时处理,甚至不处理,所以名为可屏蔽。
    • 不可屏蔽中断通过NMI线向CPU请求的中断,如电源掉电,硬件线路故障等。
  • 内部中断分为陷阱、故障、终止:
    • 陷阱:是一种有意的,预先安排的异常事件 ,一般是在编写程序时故意设下的陷阱指令,而后执行到陷阱指令后,CPU将会调用特定程序进行相应的处理,处理结束后返回到陷阱指令的下一条指令。如系统调用,程序调试功能等。
    • **故障:故障是在引起故障的指令被执行,但还没有执行结束时,CPU检测到的一类的意外事件。**出错时交由故障处理程序处理,如果能处理修正这个错误,就将控制返回到引起故障的指令即CPU重新执这条指令。如果不能处理就报错。常见的故障为缺页
    • **终止:执行指令的过程中发生了致命错误,不可修复,程序无法继续运行,只能终止,通常会是一些硬件的错误。**终止处理程序不会将控制返回给原程序,而是直接终止原程序

2.中断的流程

中断是计算机系统中一种机制,用于在处理器执行指令时暂停当前任务,并转而执行其他任务或处理特定事件。

以下是中断的基本流程:

  • 发生中断:当外部设备或者软件程序需要处理器的注意或者响应时,会发出中断信号。处理器在接收到中断信号后,会停止当前执行的指令,保存当前执行现场,并跳转到中断处理程序执行。
  • 中断响应:处理器接收到中断信号后,会根据中断向量表找到对应的中断处理程序的入口地址。 处理器会保存当前执行现场(如程序计数器、寄存器状态等),以便在中断处理完成后能够恢复执行。
  • 中断处理:处理器跳转到中断处理程序的入口地址开始执行中断处理程序。中断处理程序会根据中断类型进行相应的处理,可能涉及到保存现场、处理中断事件、执行特定任务等。

网络i/o

1.I/O模型的种类

  • 阻塞I/O模型:应用程序发起I/O操作后会被阻塞,直到操作完成才返回结果。适用于对实时性要求不高的场景。//在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO;

  • 非阻塞I/O模型:应用程序发起I/O操作后立即返回,不会被阻塞,但需要不断轮询或者使用select/poll/epoll等系统调用来检查I/O操作是否完成。适合于需要进行多路复用的场景,例如需要同时处理多个socket连接的服务器程序。//在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它数据要的数据为止。

  • I/O复用模型:通过select、poll、epoll等系统调用,应用程序可以同时等待多个I/O操作,当其中任何一个I/O操作准备就绪时,应用程序会被通知。适合于需要同时处理多个I/O操作的场景,比如高并发的服务端程序。//进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。(Redis)

  • 信号驱动I/O模型:应用程序发起I/O操作后,可以继续做其他事情,当I/O操作完成时,操作系统会向应用程序发送信号来通知其完成。适合于需要异步I/O通知的场景,可以提高系统的并发能力。//首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即刻返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。

  • 异步IO:应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。

2.服务器处理并发请求的方式

  • 单线程web服务器方式:web服务器一次处理一个请求,结束后读取并处理下一个请求,性能比较低,一次只能处理一个请求。
  • 多进程/多线程web服务器:web服务器生成多个进程或线程并行处理多个用户请求,进程或线程可以按需或事先生成。有的web服务器应用程序为每个用户请求生成一个单独的进程或线程来进行响应,不过,一旦并发请求数量达到成千上万时,多个同时运行的进程或线程将会消耗大量的系统资源。(即每个进程只能响应一个请求,并且一个进程对应一个线程)
  • I/O多路复用web服务器:web服务器可以I/O多路复用,达到只用一个线程就能监听和处理多个客户端的 i/o 事件。

3.零拷贝

传统 IO 的工作方式,从硬盘读取数据,然后再通过网卡向外发送,我们需要进行 4 上下文切换,和 4 次数据拷贝,其中 2 次数据拷贝发生在内存里的缓冲区和对应的硬件设备之间,这个是由 DMA 完成,另外 2 次则发生在内核态和用户态之间,这个数据搬移工作是由 CPU 完成的。

零拷贝技术通过一次系统调用(sendfile 方法)合并了磁盘读取与网络发送两个操作,降低了上下文切换次数。只需要 2次上下文切换和数据拷贝次数,就可以完成文件的传输

相关推荐
悟空码字2 小时前
别再让你的SpringBoot包"虚胖"了!这份瘦身攻略请收好
java·spring boot·后端
暴力求解2 小时前
Linux---进程间通信
linux·运维·服务器
哆啦A梦15882 小时前
java项目在后端做跨域配置
java·vue3
盐水冰2 小时前
【烘焙坊项目】后端搭建(13)- 数据统计--图形报表
java·后端·学习·spring
野犬寒鸦2 小时前
从零起步学习计算机操作系统:I/O篇
服务器·开发语言·网络·后端·面试
易雪寒2 小时前
Java List 根据List中对象的属性值是否相同作为同一组,分割成多个连续的子List
java·数据结构·list·分组切割
小王不爱笑1322 小时前
Kubernetes(K8s)核心知识点
java
桑榆肖物2 小时前
.NET 10 Native AOT 在 Linux 嵌入式设备上的实战
java·linux·.net·aot
YMWM_2 小时前
磁盘的分区格式MBR和GPT的区别
linux·磁盘分区