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

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

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

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

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

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

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

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

9 ответов


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

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


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

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

ЕТА:

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

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

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

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

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

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


ничто не говорит core "теперь начните запускать этот процесс".

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

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

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

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

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


на openmpi с есть библиотека для установки сродства процессора on Linux портативным способом.

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

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

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

очевидно, равномерно пронумерованные ядра находятся на первом процессоре, а странно пронумерованные ядра-на втором процессоре. Однако, если я правильно помню, была проблема с кэш. На этих процессорах Intel Xeon два ядра на каждом процессоре разделяют свои кэши L2 (я не помню, имеет ли процессор кэш L3). Я думаю, что виртуальные процессоры 0 и 2 разделили один кэш L2, 1 и 3 разделили один, 4 и 6 разделили один и 5 и 7 общей.

из-за этой странности (1,5 года назад я не мог найти никакой документации по нумерации процессов в Linux), я был бы осторожен, чтобы сделать этот вид настройки низкого уровня. Тем не менее, очевидно, что есть некоторые применения. Если ваш код работает на нескольких типах машин, возможно, стоит выполнить такую настройку. Другое приложение будет на каком-то конкретном языке домена, например StreamIt где компилятор мог бы сделать эту грязную работу и вычислить smart график.

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

чтобы узнать количество процессоров вместо использования /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

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

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

 htop

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

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

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


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

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


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


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 ядер были включены, и процесс был случайным образом запущен на core 9 (10th one)
  • после того, как мы установили сродство только к первому ядру, процесс был перемещен обязательно в ядро 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 рассеивания: как предотвратить наследование сходства ЦП дочерним разветвленным процесс?

протестировано в Ubuntu 16.04,GitHub вверх по течению.

x86 голый металл

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

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

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