什么是上下文切换
上下文切换在多任务操作系统中是一个必须的特性。多任务操作系统是指多个进程运行在一个 CPU 中互不打扰,看起来像同时运行一样。这个并行的错觉是由于上下文在高速的切换(每秒几十上百次)。当某一进程自愿放弃它的 CPU 时间或者系统分配的时间片用完时,就会发生上下文切换。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。
上下文切换又分为2种:让步式上下文切换和抢占式上下文切换。前者是指执行线程主动释放CPU,与锁竞争严重程度成正比,可通过减少锁竞争来避免;后者是指线程因分配的时间片用尽而被迫放弃CPU或者被其他优先级更高的线程所抢占,一般由于线程数大于CPU可用核心数引起,可通过调整线程数,适当减少线程数来避免。https://blog.csdn.net/u010412301/article/details/58697030
程序在执行过程中通常有用户态和内核态两种状态,CPU对处于内核态根据上下文环境进一步细分,因此有了下面三种状态:
(1)内核态,运行于进程上下文,内核代表进程运行于内核空间。
(2)内核态,运行于中断上下文,内核代表硬件运行于内核空间。
(3)用户态,运行于用户空间。
上下文就是内核重新启动一个被抢占的进程所需的状态。 上下文简单说是一个环境。
相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
(1)用户级上下文: 正文、数据、用户堆栈以及共享存储区;
(2)寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
(3)系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。
操作系统内核使用一种称为上下文切换的较高层形式的控制流来实现多任务。 内核为每一个进程维持一个上下文。
他有一些对象的值组成,这些对象包括:
通用目的寄存器
浮点寄存器
程序计数器
用户栈
状态寄存器
内核栈
各种内核数据结构
上下文切换
保存当前进程的上下文
恢复某个先前被抢占的进程被保存的上下文
将控制传递给这个新恢复的进程
上下文切换过程
上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:
(1)挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处,
(2)在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复,
(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
引起上下文切换的原因
时间片用完,CPU正常调度下一个任务
被其他优先级更高的任务抢占
执行任务碰到IO阻塞,调度器挂起当前任务,切换执行下一个任务
用户代码主动挂起当前任务让出CPU时间
多任务抢占资源,由于没有抢到被挂起
硬件中断
如何减少上下文切换:
无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,将数据Id按hash算法取模来分段,不同的线程处理不同时端的数据
CAS算法。Java的atomic包使用CAS算法来更新数据,面不需要加锁。Atomic变量的更新可以实现数据操作的原子性及可见性。这个是由volatile 原语及CPU的CAS指令来实现的。
使用最少的线程。若任务少,但创建了很多线程来处理,这样会造成大量的线程处于等等状态。
协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间切换。java语言级别原生并不支持协程,可以看下kilim,利用java增强字节码实现角色模型(actor)