• linux 进程调度2


    抢占式调度

    两种情况:

    1. 执行太久, 需切换到另一进程;
    2. 高优先级进程被唤醒

    切换到另一进程实现:

      时钟中断处理函数会调用 scheduler_tick()查看是否是需要抢占的时间点

    void scheduler_tick(void)
    {
      int cpu = smp_processor_id();
      struct rq *rq = cpu_rq(cpu);
      struct task_struct *curr = rq->curr;
    ......
      curr->sched_class->task_tick(rq, curr, 0);
      cpu_load_update_active(rq);
      calc_global_load_tick(rq);
    ......
    }
    1. 由时钟中断触发检测, 中断处理调用 scheduler_tick
    2. 取当前进程 task_struct->task_tick_fair()->取 sched_entity cfs_rq 调用 entity_tick()
    3. entity_tick() 调用 update_curr 更新当前进程 vruntime, 调用 check_preempt_tick 检测是否需要被抢占
    4. check_preempt_tick 中计算 ideal_runtime(一个调度周期中应该运行的实际时间), 若进程本次调度运行时间 > ideal_runtime, 则应该被抢占
    5. 要被抢占, 则调用 resched_curr, 设置 TIF_NEED_RESCHED, 将其标记为应被抢占进程(因为要等待当前进程运行 `__schedule`)

    高优先级进程被唤醒实现:

    当 I/O 完成, 进程被唤醒, 若优先级高于当前进程则触发抢占

    try_to_wake_up()->ttwu_queue() 将唤醒任务加入队列 调用 ttwu_do_activate 激活任务

    源码分析:

     static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync)
    {
        int cpu, this_cpu, success = 0;
        unsigned long flags;
        long old_state;
        struct rq *rq;
    #ifdef CONFIG_SMP
        struct sched_domain *sd, *this_sd = NULL;
        unsigned long load, this_load;
        int new_cpu;
    #endif
     
        rq = task_rq_lock(p, &flags);
        old_state = p->state; 
        if (!(old_state & state))
            goto out;
     
        if (p->array)
            goto out_running;
     
        cpu = task_cpu(p);
        this_cpu = smp_processor_id();
     
    #ifdef CONFIG_SMP
    ……
    //多处理器负载平衡工作 #endif /* CONFIG_SMP */ if (old_state == TASK_UNINTERRUPTIBLE) { rq->nr_uninterruptible--; /* * Tasks on involuntary sleep don't earn * sleep_avg beyond just interactive state. */ p->sleep_type = SLEEP_NONINTERACTIVE; //简单判断出非交互进程 } else if (old_state & TASK_NONINTERACTIVE) p->sleep_type = SLEEP_NONINTERACTIVE;//同上 activate_task(p, rq, cpu == this_cpu); if (!sync || cpu != this_cpu) { if (TASK_PREEMPTS_CURR(p, rq)) resched_task(rq->curr); } success = 1; out_running: trace_sched_wakeup(rq, p, success); p->state = TASK_RUNNING; out: task_rq_unlock(rq, &flags); return success; }

    1.    首先调用task_rq_lock( )禁止本地中断,并获得最后执行进程的CPU(他可能不同于本地CPU)所拥有的运行队列rq的锁。CPU的逻辑号存储在p->thread_info->cpu字段。
    2.    检查进程的状态p->state是否属于被当作参数传递给函数的状态掩码state,如果不是,就跳到第9步终止函数。
    3.    如果p->array字段不等于NULL,那么进程已经属于某个运行队列,因此跳转到第8步。
    4.    在多处理器系统中,该函数检查要被唤醒的进程是否应该从最近运行的CPU的运行队列迁移到另外一个CPU的运行队列。实际上,函数就是根据一些启发式规则选择一个目标运行队列。
    5.    如果进程处于TASK_UNINTERRUPTIBLE状态,函数递减目标运行队列的nr_uninterruptible字段,并把进程描述符的p->activated字段设置为-1。

    调用activate_task( )函数:

    static void activate_task(struct task_struct *p, struct rq *rq, int local)
    {
        unsigned long long now;
     
        now = sched_clock();
    #ifdef CONFIG_SMP
    ……
    #endif
     
        if (!rt_task(p))
            p->prio = recalc_task_prio(p, now); //计算平均睡眠时间并返回之后的优先级。
     
        if (p->sleep_type == SLEEP_NORMAL) {
            if (in_interrupt())
                p->sleep_type = SLEEP_INTERRUPTED;
            else {
                p->sleep_type = SLEEP_INTERACTIVE;
            }
        }
        p->timestamp = now; 
     
        __activate_task(p, rq);
    }
     
    static void __activate_task(struct task_struct *p, struct rq *rq)
    {
        struct prio_array *target = rq->active;
     
        trace_activate_task(p, rq);
        if (batch_task(p))
            target = rq->expired;
        enqueue_task(p, target); 
        inc_nr_running(p, rq);
    }

    调用 tt_do_wakeup()->check_preempt_curr() 检查是否应该抢占, 若需抢占则标记

    标识当前运行中的进程应该被抢占了,但是真正的抢占动作并没有发生

    抢占时机

    进程调用 `__schedule`,:分为用户态和内核态
    用户态进程
            时机-1: 从系统调用中返回, 返回过程中会调用 exit_to_usermode_loop, 检查 `_TIF_NEED_RESCHED`, 若打了标记, 则调用 schedule(),调用的过程和上一节解析的一样,会选择一个进程让出 CPU,做上下文切换。
            时机-2: 从中断中返回, 中断返回分为返回用户态和内核态(汇编代码: arch/x86/entry/entry_64.S), 返回用户态过程中会调用 exit_to_usermode_loop()->shcedule()
    内核态进程
            时机-1: 发生在 preempt_enable() 中, 内核态进程有的操作不能被中断, 会调用 preempt_disable(), 在开启时(调用 preempt_enable) 时是一个抢占时机, 会调用 preempt_count_dec_and_test(), 检测 preempt_count 和标记, 若可抢占则最终调用 `__schedule`
            时机-2: 发生在中断返回, 也会调用 `__schedule`

    总结

    参考:《趣谈Linux操作系统》调度(中)

  • 相关阅读:
    NetCore+Dapper WebApi架构搭建(三):添加实体和仓储
    NetCore+Dapper WebApi架构搭建(二):底层封装
    NetCore+Dapper WebApi架构搭建(一):基本框架
    net core WebApi——缓存神器Redis
    net core Webapi基础工程搭建(六)——数据库操作_Part 2
    net core Webapi基础工程搭建(七)——小试AOP及常规测试_Part 2
    springBoot+mybatisPlus小demo
    JAVA并发(一)
    tomcat解析
    JAVA并发-线程状态
  • 原文地址:https://www.cnblogs.com/mysky007/p/12342505.html
Copyright © 2020-2023  润新知