{"id":581,"date":"2010-04-11T22:41:49","date_gmt":"2010-04-11T20:41:49","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=581"},"modified":"2010-04-11T22:41:49","modified_gmt":"2010-04-11T20:41:49","slug":"trabajando-con-senales","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=581","title":{"rendered":"Trabajando con se\u00f1ales"},"content":{"rendered":"<p>Estos d\u00edas he seguido trasteando con Bftpd, y me puse a trabajar en resolver un problema bastante molesto: la cantidad de procesos <a href=\"http:\/\/en.wikipedia.org\/wiki\/Zombie_process\" target=\"_blank\">zombie<\/a> que deja tras de s\u00ed.<\/p>\n<p>Para entender lo que pasa, primero hay que explicar que Bftpd lanza un nuevo proceso por cada nuevo usuario que se conecta, en lugar de utilizar un solo proceso para gestionar todas las conexiones. As\u00ed, tenemos un proceso padre que se limita a escuchar, y cada vez que llega una petici\u00f3n de conexi\u00f3n crea un proceso hijo para que la gestione; tambi\u00e9n comprueba cuando un hijo se muere (porque se haya cerrado la conexi\u00f3n).<\/p>\n<p>Y aqu\u00ed es de donde surge el problema: cada vez que un hijo termina, emite una <a href=\"http:\/\/en.wikipedia.org\/wiki\/Signal_%28computing%29\" target=\"_blank\">se\u00f1al<\/a> (en concreto <a href=\"http:\/\/en.wikipedia.org\/wiki\/SIGCHLD\" target=\"_blank\">SIGCHLD<\/a>) hacia el padre, el cual, al recibirla, ejecutar\u00e1 una peque\u00f1a funci\u00f3n o callback y luego seguir\u00e1 su ejecuci\u00f3n normal. En el caso de Bftpd, dicho callback comprueba qu\u00e9 hijo es el que se ha muerto, pide su valor de retorno (para evitar que se quede zombie) y libera una serie de recursos que le reserv\u00f3. En concreto, el c\u00f3digo de dicha funci\u00f3n comienza con un:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">pid = wait(NULL);\n[codigo para liberar los recursos del proceso PID]<\/pre>\n<\/div>\n<p>Hasta aqu\u00ed todo parece correcto; por desgracia, despu\u00e9s de cada sesi\u00f3n con Bftpd quedaban varios procesos zombie. La raz\u00f3n era, obviamente, que el proceso padre no hab\u00eda le\u00eddo su valor de retorno, por lo que el sistema operativo no los pod\u00eda hacer desaparecer; sin embargo, en el callback se lee siempre dicho valor, as\u00ed que es obvio que algo raro estaba pasando.<\/p>\n<p>Para depurar el c\u00f3digo empec\u00e9 por a\u00f1adir unos printfs: uno en el punto en que se crean los procesos hijo, mostrando su PID; otro en el punto de finalizaci\u00f3n de dichos hijos, y otro en la funci\u00f3n de callback de SIGCHLD. Ejecut\u00e9 el servidor, hice unas cuantas operaciones, vi la salida por pantalla, y&#8230; \u00a1Sorpresa! \u00a1Hab\u00eda menos llamadas a la funci\u00f3n de callback que muertes! Si todo funcionase como se esperaba, el n\u00famero deber\u00eda ser exactamente el mismo (cada muerte deber\u00eda emitir una se\u00f1al SIGCHLD, la cual har\u00eda que se ejecutase el callback). Sin embargo, por alguna misteriosa raz\u00f3n, algunos procesos mor\u00edan sin emitir la se\u00f1al.<\/p>\n<p>Para aquellos a los que les de igual el por qu\u00e9, y simplemente necesiten saber el como, dar\u00e9 primero la soluci\u00f3n: se trata de leer los PIDs de todos los hijos muertos en cada llamada al callback, en lugar de leer el de uno s\u00f3lo. En otras palabras, el c\u00f3digo del callback para SIGCHLD debe ser:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">do {\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0pid = waitpid(-1,NULL,WNOHANG);\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (pid&gt;0) {\n            [codigo para liberar los recursos del proceso PID]\n        }\n} while (pid&gt;0);\n<\/pre>\n<\/div>\n<p><strong>Vale, pero \u00bfpor qu\u00e9 ocurre esto?<\/strong><\/p>\n<p>Para entender lo que ocurre hay que irse un poco a las profundidades del n\u00facleo, porque se debe a un problema de <a href=\"http:\/\/rtportal.upv.es\/apps\/rtl-signals\/posix_signals-0.1\/signals-info.pdf\">c\u00f3mo est\u00e1n implementadas las se\u00f1ales<\/a>.<\/p>\n<p>En Linux, cada proceso tiene un conjunto de bits, y cada uno representa una se\u00f1al. Cuando un proceso quiere enviar una se\u00f1al a otro, lo que hace realmente es poner a uno el bit de dicha se\u00f1al en el proceso receptor.<\/p>\n<p>\u00bfY cuando comprueba el receptor la llegada de una se\u00f1al? En cada cambio de contexto: en un sistema operativo multitarea, los procesos se van turnando en el uso de la CPU, de manera que primero la usa unos milisegundos el proceso A, luego otros milisegundos el proceso B, y as\u00ed sucesivamente hasta que se acaba la lista y se vuelve al proceso A. Cada vez que se cambia de un proceso al siguiente se realiza un cambio de contexto, en el que primero se guarda el estado del proceso actual, se busca quien ser\u00e1 el siguiente proceso, se carga su estado y se le cede el control. Pero, y aqu\u00ed est\u00e1 la cuesti\u00f3n, antes de este \u00faltimo paso se comprueba la m\u00e1scara de bits de las se\u00f1ales, y si alguna est\u00e1 activa se ejecutar\u00e1 primero el callback correspondiente.<\/p>\n<p>\u00bfY por qu\u00e9 se pierden entonces se\u00f1ales SIGCHLD? Pues porque dos o m\u00e1s procesos se mueren \u00aba la vez\u00bb; esto es, se mueren dentro del mismo intervalo entre dos ejecuciones del proceso padre. As\u00ed, lo que ocurre es que cuando se muere el primero, pone a 1 el bit de la se\u00f1al SIGCHLD del proceso padre; el repartidor de tareas, al ver que ha muerto, libera todo lo que puede y pasa al siguiente proceso; \u00e9ste muere tambi\u00e9n, y tambi\u00e9n pone a 1 el bit de la se\u00f1al SIGCHLD del proceso padre&#8230; pero ese bit ya estaba a uno, por lo que se queda como est\u00e1. Finalmente, el repartidor decidir\u00e1 que es el momento de ejecutar de nuevo el proceso padre, pero antes ver\u00e1 que el bit de la se\u00f1al SIGCHLD est\u00e1 activo, por lo que lo pondr\u00e1 a cero y\u00a0 llamar\u00e1 al callback. El resultado: se murieron dos procesos pero s\u00f3lo se ejecut\u00f3 una vez la funci\u00f3n asociada a la se\u00f1al, por culpa de que las se\u00f1ales se almacenan con un \u00fanico bit.<\/p>\n<p>Es por esto que la soluci\u00f3n indicada arriba funciona: la se\u00f1al nos indica que se ha muerto AL MENOS un proceso hijo, as\u00ed que debemos comprobar todos, y no asumir que cada uno enviar\u00e1 una se\u00f1al.<\/p>\n<p>Este caso nos demuestra que las se\u00f1ales son un sistema de comunicaci\u00f3n bastante fr\u00e1gil, por lo que no se debe abusar de \u00e9l. Cosas como \u00abcontar el n\u00famero de veces que llega una se\u00f1al\u00bb o similares pueden dar problemas incluso si hay un \u00fanico emisor, porque si emite varias veces la misma se\u00f1al antes de que el padre recupere el control de la CPU, contar\u00e1n como una \u00fanica se\u00f1al. Y como muestra para los incr\u00e9dulos, un peque\u00f1o programita que ejemplifica todo esto:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n#include &lt;sys\/types.h&gt;\n#include &lt;signal.h&gt;\n\nint v;\n\nvoid senal(int valor) {\n\n printf(\"Recibidan\");\n v++;\n}\n\nint main(int argc, char **argv) {\n\n int pid1,pid2,pid3,pid4,loop;\n\n v=0;\n signal(SIGUSR1,senal);\n pid1=getpid();\n pid2=fork();\n if(pid2==0) {\n fork();\n fork();\n printf(\"hijon\");\n kill(pid1,SIGUSR1);\n kill(pid1,SIGUSR1);\n kill(pid1,SIGUSR1);\n kill(pid1,SIGUSR1);\n kill(pid1,SIGUSR1);\n sleep(2);\n } else {\n printf(\"padren\");\n for(loop=0;loop&lt;21;loop++) {\n sleep(1);\n }\n printf(\"Total: %dn\",v);\n }\n}\n<\/pre>\n<\/div>\n<p>Este c\u00f3digo crea un primer hijo, el cual se divide en cuatro hijos en total, y cada uno emite, de golpe, cinco se\u00f1ales SIGUSR1 al padre. Sin embargo, al ejecutarlo veremos que nunca se detectan todas ellas, sino, como mucho, una por proceso hijo (y a veces ni eso).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Estos d\u00edas he seguido trasteando con Bftpd, y me puse a trabajar en resolver un problema bastante molesto: la cantidad de procesos zombie que deja tras de s\u00ed. Para entender lo que pasa, primero hay que explicar que Bftpd lanza un nuevo proceso por cada nuevo usuario que se conecta, en lugar de utilizar un &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=581\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">Trabajando con se\u00f1ales<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,7],"tags":[],"class_list":["post-581","post","type-post","status-publish","format-standard","hentry","category-programacion","category-tutoriales"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/581","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=581"}],"version-history":[{"count":0,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/581\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=581"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=581"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=581"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}