【INT的内核笔记】调度时机与抢占

el/2023/12/3 2:15:45

1. 调度时机

调度时机一般可以分成两类:主动调度和强制调度。

1.1 主动调度

  • 在形式上一般是这样的:

    内核在等待资源的时候,将当前进程移到等待队列,并主动调用schedule()放弃CPU;

  • 主动调度的例子:

    read()系统调用,会调用到wait_on_sync_kiocb(),其中有这么一段

    	while (iocb->ki_users) {set_current_state(TASK_UNINTERRUPTIBLE);if (!iocb->ki_users)break;schedule();}
    

    大致就是,资源还未准备的话,就主动调用schedule()放弃CPU;

1.2 强制调度

  • 在形式上一般是这样的:

    • 在系统调用 / 中断处理中设置TIF_NEED_SCHED;

    • 系统调用返回用户态 / 中断返回(可能返回内核态)前,

      检查TIF_NEED_SCHED(中断返回内核态还要检查preempt_count),

      如果进行了设置,则在返回前调用schedule();

  • 强制调度的例子:

    • IO中断的例子,

      阻塞read()的IO资源读取完成,进入中断处理。

      在中断处理中,调用try_to_wake_up()的封装函数,设置TIF_NEED_SCHED;

      中断返回后(假设是返回到用户态),检查到TIF_NEED_SCHED被设置,调用schedule();

    • 时钟中断的例子,

      在时钟中断的中断处理中,调用scheduler_tick(),

      最简单的情况是当普通进程的时间片耗尽后,设置TIF_NEED_SCHED;

      中断返回后(假设是返回到用户态),检查到TIF_NEED_SCHED被设置,调用schedule();

    • 系统调用的例子,

      信号机制中,想发送信号到某个进程时,会调用specific_send_sig_info()。

      在specific_send_sig_info()中,最后会调用signal_wake_up(),设置TIF_NEED_SCHED;

      (底层还是try_to_wake_up())

      系统调用返回后,检查到TIF_NEED_SCHED被设置,调用schedule();

1.3 详细调度时机总结

基本摘自此博文,文章是讲抢占的,但是抢占就是调度的一种。

Linux用户抢占和内核抢占详解(概念, 实现和触发时机)–Linux进程的管理与调度(二十)

  • 内核主动放弃CPU,具体情况就类似于上面所介绍的read()系统调用;
  • 时钟中断处理例程检查当前任务的时间片,当任务的时间片消耗完时,scheduler_tick()函数就会设置need_resched标志;
  • 信号量、等到队列、completion等机制唤醒时都是基于waitqueue的,而waitqueue的唤醒函数为default_wake_function,其调用try_to_wake_up将被唤醒的任务更改为就绪状态并设置need_resched标志。
  • 设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态;
  • 改变任务的优先级时,可能会使高优先级的任务进入就绪状态;
  • 新建一个任务时,可能会使高优先级的任务进入就绪状态;
  • 对CPU(SMP)进行负载均衡时,当前任务可能需要放到另外一个CPU上运行;

2. 抢占概述

2.1 何为抢占

抢占就是强制调度,

也就是一般是系统调用 / 中断返回前,如在处理中设置了TIF_NEED_SCHED,那么就会触发的一种调度。

上面已经进行了大致的介绍。

其实说法不少,也看过把主动放弃CPU也视作抢占的一种。

我是觉得主动放弃这种拦不住的情况,不应该叫被抢占吧,

抢占定义为可被TIF_NEED_RESCHED和preempt_count拦下来的强制调度,感觉比较合适?

抢占可分为:

  • 用户抢占,一般在系统调用返回 / 中断返回到用户态时被触发;

  • 内核抢占,一般在中断返回到内核态时被触发。

    在最原始的单核处理器下只要关闭中断就不会发生内核抢占,其他架构的细节尚未研究清楚;

2.2 内核抢占的限制

用户抢占感觉没什么好说,基本就是强制调度。

内核抢占是比较特殊的,限制比用户抢占要多,两者的控制变量为:

  • 用户抢占,TIF_NEED_RESCHED

  • 内核抢占,preempt_countTIF_NEED_RESCHED

    也就是内核多一层是否抢占的检测,

    preemot_count为0的时候表示开启抢占,总体情况比较多,细节尚未研究清楚;

而且也是个可选项,在编译内核时可以选择是否启用对内核抢占的支持。

内核在这些情况下是不允许被抢占的:

引自:Linux用户抢占和内核抢占详解(概念, 实现和触发时机)–Linux进程的管理与调度(二十)

  • 内核正进行中断处理。在Linux内核中进程不能抢占中断(中断只能被其他中断中止、抢占,进程不能中止、抢占中断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息;

  • 内核正在进行中断上下文的Bottom Half(中断下半部,即软中断)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。如果此时正在执行其它软中断,则不再执行该软中断;

  • 内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的保护状态中。内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占;

  • 内核正在执行调度程序schedule()。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序;

  • 内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占;

笔者暂时水平还是太有限,上文中的抢占对不可重入函数的影响,还有内核同步方面的介绍,

还不怎么看得懂,暂时只理解了大意。

3. 从中断和异常中返回代码分析

笔者水平有限,

本节主要引自:内核随记(二)——内核抢占与中断返回

我们会听说:

  • 从中断返回后,发生抢占或者信号处理等;
  • 从系统调用返回后,发生抢占或者信号处理等;

本节就是借由大佬对返回处理的源码分析,大致了解一下这些处理流程。

3.1 从中断返回

当内核从中断返回时,应当考虑以下几种情况:

  • 内核控制路径并发执行的数量:如果为1,则CPU返回用户态;
  • 挂起进程的切换请求:如果有挂起请求,则进行进程调度;否则,返回被中断的进程;
  • 待处理信号:如果有信号发送给当前进程,则必须进行信号处理;
  • 单步调试模式:如果调试器正在跟踪当前进程,在返回用户态时必须恢复单步模式;
  • Virtual-8086模式:如果中断时CPU处于虚拟8086模式,则进行特殊的处理;
    #从中断返回
ret_from_intr:GET_THREAD_INFO(%ebp)movl EFLAGS(%esp), %eax        # mix EFLAGS and CSmovb CS(%esp), %altestl $(VM_MASK | 3), %eax #是否运行在VM86模式或者用户态/*中断或异常发生时,处于内核空间,则返回内核空间;否则返回用户空间*/jz resume_kernel        # returning to kernel or vm86-space

3.2 返回用户态

/***返回用户空间,只需要检查need_resched**可能从中断返回,也可能从系统调用返回。*/
ENTRY(resume_userspace)  #返回用户空间,中断或异常发生时,任务处于用户空间cli                # make sure we don't miss an interrupt# setting need_resched or sigpending# between sampling and the iretmovl TI_flags(%ebp), %ecxandl $_TIF_WORK_MASK, %ecx    # is there any work to be done on# int/exception return?jne work_pending #还有其它工作要做jmp restore_all #所有工作都做完,则恢复处理器状态#恢复处理器状态
restore_all:RESTORE_ALL# perform work that needs to be done immediately before resumptionALIGN#完成其它工作
work_pending:testb $_TIF_NEED_RESCHED, %cl #检查是否需要重新调度jz work_notifysig #不需要重新调度#需要重新调度
work_resched:call schedule #调度进程cli                # make sure we don't miss an interrupt# setting need_resched or sigpending# between sampling and the iretmovl TI_flags(%ebp), %ecx/*检查是否还有其它的事要做*/andl $_TIF_WORK_MASK, %ecx    # is there any work to be done other# than syscall tracing?jz restore_all #没有其它的事,则恢复处理器状态testb $_TIF_NEED_RESCHED, %cljnz work_resched #如果need_resched再次置位,则继续调度
#VM和信号检测
work_notifysig:                # deal with pending signals and# notify-resume requeststestl $VM_MASK, EFLAGS(%esp) #检查是否是VM模式movl %esp, %eaxjne work_notifysig_v86        # returning to kernel-space or# vm86-spacexorl %edx, %edx#进行信号处理call do_notify_resumejmp restore_allALIGN
work_notifysig_v86:pushl %ecx            # save ti_flags for do_notify_resumecall save_v86_state        # %eax contains pt_regs pointerpopl %ecxmovl %eax, %espxorl %edx, %edxcall do_notify_resume #信号处理jmp restore_all

3.3 返回内核态

#ifdef CONFIG_PREEMPT 
/*返回内核空间,先检查preempt_count,再检查need_resched*/
ENTRY(resume_kernel)/*是否可以抢占,即preempt_count是否为0*/cmpl $0,TI_preempt_count(%ebp)    # non-zero preempt_count ?jnz restore_all #不能抢占,则恢复被中断时处理器状态need_resched:movl TI_flags(%ebp), %ecx    # need_resched set ?testb $_TIF_NEED_RESCHED, %cl #是否需要重新调度jz restore_all #不需要重新调度testl $IF_MASK,EFLAGS(%esp)     # 发生异常则不调度jz restore_all#将最大值赋值给preempt_count,表示不允许再次被抢占movl $PREEMPT_ACTIVE,TI_preempt_count(%ebp)sticall schedule #调度函数climovl $0,TI_preempt_count(%ebp) #preempt_count还原为0#跳转到need_resched,判断是否又需要发生被调度jmp need_resched
#endif

3.4 从异常返回

现在还不太了解。

    #从异常返回ALIGN
ret_from_exception:preempt_stop /*相当于cli,从中断返回时,在handle_IRQ_event已经关中断,不需要这步*/

3.5 从系统调用返回

  #系统调用入口
ENTRY(system_call)pushl %eax            # save orig_eaxSAVE_ALLGET_THREAD_INFO(%ebp)# system call tracing in operationtestb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)jnz syscall_trace_entrycmpl $(nr_syscalls), %eaxjae syscall_badsys
syscall_call:#调用相应的函数call *sys_call_table(,%eax,4)movl %eax,EAX(%esp)        # store the return value,返回值保存到eax
#系统调用返回
syscall_exit:cli                # make sure we don't miss an interrupt# setting need_resched or sigpending# between sampling and the iretmovl TI_flags(%ebp), %ecxtestw $_TIF_ALLWORK_MASK, %cx    # current->work,检查是否还有其它工作要完成jne syscall_exit_work
#恢复处理器状态
restore_all:RESTORE_ALL#做其它工作
syscall_exit_work:#检查是否系统调用跟踪,审计,单步执行,不需要则跳到work_pending(进行调度,信号处理)testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cljz work_pendingsti                # could let do_syscall_trace() call# schedule() insteadmovl %esp, %eaxmovl $1, %edx#系统调用跟踪call do_syscall_trace#返回用户空间jmp resume_userspace

参考

yooooooo Linux用户抢占和内核抢占详解(概念, 实现和触发时机)–Linux进程的管理与调度(二十)
YY哥 内核随记(二)——内核抢占与中断返回
《深入理解linux内核》


http://www.ngui.cc/el/4104847.html

相关文章

【INT的内核笔记】梳理sleep_avg,prio,activated,timestamp,last_ran等重要调度变量

1. sleep_avg 1.1 sleep_avg简介 sleep_avg处在task_struct数据结构中, sleep其实和平均没有什么关系,是一个睡眠时间评估值,命名可能有历史原因。 直接关系到进程动态优先级prio的计算: 动态优先级prio 静态优先级static_p…

转stackoverflow一个问题,关于内核是如何管理页表(pgd,pud,pmd,pte)本身所占的内存

碎碎念 我今天莫名开始纠结起,关于linux页表方面的问题。然后就想到, 【页表本身也是要占有一部分内存的,所以内核又是如何管理页表本身所占有的这部分内存的呢?】 找了很久,没太满意的答案。甚至都没有具体方向&…

【INT的内核笔记】Linux内核内存空间布局研究

1.Linux内核映射 从上面的页表设置可以看出: 内核对内核虚拟地址和物理地址之间的转换,是会有需求的。 很容易可以想到最简单的解决方法: 将内核虚拟空间地址,和实际物理空间逐一对应进行线性映射。 在很早期的时候&#xff…

【内核资料】stackoverflow上关于内核为何偏爱kmalloc(),而很少用vmalloc()的讨论

1. 问题 What is the difference between vmalloc and kmalloc? 2. 大致观点 涉及到DMA的话,需要物理上连续的内存;内核之所以偏好分配物理上连续内存,并不是必须的, 而主要是考虑性能: kmalloc()比vmalloc()效率高…

【INT的内核笔记】Linux信号递达过程详解

1. 什么时候执行信号递达 书上是叫递达的,但我个人比较喜欢叫信号处理吧,毕竟明明就是信号的接收进程处理信号的过程╮(╯▽╰)╭,所以文中可能会多处出现信号处理。 一般在从中断或系统调用返回到用户态前,会执行一段逻辑检查T…

【INT的内核笔记】tcp接收端相关实现

1. file_operations 在epoll_ctl(add)中有这样的调用链: 主要涉及了file->f_op和private_data中的socket结构 tfile->f_op->poll(tfile, &epq.pt)|->sock_poll|->sock->ops->poll|->tcp_poll(应该也是类似sk->sk_prot->recvmsg?…

关于char数组的输入与输出

cin一个char数组: 遇到空白符号(空格,tab制表符,回车)时读取停止,空白符号并不会被读入,但是碰到这些空白符号后,字符串末尾会放一个’\0‘ tip:空白字符的读入可以靠getchar() cout…

平衡三进制——Roj_1016

这道题目其实回顾的时候觉得还是挺容易的,但是做的时候还是忍不住看了题解。 这道题给我的主观层面的反思要远远大于客观上的,不要惧怕题目!不要惧怕题目!不要惧怕题目!!!重要的事情说三遍 *注意…

平衡三进制Ⅱ——Roj_1018

前提 平衡三进制Ⅱ就是1016的反过来计算,本来可能想转换的方法可能是这道题目的难点,但是题目直接给出了转换的方法,所以这道题的难点就转移到了如何用代码实现它所提供的方法(没有数学上的难度的 要点 1.最主要的问题&#xff1a…

字串排序——Roj_1020

这道题光看题目好像没有什么难度,但是操作起来发现了如下问题 1.输入这一串数组的方式,我刚开始采取的是多次get()到‘\n’结束,后来发现可能还是一个一个读入比较方便,因为还要考虑数组中是否存在数字(cge…