Pregunta

Puedo entender cómo se puede escribir un programa que usa múltiples procesos o hilos: bifurcar () un nuevo proceso y usar IPC, o crear múltiples hilos y usar ese tipo de mecanismos de comunicación.

También entiendo el cambio de contexto. Es decir, con solo una CPU, el sistema operativo programa el tiempo para cada proceso (y hay toneladas de algoritmos de programación disponibles) y, por lo tanto, logramos ejecutar múltiples procesos simultáneamente.

Y ahora que tenemos procesadores de múltiples núcleos (o computadoras con múltiples procesadores), podríamos tener dos procesos ejecutándose simultáneamente en dos núcleos separados.

Mi pregunta es sobre el último escenario: ¿cómo controla el núcleo en qué núcleo se ejecuta un proceso? ¿Qué llamadas al sistema (en Linux o incluso en Windows) programan un proceso en un núcleo específico?

La razón por la que pregunto: estoy trabajando en un proyecto para la escuela donde vamos a explorar un tema reciente en informática, y elegí arquitecturas multinúcleo. Parece que hay mucho material sobre cómo programar en ese tipo de entorno (cómo observar el punto muerto o las condiciones de carrera), pero no mucho sobre el control de los núcleos individuales. Me encantaría poder escribir algunos programas de demostración y presentar algunas instrucciones de ensamblaje o código C en el efecto de & Quot; Mira, estoy ejecutando un bucle infinito en el segundo núcleo, mira el pico en la utilización de la CPU para ese núcleo específico " ;.

¿Algún ejemplo de código? ¿O tutoriales?

editar: Para aclarar, muchas personas han dicho que este es el propósito del sistema operativo, y que uno debe dejar que el sistema operativo se encargue de esto. ¡Estoy completamente de acuerdo! Pero lo que estoy preguntando (o tratando de tener una idea) es qué hace realmente el sistema operativo para hacer esto. No es el algoritmo de programación, sino más & "; Una vez que se elige un núcleo, ¿qué instrucciones se deben ejecutar para que ese núcleo comience a buscar instrucciones? &";

¿Fue útil?

Solución

Como otros han mencionado, la afinidad del procesador es específica del sistema operativo . Si quieres hacer esto fuera de los límites del sistema operativo, te divertirás mucho, y con eso quiero decir dolor.

Dicho esto, otros han mencionado SetProcessAffinityMask para Win32. Nadie ha mencionado la forma del kernel de Linux para establecer la afinidad del procesador, y así lo haré. Necesita usar la función sched_setaffinity. Aquí hay un buen tutorial sobre cómo hacerlo.

Otros consejos

Normalmente, el sistema toma la decisión sobre en qué núcleo se ejecutará una aplicación. Sin embargo, puede establecer & Quot; afinidad & Quot; para que una aplicación a un núcleo específico le diga al sistema operativo que solo ejecute la aplicación en ese núcleo. Normalmente, esta no es una buena idea, pero hay algunos casos raros en los que podría tener sentido.

Para hacer esto en Windows, use el administrador de tareas, haga clic derecho en el proceso y elija " Establecer afinidad " ;. Puede hacerlo mediante programación en Windows utilizando funciones como SetThreadAffinityMask, SetProcessAffinityMask o SetThreadIdealProcessor.

ETA:

Si está interesado en cómo el sistema operativo realmente hace la programación, puede consultar estos enlaces:

Artículo de Wikipedia sobre cambio de contexto

Artículo de Wikipedia sobre programación

Programación en el kernel de Linux

Con la mayoría de los sistemas operativos modernos, el sistema operativo programa un subproceso para que se ejecute en un núcleo por un corto período de tiempo. Cuando caduca el intervalo de tiempo, o el subproceso realiza una operación de E / S que hace que ceda voluntariamente el núcleo, el sistema operativo programará otro subproceso para ejecutarse en el núcleo (si hay subprocesos listos para ejecutarse). Exactamente qué subproceso está programado depende del algoritmo de programación del sistema operativo.

Los detalles de implementación de cómo ocurre exactamente el cambio de contexto son CPU & amp; OS dependiente. Generalmente implicará un cambio al modo kernel, el sistema operativo guarda el estado del subproceso anterior, carga el estado del nuevo subproceso, luego vuelve al modo de usuario y reanuda el subproceso recién cargado. El artículo de cambio de contexto al que he vinculado más arriba tiene un poco más de detalle al respecto.

Nada le dice al núcleo " ahora comience a ejecutar este proceso " ;.

El proceso principal no ve , solo conoce el código ejecutable y varios niveles de ejecución y las limitaciones asociadas a las instrucciones que se pueden ejecutar.

Cuando la computadora arranca, por simplicidad, solo un núcleo / procesador está activo y realmente ejecuta cualquier código. Luego, si el sistema operativo es compatible con MultiProcessor, activa otros núcleos con alguna instrucción específica del sistema, otros núcleos probablemente se recuperan exactamente del mismo lugar que otro núcleo y se ejecutan desde allí.

Entonces, lo que hace el planificador es mirar a través de las estructuras internas del sistema operativo (tarea / proceso / cola de subprocesos) y elige una y la marca como ejecutándose en su núcleo. Luego, otras instancias del planificador que se ejecutan en otros núcleos no lo tocarán hasta que la tarea esté en estado de espera nuevamente (y no se marque como anclada a un núcleo específico). Una vez que la tarea se marca como en ejecución, el planificador ejecuta el cambio a userland con la tarea reanudada en el punto en que se suspendió anteriormente.

Técnicamente, no hay nada que impida que los núcleos ejecuten exactamente el mismo código al mismo tiempo (y muchas funciones desbloqueadas lo hacen), pero a menos que el código esté escrito para esperar eso, probablemente se orinará sobre sí mismo.

El escenario se vuelve más extraño con modelos de memoria más exóticos (arriba asume " usual " espacio de memoria de trabajo lineal simple) donde los núcleos no necesariamente ven la misma memoria y puede haber requisitos para obtener el código de embragues de otros núcleos, pero es mucho más fácil de manejar simplemente manteniendo la tarea anclada al núcleo (la arquitectura AFAIK Sony PS3 con SPU es así).

El proyecto OpenMPI tiene un biblioteca para configurar la afinidad del procesador en Linux de forma portátil.

Hace un tiempo, he usado esto en un proyecto y funcionó bien.

Advertencia: apenas recuerdo que hubo algunos problemas para descubrir cómo el sistema operativo numera los núcleos. Utilicé esto en un sistema de CPU 2 Xeon con 4 núcleos cada uno.

Una mirada a cat /proc/cpuinfo podría ayudar. En la caja que usé, es bastante raro. La salida reducida se encuentra al final.

Evidentemente, los núcleos con números pares están en la primera CPU y los núcleos con números impares están en la segunda CPU. Sin embargo, si no recuerdo mal, hubo un problema con los cachés. En estos procesadores Intel Xeon, dos núcleos en cada CPU comparten sus cachés L2 (no recuerdo si el procesador tiene un caché L3). Creo que los procesadores virtuales 0 y 2 compartieron un caché L2, 1 y 3 compartieron uno, 4 y 6 compartieron uno y 5 y 7 compartieron uno.

Debido a esta rareza (hace 1,5 años no pude encontrar ninguna documentación sobre la numeración de procesos en Linux), tendría cuidado de hacer este tipo de ajuste de bajo nivel. Sin embargo, claramente hay algunos usos. Si su código se ejecuta en algunos tipos de máquinas, puede valer la pena hacer este tipo de ajuste. Otra aplicación estaría en algún lenguaje específico de dominio como StreamIt donde el compilador podría hacer esto trabajo sucio y calcular un horario inteligente.

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

Para averiguar la cantidad de procesadores en lugar de usar / proc / cpuinfo simplemente ejecute:

nproc

Para ejecutar un proceso en un grupo de procesadores específicos:

taskset --cpu-list 1,2 my_command 

dirá que mi comando solo puede ejecutarse en la CPU 1 o 2.

Para ejecutar un programa en 4 procesadores haciendo 4 cosas diferentes, use la parametrización. El argumento del programa le dice que haga algo diferente:

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

Un buen ejemplo de esto es tratar con 8 millones de operaciones en una matriz para que 0 a (2mil-1) vaya al procesador 1, 2mil a (4mil-1) al procesador 2 y así sucesivamente.

Puede ver la carga en cada proceso instalando htop usando apt-get / yum y ejecutándose en la línea de comando:

 htop

Como otros han mencionado, está controlado por el sistema operativo. Dependiendo del sistema operativo, puede o no proporcionarle llamadas al sistema que le permitan afectar en qué núcleo se ejecuta un proceso determinado. Sin embargo, generalmente debería dejar que el sistema operativo realice el comportamiento predeterminado. Si tiene un sistema de 4 núcleos con 37 procesos en ejecución y 34 de esos procesos están inactivos, programará los 3 procesos activos restantes en núcleos separados.

Probablemente solo verá un aumento de velocidad al jugar con afinidades centrales en aplicaciones multiproceso muy especializadas. Por ejemplo, suponga que tiene un sistema con 2 procesadores de doble núcleo. Suponga que tiene una aplicación con 3 subprocesos, y dos de los subprocesos operan fuertemente en el mismo conjunto de datos, mientras que el tercer subproceso usa un conjunto diferente de datos. En este caso, se beneficiaría más al tener los dos subprocesos que interactúan en el mismo procesador y el tercer subproceso en el otro procesador, ya que pueden compartir una memoria caché. El sistema operativo no tiene idea de a qué memoria necesita acceder cada subproceso, por lo que es posible que no asigne subprocesos a los núcleos adecuadamente.

Si está interesado en cómo el sistema operativo, lea programación . Los detalles esenciales del multiprocesamiento en x86 se pueden encontrar en el Software de arquitectura Intel 64 e IA-32 Manuales del desarrollador . El Volumen 3A, los Capítulos 7 y 8 contienen información relevante, pero tenga en cuenta que estos manuales son extremadamente técnicos.

El sistema operativo sabe cómo hacer esto, no es necesario. Podría encontrarse con todo tipo de problemas si especificara en qué núcleo ejecutar, algunos de los cuales en realidad podrían ralentizar el proceso. Deje que el sistema operativo lo descubra, solo necesita comenzar el nuevo hilo.

Por ejemplo, si le dijo a un proceso que comenzara en el núcleo x, pero el núcleo x ya estaba bajo una carga pesada, estaría peor que si hubiera dejado que el SO lo maneje.

No sé las instrucciones de montaje. Pero la función API de Windows es SetProcessAffinityMask . Puede ver un ejemplo de algo que improvisé hace un tiempo para ejecutar Picasa en un solo núcleo

Linux sched_setaffinity C ejemplo ejecutable mínimo

En este ejemplo, obtenemos la afinidad, la modificamos y verificamos si ha tenido efecto con 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;
}

Compila y ejecuta con:

gcc -std=c99 main.c
./a.out

Salida de muestra:

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

Lo que significa que:

  • inicialmente, todos mis 16 núcleos estaban habilitados y el proceso se ejecutaba aleatoriamente en el núcleo 9 (el décimo)
  • después de establecer la afinidad solo en el primer núcleo, el proceso se movió necesariamente al núcleo 0 (el primero)

También es divertido ejecutar este programa a través de taskset:

taskset -c 1,3 ./a.out

Que da salida de forma:

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

y entonces vemos que limita la afinidad desde el principio.

Esto funciona porque la afinidad es heredada por procesos secundarios, que <=> se bifurca: ¿Cómo evitar heredar la afinidad de la CPU por el proceso bifurcado secundario?

Probado en Ubuntu 16.04, GitHub upstream .

x86 metal desnudo

Si eres tan duro: ¿Qué significa el lenguaje de ensamblaje multinúcleo parecerse?

Cómo lo implementa Linux

¿Cómo funciona sched_setaffinity ()?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top