内核代码阅读(25) - 强制调度

简介: 强制调度

强制调度

进程的强制调度是通过设置目标进程的task_struct中的need_resched来完成的。

什么时候要发生强制调度呢?有三种情况:

1) 进程运行的时间过长,这是通过时钟中断服务程序来保证。

2) 唤醒一个睡眠的进程,发现这个进程比当前进程更有资格运行。

3) 一个进程通过系统调用改变调度策略。会立即引发调度。

在设置了need_resched之后,到真正的触发schedule(),到目标进程开始被调度起来,之间有一段不确定的延迟,"dispatch latency"。

第一种情况:时钟中断update_process_times

do_timer_interrupt() -> do_timer() -> update_process_times()
    void update_process_times(int user_tick)
    {
        struct task_struct *p = current;
        int cpu = smp_processor_id(), system = user_tick ^ 1;
        update_one_process(p, user_tick, system, cpu);
        if (p->pid) {
                if (--p->counter <= 0) {
                        p->counter = 0;
                        p->need_resched = 1;
                }
                if (p->nice > 0)
                        kstat.per_cpu_nice[cpu] += user_tick;
                else
                        kstat.per_cpu_user[cpu] += user_tick;
                kstat.per_cpu_system[cpu] += system;
        } else if (local_bh_count(cpu) || local_irq_count(cpu) > 1)
                kstat.per_cpu_system[cpu] += system;
    }
1) if (--p->counter <= 0) {
   将当前正在执行的进程的时间片减1,如果小于0,则设置need_resched

第二种情况:唤醒睡眠的进程wake_up_process

inline void wake_up_process(struct task_struct * p)
    {
        unsigned long flags;
        spin_lock_irqsave(&runqueue_lock, flags);
        p->state = TASK_RUNNING;
        if (task_on_runqueue(p))
                goto out;
        add_to_runqueue(p);
        reschedule_idle(p);
    out:
        spin_unlock_irqrestore(&runqueue_lock, flags);
    }
唤醒的操作是:
1) 把进程的状态改成TASK_RUNNING。
2) 加入runqueue队列。
3) 调用reschedule_idle

唤醒一个进程后,主动的触发调度

static void reschedule_idle(struct task_struct * p)
    {
        struct task_struct *tsk;
        tsk = cpu_curr(this_cpu);
        if (preemption_goodness(tsk, p, this_cpu) > 1)
                tsk->need_resched = 1;
    }
如果当前进程的权重低于被唤醒的进程,则把被唤醒进程的need_resched置位。

第三种情况:进程主动让出CPU, sched_setscheduler()和sched_yield()

sched_setscheduler

asmlinkage long sys_sched_setscheduler(pid_t pid, int policy, 
                                      struct sched_param *param)
    {
        return setscheduler(pid, policy, param);
    }
asmlinkage long sys_sched_setparam(pid_t pid, struct sched_param *param)
    {
        return setscheduler(pid, -1, param);
    }
    static int setscheduler(pid_t pid, int policy, 
                        struct sched_param *param)
    {
        struct sched_param lp;
        struct task_struct *p;
        int retval;
        retval = -EINVAL;
        if (!param || pid < 0)
                goto out_nounlock;
        retval = -EFAULT;
        if (copy_from_user(&lp, param, sizeof(struct sched_param)))
                goto out_nounlock;
        read_lock_irq(&tasklist_lock);
        spin_lock(&runqueue_lock);
        p = find_process_by_pid(pid);
        retval = -ESRCH;
        if (!p)
                goto out_unlock;
        if (policy < 0)
                policy = p->policy;
        else {
                retval = -EINVAL;
                if (policy != SCHED_FIFO && policy != SCHED_RR &&
                                policy != SCHED_OTHER)
                        goto out_unlock;
        }
        retval = -EINVAL;
        if (lp.sched_priority < 0 || lp.sched_priority > 99)
                goto out_unlock;
        if ((policy == SCHED_OTHER) != (lp.sched_priority == 0))
                goto out_unlock;
        retval = -EPERM;
        if ((policy == SCHED_FIFO || policy == SCHED_RR) && 
            !capable(CAP_SYS_NICE))
                goto out_unlock;
        if ((current->euid != p->euid) && (current->euid != p->uid) &&
            !capable(CAP_SYS_NICE))
                goto out_unlock;
        retval = 0;
        p->policy = policy;
        p->rt_priority = lp.sched_priority;
        if (task_on_runqueue(p))
                move_first_runqueue(p);
        current->need_resched = 1;
    out_unlock:
        spin_unlock(&runqueue_lock);
        read_unlock_irq(&tasklist_lock);
    out_nounlock:
        return retval;
    }
这个函数很简单,找到pid的进程,然后设置policy,然后把当前进程的need_resched置位。在系统调用返回的时候会发生一次调度。
另外,目标进程已经在runqueue列表中了,就把这个进程移动到runqueue的头部。

sched_yield

asmlinkage long sys_sched_yield(void)
    {
        int nr_pending = nr_running;
        nr_pending--;
        if (nr_pending) {
                if (current->policy == SCHED_OTHER)
                        current->policy |= SCHED_YIELD;
                current->need_resched = 1;
        }
        return 0;
    }
如果当前的runqueue只有一个一进程,则直接返回。
否则,如果当前进程的调度策略是SCHED_OTHER,则设置策略SCHED_YIELD。
否则,把当前正在跑的进程的need_resched置位。
相关文章
|
XML 开发工具 Android开发
Repo工作原理及常用命令总结——2023.07(上)
Repo工作原理及常用命令总结——2023.07(上)
2107 0
|
3月前
|
运维 数据可视化 开发者
2025年 三个 Docker Compose 可视化管理器测评
本文对比了三款主流的 Docker Compose 可视化管理工具。随着 Docker 的普及,Compose 已成为多容器应用部署的标准,但 YAML 配置复杂、协作困难等问题也日益突出。三款工具各有侧重:Docker Desktop 适合个人本地开发,Portainer 适合小团队运维管理,而 Websoft9 则通过 GitOps 实现了强大的版本控制与团队协作能力。文章从可视化编辑、部署便捷性、版本管理等方面进行评测,为不同使用场景提供了推荐方案,展望了未来 Compose 管理向 GitOps 深度融合的发展趋势。
388 1
2025年 三个 Docker Compose 可视化管理器测评
|
9月前
|
JSON 安全 API
VMware Aria Operations for Logs 8.18.3 新增功能简介
VMware Aria Operations for Logs 8.18.3 - 集中式日志管理
182 15
VMware Aria Operations for Logs 8.18.3 新增功能简介
|
Linux C语言 Python
perf_event_open 学习 —— 通过read的方式读取硬件技术器
perf_event_open 学习 —— 通过read的方式读取硬件技术器
|
缓存 Kubernetes Java
实时计算 Flink版产品使用合集之nk任务在k8s上运行,数据量大时经常失败,并且某个TaskManager被cgroup杀掉,该如何处理
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
虚拟化
VMware克隆虚拟机后修改MAC地址、UUID、IP和主机名
VMware克隆虚拟机后修改MAC地址、UUID、IP和主机名
670 0
VMware克隆虚拟机后修改MAC地址、UUID、IP和主机名
|
机器学习/深度学习 人工智能 算法
神经网络算法——损失函数(Loss Function)
神经网络算法——损失函数(Loss Function)
2472 0
|
Dubbo Java 应用服务中间件
Dubbo 3.x结合Zookeeper实现远程服务基本调用
ZooKeeper和Dubbo是两个在分布式系统中常用的开源框架,它们可以协同工作,提供服务注册与发现、分布式协调等功能。
272 0
|
Linux
内核态的文件操作函数:filp_open、filp_close、vfs_read、vfs_write、set_fs、get_fs
内核态的文件操作函数:filp_open、filp_close、vfs_read、vfs_write、set_fs、get_fs
1621 0
|
Kubernetes Serverless 异构计算
基于ACK One注册集群实现IDC中K8s集群以Serverless方式使用云上CPU/GPU资源
在前一篇文章《基于ACK One注册集群实现IDC中K8s集群添加云上CPU/GPU节点》中,我们介绍了如何为IDC中K8s集群添加云上节点,应对业务流量的增长,通过多级弹性调度,灵活使用云上资源,并通过自动弹性伸缩,提高使用率,降低云上成本。这种直接添加节点的方式,适合需要自定义配置节点(runtime,kubelet,NVIDIA等),需要特定ECS实例规格等场景。同时,这种方式意味您需要自行
基于ACK One注册集群实现IDC中K8s集群以Serverless方式使用云上CPU/GPU资源