Introducción a la causa
Un amigo me hizo una socket
pregunta relacionada con la comunicación, el problema que hay que resolver es el siguiente:
Es necesario que exista un proceso de servidor. El proceso de servidor escuchará y será responsable de establecer una conexión de socket con el cliente. Pueden existir múltiples procesos de cliente al mismo tiempo. Los procesos de cliente pueden comunicarse entre sí, pero no se establecerán conexiones de socket entre clientes La comunicación se realiza mediante el envío de información al proceso del servidor, y el proceso del servidor busca el indicador de socket establecido con el cliente de destino para enviar la información.
Para resolver el problema anterior, este amigo personalizó una lista vinculada en el proceso del servidor para guardar el socket
nombre del cliente y socket
el identificador correspondiente de la conexión establecida con el servidor. Cuando la conexión se establece con éxito, se inserta una lista vinculada en la lista vinculada. .Elemento, cuando un cliente envía un comando de salida, el correspondiente se socket
cierra y la información se elimina de la lista vinculada. Cada vez que un cliente necesita enviar datos, debe recorrer la lista vinculada y luego encontrar el objetivo para la comunicación socket
.
fork
Al mismo tiempo, para cumplir con los requisitos de comunicación, se debe utilizar multiproceso o subproceso múltiple. Este amigo crea un subproceso para manejar la comunicación con un cliente cada vez que escucha el código del servidor. , este Un amigo solicitó una memoria compartida en el código para guardar el encabezado de la lista vinculada.
Problemas que surgen
socket
La descripción anterior parece muy razonable, pero ocurrió un fenómeno extraño después de su ejecución: cuando se inició el proceso del servidor, se iniciaron dos procesos del cliente al mismo tiempo para establecer conexiones con el proceso del servidor. Al enviar un mensaje client1
aclient2
¿Dónde aparece el bucle infinito? Según la depuración de un amigo, descubrió que en el proceso de consultar el propósito en la lista vinculada socket
, apareció un bucle infinito. Descubrió que la lista vinculada se convirtió en un anillo y, al mismo tiempo, solo había un nodo en la lista vinculada. es decir, solo tenía su propia información de nodo, lo que daba como resultado que la lista vinculada siempre estuviera en ciclo.
La causa del problema
¿Por qué ocurre un bucle infinito? La clave del problema radica en el uso que hace fork
de esta función. Para describir nuestro avance de manera más simple, se realiza un experimento simple:
Escriba un programa, cree una variable al mismo tiempo, asígnele un valor y luego llame a fork() para ver si tanto el proceso padre como el hijo pueden obtener el valor correcto de la variable e intente modificarlo al mismo tiempo. el valor de la variable en el proceso hijo. El proceso padre espera a que se complete el proceso hijo. Luego verifica el valor de la variable para ver si el valor se ha modificado correctamente.
Nota: La pregunta anterior en realidad proviene de "Introducción a los sistemas operativos" , que es un libro muy bueno y se recomienda que todos lo lean.
El código para el experimento anterior se puede escribir fácilmente:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int x = 137;
int ret = fork();
if (ret < 0) {
printf("one error occurs when fork(), the ret: %d.\n", ret);
exit(ret);
} else if (ret == 0) {
printf("child process (%d), the value of x is: %d.\n", getpid(), x);
x *= 2;
} else {
printf("parent process (%d), the value of x is: %d.\n", getpid(), x);
ret = wait(NULL);
printf("after child process write the varible, parent process (%d), the value of x is: %d.\n", getpid(), x);
}
return 0;
}
Los resultados de ejecución son los siguientes:
proceso padre (110), el valor de x es: 137.
proceso hijo (111), el valor de x es: 137.
después del proceso hijo escribe la variable, proceso padre (110), el valor de x es: 137.
Se puede ver que el proceso hijo no conoce la modificación de variables por parte del proceso padre. Este es el problema: fork
el proceso creado copiará toda la información del proceso padre (tenga en cuenta que se copia en lugar de compartirse) y fork
la modificación de variables por parte de un proceso no es visible para el otro proceso.
La copia mencionada anteriormente se refiere a copiar el proceso por completo y colocarlo en otra área de memoria para su ejecución, hay un problema, es decir, todos los valores de los dos procesos son iguales inicialmente y las direcciones virtuales también son lo mismo (Esto se refleja en el hecho de que si dos procesos solicitan memoria del montón inmediatamente, obtendrán la misma dirección. Esto también es fácil de entender. Dado que es una copia, el estado natural del montón también es el mismo (Y se obtiene la misma dirección. El espacio virtual también es razonable, pero el espacio físico real es diferente).
Con la base anterior, echemos un vistazo y pensemos por qué ocurre un bucle infinito.
La clave del problema es que la memoria compartida y la memoria compartida se usan al mismo tiempo fork
. La memoria compartida utilizada puede garantizar que las modificaciones se realicen en el proceso secundario y puedan ser vistas por otros procesos. De hecho, simulémoslo:
- Primero, el proceso del servidor se inicia y solicita la memoria compartida. Registramos la dirección como 0 y usamos
headp_address
una variable para guardar la dirección (es decirheadp_address=0
), dado que la lista vinculada en este momento está vacía, el encabezado está inicialmente vacío, es decir, ejecución*headp_address=NULL
; client1
Inicio. En este momento, se inserta un nuevo nodo en la lista vinculada. El nuevo nodo solicitamalloc
memoria. Suponiendo que la dirección solicitada en este momento es 4 , luego insertamos el nodo en el encabezado de la lista vinculada y lo ejecutamos*headp_address=new_node
. En este momentonew_node=4
;client2
Inicio, en este momento también se realiza (aprobadomalloc
) la aplicación de memoria del nuevo nodo, según la conclusión anterior, en este momento se solicitará la misma memoria, es decir, se seguirá solicitando4
(aquí está la memoria virtual ), pero debido a la modificación de la memoria compartida, los dos procesos pueden encontrar que ya hay un elemento en la lista vinculada en este momento, por lo que utilizan el método de inserción de encabezado para insertar el nodo actual al comienzo de la lista vinculada , es decir, ejecutar:
De acuerdo a la información anteriornew_node->next = *headp_address; *headp_address=new_node;
*headp_address=4
, sabemos que después de ejecutar la declaración anterior, encontramos que*headp_address=4, *headp_address->next=4
; es decir, la lista enlazada se convierte en un anillo en este momento, por lo que ocurrirá un bucle infinito cuando la información se envíe a través de un cliente más adelante. .
resolución de problemas
Varias soluciones están disponibles aquí:
- Al utilizar
pthread
el reemplazofork()
,pthread
cada proceso puede ver las modificaciones realizadas por subprocesos, lo que significa que está más sesgado hacia la realización de funciones compartidas en lugar defork()
copiar. (De hecho, ambas funciones llaman a llamadas al sistemaclone
, peropthread_create
se agregan indicadores al llamarCLONE_VM
para permitir el uso compartido). - Si es necesario usarlo
fork()
, el intercambio debe lograrse a través de la memoria compartida, es decir, la aplicación y liberación de la lista vinculada también debe realizarse en la memoria compartida. En este momento, se debe utilizar una lista vinculada estática para la administración (es decir , una lista enlazada con un tamaño de espacio fijo). - Si desea utilizar
fork()
listas vinculadas dinámicas al mismo tiempo, no se me ha ocurrido ningún método mejor.