您的位置 首页 golang

linux进程管理之进程调度与切换十六问

灵魂拷问之调度和切换 – 16问

若对下面的灵魂拷问都能理解,说明对调度和切换,是真搞懂了。

  1. 调度究竟想干什么?
  2. 调度的时机是什么?操作系统在什么时候会发生调度?
  3. 如何合理选择下一个进程?
  4. 什么是进程上下文?进程上下文包含哪些内容?
  5. 进程上下文保存到哪里?
  6. 什么是中断现场?中断现场需要保存哪些内容?
  7. 中断现场保存在什么地方?
  8. 进程切换时候究竟需要切换哪些东西?
  9. 站在 CPU 角度,进程切换时候,CPU会区分谁是prev进程,谁是next进程?
  10. 假设next进程和prev进程都是用户进程,当prev进程切换到next进程后,next进程执行的下一条语句是什么?是next进程在用户空间被打断的那条指令吗?
  11. 假设prev进程正在执行时发生了时钟中断,然后发生了进程切换,切换到next进程,那么这个时钟中断的中断现场会在什么时候恢复?
  12. 假设prev进程在时钟中断驱动下发生了进程切换,并选择next进程是新创建的进程,那么新创建进程从哪里开始执行?
  13. 接上题,由于时钟中断处理是在关中断下进行的,若新创建的进程一直在loop里执行,那么是不是系统就一直没办法再一次响应时钟中断,导致系统一直运行这个新创建的进程?
  14. 在中断处理函数中,能不能直接调用schedule()函数?为什么?
  15. 小明同学在raw_local_irq_disable()函数后直接调用schedule()函数,若调度器选择了的next进程是一个loop执行的进程,那是不是系统就不能响应时钟中断,从而瘫痪了?
  16. 为什么switch_to()函数有3个参数?prev和next就够了,为何还需要last?

进程调度和切换设计到 进程管理 ,进程切换,上下文,中断,栈,系统调用等多方面的知识。仅仅将切换是很难理解的。 我们可以从一个小问题开始思考:

假设 Linux 内核只有三个内核线程,0号 线程 创建了内核线程1和内核线程2, 它们永远不会退出。当系统时钟中断到来时,时钟中断处理函数会检查是否有进程需要被调度。当有需要调度时,调度器会选择线程1或者线程2来运行。 假设0号进程先运行,那么在这个场景下会发生什么情况?

这是一个有意思问题,涉及到调度器的实现机制、中断处理、内核抢占、新创建进程如何被调度、进程切换等一些列知识点。我们只有把这些知识点都弄明白了才能真正搞明白这个问题。

场景分析

我们来回到刚才那个问题,这个场景的主要步骤如下。

  1. 第一个出场的是0号线程。我们都知道在 Linux内核 里,0号线程就是那个系统启动时候跑的那个线程。而start_kernel()是运行在0号线程里。0号线程创建了内核线程1和内核线程2。 函数调用关系:start_kernel()->kernel_thread()->_do_ fork ()。 在_do_fork()函数会创建新线程,并且把新线程添加到调度器的就绪队列中。 0号线程创建完内核线程1和内核线程2后,进入while死循环,0号线程不会退出,它正在等待被调度出去。 2.第二个要出场的是:时钟中断。时钟中断是系统里很重要的一个东西,处理器采用时钟定时器来周期性地提供系统脉搏。时钟中断也是普通外设中断的一种。调度器利用时钟中断来定时检测当前正在运行的线程是否需要被调度。 函数调用关系:

el1_irq->handle_domain_irq->__handle_domain_irq->generic_handle_irq_desc->handle_percpu_devid_irq->arch_timer_handler_virt->timer_handler->tick_handle_periodic->tick_periodic->update_process_times->scheduler_tick

  1. 当时钟中断检测到当前线程需要被调度时,设置need_resched标志位。 在check_preempt_tick()函数里。
  2. 时钟中断返回时,根据Linux内核是否支持内核抢占来确定是否需要调度,下面分两种情况来讨论。 支持内核抢占的内核:发生在内核态的中断在返回时,检查当前线程的need_resched标志位是否置位,如果置位说明当前线程需要被调度出来。 不支持内核抢占的内核:发生在内核态的中断在中断返回时不会检查是否需要调度。

在不支持内核抢占功能的Linux内核里,即使0号线程的need_resched标志位置位了,Linux内核依然不会调度内核线程1或者内核线程2来运行。只有发生在用户态的中断返回时,或者系统调用返回用户空间时才会检查是否需要调度。比如这张图,处理流程如下。

  1. 时钟中断发生。时钟中断触发时当前进程(线程)有可能在用户态执行,也可能在内核态执行。当进程运行在用户态时发生了中断,那么会进入到异常向量表的el0_irq汇编函数中,当进程运行在内核态发生了中断,那么会进入到异常向量表的el1_irq汇编函数中。在本场景中,因为3个线程都是内核线程,因此时钟中断只能跳转到el1_irq汇编函数里。进入中断时,CPU会自动关闭中断。
  2. el 1_irq汇编函数里,首先会保存中断现场(也称为中断上下文)到当前进程的栈中,Linux内核使用pt_regs数据结构来实现一个栈框,用来保存中断现场(本节称为pt_regs栈帧)。
  3. 中断处理过程,包括切换到Linux内核的中断栈、硬件中断号的查询、中断服务程序的处理等。
  4. 当确定当前中断源是时钟中断后,scheduler_tick()函数会取检查当前进程的是否需要调度。如果需要调度,则设置当前进程的need_resched标志位(thread_info中的TIF_NEED_RESCHED标志位)。
  5. 中断返回。这里需要给中断控制器返回一个中断结束信号(End Of Interrupt, EOI)。
  6. 在el1_irq汇编函数直接恢复中断现场,这里会使用0号线程的pt_regs栈框来恢复中断现场。在不支持内核抢占的系统里,el1_irq汇编函数不会检查是否需要调度。最后打开中断,CPU继续从被中断打断的地方开始继续执行0号进程。

相关视频推荐

学习地址:

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括 C/C++,Linux,golang技术, Nginx ,ZeroMQ,MySQL, Redis ,fastdfs, MongoDB ,ZK,流媒体, CDN ,P2P,K8S, Docker ,TCP/IP,协程,DPDK, ffmpeg 等),免费分享

在支持内核抢占功能的Linux内核中,中断返回时会检查当前进程是否设置了need_resched标志位置位。如果置位,那么调用preempt_schedule_irq()函数去调度其他进程(线程)来运行。比如这张图,在支持内核抢占的Linux内核中,中断与调度的流程略有不一样。在el1_irq汇编函数即将返回中断现场时,判断当前进程是否需要被调度。如果需要调度,调度器会选择下一个进程,并且进行进程的切换。比如这图里,选择了内核线程1,则从内核线程1的pt_regs栈框中恢复中断现场并打开中断,然后继续执行内核线程1的代码。

可能大家会对之前这张图有疑问:

  1. 如果内核线程1是新创建的进程,它的栈应该是空的,那它第一次运行时如何恢复中断现场呢?
  2. 如果不能从内核线程1的栈中恢复中断现场,那是不是内核线程1一直处于关中断的状态下运行?

对于内核线程来说,在创建时会对如下两部分内容进行设置与保存。

  • 进程的硬件上下文。它是保存在进程的cpu_context数据结构,进程硬件上下文包括x19~x28寄存器、fp寄存器、sp寄存器以及pc寄存器,详见第8.1.6节内容。

对于ARM64处理器来说,设置pc寄存器为ret_from_fork,即指向ret_from_fork汇编函数。设置sp寄存器指向栈的pt_regs栈框。

  • pt_regs栈框。

文章来源:智云一二三科技

文章标题:linux进程管理之进程调度与切换十六问

文章地址:https://www.zhihuclub.com/99236.shtml

关于作者: 智云科技

热门文章

网站地图