Как контролировать, на каком ядре запущен процесс? - PullRequest
56 голосов
/ 19 марта 2009

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

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

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

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

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

Есть примеры кода? Или учебники?

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

Ответы [ 9 ]

35 голосов
/ 07 апреля 2009

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

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

31 голосов
/ 19 марта 2009

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

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

ETA:

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

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

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

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

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

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

5 голосов
/ 11 апреля 2009

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

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

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

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

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

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

4 голосов
/ 10 апреля 2009

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

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

Предостережение: Я смутно помню, что были некоторые проблемы с выяснением того, как операционная система нумерует ядра. Я использовал это в системе с процессором 2 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
3 голосов
/ 28 августа 2017

Чтобы узнать количество процессоров вместо использования / 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 до (2mil-1) переходит к процессору 1, от 2 до (4mil-1) к процессору 2 и т. Д.

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

 htop
2 голосов
/ 20 марта 2009

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

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

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

1 голос
/ 20 марта 2009

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

1 голос
/ 19 марта 2009

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

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

0 голосов

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 разветвляется: Как предотвратить наследование сходства ЦП дочерним разветвленным процессом?

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

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

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

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

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

...