调度器28—杂项汇总

一、获取绑核信息

1. 通过 /proc/<pid>/status 获取

# cat /proc/<pid>/status | grep Cpus_allowed
Cpus_allowed:    ff
Cpus_allowed_list:    0-7

调用路径和函数:

struct pid_entry tgid_base_stuff[] //fs/proc/base.c
    ONE("status", S_IRUGO, proc_pid_status),
struct pid_entry tid_base_stuff[] //fs/proc/base.c
    ONE("status", S_IRUGO, proc_pid_status),
        proc_pid_status //fs/proc/array.c
            task_cpus_allowed //fs/proc/array.c

static void task_cpus_allowed(struct seq_file *m, struct task_struct *task)
{
    //取的是 task->cpus_ptr 而不是 task->cpus_mask
    seq_printf(m, "Cpus_allowed:\t%*pb\n", cpumask_pr_args(task->cpus_ptr));
    seq_printf(m, "Cpus_allowed_list:\t%*pbl\n", cpumask_pr_args(task->cpus_ptr));
}

注:

(1) 这个cat出来的cpu会受到cgroup cpuset的限制,比如echo pid到只有cpu0-3的background分组的cpuset中时,Cpus_allowed_list就是0-3。

(2) cgroup 中 cpuset 的绑核优先级高于per-任务的绑核。比如将任务p taskset -p 7f pid 绑定到CPU0-6,但是一旦将任务echo到 top-app 分组中,任务的cpu亲和性又是0xff了。但是将任务先放到background分组中,此时cpu亲和性是0x0f,此时将任务 taskset -p 7f pid 绑定到CPU0-6,任务的cpu亲和还是0x0f。

 

2. 通过 sched_getaffinity() 系统调用获取

//系统调用
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);

//内核函数,其中 mask 为返回给用户空间的参数
long sched_getaffinity(pid_t pid, struct cpumask *mask)
{
    struct task_struct *p = find_process_by_pid(pid);

    //这里使用的是 p->cpus_mask,而且是与 cpu_active_mask 相与后的结果
    cpumask_and(mask, &p->cpus_mask, cpu_active_mask);

}

 

3. 两种获取方式的差别

sched_getaffinity() 系统调用返回的是 p->cpus_mask & cpu_active_mask 的结果,注意与上后,此结果就受到offline和isolate cpu核的影响了。sched_setaffinity() 系统调用最终设置的是 p->cpus_mask。而 cat /proc/<pid>/status 获取的直接是 task->cpus_ptr 的值,不会受到 cpu_active_mask 影响。

 

4. p->cpus_ptr 指针和 p->cpus_mask 变量的区别

sched_setaffinity() 系统调用最终调用到调度类的 .set_cpus_allowed 回调,5大调度类的此回调都指向 set_cpus_allowed_common(),它里面设置的是 p->cpus_mask。在 kernel/sched 下检索,发现 cpus_ptr 在 fair.c、deadline.c、rt.c、core.c 中使用,而 cpus_mask 只在 core.c 中使用。在 core.c 中可以看到 p->cpus_mask 只是在 sched_setaffinity()/sched_getaffinity() 系统调用执行路径中使用到。

默认其实二者是同一个东西,如下:

//fork --> copy_process --> dup_task_struct
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
    ...
    if (orig->cpus_ptr == &orig->cpus_mask)
        tsk->cpus_ptr = &tsk->cpus_mask;
    ...
}

//追溯到最初首个任务:

//init/init_task.c
struct task_struct init_task = {
    ...
    .cpus_ptr    = &init_task.cpus_mask,
    .cpus_mask    = CPU_MASK_ALL,
    .nr_cpus_allowed= NR_CPUS, //这个最初可能是32
    ...
}

p->cpus_ptr 默认是指向 p->cpus_mask 的,但是在某些特殊情况下也可能不等于,比如要放开后台线程的cpu分组,会设置 p->cpus_ptr 指向其它mask.如下:

p->cpus_ptr = new_mask;
p->nr_cpus_allowed = cpumask_weight(new_mask);

 

二、调度中的锁

1. __task_rq_lock 注释翻译

(1) 串行化规则:

锁定顺序:

    p->pi_lock
        rq->lock
            hrtimer_cpu_base->lock (hrtimer_start() 带宽控制使用)

    rq1->lock
        rq2->lock  条件: rq1 < rq2

(2) 常规状态:

正常情况下调度状态由 rq->lock 序列化。 __schedule() 获取本地 CPU 的 rq->lock,它可以选择从运行队列中删除任务,并始终查看本地 rq 数据结构以找到最符合要求的任务来运行。

任务入队也在 rq->lock 的保护下,任务可能取自另一个 CPU。 来自另一个 LLC 域的唤醒可能会使用 IPI 将入队传输到本地 CPU,以避免在周围反弹(bouncing)运行队列的状态 [参见 ttwu_queue_wakelist()]

任务唤醒,特别是涉及迁移的唤醒,为避免不得不使用两个 rq->locks,非常复杂。

(3) 特殊状态:

系统调用和任何外部操作都将使用 task_rq_lock() 获取 p->pi_lock 和 rq->lock。 因此,它们更改的状态在持有任一锁时都是稳定的:

- sched_setaffinity()/set_cpus_allowed_ptr():    p->cpus_ptr, p->nr_cpus_allowed
- set_user_nice():    p->se.load, p->*prio
- __sched_setscheduler():
    p->sched_class, p->policy, p->*prio,
    p->se.load, p->rt_priority,
    p->dl.dl_{runtime, deadline, period, flags, bw, density}
- sched_setnuma():    p->numa_preferred_nid
- sched_move_task()/cpu_cgroup_fork():    p->sched_task_group
- uclamp_update_active():    p->uclamp*

(4) p->state <- TASK_*:

使用 set_current_state(),__set_current_state() 或 et_special_state() 无锁地更改,查看它们各自的注释,或通过 try_to_wake_up()。 后者使用 p->pi_lock 针对并发自身进行序列化。

(5) p->on_rq <- { 0, 1 = TASK_ON_RQ_QUEUED, 2 = TASK_ON_RQ_MIGRATING }:

由 activate_task() 设置并由 deactivate_task() 在 rq->lock 下清除。 非零表示任务是可运行的,特殊的 ON_RQ_MIGRATING 状态用于迁移而不持有两个 rq->locks。它表示 task_cpu() 不稳定,参见 task_rq_lock()。

(6) p->on_cpu <- { 0, 1 }:

由 prepare_task() 设置并由 finish_task() 清除,这样它将在 p 被调度之前设置并在 p 被切换走之后清除,两者都在 rq->lock 的保护下。 非零表示任务正在其 CPU 上运行。[ 精明的读者会观察到,一个 CPU 上的两个任务可能同时具有 ->on_cpu = 1。]

(7) task_cpu(p):由 set_task_cpu() 改变,规则为:

- 不要在阻塞的任务上调用 set_task_cpu():
  我们不关心我们没有在上面运行的CPU,这简化了热插拔,阻塞任务的 CPU 分配不需要是有效的。

- 用于 try_to_wake_up(),在 p->pi_lock 下调用:
  这允许 try_to_wake_up() 只使用一个 rq->lock,查看它的注释。

- 用于在 rq->lock 下调用的迁移:
  [参见 task_rq_lock() 中的 task_on_rq_migrating()]
    move_queued_task()
    detach_task()

- 用于在 double_rq_lock() 下调用的迁移:
  __migrate_swap_task()
  push_rt_task() / pull_rt_task()
  push_dl_task() / pull_dl_task()
  dl_task_offline_migration()

 

三、调度类在5.10上放弃使用next指针

改为使用链接器将调度类链接在一起了,低优先级的调度类在前,高优先的调度类在后。

//fair.c 里面已经没有 next 指针了
const struct sched_class fair_sched_class __section("__fair_sched_class") = {
    ...
}

#define __section(section)    __attribute__((__section__(section)))

//include/asm-generic/vmlinux.lds.h
#define SCHED_DATA                \
    STRUCT_ALIGN();                \
    __begin_sched_classes = .;        \
    *(__idle_sched_class)            \
    *(__fair_sched_class)            \
    *(__rt_sched_class)            \
    *(__dl_sched_class)            \
    *(__stop_sched_class)            \
    __end_sched_classes = .;


//使用下面宏访问:

//kernel/sched/sched.h

/* Defined in include/asm-generic/vmlinux.lds.h */
extern struct sched_class __begin_sched_classes[];
extern struct sched_class __end_sched_classes[];

#define sched_class_highest (__end_sched_classes - 1)
#define sched_class_lowest  (__begin_sched_classes - 1)

#define for_class_range(class, _from, _to) \
    for (class = (_from); class != (_to); class--) //[ )

#define for_each_class(class) \
    for_class_range(class, sched_class_highest, sched_class_lowest)

 

四、cpumask_var_t 和 struct cpumask

typedef struct cpumask { unsigned long bits[1]; } cpumask_t;
typedef struct cpumask cpumask_var_t[1];

cpumask_var_t 类型为 "struct cpumask [1]",是一个数组长度为1的数组名,也可以认为是指向struct cpumask类型的常指针,除了做左值外等效于"struct cpumask*"。

举例:

#include <stdio.h>

typedef struct cpumask { unsigned long bits[1]; } cpumask_t;
typedef struct cpumask cpumask_var_t[1];

cpumask_var_t mt = {0xef}; //定义数组并赋值

void print_mask_bit(struct cpumask *mask)
{
    printf("mask: 0x%lx\n", mask->bits[0]);
    
}

void main()
{
    print_mask_bit(mt);    //注意传参
}

/*
$ gcc main.c -o pp
$ ./pp
mask: 0xef
*/

 

五、Cache优化

1. cache预取

prefetch_curr_exec_start
    prefetch(&curr->exec_start);

 

posted on 2022-01-22 16:02  Hello-World3  阅读(1024)  评论(0编辑  收藏  举报

导航