読者です 読者をやめる 読者になる 読者になる

sched_setscheduler(2)の挙動めも

linux kernel

ちょっとsched_setscheduler(2)の動作を調べていたのでそのメモを。

見ているのはlinux kernel 4.2。

sched_setscheduler(2)kerne/sched/core.cにある。 ここは単なる入り口でpolicyが0以下の場合にエラーを返す程度で、その他の処理はdo_sched_setscheduler()で実施。

4072         return do_sched_setscheduler(pid, policy, param);

do_sched_setscheduler()はset_schedscheduler(2)の引数のpolicy以外のチェック、パラメータの引数ををユーザ空間からカーネル空間にコピー。 そして、pidから該当のタスクを探してプロセスのtask_structを取得したら、sched_setscheduler()を呼ぶ。

3970 
3971         if (!param || pid < 0)
3972                 return -EINVAL;
3973         if (copy_from_user(&lparam, param, sizeof(struct sched_param)))
3974                 return -EFAULT;
3975 
3976         rcu_read_lock();
3977         retval = -ESRCH;
3978         p = find_process_by_pid(pid);
3979         if (p != NULL)
3980                 retval = sched_setscheduler(p, policy, &lparam);
3981         rcu_read_unlock();
3982 
3983         return retval;

sched_setscheduler()_sched_setscheduler()を呼ぶだけの関数。

_sched_setscheduler()は次に呼ぶ__sched_setscheduler()の引数を作るのが主な内容。

3907         struct sched_attr attr = {
3908                 .sched_policy   = policy,
3909                 .sched_priority = param->sched_priority,
3910                 .sched_nice     = PRIO_TO_NICE(p->static_prio),
3911         };
3912 
3913         /* Fixup the legacy SCHED_RESET_ON_FORK hack. */
3914         if ((policy != SETPARAM_POLICY) && (policy & SCHED_RESET_ON_FORK)) {
3915                 attr.sched_flags |= SCHED_FLAG_RESET_ON_FORK;
3916                 policy &= ~SCHED_RESET_ON_FORK;
3917                 attr.sched_policy = policy;
3918         }
3919 
3920         return __sched_setscheduler(p, &attr, check, true);

sched_setscheduler()の本体と言えるのはこの__sched_setscheduler()

3664 static int __sched_setscheduler(struct task_struct *p,
3665                                 const struct sched_attr *attr,
3666                                 bool user, bool pi)
3667 {

最初のチェック。policyはstruct sched_attrのsched_policyの値が代入されている。sched_setscheduler(2)の流れでくる場合は常にelse節が実行されると思う。 else節ではスケジューリングポリシーが正しいかどうかのチェックをする。

3680         /* double check policy once rq lock held */
3681         if (policy < 0) {
3682                 reset_on_fork = p->sched_reset_on_fork;
3683                 policy = oldpolicy = p->policy;
3684         } else {
3685                 reset_on_fork = !!(attr->sched_flags & SCHED_FLAG_RESET_ON_FORK);
3686 
3687                 if (policy != SCHED_DEADLINE &&
3688                                 policy != SCHED_FIFO && policy != SCHED_RR &&
3689                                 policy != SCHED_NORMAL && policy != SCHED_BATCH &&
3690                                 policy != SCHED_IDLE)
3691                         return -EINVAL;
3692         }
3693 

次のチェックではsched_attrに設定されているプライオリティが適切な範囲にあるかのチェック。 その次のif分はpolicyがdeadline(SCHED_DEADLINE)、もしくはrt(SCHED_FIFO/SCHED_RR)の場合にアトリビュートが正しく設定されているかのチェック。 dl_policy()とrt_policy()はpolicyがSCHED_XXXX(dl_policyならSCHED_DEADLINE)と同じかのチェック。

3702         if ((p->mm && attr->sched_priority > MAX_USER_RT_PRIO-1) ||
3703             (!p->mm && attr->sched_priority > MAX_RT_PRIO-1))
3704                 return -EINVAL;
3705         if ((dl_policy(policy) && !__checkparam_dl(attr)) ||
3706             (rt_policy(policy) != (attr->sched_priority != 0)))
3707                 return -EINVAL;

次は権限が無いユーザーがpriorityを変えようとした場合で、スケジューリングポリシーごとなどで操作権限のチェックをしている。ちなみにuserはbool値で、sched_setscheduer(2)の場合はtrue。

3709         /*
3710          * Allow unprivileged RT tasks to decrease priority:
3711          */
3712         if (user && !capable(CAP_SYS_NICE)) {

その次はセキュリティ機能ベースのチェック。例えばselinuxとか。

3760         if (user) {
3761                 retval = security_task_setscheduler(p);
3762                 if (retval)
3763                         return retval;
3764         }

ランキューを取得して、止まっているプロセスに対して変更をしようとしたらエラー。

3766         /*
3767          * make sure no PI-waiters arrive (or leave) while we are
3768          * changing the priority of the task:
3769          *
3770          * To be able to change p->policy safely, the appropriate
3771          * runqueue lock must be held.
3772          */
3773         rq = task_rq_lock(p, &flags);
3774 
3775         /*
3776          * Changing the policy of the stop threads its a very bad idea
3777          */
3778         if (p == rq->stop) {
3779                 task_rq_unlock(rq, p, &flags);
3780                 return -EINVAL;
3781         }

各スケジューリングクラスを変更する必要があるかチェックして、変更する必要があればchangeラベルにジャンプ。そうでなければここで終了。

3783         /*
3784          * If not changing anything there's no need to proceed further,
3785          * but store a possible modification of reset_on_fork.
3786          */
3787         if (unlikely(policy == p->policy)) {
3788                 if (fair_policy(policy) && attr->sched_nice != task_nice(p))
3789                         goto change;
3790                 if (rt_policy(policy) && attr->sched_priority != p->rt_priority)
3791                         goto change;
3792                 if (dl_policy(policy) && dl_param_changed(p, attr))
3793                         goto change;
3794 
3795                 p->sched_reset_on_fork = reset_on_fork;
3796                 task_rq_unlock(rq, p, &flags);
3797                 return 0;
3798         }

次にまたスケジューリングクラスごとのチェック。最初はリアルタイムスケジューリングのプロセスがグループスケジューリング可能かどうかのチェック。次はDEADLINEスケジューリングクラスの場合のチェック。

3801         if (user) {
3802 #ifdef CONFIG_RT_GROUP_SCHED
3803                 /*
3804                  * Do not allow realtime tasks into groups that have no runtime
3805                  * assigned.
3806                  */
3807                 if (rt_bandwidth_enabled() && rt_policy(policy) &&
3808                                 task_group(p)->rt_bandwidth.rt_runtime == 0 &&
3809                                 !task_group_is_autogroup(task_group(p))) {
3810                         task_rq_unlock(rq, p, &flags);
3811                         return -EPERM;
3812                 }
3813 #endif
3814 #ifdef CONFIG_SMP
3815                 if (dl_bandwidth_enabled() && dl_policy(policy)) {
3816                         cpumask_t *span = rq->rd->span;
3817 
3818                         /*
3819                          * Don't allow tasks with an affinity mask smaller than
3820                          * the entire root_domain to become SCHED_DEADLINE. We
3821                          * will also fail if there's no bandwidth available.
3822                          */
3823                         if (!cpumask_subset(span, &p->cpus_allowed) ||
3824                             rq->rd->dl_bw.bw == 0) {
3825                                 task_rq_unlock(rq, p, &flags);
3826                                 return -EPERM;
3827                         }
3828                 }
3829 #endif
3830         }

ここでまたpolicyのチェックをして、問題があればrecheckラベルにジャンプして関数の最初からやりなおす。

3832         /* recheck policy now with rq lock held */
3833         if (unlikely(oldpolicy != -1 && oldpolicy != p->policy)) {
3834                 policy = oldpolicy = -1;
3835                 task_rq_unlock(rq, p, &flags);
3836                 goto recheck;
3837         }
3838 

次はSCHED_DEADLINEの場合のチェックで、ここでbandwidthと言っているものはruntimeなんかのことっぽい。

3839         /*
3840          * If setscheduling to SCHED_DEADLINE (or changing the parameters
3841          * of a SCHED_DEADLINE task) we need to check if enough bandwidth
3842          * is available.
3843          */
3844         if ((dl_policy(policy) || dl_task(p)) && dl_overflow(p, policy, attr)) {
3845                 task_rq_unlock(rq, p, &flags);
3846                 return -EBUSY;
3847         }

ここまででエラーを返すような処理が終了し、次からが設定変更の処理になってくる。

piはbool値でsched_setscheduler(2)の場合はtrue。

3849         p->sched_reset_on_fork = reset_on_fork;
3850         oldprio = p->prio;
3851 
3852         if (pi) {
3853                 /*
3854                  * Take priority boosted tasks into account. If the new
3855                  * effective priority is unchanged, we just store the new
3856                  * normal parameters and do not touch the scheduler class and
3857                  * the runqueue. This will be done when the task deboost
3858                  * itself.
3859                  */
3860                 new_effective_prio = rt_mutex_get_effective_prio(p, newprio);
3861                 if (new_effective_prio == oldprio) {
3862                         __setscheduler_params(p, attr);
3863                         task_rq_unlock(rq, p, &flags);
3864                         return 0;
3865                 }
3866         }
3867 

設定変更対象のプロセスがランキューにある場合はキューから外し、プロセスの状態がTASK_RUNNINGならプロセスをenqueueする。この処理は各スケジューリングクラスのstruct sched_classのput_prev_taskに設定されている関数で実施する。

3868         queued = task_on_rq_queued(p);
3869         running = task_current(rq, p);
3870         if (queued)
3871                 dequeue_task(rq, p, 0);
3872         if (running)
3873                 put_prev_task(rq, p);

残りはこんな感じになっていて、設定変更の肝は__setscheduler()

3875         prev_class = p->sched_class;
3876         __setscheduler(rq, p, attr, pi);
3877 
3878         if (running)
3879                 p->sched_class->set_curr_task(rq);
3880         if (queued) {
3881                 /*
3882                  * We enqueue to tail when the priority of a task is
3883                  * increased (user space view).
3884                  */
3885                 enqueue_task(rq, p, oldprio <= p->prio ? ENQUEUE_HEAD : 0);
3886         }
3887 
3888         check_class_changed(rq, p, prev_class, oldprio);
3889         preempt_disable(); /* avoid rq from going away on us */
3890         task_rq_unlock(rq, p, &flags);
3891 
3892         if (pi)
3893                 rt_mutex_adjust_pi(p);
3894 
3895         /*
3896          * Run balance callbacks after we've adjusted the PI chain.
3897          */
3898         balance_callback(rq);
3899         preempt_enable();
3900 
3901         return 0;

__setscheduler()はstruct task_structにあるsched_classに適切なスケジューリングクラスを設定する。 この関数は最初に__setscheduler_params()を呼んで、task_structのpolicy変数、各種プライオリティを設定する。

3533 static void __setscheduler_params(struct task_struct *p,
3534                 const struct sched_attr *attr)
3535 {
3536         int policy = attr->sched_policy;
3537 
3538         if (policy == SETPARAM_POLICY)
3539                 policy = p->policy;
3540 
3541         p->policy = policy;
3542 
3543         if (dl_policy(policy))
3544                 __setparam_dl(p, attr);
3545         else if (fair_policy(policy))
3546                 p->static_prio = NICE_TO_PRIO(attr->sched_nice);
3547 
3548         /*
3549          * __sched_setscheduler() ensures attr->sched_priority == 0 when
3550          * !rt_policy. Always setting this ensures that things like
3551          * getparam()/getattr() don't report silly values for !rt tasks.
3552          */
3553         p->rt_priority = attr->sched_priority;
3554         p->normal_prio = normal_prio(p);
3555         set_load_weight(p);
3556 }

__setscheduler_params()が終わって__setscheduler()に戻ると、sched_setscheduler()から呼ばれたかどうかでtask_structのprio変数に設定するプライオリティが変わる。sched_setscheduler(2)の流れではkeep_boostはtrueなのでrt_mutex_get_effective_prio()のほうが呼ばれる。

そして、このプライオリティがどのスケジューリングクラスが使用する範囲にあるかチェックし、該当するスケジューリングクラスをsched_classに設定する。

3559 static void __setscheduler(struct rq *rq, struct task_struct *p,
3560                            const struct sched_attr *attr, bool keep_boost)
3561 {
3562         __setscheduler_params(p, attr);
3563 
3564         /*
3565          * Keep a potential priority boosting if called from
3566          * sched_setscheduler().
3567          */
3568         if (keep_boost)
3569                 p->prio = rt_mutex_get_effective_prio(p, normal_prio(p));
3570         else
3571                 p->prio = normal_prio(p);
3572 
3573         if (dl_prio(p->prio))
3574                 p->sched_class = &dl_sched_class;
3575         else if (rt_prio(p->prio))
3576                 p->sched_class = &rt_sched_class;
3577         else
3578                 p->sched_class = &fair_sched_class;
3579 }
3580 

これでsched_setshedule(2)でスケジューリングクラスを設定する流れが一通り完了( ´ー`)フゥー...

Working With TCP Sockets (English Edition)

Working With TCP Sockets (English Edition)