2012-07-20 8 views
7

Cuando intento abrir/proc/net/tcp desde un subproceso secundario POSIX en C++, falla con un "No dicho archivo o directorio" error. Si intento abrirlo desde el hilo padre, éste se ejecuta correctamente, y el proceso de abrirlo/cerrarlo en el hilo padre lo hace tener éxito aproximadamente un tercio del tiempo en el hilo secundario también. Puedo abrir/proc/uptime en el hilo hijo el 100% del tiempo sin problema. Aquí hay un código de ejemplo que puede ser compilado con "-pthread g ++ -Wall -o test.cc prueba":Apertura/proc/net/tcp en C++ de un subproceso POSIX falla la mayor parte del tiempo

#include <iostream> 
#include <fstream> 
#include <cstring> 
#include <cerrno> 
#include <pthread.h> 

using namespace std; 

void * open_test (void *) 
{ 
    ifstream in; 
    in.open("/proc/net/tcp"); 
    if (in.fail()) 
     cout << "Failed - " << strerror(errno) << endl; 
    else 
     cout << "Succeeded" << endl; 
    in.close(); 

    return 0; 
} 

int main (int argc, char * argv[]) 
{ 
    open_test(NULL); 

    pthread_t thread; 
    pthread_create(&thread, NULL, open_test, NULL); 
    pthread_exit(0); 
} 

Estoy funcionando esto en una caja de Ubuntu 12.04 con un i5-2520M Intel (2 núcleos * 2 núcleos virtuales) en Linux kernel 3.2.0. Aquí está la salida de mí que ejecuta el código anterior 6 veces consecutivas:

[email protected]:/tmp$ ./test 
Succeeded 
Failed - No such file or directory 
[email protected]:/tmp$ ./test 
Succeeded 
Succeeded 
[email protected]:/tmp$ ./test 
Succeeded 
Failed - No such file or directory 
[email protected]:/tmp$ ./test 
Succeeded 
Failed - No such file or directory 
[email protected]:/tmp$ ./test 
Succeeded 
Succeeded 
[email protected]:/tmp$ ./test 
Succeeded 
Failed - No such file or directory 
[email protected]:/tmp$ 

Es probablemente la pena señalar que no tengo este problema si uso tenedor en lugar de los hilos POSIX. Si uso fork, entonces el proceso hijo no tiene problemas para leer/proc/net/tcp

Solo un par de puntos de datos para arrojar ... Parece que esto es una regresión en Linux ya que parece que 2.6.35 para trabajar el 100% del tiempo 3.2.0 vomita la mayor parte del tiempo, incluso en mi lenta computadora portátil basada en Pentium M.

+0

¿Has probado con un núcleo 3.4.4? –

+0

@BasileStarynkevitch Puedo reproducir eso en 3.4.4 –

+1

@MikeCardwell, ¿ha considerado archivar esto como un error contra el kernel de Linux? –

Respuesta

2

Este comportamiento parece ser un tipo de error en el sistema de archivos virtual /proc. Si añade este código justo antes de abrir el archivo:

system("ls -l /proc/net /proc/self/net/tcp"); 

verá que /proc/net es un enlace simbólico a /proc/self/net y /proc/sec/net/tcp aparece correctamente para ambas llamadas a open_test, incluso cuando la llamada hebra generada falla .

Edición: Me acabo de dar cuenta de que la prueba anterior es falsa, ya que el auto se referiría al proceso de shell de la llamada al sistema, no a este proceso. Utilizando la siguiente función en lugar también revela el error:

void ls_command() { 
    ostringstream cmd; 
    cmd << "ls -l /proc/net " 
     << "/proc/" << getpid() 
     << "/net/tcp " 
     << "/proc/" << syscall(SYS_gettid) 
     << "/net/tcp"; 
    system(cmd.str().c_str()); 
} 

Usted verá que el subproceso generado a veces no ser capaz de ver el archivo de los padres /net/tcp. De hecho, ha desaparecido, ya que este es el proceso del shell generado que ejecuta el comando ls.

La solución a continuación permite que el hilo hijo tenga acceso confiable a lo que sería su /proc/net/tcp.

Mi teoría es que es una especie de error de condición de carrera con la configuración correcta de la entrada /proc/self para el hilo como la combinación adecuada de estado padre y estado específico del hilo. Como una prueba y obsoleto, modifiqué el código open_test para usar el "identificador de proceso" asociado con el subproceso, en lugar de intentar acceder al proceso principal (porque /proc/self hace referencia al ID del proceso principal, no al del subproceso).

Editar: A medida que la evidencia indica, el error tiene que ver con el proceso padre limpiando su estado /proc/self/... antes de que el hilo hijo ha tenido la oportunidad de leerlo. Todavía mantengo esto como un error, ya que el hilo hijo todavía es técnicamente parte del proceso. Es getpid() sigue siendo el mismo antes y después de que el hilo principal llame al pthread_exit(). La entrada /proc para el proceso primario debe permanecer válida hasta que se completen todos los subprocesos secundarios.Aunque

Edit2: Jonas argumenta que esto puede no ser un error. Como prueba de ello, hay esto desde man proc:

  /proc/[pid]/fd 
       ... 
       In a multithreaded process, the contents of this directory are 
       not available if the main thread has already terminated (typi- 
       ally by calling pthread_exit(3)).

Pero luego considerar esta entrada para /proc/self en la misma entrada man página:

  /proc/self 
       This directory refers to the process accessing the /proc file 
       system, and is identical to the /proc directory named by the 
       process ID of the same process.

Si se va a creer que esto no es un error, porque los hilos y procesos se tratan igual en Linux, entonces los hilos deberían tener una expectativa de que /proc/self funcionará. El error se puede solucionar fácilmente modificando /proc/self para cambiar a usar el valor /proc/[gettid] cuando la versión /proc/[getpid] ya no está disponible, tal como lo hace la solución a continuación.

void * open_test (void *) 
{ 
    ifstream in; 
    string file = "/proc/net/tcp"; 
    in.open(file.c_str()); 
    if (in.fail()) { 
     ostringstream ss; 
     ss << "/proc/" << syscall(SYS_gettid) << "/net/tcp"; 
     cout << "Can't access " << file 
      << ", using " << ss.str() << " instead" << endl; 
     file = ss.str(); 
     in.open(file.c_str()); 
    } 
    if (in.fail()) 
     cout << "Failed - " << strerror(errno) << endl; 
    else 
     cout << "Succeeded" << endl; 
    in.close(); 

    return 0; 
} 
+0

Tu ejemplo funciona para mí, gracias. Creo que entiendo tu explicación. Entonces, ¿esto es básicamente un error del kernel de Linux en procfs? –

+0

@MikeCardwell: sospecho que ese sea el caso. – jxh

+0

@ user315052 Es posible que desee consultar http://stackoverflow.com/questions/807506/threads-vs-processes-in-linux para ver por qué esto puede no ser un error. los procesos y los hilos son muy parecidos en Linux. Por otro lado, el hilo es/también fue incluido en/proc/$ pid/tasks/.... No está del todo claro y tal vez uno debería discutir estas cosas en la lista de correo kernel. –

2

Si se agrega una llamada pthread_join (rosca, NULL) antes de la llamada pthread_exit(), el programa funcionará correctamente.

+0

Hmm, parece que tienes razón. ¿Puedes explicar porque? En mi opinión, llamar a pthread_exit en la función principal simplemente lo haría esperar hasta que todos los demás hilos hayan terminado.Además, ¿por qué este problema no se manifiesta con/proc/uptime incluso sin el pthread_join? –

+0

@MikeCardwell: Parece que 'scott' implica que la llamada' pthread_exit' es responsable de eliminar la entrada del archivo '/ proc/self/net/tcp' antes de que el hilo hijo tenga la posibilidad de encontrarlo. Sin embargo, todavía lo describiría como un error. Como el proceso en sí aún no ha salido, el hilo hijo todavía debería poder encontrar esa entrada. – jxh

+0

En realidad, eso tiene sentido para mí. Debido a que el proceso principal no está esperando que el niño finalice, el niño puede intentar leer de la entrada '/ proc' del padre que ya se ha eliminado. Esto tiene mucho más sentido que cualquier otra explicación, ya que siempre pensé que los procfs son exactamente lo mismo que hacer syscalls. –

4

Como señala scott en su respuesta, al agregar un pthread_join(thread, NULL) se corrigen los síntomas. ¿Pero por qué?

Vamos a poner el programa en el BGF y establecer un punto de interrupción en el punto en el abierto ha fallado:

(gdb) break test.cc:14 
Breakpoint 1 at 0x400c98: file test.cc, line 14. 

entonces podemos observar dos tipos diferentes de comportamiento:

  1. (gdb) run […] 
    Succeeded 
    [New Thread 0x7ffff7fd1700 (LWP 18937)]   // <- child thread 
    [Thread 0x7ffff7fd3740 (LWP 18934) exited]  // <- parent thread 
    [Switching to Thread 0x7ffff7fd1700 (LWP 18937)] 
    Breakpoint 1, open_test() at test.cc:14 
    
  2. (gdb) run 
    Succeeded 
    [New Thread 0x7ffff7fd1700 (LWP 19427)]   // <- child thread 
    Succeeded 
    [Thread 0x7ffff7fd1700 (LWP 19427) exited] 
    [Inferior 1 (process 19424) exited normally] 
    

La primera uno sugiere que el proceso padre sale antes que el niño. Al igual que en Linux, los procesos y los hilos son prácticamente los mismos, esto implica que el PID asociado con el proceso principal se limpia. Sin embargo, nada impide que el hilo hijo se ejecute. It y su pid siguen siendo perfectamente válidos. Solo que /proc/self apunta al PID del proceso principal, que se ha eliminado en ese momento.

Cuestiones relacionadas