赞
踩
操作系统的核心组件,是系统中最底层、最基本的软件部分。它负责管理计算机的硬件资源,并为上层应用程序提供运行环境和系统服务。内核是操作系统与硬件之间的桥梁,它控制硬件的访问和操作,以及管理系统的各种资源,如内存、进程、文件系统和网络等。
内核通常运行在特权级别最高(特权0)的模式下,可以直接访问和操作硬件资源,而应用程序则运行在较低(特权3)的用户模式下,不能直接访问硬件。这种分层的设计使得内核具有很高的权限和控制权,能够保护系统的安全性和稳定性。
功能 | 描述 |
---|---|
硬件管理 | 初始化和管理硬件设备,与处理器、内存、输入输出设备等交互,确保它们正常工作。 |
进程管理 | 创建、调度和终止进程,进程间的通信和同步,保证多个应用程序同时运行而互不干扰。 |
内存管理 | 分配、回收和管理内存资源,实现虚拟内存技术,为应用程序提供更大的地址空间。 |
文件系统 | 管理文件和目录,实现文件的读写和组织,以及进行数据的持久化存储。 |
网络管理 | 提供网络通信功能,实现网络协议栈,使得计算机可连接到其他主机,进行数据交换和通信。 |
安全管理 | 确保系统的安全性,实施访问控制和权限管理,防止未授权访问和恶意行为。 |
即通过软中断实现系统调用的方式,允许用户态的应用程序请求操作系统内核的服务和功能,执行需要特权或只能由内核完成的任务,例如:读写文件,内存管理,网络通信 任务。
特征 | 用户态 (User Mode) | 内核态 (Kernel Mode) |
---|---|---|
权限级别 | 较低的权限级别,受到限制 | 较高的权限级别,具有更高的权限 |
访问硬件设备 | 不能直接访问硬件设备 | 可以直接访问和管理硬件设备 |
执行特权指令 | 不能执行特权指令 | 可以执行特权指令,如更改内存映射、启动停止硬件设备等 |
运行环境 | 应用程序运行的环境 | 操作系统内核运行的环境 |
系统调用 | 通过系统调用请求内核服务 | 内核处理系统调用请求,为应用程序提供特权服务 |
CPU抢占特性 | 不许独占CPU,用户态下的应用程序间进行任务切换,以实现多任务的并发执行 | 运行时不许被抢占,内核执行非常重要及敏感,抢占会导致系统不稳定 |
保证系统安全性与可靠性,通过特权等级限制访问作用域,针对一些危险操作如设置时钟、内存清理,若可通过用户态进行修改,面临无法修改的后果。
切换方式 | 触发原因 | 特点 |
---|---|---|
系统调用 | 用户态应用程序主动请求操作系统内核的服务 | 用户应用 通过特定的软中断指令或系统调用指令触发中断,切换到内核态,执行系统调用处理程序。处理好切换用户态执行。 |
异常 | 硬件或指令执行错误、特定事件发生被动切换 | 硬件异常或指令执行错误(如访问非法内存、除以零等)会触发异常,切换到内核态,执行异常处理程序。处理完成后,返回用户态继续执行。 |
硬盘中断 | 硬盘设备完成数据读写、硬盘状态发生特定事件,硬盘控制器向CPU主动发送中断信号 | 硬盘控制器向CPU主动发送中断信号,切换到内核态,执行硬盘中断处理程序。处理完成后,返回用户态继续执行。 |
特点 | 硬实时系统 | 软实时系统 |
---|---|---|
时间要求严格性 | 必须在规定的时刻内完成任务 | 对时间要求相对宽松,允许偶尔的轻微超时 |
后果严重性 | 任务延迟会导致系统故障或产生严重的后果 | 偶尔的轻微超时不会引起严重的后果或损害 |
应用场景 | 适于时间要求极高、安全性要求严格的场景 例如:飞行控制系统、核电厂控制系统等 | 适用于对时间要求相对宽松的场景 如:音频播放、视频播放、实时数据监控等 |
进程(正在上的课)是指正在运行程序的实例。进程是计算机系统中最基本的执行单位,它包含了程序代码、数据和执行环境,是操作系统进行资源分配和管理的基本单位。进程之间相互独立,互不影响,进程拥有自己的独立地址空间、文件描述符、系统资源等。
进程表(课程表)是记录操作系统中所有进程的信息和状态的数据结构。记录所有可执行进程的信息,操作系统根据进程表进行调度决策,记录进程的状态变化,包括就绪、运行、挂起、终止等生命周期
1. 用户线程:由应用管理,从用户的时间看到的线程,不依赖操作系统核心(ULT
1.1 守护线程:为用户线程服务的线程,当所有的用户线程执行结束之后,守护线程结束。
2. 内核线程:由OS管理,从OS内核视角看到的线程,依赖操作系统核心 (KLT
特征 | 进程(Process) | 线程(Thread) |
---|---|---|
定义 | 正在运行的程序的实例,是操作系统资源分配与管理的最小单位 | 进程中的执行单元,是进程的一部分,是CPU调度的最小单位 |
独立性 | 进程之间相互独立,互不影响 | 线程共享进程的代码和数据空间,可以并发执行,互相影响,线程在同一进程中存在共享地址空间 |
创建与销毁 | 进程在运行时由操作系统创建和销毁 | 线程由进程内的线程自己创建和销毁(开销较小) |
资源拥有 | 进程拥有自己的独立地址空间、文件描述符、系统资源等 | 线程共享进程的资源,如内存、文件等 |
调度与并发 | 进程通过进程调度算法获得CPU执行时间,实现并发执行 | 线程可以并发执行,共享进程的资源,实现更高效的并发处理 |
通信和同步 | 进程之间通信和同步需要使用进程间通信(IPC)机制,如管道、共享内存等 | 线程之间通信和同步相对容易,可以通过共享内存等直接方法实现 |
切换开销 | 1)进程切换的开销较大,需要切换地址空间和资源,2)涉及上下文切换,需要保存和恢复进程的状态。 | 线程切换的开销较小,因为共享了进程的资源(全局变量、静态变量) 只需切换执行栈和寄存器状态。 |
上下文切换 (Context Switch) 是一种 将 CPU 资源从一个进程分配给另一个进程的机制。
在切换的过程中,操作系统需要先存储当前进程的状态 (包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。
好处 | 描述 |
---|---|
并发执行 | 多线程可同时执行不同的任务,提高系统的响应速度和资源利用率。 |
响应性和交互性 | 多线程可以将耗时的操作放在一个线程中,保持程序的响应性和交互性,使得用户界面能够响应用户的输入。 |
提高性能 | 多线程允许程序在等待某些资源时继续执行其他任务,提高了程序的整体性能。 |
分工合作 | 多线程允许将复杂任务划分成多个小任务,在不同的线程中执行,简化程序设计维护。 |
数据共享与通信 | 多线程方便地进行数据共享和通信,使得不同线程之间能够相互交换数据和信息。 |
资源共享 | 多线程共享进程的代码和数据空间,节约了系统资源。 |
提高系统效率 | 充分利用计算机的多核处理器和多任务处理能力,提高系统的整体效率和吞吐量。 |
原因 | 描述 |
---|---|
正常退出(主动) | 进程完成了工作,执行退出系统调用(如 UNIX 中的 exit、Windows 中的 ExitProcess),自愿终止。 |
错误退出(主动) | 进程发现了错误,如文件不存在等,执行退出系统调用,自愿终止。 |
严重错误(被动) | 进程内部发生严重错误,如执行非法指令、引用不存在的内存等,可能会收到信号或异常,自愿或非自愿终止。 |
被其他进程杀死 | 其他进程调用系统调用(如 UNIX 中的 kill、Windows 中的 TerminateProcess)杀死某个进程,非自愿终止。 |
进程间通信方式 | 描述 |
---|---|
管道(Pipe) | 半双工通信方式,在父进程和子进程之间传递数据,适用于相关的进程间通信。 |
命名管道(Named Pipe) | 类似于管道进程间需要明确通信双方的命名,但可在不相关的进程之间传递数据,适用于不相关的进程间通信。 |
消息队列(Message Queue) | 消息传递机制,可在不相关的进程之间传递消息,适用于不相关的进程间通信。克服信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限缺点。 |
共享内存(Shared Memory) | 允许多个进程共享同一块物理内存,高效地进行数据传递,适用于需要高性能数据交换的进程间通信。 |
信号量(Semaphore) | 控制多个进程对共享资源的访问,防止竞态条件(即两个或多个线程同时对一共享数据进行修改,从而影响程序运行的正确性时),适用于需要控制资源访问的进程间通信。通过计数器实现锁机制 |
套接字(Socket) | 网络编程通信方式,适用于在不同主机的进程之间进行通信,也可用于本地进程间通信。 |
进程状态 | 描述 |
---|---|
运行态 | 进程实际占用 CPU 时间片运行,正在执行其指令和任务。 |
就绪态 | 进程具备运行条件,但因其他进程正在运行而处于等待状态,等待系统调度分配 CPU 时间片。本质仍然可运行,但是需要获得CPU的时间分片 |
阻塞态 | 进程暂时不具备运行条件,正在等待某种事件发生,例如等待输入/输出操作完成或等待某个资源就绪。 |
进程状态 | 描述 |
---|---|
新建态 | 进程刚创建出来,但尚未被系统调度执行。 新建流程:1)为新进程分配所需的资源和空间,2)设置进程为就绪态,等待调度执行。 |
就绪态 | 进程具备运行条件,已经准备好执行,但由于其他进程正在运行而处于等待状态,等待系统调度分配 CPU 时间片。 |
运行态 | 进程实际占用 CPU 时间片运行,正在执行其指令和任务。 |
阻塞态 | 进程暂时不具备运行条件,正在等待某种事件发生,例如等待输入/输出操作完成或等待某个资源就绪。 |
终止态 | 进程执行完毕到达结束点,或者因为错误而被中止,进程处于终止态。 终止流程:1)等待操作系统或相关进程进行善后处理,2)回收占用资源被系统删除。 |
线程通信方式 | 描述 |
---|---|
共享变量 | 多个线程访问和修改共享的变量,通过读写共享变量实现线程之间的数据共享和通信。需要使用互斥机制保护共享变量。 |
互斥量(Mutex) | 互斥量用于保护共享资源,确保在同一时刻只有一个线程可以访问共享资源。线程在访问共享资源前先尝试获取互斥量的锁。 |
信号量(Semaphore) | 信号量是一种计数器,用于控制多个线程对共享资源的访问。线程执行P操作尝试获取信号量,执行V操作释放信号量并通知其他线程。 |
条件变量(Condition Variable) | 条件变量用于在线程之间传递信号和信息,一个线程等待某个条件满足,另一个线程在满足条件时发出信号,通知等待的线程继续执行。 |
屏障(Barrier) | 屏障用于在多个线程之间同步操作,当所有线程都到达屏障点时,才允许它们继续执行。 |
读写锁(Read-Write Lock) | 读写锁允许多个线程同时读取共享资源,但在有写操作时,其他线程无法读取或写入共享资源。适用于读操作频繁、写操作较少的情况。 |
多个线程同时访问同一资源,所产生问题的解决方案
每个进程访问临界资源的那段程序成为临界区,一次仅允许一个进程使用的资源叫临界资源
解决冲突方法:
根据系统的资源分配策略规定的资源分配算法。
步骤 | 描述 |
---|---|
进程到达 | 当进程被创建或者从阻塞态切换到就绪态时,它进入就绪队列,按照到达的先后顺序排队等待 CPU 的执行。 |
执行 | CPU 从就绪队列中选择队首的进程来执行。该进程占用 CPU 执行一段时间,直到它完成任务或被阻塞(等待 I/O 操作等)。 |
完成或阻塞 | 当进程执行完毕,或因等待 I/O 操作等原因被阻塞,它从 CPU 移出,进入终止态或阻塞态。 |
下一个进程 | CPU 从就绪队列中选择队首的下一个进程,按照先来先服务顺序执行,直到all进程都完成。 |
优点: | 1)简单直观 2)一个单链表记录所有就绪进程 3)不会出现饥荒现象 4)公平(无优先级) |
缺点: | 1)不考虑优先级和执行时间,对于密集型进程后进容易造成系统响应时间过长 2)不灵活无法根据实际优先级情况进行动态调整。 |
步骤 | 描述 |
---|---|
进程到达 | 当进程被创建或从阻塞态切换到就绪态时,它进入就绪队列,并按照预计的运行时间排队等待 CPU 的执行。 |
选择最短作业 | CPU 从就绪队列中选择预计运行时间最短的进程来执行,优先选择执行时间短的作业。 |
执行 | 所选进程占用 CPU 执行一段时间,直到它完成任务或被阻塞(等待 I/O 操作等)。 |
完成或阻塞 | 当进程执行完毕或因等待 I/O 操作等原因被阻塞时,它从 CPU 移出,进入终止态或阻塞态。 |
下一个进程 | CPU 从就绪队列中选择预计运行时间最短的下一个进程,继续按照最短作业优先执行。 |
优点 | 1)最大程度减少平均等待时间 2)提高系统吞吐量 3)公平 4)无饥荒现象 |
缺点 | 1)需要知道每个进程运行时间,实际很难做到 2)对于某些长时间运行proc对短时pro影响大 |
平均运行时间 = 累加(权值i * 单进程i运行时间)/ 进程个数;
本例 : 4a + 3b + 2c + d)/4
调度程序总是选择剩余运行时间最短的那个进程运行,来个新进程进入队列就使用调度程序判断。
优点 | 1)最大程度减少平均等待时间 2)提高系统吞吐量 3)公平 4)无饥荒现象 5)不会长时间占用cpu |
缺点 | 1)需要知道每个进程运行时间,实际很难做到存在误差 2)频繁调度最短剩余时间进程,算法开销大 |
工作原理 | 描述 |
---|---|
进程到达: | 当进程被创建或从阻塞态切换到就绪态时,它进入就绪队列,并按照到达的先后顺序排队等待 CPU 的执行。 |
执行: | CPU 从队首选择第一个进程来执行,该进程被分配一个固定大小的时间片进行执行。 |
时间片用完: | 当进程执行的时间片用完后,它会被从 CPU 移出,并放回队尾,等待下一次调度。 |
下一个进程: | CPU 从队首选择下一个进程来执行,按照时间片轮询的方式继续执行。 |
循环: | 进程会依次在就绪队列中循环执行,每个进程都有机会得到执行。 |
优点 | 1)公平,每个进程都有机会得到执行,避免饥饿现象。2)简单 3) 响应时间相对较短,适用于交互式系统。 |
缺点 | 1) 时间片大小需要合理设置,如果时间片过小,频繁的上下文切换会带来较大的开销;如果时间片过大,可能导致长时间运行的进程响应较慢。 2) 长作业的平均等待时间相对较长,不适用于长时间运行的计算密集型任务。 |
它根据进程的优先级来选择下一个要执行的进程。每个进程被分配一个优先级,优先级较高的进程会优先于优先级较低的进程获得 CPU 的执行权。优先级调度可以根据不同的策略来设置进程的优先级,例如静态优先级和动态优先级。
给每个进程分配一定数量多彩票,中奖的进程进入运行态。
根据用户个数平分cpu占用时间,而非根据用户所持进程数
例如 A 9进程 B 1进程 -----> A 享有cpu占用时间与B相同均为 50%
初始化:优先度由高到低;时间片由少到多。
运行:上级队列来进程先执行,上级无进程抢占cpu,下级队列依次处理,若下级未处理完,上级来进程,则需将下级未处理完的进程重新放在下级队列,等上级队列处理后,再继续处理。
【进程管理】多级反馈队列调度算法_哔哩哔哩_bilibili
概念 | 定义 |
---|---|
CPU使用率 | CPU正在执行任务(非空闲状态)的时间百分比。 |
等待时间 | 进程轮流执行的时间,也即进程切换的时间。 |
吞吐量 | 单位时间内完成的进程数量。 |
响应时间 | 从提交进程到获得有用输出所经过的时间。 |
周转时间 | 从提交进程到完成进程所经过的时间。 |
定义:
僵尸进程是在子进程执行结束后,父进程没有及时回收子进程资源而产生的。当一个进程结束时,其实际资源会被回收,但是内核仍然需要保留其进程表项,以便父进程在调用wait()或waitpid()来获取子进程的退出状态。如果父进程没有调用这些函数来回收子进程的资源信息,那么子进程的进程表项就会一直存在,成为僵尸进程。
危害:
僵尸进程不会占用系统的CPU时间和内存资源,但如果僵尸进程过多,可能会占用过多的进程表项,导致系统的进程表耗尽。这种情况下,系统将无法创建新的进程,从而影响系统的正常运行。
解决方法:
定义:
孤儿进程(Orphan Process)是指一个子进程在其父进程终止后继续运行,而此时该子进程的新父进程会被系统指定为init进程(进程号为1(认新父))。通常,init进程是所有进程的祖先进程,它会负责回收孤儿进程的资源,保证系统的正常运行。
处理过程:
孤儿进程并不会对系统造成问题,因为它的资源会被init进程回收。当子进程终止时,init进程会调用wait()或waitpid()系统调用来获取子进程的退出状态,清理僵尸进程,并释放其占用的资源。
0)资源竞争 1)程序执行顺序不当
必要条件 | 描述 |
---|---|
竞争资源 | 多个进程同时竞争有限的资源,如独占设备、共享内存等。 当多个进程都需要获取同一资源时,可能导致死锁。 |
循环等待 | 进程之间存在资源申请的循环链,每个进程都在等待下一个进程释放它所需要的资源,形成循环等待的情况。 |
互斥条件 | 资源一次只能被一个进程占用,当一个进程持有了某些资源时,其他进程必须等待该资源释放,产生竞争和死锁。 |
不可剥夺条件 | 进程已获得的资源不能被其他进程抢占,只能由该进程主动释放。如果一个进程因无法获取所需资源而不释放已获得的资源,就会导致其他进程无法继续执行。 |
恢复方式 | 描述 |
---|---|
进程终止 | 终止部分或全部死锁进程,释放它们占用的资源,以解除死锁。 终止进程的选择可以根据优先级、资源使用量等进行调度。 (小孩抢玩具,老师强制谁也别想玩,放进玩具筐中) |
资源抢占 | 系统从某些死锁进程中抢占资源,然后将这些资源分配给其他进程,使其他进程能够继续执行。 (小孩抢玩具,老师强制一些小孩的玩具给傻小孩) |
回滚 | 在分布式系统中,采用事务回滚的方式解除死锁。当发现死锁时,回滚受影响的事务到某个已知的安全点,然后重新调度这些事务。 |
杀死进程 | 出现死锁时,系统选择直接杀死整个死锁进程组,以释放所有相关资源。 (小孩抢玩具,老师强制抢玩具的孩子别想玩玩具,放进玩具筐中) |
时间限制 | 设置资源申请的时间限制,若进程在一定时间内无法获得所需资源,则放弃资源请求,释放已获得的资源,然后重新发起资源请求。 (小孩抢玩具,老师强制小孩只能玩5min,然后就回座位上去) |
方法 | 描述 |
---|---|
破坏互斥条件 | 允许多个进程同时访问某些资源,即资源不再是互斥的。可以对某些可共享的资源允许多个进程同时获取。 |
破坏请求与保持条件 | 进程在申请资源时,需要一次性获取全部所需资源,而不是先获取部分资源后再请求其他资源。 |
破坏不可剥夺条件 | 当进程持有某些资源并请求其他资源时,如果不能满足其全部资源需求,可以强制剥夺该进程持有的资源。或虚拟化实现 |
破坏循环等待条件 | 通过对资源进行全局编号,要求进程按照编号顺序申请资源,避免形成循环等待。 或引入资源有序分配策略。 |
死锁类型 | 描述 |
---|---|
饥饿死锁 | 一个或多个进程由于无法获取所需资源而一直处于等待状态,无法继续执行。 虽不是真正的死锁,但会导致资源浪费。 (A只玩玩具,但是A因为个头太高了,总被个头低的小朋友抢先分配获得玩具) |
资源死锁 | 多个进程相互等待彼此占用的资源,导致无法进行进一步执行。 (A想玩B的玩具,B想玩A的玩具,但是他俩又不能存在没有玩具的时候) |
时间死锁 | 进程在不同的时间点请求资源,形成时间上的竞争,从而导致死锁。 |
通信死锁 | 进程 A 给进程 B 发了一条消息,然后进程 A 阻塞直到进程 B 返回响应。假设请求消息丢失了,那么进程 A 在一直等着回复,进程 B 也会阻塞等待请求消息到来。 (A只玩玩具a,A把玩具a借给B了,B把玩具a给卖了(搞丢了),A就不玩其他的东西了) |
活锁 | 某些情况下,当进程意识到它不能获取所需要的下一个锁时,就会尝试礼貌的释放已经获得的锁,然后等待非常短的时间再次尝试获取。(未出现阻塞,但是依旧无法获得资源) (A想玩玩具,B也想玩玩具,但是这两个属孔融的,都很谦让然而谁也没有真正拥有玩具) |
名称 | 描述 |
---|---|
物理地址 | 计算机中实际内存的位置或存储单元的编号。(唯一的门牌号) 它是直接与硬件相关的地址,用于访问实际硬件设备或内存位置。 |
逻辑地址 | 相对于计算机的逻辑地址空间的地址。( 段号 + 段内偏移量(有效地址 ) 不考虑具体的硬件实现和内存分布情况。 |
有效地址 | 程序中使用的地址或指针对应的地址。(段内偏移地址,门户号) 在程序执行过程中,逻辑地址会被转换为有效地址,用于访问相应的数据。 |
线性地址 | 是逻辑地址到物理地址之间的一个中间层。 通过线性地址映射机制,将逻辑地址转换为物理地址,用于访问内存中的数据。 |
虚拟地址 | 是逻辑地址的一种特殊类型,通过虚拟内存机制,将逻辑地址映射为线性地址,再通过线性地址映射为物理地址。 |
段基址+逻辑地址=线性地址 ,若无分页机制 , 线性地址 = 物理地址
真实内存地址转换:逻辑地址---> 有效地址 ---> 线性地址映射 -----> MMU(内存管理单元)---> 物理地址
传统存储管理方式同时将多个进程保存在内存中以便允许多道程序设计。它们都具有以下两个共同的特征:
一次性: 作业必须一次性全部装入内存后,方能开始运行。这会导致两种情况发生:
1)当作业很大,不能全部被装入内存时,将使该作业无法运行;
2)当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。
驻留性: 作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程,会因等待 I/O 而被阻塞,可能处于长期等待状态
虚拟内存中,允许将一个作业分多次调入内存。釆用连续分配方式时,会使相当一部分内存空间都处于暂时或永久的空闲状态,造成内存资源的严重浪费,而且也无法从逻辑上扩大内存容量。因此,虚拟内存的实需要建立在离散分配的内存管理方式的基础上。虚拟内存的实现有以下三种方式:
硬性条件:
1. 物理内存:物理内存有四个层次,分别是寄存器、高速缓存、主存、磁盘。
寄存器:速度最快、量少、价格贵。
高速缓存:次之。
主存:再次之。
磁盘:速度最慢、量多、价格便宜。
操作系统会对物理内存进行管理,有一个部分称为内存管理器(memory manager),它的主要工
作是有效的管理内存,记录哪些内存是正在使用的,在进程需要时分配内存以及在进程完成时回收
内存。
2. 虚拟内存:操作系统为每一个进程分配一个独立的地址空间,但是虚拟内存。
虚拟内存与物理内存存在映射关系,通过页表寻址完成虚拟地址和物理地址的转换。
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:*brk和mmap
见Linux 操作系统 之 虚拟内存_linux 虚拟内存_chenyfan_的博客-CSDN博客
或 全版这可能最全的操作系统面试题_程序员cxuan的博客-CSDN博客
同步 I/O(Input/Output)是一种 I/O 操作模式,指的是在进行输入/输出操作时,程序会等待 I/O 操作完成后(用户系统轮询)再继续执行后续的代码。在同步 I/O 模式下,I/O 操作是阻塞的,即程序会被阻塞在 I/O 操作上,直到数据读取或写入完成后才能继续执行。
同步 I/O 就像你在餐厅点了一份菜,然后坐在桌子旁等待菜做好。在这个过程中,你需要等待厨师把你点的菜烹饪完成,然后服务员将菜端到你面前,你才能开始享用。在这期间,你无法做其他事情,只能等待菜做好。
如果涉及大量的 I/O 操作,或者需要同时处理多个任务,同步 I/O 可能会导致程序性能下降,因为等待 I/O 完成会浪费时间。
异步 I/O 就是程序在进行输入/输出操作时,不需要等待 I/O 操作完成,而是可以继续执行后续的代码。这种模式下,程序不会被“阻塞”,而是可以继续处理其他任务。当 I/O 操作完成后,系统会通知程序处理结果(内核通知)。这样可以提高程序的并发能力和性能,特别是在需要处理大量 I/O 操作或同时处理多个任务时,异步 I/O 是一种更高效的方式。
异步 I/O 就像你在快餐店使用自助点餐机。你在点餐机上选择了你想要的食物,然后支付完成后,点餐机会发给你一张取餐号。此时,你可以继续进行其他活动,不需要等待食物做好。在厨房里,厨师会根据你的订单开始准备食物。当食物做好后,服务员会根据你的取餐号把食物端到你面前。你可以在此期间继续坐着、聊天、读书,或者做其他事情,而不需要一直等待食物准备完成。
阻塞 I/O 即程序在进行输入/输出操作时,必须等待 I/O 操作完成后才能执行后续的代码。
阻塞I/O在用户请求系统调用时,用户空间与内核空间均是阻塞的,只有将数据从内核空间中拷贝到用户空间,此时用户进程才解除阻塞的的状态
特点 | 非阻塞 I/O | 异步 I/O |
---|---|---|
是否需要轮询 | 需要,程序需要主动询问 I/O 操作状态 | 不需要,操作系统或处理单元负责通知 I/O 完成 |
控制权 | 由程序控制 | 由操作系统或处理单元控制 |
实现机制 | 用轮询等待 I/O 就绪,增加编程复杂性 | 使用回调函数或事件通知机制,简化编程 |
处理结果通知 | 需要程序主动检查 I/O 操作结果或状态 没准备好数据直接返回error | 通过回调函数或事件通知机制处理 I/O 结果 |
编程复杂性 | 相对较复杂,需要手动处理轮询和状态检查 | 相对简单,利用回调函数或事件处理结果 |
适用场景 | 简单任务和少量并发操作 | 高并发、大量 I/O 操作或复杂的应用场景 |
多路复用 I/O 指程序(单个进程)可同时处理多个 I/O 操作,但不是同时进行,而是通过灵活地切换不同的 I/O 任务,以便有效利用系统资源。多路复用 I/O 可以同时监视多个 I/O 通道的状态,当其中一个通道就绪时,就会切换到相应的通道处理 I/O 操作,然后再切换到其他通道,从而实现了同时处理多个 I/O 任务的效果。
多路复用 I/O 就像你在客服中心同时面对着多个电话线,每条电话线代表一个客户。而你只有一个耳机和一个嘴巴,不能同时和所有客户同时通话,但你可以灵活地切换不同的电话线,与每个客户交流。当有来电时,你可以看到来电的号码,并根据需要选择接听其中一条电话线。当与某个客户交流时,其他客户的电话仍在等待,但你可以快速切换回其他电话,与其他客户进行交流。
使用场景 | 描述 |
---|---|
处理多个描述符 | 程序需要同时处理多个描述符(如套接字、文件描述符等),使用 I/O 复用轮询监视这些描述符的状态,避免多次阻塞和等待,提高效率。 |
高并发网络连接 | 服务器在高并发处理网络连接时,使用 I/O 复用管理大量连接,避免为每个连接创建线程或进程,节省系统资源。 |
监听套接口和已连接套接口 | 服务器需同时处理监听套接口(用于接收新连接)和已连接套接口(用于与客户端通信),使用 I/O 复用同时监视这两类套接字。 |
处理 TCP 和 UDP | 服务器需要同时处理 TCP 协议(面向连接)和 UDP 协议(无连接),使用 I/O 复用同时处理这两种协议的通信。 |
处理多个服务或协议 | 服务器需要同时处理多个服务或多个协议(例如 Web 服务、FTP 服务、SMTP 服务等),使用 I/O 复用监听和处理这些请求。 |
注意:select/poll/epoll本质是同步IO
特点 | I/O 复用 | 多线程和多进程模型 |
---|---|---|
资源占用 | 不需要为每个连接创建独立的线程或进程,节省系统资源 | 需要为每个连接创建独立的线程或进程,占用更多资源 |
内存占用 | 共享同一组文件描述符,减少内存占用 | 每个线程或进程需要独立的内存空间 |
上下文切换开销 | 通过一个线程或进程同时处理多个连接的 I/O 操作,避免上下文切换 | 需要频繁进行线程或进程的上下文切换 |
并发性能 | 可同时监视多个连接状态,一次调用处理多个连接的 I/O 操作 | 处理并发连接的性能取决于线程或进程的数量和调度策略 |
可扩展性 | 适用于高并发情况,具有较好的可扩展性 | 受限于线程或进程的数量和系统资源 |
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
参数 | 描述 |
---|---|
maxfdp1 | 指定感兴趣的套接字描述符个数,其值为最大套接字描述符加1,所有套接字描述符范围为0到 maxfdp1-1。 |
readset | 指定一个集合,在该集合中设置需要监视可读事件的套接字描述符。当其中任一套接字描述符可读时,select 将返回。 |
writeset | 指定一个集合,在该集合中设置需要监视可写事件的套接字描述符。当其中任一套接字描述符可写时,select 将返回。 |
exceptset | 指定一个集合,在该集合中设置需要监视异常事件的套接字描述符。当其中任一套接字描述符发生异常时,select 将返回。 |
timeout | 指定超时时间,用于设置 select 的等待时间。如果在指定的时间内没有任何套接字描述符就绪,select 将返回。 |
返回值:就绪socket
描述符的数目,超时返回0,出错返回-1。
步骤 | 描述 |
---|---|
准备文件描述符集合 | 创建并初始化文件描述符集合,将要监视的文件描述符添加到集合中,通常分为读集合、写集合和异常集合。(红框内) |
设置超时时间(可选) | 可选步骤,设置一个超时时间,让 select 在指定时间内返回,避免 select 调用永久阻塞。 |
调用 select | 调用 select 函数,传入准备好的文件描述符集合和超时时间,等待文件描述符的就绪事件发生。 |
检查就绪事件 | select 返回后,检查哪些文件描述符处于就绪状态,即可读、可写或异常条件满足。 |
处理就绪事件 | 根据文件描述符的就绪状态,进行相应的读写操作或处理异常情况。 |
重复调用 select (可选) | 如果需要持续进行 I/O 复用,可重复执行上述步骤,实现持续的多路复用操作。 |
缺点 | 描述 |
---|---|
低效 | 调用 select 需要轮询检查所有的文件描述符,导致 CPU 的浪费,特别在大量文件描述符时效率低。 |
连接数限制 | 某些系统对 select 函数支持的文件描述符数目有限制(一般为bitmap 1024位),限制了其在大规模连接场景下的可扩展性。 |
慢速系统调用 | select 返回后仍需进行实际的 I/O 操作,可能导致慢速系统调用,影响程序整体性能。 |
数据拷贝 | 每次调用 select 需要在每次调用后,将就绪的数据从内核缓冲区拷贝到用户缓冲区,额外开销大。 |
不支持事件驱动 | select 是阻塞式 I/O 复用,不能像事件驱动模型那样通过回调函数或事件通知处理 I/O 事件,编程复杂。 |
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
nfds = 文件描述符个数;
解决了select连接数的限制,以及处理数据再次轮询所需的初始化步骤,但是仍存在用户内核态切换开销过大的问题,以及检查就绪文件描述符的遍历过程不适用于多进程大数据的情况。
int epoll_create(int size);
size: 用来告诉内核这个监听的数目一共有多大,它其实是在内核申请一空间,用来存放用户想监听的socket fd上是否可读可行或者其他异常,只要有足够的内存空间,size可以随意设置大小,1G的内存上能监听约10万个端口。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epdf:由epoll_create()函数返回的epoll文件描述符(句柄)。
op:op是操作的选项,目前有以下三个选项:
EPOLL_CTL_ADD:注册要监听的目标socket描述符fd到epoll句柄中。
EPOLL_CTL_MOD:修改epoll句柄已经注册的fd的监听事件。
EPOLL_CTL_DEL:从epoll句柄删除已经注册的socket描述符。
fd:指定监听的socket描述符。
event:
- typedef union epoll_data {
- void *ptr;
- int fd;
- uint32_t u32;
- uint64_t u64;
- } epoll_data_t;
-
- struct epoll_event {
- uint32_t events; /* Epoll events */
- epoll_data_t data; /* User data variable */
- };
events可以是以下几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭)。
EPOLLOUT:表示对应的文件描述符可以写。
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)。
EPOLLERR:表示对应的文件描述符发生错误。
EPOLLHUP:表示对应的文件描述符被挂断。
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数:
events:用来从内核得到事件的集合。
maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的指定的size。
timeout是超时时间。
函数的返回值表示需要处理的事件数目,如返回0表示已超时。
高效优势 | 描述 |
---|---|
时间复杂度为 O(1) | 当调用 epoll_wait() 后,返回的是就绪描述符的数量,而不是实际的描述符,因此从指定的数组中获取相应数量的就绪描述符是 O(1) 的时间复杂度。 |
使用内存映射技术 | epoll 使用内存映射(mmap )技术,将用户空间和内核空间的一块物理内存地址映射到相同的虚拟地址,避免了从用户空间到内核空间的数据拷贝和交换,减少了用户态和内核态之间的开销,提高了效率。 |
基于事件的就绪通知方式 | epoll 采用基于事件的就绪通知方式,在调用 epoll_ctl() 注册描述符后,一旦检测到 epoll 管理的描述符就绪,内核会采用类似回调的机制,快速激活描述符,当调用 epoll_wait() 时即可得到就绪的通知,避免了遍历扫描所有描述符。 |
效率与监视的描述符数量无关 | epoll 最大的优点在于它只关注就绪的描述符,而不受监视的描述符总数的影响。因此,在高并发情况下,使用 epoll 可以更高效地处理大量并发连接。 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。