实时调度类及SMP 和NUMA


实时调度类

  1. Linux 进程可分为两大类:实时进程和普通进程,实时进程与普通进程根本不同之处,如果系统中有一个实时进程且可执行,那么调度器总是会选择它,除非另有一个优先级更高实时进程。 SCHED_FIFO: 没有时间片,在被调度器选择之后,可以运行任意时长时间; SCHED_RR: 有时间片,其值在进程运行时会减少

实时调度实体sched_rt_entity 数据结构

表示实时调度实体,包含整个实时调度数据信息,

struct sched_rt_entity {
    struct list_head run_list;//专门用于加入到优先级队列中
    unsigned long timeout;//设置的时间超时
    unsigned long watchdog_stamp; //用于记录jiffies值
    unsigned int time_slice;//时间片
    unsigned short on_rq;
    unsigned short on_list;
    struct sched_rt_entity *back;  //临时用于从上往下链接RT调度实体使用
    #ifdef CONFIG_RT_CONFIG_SCHED
        struct sched_rt_entity *parent; //指向父RT调度实体
        struct rt_rq *rt_rq;//RT调度实体所属的实时运行队列,被调度
        struct rt_rq *my_q; //RT 调度实体所拥有的实时运行队列,用于管理任务或子组任务
    #endif
} __randomize_layout;

实时调度类rt_sched_class 数据结构

 const struct sched_class rt_sched_class = {
    .next = &fair_sched_class,
    .enqueue_task = enqueue_task_rt, //将一个task存放到就绪队列或者尾部
    .dequeue_task = dequeue_task_rt, //将一个task从就绪队列尾部
    .yield_task = yield_task_rt,//主动放弃执行
    .check_preempt_curr = check_preempt_curr_rt,
    .pick_next_task = pick_next_task_rt, //核心调度器选择就绪队列当中的哪个任务将要被调度,prev是将要被调度出的任务,返回值是将要被调度的任务。
    .put_prev_task = put_prev_task_rt,//当一个任务将要被调度出时候执行
    .set_next_task = set_next_task_rt,
 }

解析一下rt_rq 源码

 struct rt_rq {
    struct rt_prio_array active; //优先级队列
    unsigned int rt_nr_running;// 在rt 运行队列当中所有活动的任务数
    unsigned int rr_nr_running;
#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED
    struct {
        int curr;  //当前 rt任务的最高优先级
#ifdef  CONFIG_SMP
        int next; //下一个要运行的RT任务优先级。如果两个任务都有最高优先级,cur==next
#endif
    } highest_prio;
#endif
#ifdef CONFIG_SMP
    unsigned long rt_nr_migratory;
    unsigned long rt_nr_total;
    int overloaded;
    struct plist_head pushable_tasks;
#endif
int rt_queued;
int rt_throttled;
u64 rt_time;
u64 rt_runtime;
raw_spinlock_t rt_runtime_lock;

 }

实时调度类操作核心函数enqueue_task_rt(...)->插入进程,具体Linux内核源码如下. 更新调度信息,将调度实体插入到相应优先级队列的末尾

    static void enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
    {
        struct sched_rt_entity *rt_se = &p->rt;
        if(flags & ENQUEUE_WAKEUP)
            rt_se -> timeout = 0;
        enqueue_rt_entity(rt_se,flags);
        if(!task_current(rq,q) && p->nr_cups_allowed > 1)
            enqueue_pushable_task(rq,p);
    }

实时调度类操作核心函数pick_next_rt_entity(...)->选择进程,实时调度会选择最高优先级的实时进程来运行:

    static struct sched_rt_entity *pick_next_rt_entity(struct rq *rq,struct rt_rq *rt_rq)
    {
        struct rt_prio_array *array = &rt_rq->active;
        struct sched_rt_entity *next = NULL;
        struct list_head *queue;
        int idex;
        //第一个找到一个可用的实体
        idx = sched_find_first_bit(array->bitmap);

        BUG_ON(idx >= MAX_RT_PRIO);
        //从链表组中找到对应的链表
        queue = array->queue + idx;
        next = list_entry(queue->next,struct sched_rt_entity,run_list);
        return next; // 返回找到运行实体
    }

实时调度类操作核心函数dequeue_task_rt(...)->删除进程,从优先级队列中删除实时进程,并更新调度信息,然后把这个进程添加队尾 删除进程

    static void dequeue_task_rt(struct rq *rq,struct task_struct *p, int flags)
    {
        struct sched_rt_entity *rt_se = &p->rt;
        update_curr_rt(rq); //更新调度数据信息等等
        dequeue_rt_entity(rt_se,flags);//将rt_se 从运行队列当中删除,然后添加到队列尾部
        dequeue_pushable_task(rq,p); //从hash 表当中进行删除
    }

SMP和NUMA

SMP(对称多处理器结构)

对称多处理器结构(SMP),在对称多处理器系统中,所有处理器的地位都是平等的,所有的CPU共享全部资源,比如内存,总线,中断以及I/O系统等等,都具有相同的可访问性,消除结构上的障碍,最大特点就是共享所有资源。
在多处理器系统当中,内核必须考虑几个额外的问题,主要以确保良好的调度。
    CPU负荷必须尽可能公平在所有处理器上共享。
    进程与系统中某些处理器的亲和性必须是可设置的。
    内核必须能够将进程从一个CPU迁移到另一个。
Linux SMP调度就是将进程安排/迁移到合适的CPU中去,保持各CPU负载均衡的过程。
当前使用的OTLP程序当中,用户访问一个中断数据库,如果采用SMP系统架构,它的效率要比MPP架构要快。

NUMA(非一致内存访问结构)

NUMA为是处理器计算机,系统各个CPU 都有本地内存,可以支持超快的访问能力,各个处理器之间通过总线连接起来,支持对其他CPU的本地内存访问(但比访问自己的内存要慢点)
一台物理服务器内部集成多个CPU,是系统具有较高事务处理能力,MUNA架构适合OLTP事务处理环境

CPU域初始化

根据实际物理属性,CPU分类(SMT,MC,SoC)Linux 内核分类(CONFIG_SCHED_SMT,CONFIG_SCHED_MC,DIE),Linux 内核对CPU 的管理是通过bitmap来管理,并且定义4种状态possible/present/online/active
```
extern struct cpumask __cpu_possible_mask;
extern struct cpumask __cpu_online_mask;
extern struct cpumask __cpu_present_mask;
extern struct cpumask __cpu_active_mask;
// 表示系统当中有多少个可以执行的CPU核心
#define cpu_possible_mask((const struct cpumask *) &__cpu_possible_mask)
//表示系统当中有多少个正在运行状态的CPU 核心
#define cpu_online_mask((const struct cpumask *) &__cpu_online_mask)
// 表示系统当中有多个具备online条件的CPU核心,他们不一定都处于online,有的cpu核心可能被热插拔
#define cpu_present_mask((const struct cpumask *) &__cpu_present_mask)
//表示系统当中有多个少活跃的CPU核心
#define cpu_active_mask((const struct cpumask *) &__cpu_active_mask)
```