Rumah  >  Artikel  >  Tutorial sistem  >  Mekanisme pengimbangan beban CPU kernel Linux: prinsip, proses dan pengoptimuman

Mekanisme pengimbangan beban CPU kernel Linux: prinsip, proses dan pengoptimuman

王林
王林ke hadapan
2024-02-09 13:50:11906semak imbas

Pengimbangan beban CPU merujuk kepada memperuntukkan proses atau tugas yang sedang berjalan kepada CPU yang berbeza dalam sistem berbilang teras atau berbilang pemproses supaya beban setiap CPU seimbang sebanyak mungkin, seterusnya meningkatkan prestasi dan kecekapan sistem. Pengimbangan beban CPU ialah fungsi penting kernel Linux Ia membolehkan sistem Linux memanfaatkan sepenuhnya berbilang teras atau berbilang pemproses untuk menyesuaikan diri dengan senario dan keperluan aplikasi yang berbeza. Tetapi, adakah anda benar-benar memahami mekanisme pengimbangan beban CPU bagi kernel Linux? Adakah anda tahu prinsip kerja, proses dan kaedah pengoptimumannya? Artikel ini akan memperkenalkan anda kepada pengetahuan yang berkaitan tentang mekanisme pengimbangan beban CPU bagi kernel Linux secara terperinci, membolehkan anda menggunakan dan memahami fungsi kernel yang berkuasa ini dengan lebih baik di bawah Linux.

Ia masih disebabkan oleh masalah penjadualan proses yang ajaib Sila rujuk analisis mekanisme penjadualan kumpulan proses Linux Mekanisme penjadualan kumpulan didapati dengan jelas semasa proses restart, banyak timbunan panggilan kernel fungsi double_rq_lock, dan double_rq_lock dicetuskan oleh load_balance , saya mengesyaki bahawa terdapat masalah dengan penjadualan antara teras pada masa itu, dan interlocking berbilang teras berlaku dalam senario yang bertanggungjawab Kemudian, saya melihat pelaksanaan kod di bawah beban CPU mengimbangi dan menulis ringkasan.

Versi kod kernel: kernel-3.0.13-0.27.

Fungsi kod kernel bermula dari fungsi load_balance Dari fungsi load_balance, anda boleh mencari fungsi jadual di sini dengan melihat fungsi yang merujuknya Dari sini, lihat ke bawah.

if (unlikely(!rq->nr_running))
   idle_balance(cpu, rq);

Ia boleh dilihat daripada perkara di atas apabila kernel akan cuba melakukan pengimbangan beban CPU: iaitu, apabila baris gilir larian CPU semasa adalah NULL.
Terdapat dua kaedah pengimbangan beban CPU: tarik dan tolak, iaitu, CPU terbiar menarik proses daripada baris gilir CPU sibuk lain ke baris gilir CPU semasa atau baris gilir CPU yang sibuk menolak proses ke baris gilir CPU terbiar. Apa yang dilakukan idle_balance ialah tarik Tolakan khusus akan disebutkan di bawah.
Dalam idle_balance, terdapat injap proc untuk mengawal sama ada CPU semasa ditarik:

if (this_rq->avg_idle return;

Fail kawalan proc yang sepadan bagi sysctl_sched_migration_cost ialah /proc/sys/kernel/sched_migration_cost Suis bermakna jika baris gilir CPU melahu selama lebih daripada 500us (sysctl_sched_migration_cost nilai lalai), tarik akan dilakukan, jika tidak, ia akan dikembalikan.
for_each_domain(this_cpu, sd) merentasi domain penjadualan di mana CPU semasa berada. Ia boleh difahami secara intuitif sebagai kumpulan CPU, sama seperti imbangan antara teras merujuk kepada baki dalam kumpulan. Terdapat percanggahan dalam pengimbangan beban: kekerapan pengimbangan beban dan kadar pukulan cache CPU adalah bercanggah Domain penjadualan CPU membahagikan setiap CPU kepada kumpulan dengan tahap yang berbeza tahap tinggi untuk pemprosesan, yang mengelakkan Kesan kadar hit cache.

Gambar adalah seperti berikut;
Linux 内核的 CPU 负载均衡机制:原理、流程和优化

Akhirnya mari kita sampai ke titik melalui load_balance.

Pertama, dapatkan kumpulan penjadualan tersibuk dalam domain penjadualan semasa melalui find_busiest_group Mula-mula, update_sd_lb_stats mengemas kini status sd, iaitu, merentasi sd yang sepadan dan mengisi data struktur dalam sds, seperti berikut:

struct sd_lb_stats {
    struct sched_group *busiest; /* Busiest group in this sd */
    struct sched_group *this;  /* Local group in this sd */
    unsigned long total_load;  /* Total load of all groups in sd */
    unsigned long total_pwr;   /*    Total power of all groups in sd */
    unsigned long avg_load;       /* Average load across all groups in sd */
 
    /** Statistics of this group */
    unsigned long this_load; //当前调度组的负载
    unsigned long this_load_per_task; //当前调度组的平均负载
    unsigned long this_nr_running; //当前调度组内运行队列中进程的总数
    unsigned long this_has_capacity;
    unsigned int  this_idle_cpus;
 
    /* Statistics of the busiest group */
    unsigned int  busiest_idle_cpus;
    unsigned long max_load; //最忙的组的负载量
    unsigned long busiest_load_per_task; //最忙的组中平均每个任务的负载量
    unsigned long busiest_nr_running; //最忙的组中所有运行队列中进程的个数
    unsigned long busiest_group_capacity;
    unsigned long busiest_has_capacity;
    unsigned int  busiest_group_weight;
do
{
local_group = cpumask_test_cpu(this_cpu, sched_group_cpus(sg));
         if (local_group) {
                      //如果是当前CPU上的group,则进行赋值
            sds->this_load = sgs.avg_load;
            sds->this = sg;
            sds->this_nr_running = sgs.sum_nr_running;
            sds->this_load_per_task = sgs.sum_weighted_load;
            sds->this_has_capacity = sgs.group_has_capacity;
            sds->this_idle_cpus = sgs.idle_cpus;
        } else if (update_sd_pick_busiest(sd, sds, sg, &sgs, this_cpu)) {
                     //在update_sd_pick_busiest判断当前sgs的是否超过了之前的最大值,如果是
                     //则将sgs值赋给sds
            sds->max_load = sgs.avg_load;
            sds->busiest = sg;
            sds->busiest_nr_running = sgs.sum_nr_running;
            sds->busiest_idle_cpus = sgs.idle_cpus;
            sds->busiest_group_capacity = sgs.group_capacity;
            sds->busiest_load_per_task = sgs.sum_weighted_load;
            sds->busiest_has_capacity = sgs.group_has_capacity;
            sds->busiest_group_weight = sgs.group_weight;
            sds->group_imb = sgs.group_imb;
        }
        sg = sg->next;
} while (sg != sd->groups);

Kriteria rujukan untuk membuat keputusan untuk memilih kumpulan paling sibuk dalam domain penjadualan ialah jumlah beban pada semua CPU dalam kumpulan Kriteria rujukan untuk mencari baris gilir larian sibuk dalam kumpulan ialah panjang baris gilir CPU, itu ialah, beban, dan Semakin besar nilai beban, semakin sibuk. Semasa proses pengimbangan, dengan membandingkan status beban baris gilir semasa dengan yang paling sibuk yang direkodkan sebelum ini, pembolehubah ini dikemas kini tepat pada masanya supaya yang paling sibuk sentiasa menunjuk kepada kumpulan paling sibuk dalam domain untuk carian mudah.
Pengiraan beban purata domain penjadualan

sds.avg_load = (SCHED_POWER_SCALE * sds.total_load) / sds.total_pwr;
if (sds.this_load >= sds.avg_load)
    goto out_balanced;

Dalam proses membandingkan saiz beban, apabila didapati kumpulan paling sibuk dalam CPU yang sedang berjalan kosong, atau baris gilir CPU yang sedang berjalan adalah yang paling sibuk, atau beban baris gilir CPU semasa tidak kurang daripada purata dalam kumpulan ini Apabila beban berada di bawah beban, atau apabila jumlah tidak seimbang tidak besar, nilai NULL akan dikembalikan, iaitu, pengimbangan antara kumpulan tidak diperlukan apabila beban kumpulan paling sibuk adalah kurang daripada beban purata domain penjadualan, hanya julat kecil beban yang perlu dijalankan Baki apabila jumlah tugas yang akan dipindahkan adalah kurang daripada beban purata setiap proses, kumpulan penjadualan yang paling sibuk diperolehi.
Kemudian cari baris gilir penjadualan paling sibuk dalam find_busiest_queue, lalui semua baris gilir CPU dalam kumpulan dan cari baris gilir paling sibuk dengan membandingkan beban setiap baris gilir.

or_each_cpu(i, sched_group_cpus(group)) {
/*rq->cpu_power表示所在处理器的计算能力,在函式sched_init初始化时,会把这值设定为SCHED_LOAD_SCALE (=Nice 0的Load Weight=1024).并可透过函式update_cpu_power (in kernel/sched_fair.c)更新这个值.*/
        unsigned long power = power_of(i);
        unsigned long capacity = DIV_ROUND_CLOSEST(power,SCHED_POWER_SCALE);
        unsigned long wl;
        if (!cpumask_test_cpu(i, cpus))
            continue;
 
        rq = cpu_rq(i);
/*获取队列负载cpu_rq(cpu)->load.weight;*/
        wl = weighted_cpuload(i);
 
        /*
         * When comparing with imbalance, use weighted_cpuload()
         * which is not scaled with the cpu power.
         */
        if (capacity && rq->nr_running == 1 && wl > imbalance)
            continue;
 
        /*
         * For the load comparisons with the other cpu's, consider
         * the weighted_cpuload() scaled with the cpu power, so that
         * the load can be moved away from the cpu that is potentially
         * running at a lower capacity.
         */
        wl = (wl * SCHED_POWER_SCALE) / power;
 
        if (wl > max_load) {
            max_load = wl;
            busiest = rq;
        }

Melalui pengiraan di atas, kami mendapat giliran yang paling sibuk.
Apabila nombor paling sibuk->nr_running lebih daripada 1, operasi tarik dilakukan Sebelum tarik, tugas_gerak dikunci oleh double_rq_lock.

double_rq_lock(this_rq, busiest);
ld_moved = move_tasks(this_rq, this_cpu, busiest,
        imbalance, sd, idle, &all_pinned);
double_rq_unlock(this_rq, busiest);

Tugas tarik proses move_tasks dibenarkan gagal, iaitu, move_tasks->balance_tasks Di sini, terdapat suis sysctl_sched_nr_migrate untuk mengawal bilangan migrasi proses yang sepadan ialah /proc/sys/kernel/sched_nr_migrate.
Di bawah ialah fungsi can_migrate_task untuk menyemak sama ada proses yang dipilih boleh dipindahkan. Proses Cache masih panas, dan ini juga untuk memastikan kadar hit cache.

    /*关于cache cold的情况下,如果迁移失败的个数太多,仍然进行迁移
     * Aggressive migration if:
     * 1) task is cache cold, or
     * 2) too many balance attempts have failed.
     */
 
    tsk_cache_hot = task_hot(p, rq->clock_task, sd);
    if (!tsk_cache_hot ||
        sd->nr_balance_failed > sd->cache_nice_tries) {
#ifdef CONFIG_SCHEDSTATS
        if (tsk_cache_hot) {
            schedstat_inc(sd, lb_hot_gained[idle]);
            schedstat_inc(p, se.statistics.nr_forced_migrations);
        }
#endif
        return 1;
    }

Tentukan sama ada cache proses adalah sah dan tentukan keadaan Masa berjalan proses lebih besar daripada suis kawalan proc sysctl_sched_migration_cost, sepadan dengan direktori /proc/sys/kernel/sched_migration_cost_ns

static int
task_hot(struct task_struct *p, u64 now, struct sched_domain *sd)
{
        s64 delta;
    delta = now - p->se.exec_start;
    return delta 

在load_balance中,move_tasks返回失败也就是ld_moved==0,其中sd->nr_balance_failed++对应can_migrate_task中的”too many balance attempts have failed”,然后busiest->active_balance = 1设置,active_balance = 1。

if (active_balance)
//如果pull失败了,开始触发push操作
stop_one_cpu_nowait(cpu_of(busiest),
    active_load_balance_cpu_stop, busiest,
    &busiest->active_balance_work);

push整个触发操作代码机制比较绕,stop_one_cpu_nowait把active_load_balance_cpu_stop添加到cpu_stopper每CPU变量的任务队列里面,如下:

void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg,
            struct cpu_stop_work *work_buf)
{
    *work_buf = (struct cpu_stop_work){ .fn = fn, .arg = arg, };
    cpu_stop_queue_work(&per_cpu(cpu_stopper, cpu), work_buf);
}

而cpu_stopper则是cpu_stop_init函数通过cpu_stop_cpu_callback创建的migration内核线程,触发任务队列调度。因为migration内核线程是绑定每个核心上的,进程迁移失败的1和3问题就可以通过push解决。active_load_balance_cpu_stop则调用move_one_task函数迁移指定的进程。

上面描述的则是整个pull和push的过程,需要补充的pull触发除了schedule后触发,还有scheduler_tick通过触发中断,调用run_rebalance_domains再调用rebalance_domains触发,不再细数。

void __init sched_init(void)
{
      open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
}

通过本文,你应该对 Linux 内核的 CPU 负载均衡机制有了一个深入的了解,知道了它的定义、原理、流程和优化方法。你也应该明白了 CPU 负载均衡机制的作用和影响,以及如何在 Linux 下正确地使用和配置它。我们建议你在使用多核或多处理器的 Linux 系统时,使用 CPU 负载均衡机制来提高系统的性能和效率。同时,我们也提醒你在使用 CPU 负载均衡机制时要注意一些潜在的问题和挑战,如负载均衡策略、能耗、调度延迟等。希望本文能够帮助你更好地使用 Linux 系统,让你在 Linux 下享受 CPU 负载均衡机制的优势和便利。

Atas ialah kandungan terperinci Mekanisme pengimbangan beban CPU kernel Linux: prinsip, proses dan pengoptimuman. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:lxlinux.net. Jika ada pelanggaran, sila hubungi admin@php.cn Padam