多处理器架构

多处理器与单处理器最大的区别在于对硬件缓存的使用,以及处理器之间共享数据的方式。

在单CPU系统中,存在多级硬件缓存,一般会让程序执行更快。

假设一个程序需要从内存中加载指令并读取一个值,在它第一次读取时,需要从内存中读取,之后处理器判定在短期内很可能会再次使用这个数据,会将其放入缓存中,下一次再请求时,直接从缓存中取即可。

在多CPU系统中,缓存则会变得复杂。

假如有两个CPU,一个内存。现在有一个运行在CPU1上的程序,要去地址A读值,但是CPU1里面没有该数据的缓存,所以需要去内存中读取,读取完成后,程序要修改地址A上的值,假设改为D,它会先写入缓存中,然后再去写入内存。但是写入内存时发生了中断,中断完后此程序交由CPU2执行,但是CPU2中没有数据的缓存,就需要从内存中读,这时候就会读到旧得数据,而不是修改后得D。

硬件提供了这个问题得解决方案:通过监控内存得访问,硬件可以保证获得正确数据,并确保共享内存的唯一性。

单队列调度

这种调度方式就是采用一个队列来存储任务,然后有多个cpu从中获取任务,然后执行。但是这里需要用加锁的方式来保证调度的正确性,而加锁又会带来性能上的损耗。而且可能存在一种情况,一个任务频繁的在不同的cpu上运行,导致cpu的缓存几乎不起作用。我们应该尽可能保证一个任务一直在一个cpu上处理,来尽可能多的提高缓存命中率。

一个cpu缓存命中率特别低的情况:

image-20230414205315810

多队列调度

这种设计下,队列与CPU是多对多的关系,我们可以让一个队列对应一个CPU。

这样锁的竞争就会小很多,如果每一个CPU只从固定的一个队列获取任务,甚至可以消除锁竞争。而且它的缓存亲和性比较好,一个队列里面的任务会固定在一个CPU上运行。

如果按照一个CPU只处理自己对应队列的任务,会存在一种情况,cpu1里的任务已经执行完了,而cpu2里的任务还没有,那么cpu2一直在忙碌 ,cpu1却在空闲,导致负载不均衡。

如下图:A独占cpu0,而B 和 D交替执行在cpu1上。

image-20230414205421683

若A执行完成,则会变成如下:

image-20230414205507938

解决办法:迁移。通过工作的跨cpu迁移,可以实现负载均衡。

即cpu1可以处理cpu2对应队列中的任务。

假如cpu1队列中一开始只有任务A,而cpu2中有任务B和D,那么在迁移的设计下,执行过程如下:

image-20230414205237310

参考

《操作系统导论》