Как контролировать, на каком ядре работает процесс?

Я могу понять, как можно написать программу, которая использует несколько процессов или потоков: fork() новый процесс и использование IPC, или создать несколько потоков и использовать те виды механизмов связи.

Я также понимаю контекстное переключение. То есть, только с CPU, операционная система планирует время для каждого процесса (и там есть тонны алгоритмов планирования), и поэтому мы достигаем одновременного запуска нескольких процессов.

И теперь, когда у нас есть многоядерные процессоры (или многопроцессорные компьютеры), мы могли бы одновременно запускать два процесса на двух отдельных ядрах.

Мой вопрос касается последнего сценария: как ядро ​​контролирует, с какого ядра работает процесс? Какие системные вызовы (в Linux или даже Windows) планируют процесс на конкретном ядре?

Причина, по которой я спрашиваю: я работаю над проектом для школы, где мы должны изучить недавнюю тему в области вычислений - и я выбрал многоядерные архитектуры. Кажется, что есть много материалов о том, как программировать в такой среде (как смотреть на тупик или условия гонки), но не так много на управление отдельными ядрами. Я хотел бы иметь возможность написать несколько демонстрационных программ и представить некоторые инструкции по сборке или код C к эффекту "Смотрите, я запускаю бесконечный цикл на втором ядре, смотрю на всплеск загрузки процессора для этого конкретного ядра".

Любые примеры кода? Или учебники?

edit: для разъяснения - многие люди говорили, что это цель ОС, и что нужно позволить OS позаботиться об этом. Я полностью согласен! Но то, что я прошу (или пытаюсь понять), - это то, что на самом деле делает операционная система. Не алгоритм планирования, а больше "когда выбран ядро, какие инструкции должны выполняться, чтобы заставить это начало запускать инструкции?"

Ответ 1

Как уже упоминалось, сродство процессора зависит от операционной системы. Если вы хотите сделать это за пределами операционной системы, вам будет очень весело, и под этим я подразумеваю боль.

Тем не менее, другие упоминали SetProcessAffinityMask для Win32. Никто не упомянул способ ядра Linux установить привязку к процессору, и я так и сделаю. Вам нужно использовать функцию sched_setaffinity. Здесь хороший учебник о том, как.

Ответ 2

Обычно решение о том, какое ядро ​​приложение будет запускать, производится системой. Тем не менее, вы можете установить "близость" приложения к определенному ядру, чтобы сообщить операционной системе только запустить приложение на этом ядре. Обычно это не очень хорошая идея, но есть некоторые редкие случаи, когда это может иметь смысл.

Чтобы сделать это в Windows, используйте диспетчер задач, щелкните правой кнопкой мыши процесс и выберите "Установить близость". Вы можете сделать это программно в Windows, используя такие функции, как SetThreadAffinityMask, SetProcessAffinityMask или SetThreadIdealProcessor.

ETA:

Если вас интересует, как ОС на самом деле выполняет планирование, вы можете проверить эти ссылки:

Статья в Википедии о переключении контекста

Статья в Википедии о планировании

Планирование в ядре linux

В большинстве современных ОС ОС расписывает поток для выполнения на ядре на короткий отрезок времени. Когда временной срез истекает, или поток выполняет операцию ввода-вывода, которая заставляет его добровольно выдать ядро, ОС запланирует выполнение другого потока на ядре (если есть какие-либо потоки, готовые к запуску). Точно, какой поток запланирован, зависит от алгоритма планирования ОС.

Детали реализации того, как происходит контекстный переключатель, зависят от процессора и ОС. Обычно он включает в себя переход в режим ядра, ОС, сохраняющий состояние предыдущего потока, загрузку состояния нового потока, затем переход в пользовательский режим и возобновление вновь загруженного потока. Связанная с этим статья переключения контекста я немного подробнее об этом.

Ответ 3

Ничего не говорит о ядре "теперь начните этот процесс".

Ядро не видит процесса, оно знает только о исполняемом коде и различных уровнях выполнения и связанных ограничениях с инструкциями, которые могут быть выполнены.

Когда компьютер загружается, для простоты активен только один ядро ​​/процессор и фактически запускает любой код. Затем, если ОС поддерживает MultiProcessor, он активирует другие ядра с некоторой инструкцией по конкретной системе, другие ядра, скорее всего, собираются с одного и того же места с другим ядром и запускаются оттуда.

Итак, что делает планировщик, он просматривает внутренние структуры ОС (очередь задач/процессов/потоков) и выбирает один и отмечает, что он работает по своему ядру. Тогда другие экземпляры планировщика, запущенные на других ядрах, не будут касаться его до тех пор, пока задача не будет в состоянии ожидания снова (и не будет помечена как прикрепленная к конкретному ядру). После того, как задача отмечена как выполняющаяся, планировщик выполняет переход на пользовательскую область с возобновлением задачи в момент, когда она была ранее приостановлена.

Технически нет ничего, что бы остановить ядра от запуска одного и того же кода в одно и то же время (и много разблокированных функций), но если код не написан, чтобы ожидать этого, он, вероятно, будет мочиться сам по себе.

Сценарий идет более странно с более экзотическими моделями памяти (выше предполагает "обычное" линейное одиночное рабочее пространство памяти), где ядра не обязательно все видят одну и ту же память, и могут возникнуть требования по извлечению кода из других кладок ядра, но это намного проще (простое управление AFAIK Sony PS3 с SPU).

Ответ 4

Проект OpenMPI имеет библиотеку для установки близости процессора на Linux в переносном режиме.

Некоторое время назад я использовал это в проекте, и он работал нормально.

Предостережение: Я смутно помню, что были некоторые проблемы в определении того, как операционная система числится в ядрах. Я использовал это в двухсистемной системе Xeon с 4 ядрами.

Возможно, посмотрите на cat /proc/cpuinfo. На ящике, который я использовал, это довольно странно. Свернутый выход заканчивается.

Очевидно, что равномерно пронумерованные ядра находятся на первом процессоре, а ядра с нечетным номером находятся на втором процессоре. Однако, если я правильно помню, была проблема с кэшами. На этих процессорах Intel Xeon два ядра на каждом процессоре делят свои кэши 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

Ответ 5

Чтобы узнать количество процессоров вместо использования /proc/cpuinfo, просто запустите:

nproc

Чтобы запустить процесс в группе конкретных процессоров:

taskset --cpu-list 1,2 my_command 

скажет, что моя команда может работать только на процессоре 1 или 2.

Для запуска программы на 4-х процессорах, использующих 4 разных элемента, используется параметризация. Аргумент программы говорит ему сделать что-то другое:

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

Хорошим примером этого является работа с 8 миллионами операций в массиве, так что 0 to (2mil-1) переходит к процессору 1, 2mil (4mil-1) процессору 2 и т.д.

Вы можете посмотреть нагрузку на каждый процесс, установив htop с помощью apt-get/yum и работая в командной строке:

 htop

Ответ 6

Как отмечали другие, он контролируется операционной системой. В зависимости от ОС он может или не может предоставить вам системные вызовы, которые позволят вам повлиять на то, на каком ядре выполняется данный процесс. Однако вы обычно должны просто позволить ОС выполнять поведение по умолчанию. Если у вас есть 4-ядерная система с 37 процессами, и 34 из этих процессов спят, она планирует запланировать оставшиеся 3 активных процесса на отдельные ядра.

Вероятно, вы увидите только ускорение скорости при игре с основными аффинностями в очень специализированных многопоточных приложениях. Например, предположим, что у вас есть система с двумя двухъядерными процессорами. Предположим, что у вас есть приложение с 3 потоками, а два потока работают в основном на одном наборе данных, тогда как третий поток использует другой набор данных. В этом случае вы бы выиграли от использования двух потоков, которые взаимодействуют на одном процессоре и третьего потока на другом процессоре, так как тогда они могут совместно использовать кеш. ОС не знает, к какой памяти должен иметь доступ каждый поток, поэтому он не может соответствующим образом распределять потоки.

Если вам интересно, как операционная система, прочитайте scheduling. Подробные подробные сведения о многопроцессорности на x86 можно найти в Руководствах разработчиков программного обеспечения Intel 64 и IA-32.. Том 3А, главы 7 и 8 содержат соответствующую информацию, но имейте в виду, что эти руководства чрезвычайно технические.

Ответ 7

ОС знает, как это сделать, вам не обязательно. Вы можете столкнуться со всеми проблемами, если вы указали, какое ядро ​​запускается, некоторые из которых могут фактически замедлить процесс. Позвольте ОС понять это, вам просто нужно запустить новый поток.

Например, если вы сказали, что процесс запускается на ядре x, но ядро ​​x уже находится под большой нагрузкой, вам будет хуже, чем если бы вы просто позволили ОС обрабатывать его.

Ответ 8

Я не знаю инструкции по сборке. Но функция API окон SetProcessAffinityMask. Вы можете увидеть пример того, что я собрал вместе, чтобы запустить Picasa только на одном ядре

Ответ 9

Linux sched_setaffinity C минимальный исполняемый пример

В этом примере мы получаем сходство, модифицируем его и проверяем, вступило ли оно в силу с 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);
    } else {
        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;
}

Скомпилируйте и запустите с:

gcc -std=c99 main.c
./a.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 разветвляется: как предотвратить наследование taskset процессора дочерним разветвленным процессом?

Протестировано в Ubuntu 16.04, GitHub upstream.

х86 голый металл

Если вы тот хардкор: как выглядит многоядерный язык ассемблера?

Как Linux это реализует

Как работает sched_setaffinity()?