【深入Linux内核架构笔记】第二章 进程调度(4)--实时调度RT

551 阅读2分钟

来看一个比完全公平调度简单N倍的实时调度RT

2.7 实时调度类

2.7.1 性质

  • 实时进程:相比普通进程,实时进程实时抢占当前进程运行,适合一些实时性较高的应用
    • 进程转换为实时进程,必须使用sched_setscheduler系统调用
  • 调度特点:如果系统中有一个实时进程在运行,那么调度器总是会选中它运行,除非有一个优先级更高的实时进程
  • 实时调度策略
    • 轮询(SCHED_RR):含时间片,优先级相同的实时进程依次执行,时间片到期置于队列末尾
    • 先进先出(SCHED_FIFO):不含时间片,调度器选择实时进程执行后,可以运行任意长时间

2.7.2 数据结构

  • 就绪队列:只需使用一个链表数组保存
    • active.queue:实时进程的链表数组,类似哈希表结构,如图:

image.png

#define BITS_TO_LONGS(nr)   DIV_ROUND_UP(nr, BITS_PER_LONG)
#define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)]

struct rt_rq {
    struct rt_prio_array active;    //实时进程的链表数组
......
};

struct rt_prio_array {
    DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1);  //位图,unsigned long bitmap[4],链表内是否有进程
    struct list_head queue[MAX_RT_PRIO];    //实时进程的100个优先级对应的链表
};
  • 关键函数update_curr:相比CFS没有虚拟时钟,只需要更新实际时间即可
/* 更新实时进程时间 */
static void update_curr_rt(struct rq *rq) {
    struct task_struct *curr = rq->curr;
    delta_exec = rq->clock - curr->se.exec_start;
......
    //当前进程在CPU上执行的时间记录到sum_exec_runtime
    curr->se.sum_exec_runtime += delta_exec;
    curr->se.exec_start = rq->clock;
......
}

2.7.3 调度器操作

  • 进程加入队列enqueue_task_rt()
    • 加入对应优先级的链表的尾部
    • 设置对应优先级的比特位
static void enqueue_task_rt(struct rq *rq, struct task_struct *p, int wakeup) {
    struct rt_prio_array *array = &rq->rt.active;

    list_add_tail(&p->run_list, array->queue + p->prio);
    __set_bit(p->prio, array->bitmap);
}
  • 选择下一个要执行的进程pick_next_task_rt()
    • 取最高优先级的链表中的第一个进程调度
static struct task_struct *pick_next_task_rt(struct rq *rq) {
    struct rt_prio_array *array = &rq->rt.active;
    struct task_struct *next;
    struct list_head *queue;
    int idx;
    //找最高优先级的实时进程链表
    idx = sched_find_first_bit(array->bitmap);
    if (idx >= MAX_RT_PRIO)
        return NULL;
    
    //取链表的第一个进程调度
    queue = array->queue + idx;
    next = list_entry(queue->next, struct task_struct, run_list);

    //进程调度时间更新
    next->se.exec_start = rq->clock;
    return next;
}
  • 处理抢占task_tick_rt()
    • RR策略:时间片与HZ有关,时间片到的时候,将进程排队到队列末尾
    • FIFO策略:不处理抢占,可以运行任意长时间(类似批处理)
/*
 * These are the 'tuning knobs' of the scheduler:
 *
 * default timeslice is 100 msecs (used only for SCHED_RR tasks).
 * Timeslices get refilled after they expire.
 */
#define DEF_TIMESLICE       (100 * HZ / 1000)

static void task_tick_rt(struct rq *rq, struct task_struct *p) {
    //更新时间
    update_curr_rt(rq);
    //FIFO策略:没有时间片,不处理抢占,除非使用yield
    if (p->policy != SCHED_RR)
        return;
    //RR策略,时间片不为0时递减
    if (--p->time_slice)
        return;

    //时间片为0时复位,默认100ms
    p->time_slice = DEF_TIMESLICE;
    // 如果不是队列上的唯一成员,则重新排队到末尾
    if (p->run_list.prev != p->run_list.next) {
        requeue_task_rt(rq, p);
        set_tsk_need_resched(p);
    }
}