58

我可以理解如何编写一个使用多个进程或线程的程序:fork() 一个新进程并使用 IPC,或者创建多个线程并使用这些类型的通信机制。

我也理解上下文切换。也就是说,只有一个 CPU,操作系统为每个进程安排时间(并且有大量的调度算法),从而我们实现了同时运行多个进程。

现在我们有了多核处理器(或多处理器计算机),我们可以让两个进程在两个独立的内核上同时运行。

我的问题是关于最后一个场景:内核如何控制进程在哪个内核上运行?哪些系统调用(在 Linux 甚至 Windows 中)在特定内核上调度进程?

我问的原因是:我正在为学校开展一个项目,我们将在其中探索最近的计算主题——我选择了多核架构。似乎有很多关于如何在这种环境中编程(如何观察死锁或竞争条件)的材料,但关于控制单个内核本身的材料却不多。我希望能够编写一些演示程序并提供一些汇编指令或 C 代码,以达到“看,我在第二个核心上运行一个无限循环,看看那个特定核心的 CPU 利用率峰值”的效果.

任何代码示例?还是教程?

编辑:为了澄清 - 许多人说这是操作系统的目的,应该让操作系统来处理这个问题。我完全同意!但是我要问(或试图感受)操作系统实际上做了什么来做到这一点。不是调度算法,而是更多“一旦选择了一个核心,必须执行哪些指令才能让该核心开始获取指令?”

4

9 回答 9

40

正如其他人所提到的,处理器亲和性是操作系统特定的。如果您想在操作系统范围之外执行此操作,那么您将获得很多乐趣,而我的意思是痛苦。

也就是说,其他人已经提到SetProcessAffinityMask了 Win32。没有人提到 Linux 内核设置处理器亲和性的方法,所以我会。您需要使用sched_setaffinity(2)系统调用。这是一个很好的教程

此系统调用的命令行包装器是taskset(1). 例如
taskset -c 2,3 perf stat awk 'BEGIN{for(i=0;i<100000000;i++){}}',将繁忙循环的 perf-stat 限制为在核心 2 或 3 上运行(仍然允许它在核心之间迁移,但只能在这两个核心之间迁移)。

于 2009-04-07T17:52:42.490 回答
31

通常,关于应用程序将在哪个内核上运行的决定是由系统决定的。但是,您可以将应用程序的“亲和性”设置为特定内核,以告诉操作系统仅在该内核上运行应用程序。通常这不是一个好主意,但在极少数情况下它可能有意义。

要在 Windows 中执行此操作,请使用任务管理器,右键单击该进程,然后选择“设置关联”。您可以在 Windows 中使用 SetThreadAffinityMask、SetProcessAffinityMask 或 SetThreadIdealProcessor 等函数以编程方式执行此操作。

预计到达时间:

如果您对操作系统如何实际进行调度感兴趣,您可能需要查看以下链接:

关于上下文切换的维基百科文章

维基百科关于调度的文章

linux内核中的调度

对于大多数现代操作系统,操作系统会安排一个线程在内核上执行一小段时间。当时间片到期,或线程执行 IO 操作导致其自愿让出内核时,操作系统将调度另一个线程在内核上运行(如果有任何线程准备好运行)。具体调度哪个线程取决于操作系统的调度算法。

上下文切换的具体实现细节取决于 CPU 和操作系统。它通常会涉及切换到内核模式,操作系统保存前一个线程的状态,加载新线程的状态,然后切换回用户模式并恢复新加载的线程。我上面链接的上下文切换文章对此有更多详细信息。

于 2009-03-19T20:42:26.067 回答
6

没有什么告诉核心“现在开始运行这个过程”。

内核看不到进程,它只知道可执行代码和各种运行级别以及对可执行指令的相关限制。

当计算机启动时,为简单起见,只有一个内核/处理器处于活动状态并实际运行任何代码。然后,如果操作系统支持多处理器,它会使用一些系统特定指令激活其他内核,其他内核很可能从与其他内核完全相同的位置拾取并从那里运行。

所以调度程序所做的是它查看操作系统内部结构(任务/进程/线程队列)并选择一个并将其标记为在其核心运行。然后在其他核心上运行的其他调度程序实例不会触及它,直到任务再次处于等待状态(并且未标记为固定到特定核心)。在任务被标记为正在运行后,调度程序执行切换到用户空间,任务在之前暂停的点恢复。

从技术上讲,没有什么可以阻止内核在完全相同的时间运行完全相同的代码(许多未锁定的函数会这样做),但除非编写代码是为了期望这一点,否则它可能会自找麻烦。

使用更奇特的内存模型(上面假设“通常”的线性单一工作内存空间)的场景变得更奇怪,其中核心不一定都看到相同的内存,并且可能需要从其他核心的离合器中获取代码,但它更容易通过简单地处理将任务固定在核心上(带有 SPU 的 AFAIK 索尼 PS3 架构就是这样)。

于 2009-04-11T10:40:51.580 回答
5

要找出处理器的数量,而不是使用 /proc/cpuinfo,只需运行:

nproc

在一组特定处理器上运行进程:

taskset --cpu-list 1,2 my_command 

会说我的命令只能在 cpu 1 或 2 上运行。

要在 4 个处理器上运行程序,做 4 种不同的事情,请使用参数化。程序的参数告诉它做一些不同的事情:

for i in `seq 0 1 3`;
do 
  taskset --cpu-list $i my_command $i;
done

一个很好的例子是处理数组中的 800 万次操作,因此 0 到 (2mil-1) 到处理器 1,2mil 到 (4mil-1) 到处理器 2,依此类推。

您可以通过使用 apt-get/yum 安装 htop 并在命令行运行来查看每个进程的负载:

 htop
于 2017-08-28T10:13:03.143 回答
4

OpenMPI项目有一个库,可以Linux以可移植的方式设置处理器亲和性。

前段时间,我在一个项目中使用过它,它运行良好。

警告:我依稀记得在找出操作系统如何对内核进行编号时存在一些问题。我在一个 2 Xeon CPU 系统中使用了它,每个系统有 4 个内核。

看看cat /proc/cpuinfo可能会有所帮助。在我使用的盒子上,它很奇怪。归结输出是在最后。

显然,偶数核心在第一个 cpu 上,奇数核心在第二个 cpu 上。但是,如果我没记错的话,缓存有问题。在这些 Intel Xeon 处理器上,每个 CPU 上的两个内核共享它们的 L2 缓存(我不记得处理器是否有 L3 缓存)。我认为虚拟处理器0和2共享一个L2缓存,1和3共享一个,4和6共享一个,5和7共享一个。

由于这种怪异(1.5 年前我在 Linux 中找不到任何关于进程编号的文档),我会小心地进行这种低级别的调整。但是,显然有一些用途。如果您的代码在几种机器上运行,那么进行这种调整可能是值得的。另一个应用程序将使用某些特定领域的语言,例如StreamIt,编译器可以在其中完成这项肮脏的工作并计算智能调度。

processor       : 0
physical id     : 0
siblings        : 4
core id         : 0
cpu cores       : 4

processor       : 1
physical id     : 1
siblings        : 4
core id         : 0
cpu cores       : 4

processor       : 2
physical id     : 0
siblings        : 4
core id         : 1
cpu cores       : 4

processor       : 3
physical id     : 1
siblings        : 4
core id         : 1
cpu cores       : 4

processor       : 4
physical id     : 0
siblings        : 4
core id         : 2
cpu cores       : 4

processor       : 5
physical id     : 1
siblings        : 4
core id         : 2
cpu cores       : 4

processor       : 6
physical id     : 0
siblings        : 4
core id         : 3
cpu cores       : 4

processor       : 7
physical id     : 1
siblings        : 4
core id         : 3
cpu cores       : 4
于 2009-04-09T21:59:10.017 回答
3

Linux sched_setaffinityC 最小可运行示例

在这个例子中,我们获取了affinity,修改它,并检查它是否已经生效sched_getcpu()

主程序

#define _GNU_SOURCE
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void print_affinity() {
    cpu_set_t mask;
    long nproc, i;

    if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_getaffinity");
        assert(false);
    }
    nproc = sysconf(_SC_NPROCESSORS_ONLN);
    printf("sched_getaffinity = ");
    for (i = 0; i < nproc; i++) {
        printf("%d ", CPU_ISSET(i, &mask));
    }
    printf("\n");
}

int main(void) {
    cpu_set_t mask;

    print_affinity();
    printf("sched_getcpu = %d\n", sched_getcpu());
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);
    if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_setaffinity");
        assert(false);
    }
    print_affinity();
    /* TODO is it guaranteed to have taken effect already? Always worked on my tests. */
    printf("sched_getcpu = %d\n", sched_getcpu());
    return EXIT_SUCCESS;
}

GitHub 上游.

编译并运行:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out

样本输出:

sched_getaffinity = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
sched_getcpu = 9
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 0

意思就是:

  • 最初,我的所有 16 个内核都已启用,并且该进程在内核 9(第 10 个)上随机运行
  • 在我们将亲和性设置为仅第一个核心后,进程必然移动到核心 0(第一个)

通过以下方式运行此程序也很有趣taskset

taskset -c 1,3 ./a.out

这给出了形式的输出:

sched_getaffinity = 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 2
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
sched_getcpu = 0

所以我们看到它从一开始就限制了亲和力。

这是因为亲和性是由子进程继承的,即taskset分叉:如何防止子分叉进程继承 CPU 亲和性?

在 Ubuntu 16.04 中测试。

x86 裸机

如果你是那个铁杆:多核汇编语言是什么样的?

Linux如何实现它

sched_setaffinity() 是如何工作的?

蟒蛇:os.sched_getaffinityos.sched_setaffinity

请参阅:如何使用 python 找出 CPU 的数量

于 2018-05-07T08:28:40.440 回答
2

正如其他人所提到的,它由操作系统控制。根据操作系统,它可能会或可能不会为您提供允许您影响给定进程执行的核心的系统调用。但是,您通常应该让操作系统执行默认行为。如果您有一个运行 37 个进程的 4 核系统,其中 34 个进程处于休眠状态,它将把剩余的 3 个活动进程调度到不同的核心上。

您可能只会看到在非常专业的多线程应用程序中使用核心亲和性会提高速度。例如,假设您有一个带有 2 个双核处理器的系统。假设您有一个具有 3 个线程的应用程序,其中两个线程对同一组数据进行大量操作,而第三个线程使用不同的一组数据。在这种情况下,让两个线程在同一个处理器上交互,第三个线程在另一个处理器上交互,你会受益最大,因为那时它们可以共享一个缓存。操作系统不知道每个线程需要访问什么内存,因此它可能无法适当地将线程分配给内核。

如果您对操作系统的方式感兴趣,请阅读调度。x86 上的多处理细节可以在Intel 64 and IA-32 Architectures Software Developer's Manuals中找到。第 3A 卷第 7 章和第 8 章包含相关信息,但请记住,这些手册非常技术性。

于 2009-03-19T21:14:00.347 回答
1

操作系统知道如何执行此操作,您不必这样做。如果您指定要在哪个核心上运行,您可能会遇到各种问题,其中一些问题实际上可能会减慢进程。让操作系统弄清楚,您只需要启动新线程即可。

例如,如果您告诉一个进程在核心 x 上启动,但核心 x 已经处于繁重的负载之下,那么与让操作系统处理它相比,您的情况会更糟。

于 2009-03-19T20:39:45.700 回答
1

我不知道组装说明。但是 Windows API 函数是SetProcessAffinityMask。您可以看到我不久前拼凑起来仅在一个内核上运行 Picasa的示例

于 2009-03-19T21:00:45.260 回答