什么是进程
- 进程指的是系统中正在运行的一个应用程序,程序一旦运行就是进程
- 进程可以认为是程序执行的一个实例,进程是系统进程资源分配的最小单位(系统进行调度的最小单位是线程),而且每个进程都有自己独立的地址空间
- 一个进程无法直接访问另一个进程的变量和数据结构,如果希望一个进程去访问另一个进程的资源,需要使用进程间通信
- 线程是进程的在一个实体,是进程的一条执行路径;比进程更小的独立运行的基本单位,线程也被称为轻量级进程,一个程序至少有一个进程,一个进程至少有一个线程
- 进程调度算法:先来先到服务调度算法、短作业优先调度算法、非抢占式优先级调度算法、抢占式优先级调度算法、高响应比优先调度算法、时间片轮转调度算法
进程有五种状态:
- 新状态:进程已经创建
- 就绪态:进程做好了准备,准备执⾏,等待分配处理机
- 执⾏态:该进程正在执⾏;
- 阻塞态:等待某事件发⽣才能执⾏,如等待I/O完成;
- 终⽌状态
孤⼉进程和僵⼫进程的区别?怎么避免这两类进程?
-
一般情况下,父进程是由子进程创建,而子进程和父进程的退出是无顺序的,两者之间都不知道谁先1退出。正常情况下父进程会会调⽤ wait 或者 waitpid 函数等待⼦进程完成再退出,如果父进程不等待直接退出,则剩下的子进程会被init(pid=1)进程接受,称为孤儿进程
-
如果子进程先退出了,父进程还没有结束而且没有调用wait或者waitpid函数换取子进程的状态信息,则子进程残留状态信息(task_struct结构和少量资源信息)会变成僵尸进程。
-
避免:⼦进程退出时向⽗进程发送SIGCHILD信号,⽗进程处理SIGCHILD信号。在信号处理函数中调⽤wait进⾏处理僵⼫进程。
- 孤儿进程并不会有什么危害,真正会对系统构成威胁的是僵尸进程。
- 那么,什么情况下僵尸进程会威胁系统的稳定呢?设想有这样一个父进程:它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵尸进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。
- 严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵尸进程时,答案就是把产生大量僵尸进程的那个元凶枪毙掉(通过kill发送SIGTERM或者SIGKILL信号)。枪毙了元凶进程之后,它产生的僵尸进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经“僵尸”的孤儿进程就能瞑目而去了。
守护进程是什么?怎么实现?
-
守护进程( daemon) 是指在后台运⾏,没有控制终端与之相连的进程。它独⽴于控制终端,通常周期性地执⾏某种任务 。守护进程脱离于终端是为了避免进程在执⾏过程中的信息在任何终端上显示并且进程也不会被任何终端所产⽣的终端信息所打断。
-
守护进程特点:
- 守护进程最重要的特性是后台运⾏
- 守护进程必须与其运⾏前的环境隔离开来。这些环境包括未关闭的⽂件描述符,控制终端,会话和进程组,⼯作⽬录以及⽂件创建掩模等。这些环境通常是守护进程从执⾏它的⽗进程(特别是shell)中继承下来的。
- 守护进程的启动⽅式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由⽤户终端(shell)执⾏。
-
实现:
- 在⽗进程中执⾏fork并exit退出;
- 在⼦进程中调⽤setsid函数创建新的会话;
- 在⼦进程中调⽤chdir函数,让根⽬录 ”/” 成为⼦进程的⼯作⽬录;
- 在⼦进程中调⽤umask函数,设置进程的umask为0;
- 在⼦进程中关闭任何不需要的⽂件描述符
进程 VS 线程
-
同一进程的线程共享本进程的地址空间和资源,而进程之间是独立的地址空间和资源
- 进程在执⾏过程中拥有独⽴的内存单元,⽽多个线程共享内存,从⽽极⼤地提⾼了程序的运⾏效率
- 多线程的意义在于一个应用程序中,有多个执行部分可以同时执行,但是操作系统并没有将多个线程看作多个独立的应用,来实现进程的调度和管理以及资源分配
-
⼀个进程中的所有线程共享该进程的地址空间,但它们有各⾃独⽴的(/私有的)栈(stack),Windows 线程的缺省堆栈⼤⼩为1M。堆(heap)的分配与栈有所不同,⼀般是⼀个进程有⼀个C运⾏时堆,这个堆为本进程中所有线程共享,windows 进程还有所谓进程默认堆,⽤户也可以创建⾃⼰的堆
-
线程私有:线程栈,寄存器,程序寄存器
-
共享:堆,地址空间,全局变ᰁ,静态变ᰁ
-
进程私有:地址空间,堆,全局变ᰁ,栈,寄存器
-
共享:代码段,公共数据,进程⽬录,进程ID
-
一个进程崩溃之后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程崩溃,所以多进程比多线程健壮
-
进程切换,消耗的资源大。所以涉及到频繁的切换,使用线程好于进程
-
每个独立的进程都有一个程序的入口、程序的出口,但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
线程⽐进程具有哪些优势?
- 线程在程序中是独立的,并发的执行流,但是,进程中的线程之间的隔离程度要小
- 线程比进程更具有更高的性能,这是由于同一个进程中的线程都有共性:多个线程将共享一个进程虚拟空间
- 当操作系统创建一个进程时,必须为进程分配独立的内存空间,并分配大量相关的资源
什么时候⽤多进程?什么时候⽤多线程?
- 需要频繁创建销毁的优先⽤线程;
- 需要进⾏⼤ᰁ计算的优先使⽤线程;
- 强相关的处理⽤线程,弱相关的处理⽤进程;
- 可能要扩展到多机分布的⽤进程,多核分布的⽤线程;
什么是协程
- 协程是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程;协程不是被操作系统内核管理,而是完全由程序所控制
- 协程的开销远远小于线程
- 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈
- 每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源
- 跨平台、跨体系架构、无需线程上下文切换的开销、方便切换控制流,简化编程模型
- 协程⼜称为微线程,协程的完成主要靠yeild关键字,协程执⾏过程中,在⼦程序内部可中断,然后转⽽执⾏别
的⼦程序,在适当的时候再返回来接着执⾏; - 协程极⾼的执⾏效率,和多线程相⽐,线程数ᰁ越多,协程的性能优势就越明显;
- 不需要多线程的锁机制;
进程的创建过程?需要哪些函数?需要哪些数据结构
- fork函数创建的子进程是父进程的完整副本,复制了父进程的资源,包括内存的内容task_struct内容
- vfork创建的子进程与父进程共享数据段,而且vfork创建的子进程将先于父进程运行
- llinux上创建线程⼀般使⽤的是pthread库,实际上linux也给我们提供了创建线程的系统调⽤,就是clone;
进程间通信⽅式有⼏种
(1)无名管道(pipe)
(2)FiFO(有名管道)
(3)消息队列
- ① 消息队列,是消息的连接表,存放在内核中。⼀个消息队列由⼀个标识符来标识;
- ② 消息队列是⾯向记录的,其中的消息具有特定的格式以及特定的优先级;
- ③ 消息队列独⽴于发送与接收进程。进程终⽌时,消息队列及其内容并不会被删除;
- ④ 消息队列可以实现消息的随机查询
(4)信号量
- ① 信号量是⼀个计数器,信号量⽤于实现进程间的互斥与同步,⽽不是⽤于存储进程间通信数据;
- ② 信号量⽤于进程间同步,若要在进程间传递数量ᰁ的操作都是原⼦操作;
(5)共享内存
- ① 共享内存,指两个或多个进程共享⼀个给定的存储区;
- ② 共享内存是最快的⼀种进程通信⽅式,因为进程是直接对内存进⾏存取;
- ③ 因为多个进程可以同时操作,所以需要进⾏同步;
- ④ 信号量+共享内存通常结合在⼀起使⽤。‘
线程同步
- 线程同步只是多线程通过特定的设置来控制线程之间的执行顺序,也就是说线程之间通过同步建立执行顺序的关系
- 主要有四种方式:临界区、互斥对象、信号量、事件对象。其中临界区和互斥对象主要用于互斥控制,信号量和事件对象主要用于同步控制
- 临界区:通过对多线程的串行化来访问公共资源会在一段代码,速度快,适合控制数据访问。在任意一个时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区被释放之后,其他线程才可以抢占
- 互斥对象::互斥对象和临界区很像,采⽤互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有⼀个,所以能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。
- 信号量:它允许多个线程在同⼀时刻访问同⼀资源,但是需要限制在同⼀时刻访问此资源的最⼤线程数⽬。在⽤CreateSemaphore()创建信号量时即要同时指出允许的最⼤资源计数和当前可⽤资源计数。⼀般是将当前可⽤资源计数设置为最 ⼤资源计数,每增加⼀个线程对共享资源的访问,当前可⽤资源计数就会减1 ,只要当前可⽤资源计数是⼤于0 的,就可以发出信号量信号。但是当前可⽤计数减⼩ 到0 时则说明当前占⽤资源的线程数已经达到了所允许的最⼤数⽬,不能在允许其他线程的进⼊,此时的信号量信号将⽆法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore ()函数将当前可⽤资源计数加1 。在任何时候当前可⽤资源计数决不可能⼤于最⼤资源计数。
- 事件对象:通过通知操作的⽅式来保持线程的同步,还可以⽅便实现对多个线程的优先级⽐较的操作
页和段的区别
- 页是信息的物理单位,分页是由于系统管理的需要。段是信息的逻辑单位,分段是为了满足用户的需求
- 页的大小固定而且是由系统决定,段的长度不固定,决定于用户编写的程序,通常由编译程序对源程序进行编程时,根据信息的性质来划分
- 分⻚的作业的地址空间是⼀维的,程序员只需要利⽤⼀个记忆符,即可表示⼀个地址。分段的作业地址空间则是⼆维的,程序员在标识⼀个地址时,既需要给出段名,⼜需要给出段的地址值。
递归锁?
-
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁。互斥锁为资源引⼊⼀个状态:锁定/⾮锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“⾮锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性。
-
读写锁从广义的逻辑上来讲,也可以认为是一种共享版的互斥锁。如果对一个临界区大部分是读操作而只有少量的写操作,读写锁在一定程度上能够降低线程互斥产生的代价
-
Mutex可以分为递归锁(recursive mutex)和⾮递归锁(non-recursive mutex)。可递归锁也可称为可重⼊锁(reentrant mutex),⾮递归锁⼜叫不可重⼊锁(non-reentrant mutex)。⼆者唯⼀的区别是,同一个线程可以多次获取同一个递归树,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁
⽤户态和内核态的区别
- 内核态与⽤户态是操作系统的两种运⾏级别,当程序运⾏在3级特权级上时,就可以称之为运⾏在⽤户态,因为这是最低特权级,是普通的⽤户进程运⾏的特权级,⼤部分⽤户直接⾯对的程序都是运⾏在⽤户态;反之,当程序运⾏在0级特权级上时,就可以称之为运⾏在内核态。运⾏在⽤户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执⾏⼀个程序时,⼤部分时间是运⾏在⽤户态下的,在其需要操作系统帮助完成某些它没有权⼒和能⼒完成的⼯作时就会切换到内核态。
- 这两种状态的主要差别是: 处于⽤户态执⾏时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ; ⽽处于核⼼态执⾏中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。
用户态到内核态的转化原理
-
系统调用:这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。系统调用机制的核心还是使用了操作系统为用户特别开放的一个中断来实现。
-
异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可预知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常
-
外围设备的中断:当外围设备完成⽤户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执⾏下⼀条即将要执⾏的指令转⽽去执⾏与中断信号对应的处理程序,如果先前执⾏的指令是⽤户态下的程序,那么这个转换的过程⾃然也就发⽣了由⽤户态到内核态的切换。⽐如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执⾏后续操作等。
CPU中断
- CPU中断是什么
① 计算机处于执⾏期间;
② 系统内发⽣了⾮寻常或⾮预期的急需处理事件;
③ CPU暂时中断当前正在执⾏的程序⽽转去执⾏相应的事件处理程序;
④ 处理完毕后返回原来被中断处继续执⾏; - CPU中断的作⽤
① 可以使CPU和外设同时⼯作,使系统可以及时地响应外部事件;
② 可以允许多个外设同时⼯作,⼤⼤提⾼了CPU的利⽤率;
③ 可以使CPU及时处理各种软硬件故障。
中断的实现与作⽤,中断的实现过程?
- 过程如下:
① 关中断,进⼊不可再次响应中断的状态,由硬件实现。
② 保存断点,为了在[中断处理]结束后能正确返回到中断点。由硬件实现。
③ 将[中断服务程序]⼊⼝地址送PC,转向[中断服务程序]。可由硬件实现,也可由软件实现。
④ 保护现场、置屏蔽字、开中断,即保护CPU中某些寄存器的内容、设置[中断处理]次序、允许更⾼级的中断请求得到响应,实现中断嵌套由软件实现。
⑤ 设备服务,实际上有效的中断处理⼯作是在此程序段中实现的。由软件程序实现
⑥ 退出中断。在退出时,⼜应进⼊不可中断状态,即关中断、恢复屏蔽字、恢复现场、开中断、中断返回。由软件实现。
执⾏⼀个系统调⽤时,OS 发⽣的过程,越详细越好
- 执⾏⽤户程序(如:fork)
- 根据glibc中的函数实现,取得系统调⽤号并执⾏int $0x80产⽣中断。
- 进⾏地址空间的转换和堆栈的切换,执⾏SAVE_ALL。(进⾏内核模式)
- 进⾏中断处理,根据系统调⽤表调⽤内核函数。
- 执⾏内核函数。
- 执⾏ RESTORE_ALL 并返回⽤户模式
函数调⽤和系统调⽤的区别?
-
系统调⽤
① 操作系统提供给⽤户程序调⽤的⼀组特殊的接⼝。⽤户程序可以通过这组特殊接⼝来获得操作系统内核提供的服
务;
② 系统调⽤可以⽤来控制硬件;设置系统状态或读取内核数据;进程管理,系统调⽤接⼝⽤来保证系统中进程能以
多任务在虚拟环境下运⾏;
③ Linux中实现系统调⽤利⽤了0x86体系结构中的软件中断; -
函数调⽤
① 函数调⽤运⾏在⽤户空间;
② 它主要是通过压栈操作来进⾏函数调⽤;
虚拟内存?使⽤虚拟内存的优点?什么是虚拟地址空间?
- 虚拟内存是⼀种内存管理技术,它会使程序⾃⼰认为⾃⼰拥有⼀块很⼤且连续的内存,然⽽,这个程序在内存中不是连续的,并且有些还会在磁盘上,在需要时进⾏数据交换;
- 优点:可以弥补物理内存⼤⼩的不⾜;⼀定程度的提⾼反应速度;减少对物理内存的读取从⽽保护内存延⻓内存使⽤寿命;
- 缺点:占用一定的物理磁盘空间;加大了对硬盘的读写;设置不当会影响整机的稳定性和速度
- 虚拟地址空间是对于⼀个单⼀进程的概念,这个进程看到的将是地址从0000开始的整个内存空间。虚拟存储器是⼀个抽象概念,它为每⼀个进程提供了⼀个假象,好像每⼀个进程都在独占的使⽤主存。每个进程看到的存储器都是⼀致的,称为虚拟地址空间。从最低的地址看起:程序代码和数据,堆,共享库,栈,内核虚拟存储器。
线程安全?如何实现?
- 如果你的代码所在的进程中有多个线程在同时运⾏,⽽这些线程可能会同时运⾏这段代码。如果每次运⾏结果和[单线程]运⾏的结果是⼀样的,⽽且其他的变ᰁ的值也和预期的是⼀样的,就是线程安全的。
- 线程安全问题都是有全局变量和静态变量引起的。如果每个进程对全局变量、静态变量只有读操作⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般需要考虑【线程同步】,否则就可能引起线程安全
- 对于线程不安全的对象我们可以通过如下⽅法来实现线程安全:
- 加锁:利⽤Synchronized或者ReenTrantLock来对不安全对象进⾏加锁,来实现线程执⾏的串⾏化,从⽽保证多线程同时操作对象的安全性,⼀个是语法层⾯的互斥锁,⼀个是API层⾯的互斥锁.
- 非阻塞同步:通俗的讲,就是先进性操作,如果没有其他线程争用共享资源,那操作就成功了;如果共享数据有争用,产生冲突,那就再采取其他措施(最常⻅的措施就是不断地᯿试,直到成功为止)。这种方法需要硬件的支持,因为我们操作和冲突检测这两个步骤句柄原子性。通常这种指令包括CAS等
- 线程本地化。⼀种⽆同步的⽅案,就是利⽤Threadlocal来为每⼀个线程创造⼀个共享变ᰁ的副本来(副本之间是⽆关的)避免⼏个线程同时操作⼀个对象时发⽣线程安全问题。
常⻅的IO模型
四个概念:
- 同步:就是在发出⼀个功能调⽤时,在没有得到结果之前,该调⽤就不返回。也就是必须⼀件⼀件事做,等前⼀件做完了才能做下⼀件事。就是我调⽤⼀个功能,该功能没有结束前,我死等结果。
- 异步:当⼀个异步过程调⽤发出后,调⽤者不能⽴刻得到结果。实际处理这个调⽤的部件在完成后,通过状态、通知和回调来通知调⽤者。就是我调⽤⼀个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
- 阻塞:阻塞调⽤是指调⽤结果返回之前,当前线程会被挂起(线程进⼊⾮可执⾏状态,在这个状态下,cpu不会给线程分配时间⽚,即线程暂停运⾏)。函数只有在得到结果之后才会返回。对于同步调⽤来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回⽽已。 就是调⽤我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
- ⾮阻塞:指在不能⽴刻得到结果之前,该函数不会阻塞当前线程,⽽会⽴刻返回。就是调⽤我(函数),我(函数)⽴即返回,通过select通知调⽤者。
五种IO:
- 阻塞IO:应⽤程序调⽤⼀个IO函数,导致应⽤程序阻塞,等待数据准备好。 如果数据没有准备好,⼀直等待….数据准备好了,从内核拷⻉到⽤户空间,IO函数返回成功指示。
- 非阻塞IO:把一个IO设置为非阻塞就是告诉内核,当锁请求的IO操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的IO操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。这个不断测试的过程将会大量占用CPU的时间
- IO复用: I/O复⽤模型会⽤到select、poll、epoll函数,这⼏个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这三个函数可以同时阻塞多个I/O操作。⽽且可以同时对多个读操作,多个写操作的I/O函数进⾏检测,直到有数据可读或可写时,才真正调⽤I/O操作函数。
- 信号驱动I/O:⾸先我们允许套接⼝进⾏信号驱动I/O,并安装⼀个信号处理函数,进程继续运⾏并不阻塞。当数据准备好时,进程会收到⼀个SIGIO信号,可以在信号处理函数中调⽤I/O操作函数处理数据。
- 异步I/O:当⼀个异步过程调⽤发出后,调⽤者不能⽴刻得到结果。实际处理这个调⽤的部件在完成后,通过状态、通知和回调来通知调⽤者的输⼊输出操作。
IO复⽤的原理?epoll 的 LT 和 ET 模式的理解。
IO复⽤的原理?
- IO复用是linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或者多个IO条件就绪,就通过进程进行处理,从而不会在单个IO上阻塞了。Linux中,提供了select、poll、epoll三种接口来实现IO调用
select、poll、epoll
select缺点:
- 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024。由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差
- 内核/用户空间内存拷贝问题,select需要大量句柄数据结构,产生巨大开销
- select返回的是整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生事件
- select的触发方式是水平触发,应该程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么每次select调用还会将这些文件描述符通知进程
poll缺点:
- 和select相比,poll使用链表保存文件描述符,没有了监视文件数量的限制,但是仍然存在其他三个缺点
epoll:
- 上面的缺点不再存在。epoll使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放在内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。epoll是事件触发的,不是轮询查询的,没有最大并发连接限制,内存拷贝利用mmap()文件映射内存加速和内核空间的消息传递
区别总结:
(1)⽀持⼀个进程所能打开的最⼤连接数
① select最⼤1024个连接,最⼤连接数有FD_SETSIZE宏定义,其⼤⼩是32位整数表示,可以改变宏定义进⾏修改,可以᯿新编译内核,性能可能会影响;
② poll没有最⼤连接限制,原因是它是基于链表来存储的;
③ epoll连接数限数有上限,但是很⼤;
(2)FD剧增后带来的IO效率问题
① 因为每次进⾏线性遍历,所以随着FD的增加会造成遍历速度下降,效率降低;
② Poll同上;
③ 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的现象下降的性能问题
(3)消息传递方式
① select内核需要将消息传递到⽤户空间,都需要内核拷⻉;
② poll同上;
③ epoll通过内核和用户空间共享实现的
epoll 的 LT 和 ET 模式的理解
epoll对⽂件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger),LT是默认模式,区别:
- LT模式:当epoll_wait检测到描述符事件发⽣并将此事件通知应⽤程序,应⽤程序可以不⽴即处理该事件。下次调⽤epoll_wait时,会再次响应应⽤程序并通知此事件。
- ET模式:当epoll_wait检测到描述符事件发⽣并将此事件通知应⽤程序,应⽤程序必须⽴即处理该事件。如果不处理,下次调⽤epoll_wait时,不会再次响应应⽤程序并通知此事件。
在 select/poll中,进程只有在调⽤⼀定⽅法后,内核才对所有监视的⽂件描述符进⾏扫描,⽽epoll事先通过
epoll_ctl()来注册⼀个⽂件描述符,⼀旦某个⽂件描述符就绪时,内核会采⽤类似callback的回调机制,迅速激活这个⽂件描述符,当进程调⽤epoll_wait时便得到通知(此处去掉了遍历⽂件描述符,⽽是通过监听回调的机制,这也是epoll的魅⼒所在)。
Epoll 的优点主要体现在如下⼏个⽅⾯:
- 监视的描述符不受限制,它所⽀持的FD上限是最⼤可以打开⽂件的数⽬,这个数字⼀般远⼤于2048,举个栗⼦,具体数⽬可以在cat/proc/sys/fs/file-max 查看,⼀般来说,这个数⽬和内存关系很⼤。
- select最⼤的缺点是进程打开的fd数⽬是有限制的,这对于连接数⽬较⼤的服务器来说根本不能满⾜,虽然也可以选择多进程的解决⽅案(Apache就是如此);不过虽然linux上⾯创建进程的代价较⼩,但仍旧不可忽视,加上进程间数据同步远⽐不上线程间同步⾼效,所以并不是⼀种完美的解决⽅案。
- epoll的IO效率不会随着监视fd的数量的增⻓⽽下降,epoll不同于select和poll的轮询⽅式,⽽是通过每个fd定义的回调函数来实现,只有就绪的fd才会执⾏回调函数。
- 如果没有⼤量的idle -connection或者dead-connection,epoll的效率并不会⽐select/poll⾼很多,但是当遇到⼤量的idle- connection,就会发现epoll的效率⼤⼤⾼于select/poll。