我们都知道Linux是一个多任务操作系统,它支持的任务远大于同时运行的CPU数量。当然,这些任务实际上并不是同时运行的,而是因为系统在短时间内轮流分配CPU给它们,造成了多个任务同时运行的错觉。在每个任务运行之前,CPU需要知道任务从哪里加载并开始运行。也就是说,系统需要预先为它设置好CPU寄存器和程序计数器。什么是CPU上下文?CPU寄存器和程序计数器是CPU上下文,因为它们都是CPU在运行任何任务之前必须运行的依赖环境。CPU寄存器是内置于CPU中的小但速度极快的内存。程序计数器用于存储CPU正在执行的指令的位置,或者下一条要执行的指令的位置。什么是CPU上下文切换?就是先保存上一个任务的CPU上下文(即CPU寄存器和程序计数器),然后把新任务的上下文加载到这些寄存器和程序计数器中,最后跳转到程序指向的新的柜台。位置,运行新任务。这些保存的上下文将存储在系统内核中,并在任务重新安排执行时再次加载。这样就不会影响任务原来的状态,看起来任务一直在运行。CPU上下文切换的类型根据任务的不同可以分为以下三种进程上下文切换线程上下文切换中断上下文切换进程上下文切换Linux根据权限级别,进程的运行空间分为内核空间和用户空间空间,对应下图中CPU的权限级别为Ring0和Ring3。内核空间(Ring0)的权限最高,可以直接访问所有资源;用户空间(Ring3)只能访问受限资源,不能直接访问内存等硬件设备。它必须被困在内核中,通过系统调用来访问这些特权资源。进程可以在用户空间和内核空间中运行。进程运行在用户空间时,称为进程的用户态,落入内核空间时,称为进程的内核态。系统调用从用户态到内核态的转换需要通过系统调用来完成。比如我们查看一个文件的内容,需要多次系统调用才能完成:先调用open()打开文件,再调用read()读取文件内容,调用write()写入内容到标准输出,最后调用close()关闭文件。在这个过程中,发生了一次CPU上下文切换,整个过程如下:1.将原来的用户态指令位保存在CPU寄存器中2.为了执行内核态代码,需要更新CPU寄存器为内核模式指令的新位置。3.跳转到内核模式运行内核任务。4、系统调用结束后,CPU寄存器需要恢复原来保存的用户态,然后切换到用户空间继续运行进程。因此,在一次系统调用的过程中,实际上有两次CPU上下文切换。(用户态-内核态-用户态)但是需要注意的是,在系统调用过程中,不会涉及到进程用户态的虚拟内存等资源,也不会切换进程。这与我们通常所说的进程上下文切换不同:进程上下文切换是指从一个进程切换到另一个进程;而同一进程在系统调用期间始终运行。因此,系统调用过程通常被称为特权模式切换,而不是上下文切换。系统调用是同一进程内的CPU上下文切换。但实际上,在系统调用过程中,CPU的上下文切换还是不可避免的。进程上下文切换和系统调用有什么区别?首先,进程由内核管理和调度,进程切换只能发生在内核态。因此,进程的上下文不仅包括虚拟内存、栈、全局变量等用户空间资源,还包括内核栈、寄存器等内核空间的状态。因此,进程的上下文切换比系统调用多了一步:在保存内核态资源(当前进程的内核态和CPU寄存器)之前,进程的用户态资源(虚拟内存、栈等)该过程需要先保存。;加载下一个进程的内核态后,需要刷新该进程的虚拟内存和用户栈。如下图所示,保存和恢复上下文的过程并不是“免费”的,需要内核运行在CPU上才能完成。进程上下文切换的潜在性能问题根据Tsuna的测试报告,每次上下文切换需要几十纳秒到几微秒的CPU时间。这个时间还是相当可观的,尤其是在大量进程上下文切换的情况下,很容易导致CPU耗费大量时间在寄存器、内核栈、虚拟内存等资源的保存和恢复上,这大大缩短了实际执行时间。过程的时间。这也是导致平均负载增加的重要因素。另外,我们知道Linux是通过TLB(TranslationLookasideBuffer)来管理虚拟内存和物理内存的映射关系的。当虚拟内存更新时,TLB也需要刷新,内存访问也会变慢。特别是在多处理器系统上,缓存由多个处理器共享。刷新缓存不仅会影响当前处理器的进程,还会影响共享缓存的其他处理器的进程。发生进程上下文切换的场景为了保证所有进程都能被公平调度,将CPU时间划分为时间片,这些时间片依次分配给各个进程。这样当某个进程的时间片用完后,就会被系统挂起,切换到其他等待CPU的进程运行。当系统资源不足时(如内存不足),直到资源被满足后,进程才能运行。这时进程也会被挂起,系统会调度其他进程运行。当一个进程通过睡眠函数sleep等方式主动挂起自己时,自然会被重新调度。当有更高优先级的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起。当高优先级进程运行时发生硬件中断,CPU上的进程就会被中断挂起。并在内核中执行中断服务程序。线程上下文切换线程和进程最大的区别在于,线程是调度的基本单位,而进程是资源拥有的基本单位。说白了,内核中所谓的任务调度,其实就是调度线程;进程只为线程提供虚拟内存和全局变量等资源。因此,对于线程和进程,我们可以这样理解:当一个进程只有一个线程时,可以认为进程等于线程。当一个进程有多个线程时,这些线程共享相同的资源,如虚拟内存和全局变量。在上下文切换期间不需要修改这些资源。另外,线程也有自己的私有数据,比如栈、寄存器等,在上下文切换时也需要保存。线程上下文切换场景前后的两个线程属于不同的进程。这时候因为没有共享资源,所以切换过程和进程上下文切换是一样的。前后两个线程属于同一个进程。此时由于虚拟内存是共享的,切换时虚拟内存的资源不变,只需要切换线程的私有数据,寄存器等不共享的数据。中断上下文切换为了快速响应硬件事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序来响应设备事件。在打断其他进程的时候,需要保存进程当前的状态,这样中断结束后,进程仍然可以从原来的状态恢复。与进程上下文不同,中断上下文的切换不涉及进程的用户态。因此,即使中断进程中断了一个用户态进程,也不需要保存和恢复该进程的虚拟内存、全局变量等用户态资源。中断上下文实际上只包括内核态中断服务程序执行所必需的状态,包括CPU寄存器、内核栈、硬件中断参数等。对于同一个CPU,中断处理的优先级高于进程,所以中断上下文切换不会和进程上下文切换同时发生。同理,因为中断会打断正常进程的调度和执行,所以大多数中断处理程序都是短小精悍的,以便尽快结束执行。另外,和进程上下文切换一样,中断上下文切换也会消耗CPU,过多的切换会消耗大量的CPU,甚至会严重降低系统的整体性能。因此,当你发现中断过多时,需要注意是否会对你的系统造成严重的性能问题。本文编译自极客时间:《Linux性能优化实战》如果您觉得文章不错,希望得到您的关注:七夕在学Java
