第4章进程调度(六)

4.7 实时调度策略

Linux提供两种实时调度策略:SCHED_FIFO和SCHED_RR。普通的、非实时的调度策略是SCHED_NORMAL。借助调度类的框架,这些实时策略被一个特殊的实时调度器管理。具体实现定义在kernel/sched_rt.c中。

SCHED_FIFO是一种简单的、先入先出的调度算法:不使用时间片。处于可运行状态的SCHED_FIFO级的进程比任何SCHED_NORMAL级的进程先得到调度。一旦一个SCHED_FIFO级进程处于可执行状态,会一直执行,直到自己受阻或显示地释放处理器为止,它不基于时间片,可以一直执行下去。只有更高优先级的SCHED_FIFO或者SCHED_RR任务才能抢占SCHED_FIFO任务。如果有两个或者更多的同优先级的SCHED_FIFO级进程,它们会轮流执行,但是依然只有在它们愿意让出处理器时才会退出。只要有SCHED_FIFO级进程在执行,其它级别较低的进程就只能等待它变为不可运行态后才有机会执行。

SCHED_RR与SCHED_FIFO大体相同,只是SCHED_RR级的进程在耗尽事先分配给它的时间后就不能再继续执行了。SCHED_RR是带有时间片的SCHED_FIFO——是一种实时轮流调度算法。当SCHED_RR任务耗尽它的时间片时,在同一优先级的其他实时进程被轮流调度。时间片只用来重新调度同一优先级的进程。对于SCHED_FIFO进程,高优先级总是立即抢占低优先级,但低优先级进程不能抢占SCHED_RR任务,即使它的时间片耗尽。

这两种实时算法实现的都是静态优先级。内核不为实时进程计算动态优先级。这能保证给定优先级别的实时进程总能抢占优先级比它低的进程。

Linux的实时调度算法提供一种软实时工作方式。软实时含义是,内核调度进程,尽力使进程在它的限定时间到来前运行,但内核不保证总能满足这些进程的要求。硬实时系统保证在一定条件下,可满足任何调度的要求。Linux对于实时任务的调度不做任何保证。

实时优先级范围从0到MAX_RT_PRIO减1。默认情况下,MAX_RT_PRIO为100——默认的实时优先级范围是从0到99。SCHED_NORMAL(普通的、非实时进程)级进程的nice值共享了这个取值空间;它的取值范围从MAX_RT_PRIO到(MAX_RT_PRIO+40)。在默认情况下,nice值从-20到+19直接对应的是从100到140的实时优先级范围。

4.8 与调度相关的系统调用

Linux提供一个系统调用族,用于管理与调度程序相关的参数。这些系统调用可以用来操作和处理进程优先级、调度策略及处理器绑定,还提供了显式地将处理器交给其他进程的机制。

1、与调度策略和优先级相关的系统调用

sched_setscheduler()和sched_getscheduler()分别用于设置和获取进程的调度策略。其实现是由许多参数检查、初始化和清理构成的。其实最重要的工作在于读取和设置进程task_struct的policy。

sched_setparam()和sched_getparam()分别用于设置和获取进程的实时优先级。这两个系统调用获取封装在sched_param特殊结构体的sched_priority中。

sched_get_priority_max()和sched_get_priority_min()分别用于返回给定调度策略的最大和最小优先级。实时调度策略的最大优先级是MAX_USER_RT_PRIO减1,最小优先级等于1。

对于一个普通的进程,nice()函数可以将给定进程的静态优先级增加一个给定的量。只有超级用户才能在调用它时使用负值,从而提高进程的优先级。nice()会调用内核的set_user_nice(),这个函数会设置进程的task_struct的prio, static_prio值。

void set_user_nice(struct task_struct *p, long nice)
{
        int old_prio, delta, on_rq;
        unsigned long flags;
        struct rq *rq;

        if (TASK_NICE(p) == nice || nice < -20 || nice > 19)
                return;
        /*
         * We have to be careful, if called from sys_setpriority(),
         * the task might be in the middle of scheduling on another CPU.
         */
        rq = task_rq_lock(p, &flags);
        update_rq_clock(rq);
        /*
         * The RT priorities are set via sched_setscheduler(), but we still
         * allow the 'normal' nice value to be set - but as expected
         * it wont have any effect on scheduling until the task is
         * SCHED_FIFO/SCHED_RR:
         */
        if (task_has_rt_policy(p)) {
                p->static_prio = NICE_TO_PRIO(nice);
                goto out_unlock;
        }
        on_rq = p->se.on_rq;
        if (on_rq)
                dequeue_task(rq, p, 0);

        p->static_prio = NICE_TO_PRIO(nice);
        set_load_weight(p);
        old_prio = p->prio;
        p->prio = effective_prio(p);
        delta = p->prio - old_prio;

        if (on_rq) {
                enqueue_task(rq, p, 0, false);
                /*
                 * If the task increased its priority or is running and
                 * lowered its priority, then reschedule its CPU:
                 */
                if (delta < 0 || (delta > 0 && task_running(rq, p)))
                        resched_task(rq->curr);
        }
out_unlock:
        task_rq_unlock(rq, &flags);
}

2、与处理器绑定有关的系统调用

Linux调度程序提供强制的处理器绑定机制。虽然它尽力通过一种软的亲和性视图使进程尽量在同一个处理器上运行,但它也允许用户强制指定这个进程都必须在这些处理器上运行。这种强制的亲和性保存在进程描述符task_struct的cpus_allowed这个位掩码标志中。该掩码标志的每一位对应一个系统可用的处理器。默认,所有的位都被设置,进程可以在系统中所有可用的处理器上执行。可通过sched_setaffinity()设置不同的一个或几个位组合的位掩码,而调用sched_getaffinity()则返回当前的cpus_allowed位掩码。

内核提供的强制处理器绑定的方法很简单。首先,当处理进行第一次创建时,它继承了其父进程的相关掩码。由于父进程运行在指定处理器上,子进程也运行在相应处理器上。其次,当处理器绑定关系改变时,内核会采用移植线程把任务推到合法的处理器上。最后,加载平衡器只把任务拉到允许的处理器上,因此,进程只运行在指定处理器上,对处理器的指定是由该进程描述符的cpus_allowed域设置的。

3、放弃处理器时间

Linux通过sched_yield()系统调用,提供了一种让进程显式地将处理器时间让给其他等待执行进程的机制。这是通过将进程从活动队列中移到过期队列中实现的。由此产生的效果不仅抢占了该进程并将其放入优先级队列的最后面,还将其放入过期队列中——这样能确保在一段时间内它都不会再被执行了。由于实时进程不会过期,所以属于例外。现在,应用程序甚至内核代码调用sched_yield()之前,应该仔细考虑是否真的希望放弃处理器时间。

内核代码为了方便,可以直接调用yield(),先要确定给定进程确实处于可执行状态,然后再调用sched_yield()。用户空间的应用程序直接使用sched_yield()系统调用就可以了。

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/81131067