Reaprendizaje del sistema operativo ---- 17 | Procesos y subprocesos: ¿Dónde está la sobrecarga de los procesos más grande que los subprocesos?

Tabla de contenido

 

Uno, proceso e hilo

2. Asignación de recursos

Tres, proceso ligero

Cuatro, tiempo compartido y programación

Cinco, asignando fragmentos de tiempo

Seis, el estado de los procesos y subprocesos.

Siete, proceso y diseño de hilo

7.1 Representación de procesos e hilos

7.2 Plan de aislamiento

7.3 Cambio de proceso (hilo)

7.4 Procesamiento de múltiples núcleos

7.5 API para crear procesos (hilos)

Ocho, el problema


Uno, proceso e hilo

Proceso, como su nombre lo indica, es la aplicación que se está ejecutando, que es la copia de ejecución del software. El hilo es un proceso ligero.

El proceso es la unidad básica para la asignación de recursos. Durante mucho tiempo, los subprocesos se denominaron proceso ligero ponderado (proceso ligero ponderado), que es la unidad básica de ejecución del programa.

En la era en que las computadoras recién nacieron, los programadores tomaron una tarjeta de memoria flash con un programa escrito y la insertaron en la máquina, y luego la energía eléctrica empujó al chip para calcular. El chip lee una instrucción de la tarjeta de memoria flash cada vez, y luego lee la siguiente instrucción después de la ejecución. Después de ejecutar todas las instrucciones en la memoria flash, la computadora se apaga.

 

Este modelo de una sola tarea se llamaba Trabajo en esa época . En ese momento, las computadoras estaban diseñadas para poder procesar múltiples trabajos.

Después de la aparición de la interfaz gráfica, la gente comenzó a usar las computadoras para la oficina, ir de compras, charlar, jugar, etc. Por lo tanto, el programa que estaba ejecutando una máquina se cortaba de un lado a otro en cualquier momento. Entonces, la gente pensó en diseñar procesos e hilos para resolver este problema.

Cada aplicación, como un juego, es un proceso después de la ejecución. Sin embargo, el juego requiere procesamiento de gráficos, red y operaciones del usuario. Estos comportamientos no pueden bloquearse entre sí y deben realizarse al mismo tiempo, por lo que están diseñados como subprocesos .

2. Asignación de recursos

Para diseñar procesos e hilos, el sistema operativo necesita pensar en la asignación de recursos. Los tres recursos más importantes son:

  1. Recursos informáticos (CPU)
  2. Recursos de memoria
  3. Recurso de archivo

En el diseño inicial del sistema operativo, no había subprocesos. Los tres tipos de recursos se asignaron a los procesos . Se ejecutaron varios procesos alternativamente a través de la tecnología de tiempo compartido y los procesos se comunicaron a través de la tecnología de canalización .

Pero al hacerlo, los diseñadores descubren que los usuarios (programadores) a menudo necesitan abrir varios procesos para una aplicación, porque las aplicaciones siempre tienen muchas cosas que deben hacerse en paralelo.

El paralelismo no significa simultaneidad absoluta , sino la necesidad de hacer que estas cosas parezcan estar sucediendo al mismo tiempo, como la representación de gráficos y la respuesta a la entrada del usuario.

Entonces, los diseñadores pensaron que bajo el proceso, se necesita una especie de unidad de ejecución del programa, y solo se asignan recursos de CPU, que es el hilo .

Tres, proceso ligero

Una vez diseñado el hilo, se denomina proceso ligero porque solo se le asignan recursos informáticos (CPU). La forma de ser asignada es programar subprocesos por el sistema operativo. Después de que el sistema operativo crea un proceso, el programa de entrada del proceso se asigna a un subproceso principal para su ejecución, por lo que parece que el sistema operativo está programando el proceso, pero en realidad es un subproceso en el proceso de programación.

Este tipo de hilo que está programado directamente por el sistema operativo, también se convierte en un hilo a nivel de kernel. Además, en algunos lenguajes de programación o aplicaciones, los propios usuarios (programadores) también implementan subprocesos. Es equivalente a que el sistema operativo programe el hilo principal, y el programa del hilo principal utiliza algoritmos para implementar subprocesos, situación que se denomina hilos a nivel de usuario. La API PThread de Linux es un hilo a nivel de usuario y la API KThread es un hilo a nivel de kernel.

Cuatro, tiempo compartido y programación

Debido a que generalmente la cantidad de núcleos de CPU en una máquina es pequeña (de unos pocos a docenas), y la cantidad de procesos y subprocesos es grande (de decenas a cientos o incluso más), puede compararlo con menos motores y más máquinas, por lo que los procesos son El sistema operativo solo se puede poner en cola para su ejecución uno por uno. Cada proceso recibirá un intervalo de tiempo asignado por el sistema operativo cuando se ejecute. Si se excede este tiempo, será el siguiente proceso (subproceso) en ejecutarse. Nuevamente, los sistemas operativos modernos programan subprocesos directamente, no procesos.

Cinco, asignando fragmentos de tiempo

Como se muestra en la figura siguiente, el proceso 1 requiere 2 porciones de tiempo, el proceso 2 tiene solo 1 porción de tiempo y el proceso 3 requiere 3 porciones de tiempo. Por lo tanto, cuando el proceso 1 se ejecuta a la mitad, se suspenderá primero y luego el proceso 2 comenzará a ejecutarse; el proceso 2 se puede ejecutar a la vez, y luego el proceso 3 comenzará a ejecutarse, pero el proceso 3 no se puede ejecutar a la vez. Después de que se ejecuta 1 segmento de tiempo, el Proceso 1 comienza a ejecutarse y se repite así. Esta es la tecnología de tiempo compartido.

La siguiente imagen es más intuitiva. El proceso P1 primero ejecuta un segmento de tiempo, luego el proceso P2 comienza a ejecutar un segmento de tiempo, luego el proceso P3, luego el proceso P4 ...

Tenga en cuenta que las dos imágenes anteriores se muestran en unidades de procesos. Si cambia a subprocesos, el sistema operativo aún los maneja de esta manera.

Seis, el estado de los procesos y subprocesos.

Un proceso (subproceso) pasa por los siguientes tres estados:

Después de que se crea el proceso (hilo), comienza a hacer cola, en este momento estará en el estado "Listo" (Listo);

Cuando sea el turno del proceso (subproceso) de ejecutarse, se convertirá en "Ejecutando";

Cuando un proceso (subproceso) se queda sin los intervalos de tiempo asignados por el sistema operativo, vuelve al estado "Listo".

Siempre he usado procesos (subprocesos) aquí porque el antiguo sistema operativo programa procesos sin subprocesos; los sistemas operativos modernos programan subprocesos.

A veces, un proceso (subproceso) esperará a que el disco lea los datos o esperará a que la impresora responda. En este momento, el proceso en sí entrará en el estado "Bloquear".

Debido a que la respuesta de la computadora no se puede dar de inmediato en este momento, debe esperar a que se complete el procesamiento del disco y la impresora y notificar a la CPU a través de interrupciones, y luego la CPU ejecuta un programa de control de interrupciones corto para transferir el control al operador. y el sistema operativo luego transfiere el original. El proceso bloqueado (subproceso) se coloca en el estado "Listo" y se vuelve a poner en cola.

Además, una vez que un proceso (subproceso) entra en el estado de bloqueo, el proceso (subproceso) no tiene nada que hacer en este momento, pero no se puede volver a poner en cola (porque necesita esperar una interrupción), por lo que el proceso (subproceso) necesita para agregar un estado de "bloqueo" (Bloquear)

Tenga en cuenta que debido a que un proceso (subproceso) que está "Listo" todavía está en cola, el programa en el proceso (subproceso) no se puede ejecutar, es decir, no activará la operación de lectura de datos del disco. En este momento, "Listo" (Listo) El estado no se puede cambiar a un estado bloqueado, por lo que no hay una flecha de listo a bloqueado en la figura siguiente.

Y si el proceso (subproceso) en el estado "Bloquear" recibe los datos leídos desde el disco, es necesario volver a ponerlo en cola, por lo que no puede volver directamente al estado "En ejecución", por lo que no hay una flecha desde el estado de bloqueo. al estado de ejecución.

Siete, proceso y diseño de hilo

A continuación, pensamos en varias limitaciones de diseño fundamentales:

¿Cómo se representan los procesos y los subprocesos en la memoria? ¿Qué campos son obligatorios?

Los procesos representan aplicaciones y deben aislarse unos de otros ¿Cómo diseñar este esquema de aislamiento?

El sistema operativo programa subprocesos y cambia constantemente entre subprocesos ¿Cómo darse cuenta de esta situación?

Necesita admitir un entorno de núcleo de múltiples CPU, ¿cómo diseñar para esta situación?

7.1 Representación de procesos e hilos

Se puede diseñar así: se diseñan dos tablas en memoria, una es la tabla de procesos y la otra es la tabla de subprocesos.

La tabla de procesos registra la ubicación de almacenamiento del proceso en la memoria, cuál es su PID, cuál es su estado actual, cuánta memoria está asignada, a qué usuario pertenece, etc. Esta tiene la tabla de procesos. Sin esta tabla, el proceso se perderá y el sistema operativo no sabrá qué proceso tiene. Esta tabla se puede considerar directamente en el kernel.

Para la subdivisión, la tabla de proceso necesita este tipo de información.

  • Información descriptiva: esta parte es el número de identificación único que describe el proceso, es decir, el PID, incluyendo el nombre del proceso, el usuario al que pertenece, etc.
  • Información de recursos: esta parte se utiliza para registrar los recursos que posee el proceso, como cómo se asignan el proceso y la memoria virtual, qué archivos son propiedad, qué dispositivos de E / S están en uso, etc. Por supuesto, dispositivos de E / S también son archivos.
  • Disposición de la memoria: el sistema operativo también estipula cómo el proceso utiliza la memoria. Como se muestra en la figura siguiente, describe cómo un proceso se divide aproximadamente en varias áreas y para qué se utiliza cada área. Llamamos a cada área un segmento.

El sistema operativo también necesita una tabla para administrar subprocesos, que es la tabla de subprocesos. Thread también necesita ID, que se puede llamar ThreadID. Luego, el hilo necesita registrar su propio estado de ejecución (bloqueado, en ejecución, listo), prioridad, contador de programa y los valores de todos los registros, etc. Los subprocesos necesitan registrar los valores de los contadores y registros del programa porque varios subprocesos necesitan compartir la misma CPU, y los subprocesos a menudo cambian de un lado a otro, por lo que los valores de los registros y los punteros de la PC deben almacenarse en la memoria.

Existe una relación de mapeo entre los subprocesos a nivel de usuario y los subprocesos a nivel de kernel, por lo que puede considerar mantener una tabla de subprocesos a nivel de kernel en el kernel, incluidos los campos mencionados anteriormente.

Si considera esta relación de mapeo, como el mapeo de muchos a muchos de nm, la información del subproceso aún se puede almacenar en el proceso y el subproceso a nivel de kernel se usa cada vez que se ejecuta. Es equivalente a un grupo de subprocesos en el kernel, esperando que se use el espacio de usuario. Cada vez que un subproceso a nivel de usuario pasa el contador del programa, etc., después de que finaliza la ejecución, el subproceso del kernel no se destruye y espera la siguiente tarea. En realidad, aquí hay muchas implementaciones flexibles. En general, la creación de un proceso es costosa y costosa, la creación de un hilo tiene una pequeña sobrecarga y un bajo costo.

7.2 Plan de aislamiento

Hay una gran cantidad de procesos en ejecución en el sistema operativo. Para evitar que interfieran entre sí, puede considerar asignar áreas de memoria que estén completamente aisladas entre sí. Incluso si el programa interno del proceso lee la misma dirección, la dirección física real no será la misma. Es como si el edificio número 10 808 en el distrito A y el edificio número 10 808 en el distrito B no fueran un conjunto de casas. Este método se llama espacio de direcciones.

Por lo tanto, en circunstancias normales, el proceso A no puede acceder a la memoria del proceso B, a menos que el proceso A encuentre una vulnerabilidad en un sistema operativo y manipule maliciosamente la memoria del proceso B.

Para varios subprocesos en un proceso, puede considerar compartir los recursos de memoria asignados por el proceso, de modo que a los subprocesos solo se les asigne recursos de ejecución.

7.3 Cambio de proceso (hilo)

Los procesos (subprocesos) se cambian constantemente en el sistema operativo, y solo el cambio de subprocesos en los sistemas operativos modernos. Cada vez que cambia, necesita guardar la memoria del valor del registro actual.Tenga en cuenta que el puntero de la PC también es una especie de registro. Cuando se reanuda la ejecución, es necesario leer todos los registros de la memoria, restaurar el estado anterior y luego ejecutar.

El contenido mencionado anteriormente se puede resumir en los siguientes 5 pasos:

  1. Cuando el sistema operativo encuentra que es necesario cambiar un proceso (subproceso), es muy peligroso controlar directamente el salto del puntero de la PC, por lo que el sistema operativo debe enviar una señal de "interrupción" a la CPU para detener el proceso en ejecución (subproceso ).
  2. Cuando la CPU recibe una señal de interrupción, el proceso de ejecución (subproceso) se detendrá inmediatamente. Tenga en cuenta que debido a que el proceso (subproceso) se detiene inmediatamente, no tiene tiempo para guardar su propio estado, por lo que el sistema operativo posterior debe completar este asunto.
  3. Una vez que el sistema operativo se hace cargo de la interrupción, mientras que los datos del registro no se han destruido, se debe ejecutar inmediatamente una pequeña sección del programa de muy bajo nivel (generalmente escrito en ensamblador) para ayudar al registro a guardar el estado del proceso anterior (hilo ).
  4. Una vez que el sistema operativo guarda el estado del proceso, ejecuta el planificador y determina el siguiente proceso (subproceso) que se ejecutará.
  5. Finalmente, el sistema operativo ejecuta el siguiente proceso (hilo).

Por supuesto, después de seleccionar un proceso (subproceso) para su ejecución, continuará completando la tarea cuando se interrumpió. Esto requiere que el sistema operativo ejecute una pequeña sección del programa subyacente para ayudar al proceso (subproceso) a restaurar el estado .

Un posible algoritmo es a través de la estructura de datos de la pila . Después de que se interrumpe el proceso (subproceso), el sistema operativo es responsable de insertar datos clave (como registros) en la pila. Al restaurar la ejecución, el sistema operativo es responsable de salir de la pila y restaurar el valor del registro.

7.4 Procesamiento de múltiples núcleos

En un sistema multinúcleo, los principios de diseño que mencionamos anteriormente siguen siendo válidos, pero con más potencia, procesos (subprocesos) que se pueden ejecutar en paralelo. Normalmente, si la CPU tiene varios núcleos, puede ejecutar varios procesos (subprocesos) en paralelo. Aquí enfatizamos un concepto, solemos decir concurrencia, el inglés es concurrente, lo que significa que varias tareas parecen ejecutarse al mismo tiempo dentro de un período de tiempo (no se requiere multi-core); mientras que paralelo, el inglés es paralelo, tareas debe ejecutarse absolutamente al mismo tiempo (requiere Multi-core).

Por ejemplo, una CPU de 4 núcleos parece tener 4 canalizaciones y puede ejecutar 4 tareas en paralelo. La ejecución de múltiples subprocesos en un proceso provocará condiciones de carrera, que le presentaremos en la sección "19 Conferencias" de bloqueos y semáforos. Debido a que el sistema operativo brinda la capacidad de guardar y restaurar el estado del proceso, el proceso (subproceso) también se puede cambiar entre múltiples núcleos.

7.5 API para crear procesos (hilos)

La forma más directa para que un usuario cree un proceso es ejecutar un programa desde la línea de comandos o hacer doble clic para abrir una aplicación. Pero para los programadores, existe una clara necesidad de un mejor diseño.

Desde el punto de vista de un diseñador, puedes pensar así: primero, debe haber una API para abrir una aplicación, por ejemplo, una aplicación se puede abrir a través de una función; por otro lado, si el programador quiere realizar una costosa proceso de inicialización, el programa actual El estado de se copia varias veces y se convierte en un solo proceso de ejecución, luego el sistema operativo proporciona la instrucción de bifurcación.

Es decir, cada fork creará un proceso clonado, este proceso clonado, todos los estados son iguales al proceso original, pero tendrá su propio espacio de direcciones. Si desea crear dos procesos de clonación, debe bifurcar dos veces.

Puede preguntar: ¿Qué pasa si solo quiero comenzar un nuevo programa?

Dije anteriormente: el sistema operativo proporciona una API para iniciar nuevos programas.

También puede preguntar: Si solo quiero usar un nuevo proceso para ejecutar un programa pequeño, por ejemplo, cada vez que el servidor recibe una solicitud del cliente, quiero usar un proceso para manejar la solicitud.

Si este es el caso, le sugiero que no inicie el proceso por separado, sino que utilice subprocesos. Debido a que el costo de creación del proceso es demasiado alto, no se recomienda hacer tales cosas: crear entradas, asignar memoria, especialmente para formar un segmento en la memoria, dividido en diferentes áreas. Por lo general, tendemos a crear más hilos.

Los diferentes lenguajes de programación proporcionan API para crear subprocesos. Por ejemplo, Java tiene la clase Thread; go tiene go-rutina (tenga en cuenta que no es una corrutina, sino un subproceso).

Ocho, el problema

¿Dónde es mayor la sobrecarga del proceso que la del hilo?

La creación de un proceso en Linux creará naturalmente un hilo, que es el hilo principal. Crear un proceso requiere dividir un espacio de memoria completo para el proceso, y hay muchas operaciones de inicialización, como segmentar la memoria (pila, área del cuerpo, etc.). Crear un subproceso es mucho más simple. Solo necesita determinar el valor del puntero y registro de la PC, y asignar una pila al subproceso para ejecutar el programa. Varios subprocesos en el mismo proceso pueden reutilizar la pila. Por lo tanto, crear un proceso es más lento que crear un subproceso y la sobrecarga de memoria del proceso es mayor.

tenedor()

tenedor()

tenedor()

print ("Hola mundo \ n")

Después de ejecutar este programa, ¿cuántas veces se imprimirá la salida Hello World?

La respuesta se puede obtener analizando cuidadosamente los siguientes procedimientos. 


#include <unistd.h>
#include <stdio.h> 
int main () 
{ 
	pid_t fpid; //fpid表示fork函数返回的值
	int count=0;
	fpid=fork(); 
	if (fpid < 0) 
		printf("error in fork!"); 
	else if (fpid == 0) {
		printf("i am the child process, my process id is %d/n",getpid()); 
		printf("我是爹的儿子/n");
		count++;
	}
	else {
		printf("i am the parent process, my process id is %d/n",getpid()); 
		printf("我是孩子他爹/n");
		count++;
	}
	printf("统计结果是: %d/n",count);
	return 0;
}

 Antes de la instrucción fpid = fork (), solo un proceso está ejecutando este código, pero después de esta instrucción, se convierte en dos procesos en ejecución.Los dos procesos son casi idénticos, y la siguiente instrucción que se ejecutará Ambos son if (fpid <0) ......
    ¿Por qué los fpids de los dos procesos son diferentes? Esto está relacionado con las características de la función fork. Una de las cosas maravillosas de la llamada a la bifurcación es que solo se llama una vez, pero puede regresar dos veces. Puede tener tres valores de retorno diferentes:
    1) En el proceso padre, la bifurcación devuelve el ID de proceso del proceso hijo recién creado;
    2 ) En el proceso hijo , fork devuelve el ID de proceso del proceso hijo recién creado. Durante el proceso, fork devuelve 0;
    3) Si ocurre un error, fork devuelve un valor negativo;

    Después de que se ejecuta la función de bifurcación, si el nuevo proceso se crea con éxito, aparecerán dos procesos, uno es el proceso hijo y el otro es el proceso padre. En el proceso hijo, la función fork devuelve 0, y en el proceso padre, fork devuelve el ID de proceso del proceso hijo recién creado. Podemos usar el valor devuelto por la bifurcación para determinar si el proceso actual es un proceso hijo o un proceso padre.

    Citando a un internauta para explicar por qué el valor de fpid es diferente en el proceso padre-hijo. "De hecho, es equivalente a una lista vinculada. El proceso forma una lista vinculada. El fpid (p significa punto) del proceso principal apunta al ID de proceso del proceso secundario. Debido a que el proceso secundario no tiene un proceso secundario , su fpid es 0.
    Puede haber dos razones para los errores de bifurcación:
    1) El número actual de procesos ha alcanzado el límite superior especificado por el sistema, y ​​el valor de errno está establecido en EAGAIN.2
    ) La memoria del sistema es insuficiente, y el valor de errno se establece en ENOMEM.
    Después de que el nuevo proceso se crea con éxito, aparecen dos en el sistema Básicamente el mismo proceso, no hay un orden fijo para la ejecución de estos dos procesos. El proceso que se ejecuta primero depende de la estrategia de programación de procesos.
    Cada proceso tiene un identificador de proceso único (diferente) (ID de proceso), se puede obtener a través de la función getpid (), y hay una variable que registra el pid del proceso padre y el valor de la variable se puede obtener a través de la función getppid (). Después de
    ejecutar la bifurcación, aparecen dos procesos,

 Algunas personas dicen que el contenido de los dos procesos es exactamente el mismo, y que los resultados impresos son diferentes. Eso se debe a las condiciones de juicio. La lista anterior es solo el código y las instrucciones del proceso, así como las variables.
    Después de ejecutar la bifurcación, la variable del proceso 1 es count = 0, fpid! = 0 (proceso padre). Las variables del proceso 2 son count = 0 y fpid = 0 (proceso hijo) Las variables de estos dos procesos son independientes y existen en diferentes direcciones y no se comparten, preste atención a esto. Se puede decir que usamos fpid para identificar y manipular procesos padre-hijo.
    Otros pueden preguntarse por qué no se copia el código de #include. Esto se debe a que la bifurcación copia la situación actual del proceso. Cuando se ejecuta la bifurcación, el proceso se ha ejecutado. Int count = 0; la bifurcación solo copia el siguiente. código ejecutado a un nuevo proceso.


#include <unistd.h>
#include <stdio.h>
int main(void)
{
   int i=0;
   printf("i son/pa ppid pid  fpid/n");
   //ppid指当前进程的父进程pid
   //pid指当前进程的pid,
   //fpid指fork返回给当前进程的值
   for(i=0;i<2;i++){
       pid_t fpid=fork();
       if(fpid==0)
    	   printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
    	   printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;
}

Referencia: proceso de comprensión de la bifurcación https://blog.csdn.net/jason314/article/details/5640969

Supongo que te gusta

Origin blog.csdn.net/MyySophia/article/details/114106985
Recomendado
Clasificación