ちょっと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)
- 作者: Jesse Storimer
- 発売日: 2012/10/24
- メディア: Kindle版
- この商品を含むブログを見る