1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文
对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
切换的性能消耗:
1、线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过 *** 作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
2、另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor's Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。
我们知道,进程运行需要各种各样的系统资源,如内存、文件、打印机和最
宝贵的 CPU 等,所以说,调度的实质就是资源的分配。系统通过不同的调度算法(Scheduling Algorithm)来实现这种资源的分配。通常来说,选择什么样的调度算法取决于资源分配的策略(Scheduling Policy)。
有关调度相关的结构保存在 task_struct 中,如下:
active_mm 是为内核线程而引入的,因为内核线程没有自己的地址空间,为了让内核线程与普通进程具有统一的上下文切换方式,当内核线程进行上下文切换时,让切换进来的线程的 active_mm 指向刚被调度出去的进程的 active_mm(如果进程的mm 域不为空,则其 active_mm 域与 mm 域相同)。
在 linux 2.6 中 sched_class 表示该进程所属的调度器类有3种:
进程的调度策略有5种,用户可以调用调度器里不同的调度策略:
在每个 CPU 中都有一个自身的运行队列 rq,每个活动进程只出现在一个运行队列中,在多个 CPU 上同时运行一个进程是不可能的。
运行队列是使用如下结构实现的:
tast 作为调度实体加入到 CPU 中的调度队列中。
系统中所有的运行队列都在 runqueues 数组中,该数组的每个元素分别对应于系统中的一个 CPU。在单处理器系统中,由于只需要一个就绪队列,因此数组只有一个元素。
内核也定义了一下便利的宏,其含义很明显。
Linux、c/c++服务器开发篇-------我们来聊聊进程的那些事
Linux内核 进程间通信组件的实现
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括 C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享
在分析调度流程之前,我们先来看在什么情况下要执行调度程序,我们把这种情况叫做调度时机。
Linux 调度时机主要有。
时机1,进程要调用 sleep() 或 exit() 等函数进行状态转换,这些函数会主动调用调度程序进行进程调度。
时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4 是一样的。
时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查 need_resched 的值,如果必要,则调用调度程序 schedule() 主动放弃 CPU。
时机4 , 如前所述, 不管是从中断、异常还是系统调用返回, 最终都调用 ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。
Linux 的调度程序是一个叫 Schedule() 的函数,这个函数来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等。
从代码分析来看,Schedule 主要完成了2个功能:
进程上下文切换包括进程的地址空间的切换和执行环境的切换。
对于 switch_mm 处理,关键的一步就是它将新进程页面目录的起始物理地址装入到寄存器 CR3 中。CR3 寄存器总是指向当前进程的页面目录。
switch_to 把寄存器中的值比如esp等存放到进程thread结构中,保存现场一边后续恢复,同时调用 __switch_to 完成了堆栈的切换。
在进程的 task_struct 结构中有个重要的成分 thread,它本身是一个数据结构 thread_struct, 里面记录着进程在切换时的(系统空间)堆栈指针,取指令地址(也就是“返回地址”)等关键性的信息。
关于__switch_to 的工作就是处理 TSS (任务状态段)。
TSS 全称task state segment,是指在 *** 作系统进程管理的过程中,任务(进程)切换时的任务现场信息。
linux 为每一个 CPU 提供一个 TSS 段,并且在 TR 寄存器中保存该段。
linux 中之所以为每一个 CPU 提供一个 TSS 段,而不是为每个进程提供一个TSS 段,主要原因是 TR 寄存器永远指向它,在任务切换的适合不必切换 TR 寄存器,从而减小开销。
在从用户态切换到内核态时,可以通过获取 TSS 段中的 esp0 来获取当前进程的内核栈 栈顶指针,从而可以保存用户态的 cs,esp,eip 等上下文。
TSS 在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务的执行。
在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到 TR(任务寄存器)所指定的任务的 TSS 中;然后,下一任务的 TSS 被装入 TR;最后,从 TR 所指定的 TSS 中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在 TSS 中保存任务现场各寄存器状态的完整映象,实现任务的切换。
因此,__switch_to 核心内容就是将 TSS 中的内核空间(0级)堆栈指针换成 next->esp0。这是因为 CPU 在穿越中断门或者陷阱门时要根据新的运行级别从TSS中取得进程在系统空间的堆栈指针。
thread_struct.esp0 指向进程的系统空间堆栈的顶端。当一个进程被调度运行时,内核会将这个变量写入 TSS 的 esp0 字段,表示这个进程进入0级运行时其堆栈的位置。换句话说,进程的 thread_struct 结构中的 esp0 保存着其系统空间堆栈指针。当进程穿过中断门、陷阱门或者调用门进入系统空间时,处理器会从这里恢复期系统空间栈。
由于栈中变量的访问依赖的是段、页、和 esp、ebp 等这些寄存器,所以当段、页、寄存器切换完以后,栈中的变量就可以被访问了。
因此 switch_to 完成了进程堆栈的切换,由于被切进的进程各个寄存器的信息已完成切换,因此 next 进程得以执行指令运行。
由于 A 进程在调用 switch_to 完成了与 B 进程堆栈的切换,也即是寄存器中的值都是 B 的,所以 A 进程在 switch_to 执行完后,A停止运行,B开始运行,当过一段时间又把 A 进程切进去后,A 开始从switch_to 后面的代码开始执行。
schedule 的调用流程如下:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)