===== Procesos ===== Un proceso se crea con la llamada al sistema **fork**(2). Esto crea un clon del proceso actual, pero cada proceso resultante sabe si él es el padre o el hijo. * Al hijo se le asigna un pid (process id) único. * La llamada **fork()** retorna 2 veces: una vez en el padre y otra en el hijo. * En el hijo, fork retorna 0. * En el padre, fork retorna el pid del hijo. * Para el hijo se crea un nuevo espacio de direcciones de memoria que parte inicialmente con una copia de toda la memoria del padre. De ahí en adelante las modificaciones que haga el hijo en su memoria no se verán reflejadas en la memoria del padre, y viceversa. Son espacios de direcciones independientes. * El hijo hereda todos los archivos abiertos por el padre. Los fds (file descriptors) del padre siguen siendo válidos en el hijo (y también en el padre). * El tiempo de ejecución del proceso hijo parte de 0. * El hijo parte sin alarmas ni señales pendientes. Esquema típico: #include pid_t child= fork(); if (child==0) { // aquí se ejecuta el hijo ... cuerpo del hijo ... exit(...); // termina el hijo entregando un código de retorno } else { // aquí se ejecuta el padre // la variable "child" contiene el pid del hijo ... código que se ejecuta en paralelo con el cuerpo del hijo ... int status; waitpid(child, &status, 0); // espera hasta que el hijo termine int rc= WEXITSTATUS(status); // código de retorno del hijo } ==== Formas de terminar un proceso ==== ^ mecanismo ^ notas ^ | return del main | Si el valor retornado de 0 se considera que el fin fue exitoso | | exit(value); | Esta función no retorna | | abort(); | El efecto de esta última llamada es indefinido, pero puede generar un core | Para matar a otro proceso se usa: #include #include pid_t pid; kill(pid, SIGKILL); ==== Esperar el término de un proceso ==== Para esperar que un proceso termine se usa la llamada al sistema **wait**(2): #include #include int status; wait(&status); entonces el padre espera hasta que algún hijo muera y retorna el pid de ese hijo. A la variable status (int) se le asigna la causa del deceso (ver manual). Si en el momento de ejecutar wait ya había algún proceso que ya había muerto ("zombie"), la llamada retorna inmediatamente. Un wait también termina si llega una señal que cause la terminación del proceso o la invocación de una función de manejo de señales. También existe una función que permite un manejo más específico: #include #include int status; pit_t pid; int flags= ...; waitpid(pid, &status, flags); Si pid==-1 se espera a cualquier proceso (como hace wait). Si pid>0 se espera al proceso que posee ese pid. Los flags incluyen como valor posible WNOHANG que hace que la llamada no se bloquee. ==== Deberes del padre ==== En Unix, un padre es completamente independiente de sus hijos, pero debe cumplir con una responsabilidad fundamental: enterrarlos cuando mueren. Para esto, basta con que invoque la función wait en cualquier momento, pero es indispensable que sea informado que su hijo murió para que Linux pueda deshacerse por completo de él. Un hijo muerto pero no enterrado (cuyo padre nunca ha sido informado de su muerte vía wait) es un Zombie: un proceso que ya no existe pero no puede reciclarse por completo. Siempre deben evitar generar Zombies en el sistema y es su responsabilidad hacer wait de todos sus hijos muertos. Para observar lo que sucede mientras el padre no ha invocado wait compile y ejecute [[start#deberes_de_un_padre|este ejemplo]]. ==== Pipes ==== Un pipe es un canal de comunicación entre dos procesos. Se comporta como una cola (FIFO) en donde lo que se escribe por un extremo se lee por el otro. Fue el primer mecanismo que tuvo Unix para comunicar procesos y todavía se usa cuando desde el shell Ud. invoca por ejemplo ''ls | more''. Para crear un pipe se invoca: #include int fds[2]; pipe(fds); en donde fds es un arreglo de tamaño 2. Después de ejecutar esta llamada, se obtienen 2 fds: fds[0] permite leer fds[1] permite escribir Después de invocar pipe, se duplica el proceso mediante fork. Uno de los procesos cierra el fd de lectura y el otro cierra el de escritura. Este pipe sirve para comunicar unidireccionalmente ambos procesos. El sistema garantiza que un write de tamaño menor o igual a PIPE_BUF es indivisible. === Ejemplo 1: quicksort paralelo === El siguiente programa ordena un arreglo por medio de 2 procesos. Después de llamar a particionar se llama a fork. La parte inferior del arreglo se ordena en el proceso hijo y la parte superior en el padre. El ejemplo completo se encuentra [[http://users.dcc.uchile.cl/~lmateu/CC3301/download/pquicksort.zip|acá]]. int leer(int fd, void *buf, int n) { if (n==0) return 0; do { int rc= read(fd, buf, n); if (rc<=0) return 1; /* fracaso: error o fin del archivo/pipe/socket */ n-= rc; /* descontamos los bytes leídos */ buf= (char*)buf + rc; /* avanzamos el buffer */ } while (n>0); /* mientras no leamos todo lo que esperamos */ return 0; /* exito */ } void pquicksort(int a[], int i, int j) { if (i Observe que si se reemplazara la llamada de la función leer por read, podría funcionar en algunas plataformas, pero sería incorrecto. La llamada al sistema read no siempre entregará la cantidad de bytes pedidos. Puede entregar menos bytes cuando se piden más bytes que la capacidad del buffer que utiliza un pipe. Por otra parte, se garantiza que write siempre escribirá la cantidad de bytes pedidos. Si se escribe menos es porque ocurrió un error. Yo hice el experimento en Linux y read siempre leyó la cantidad de bytes pedidos. Cuando user fork nunca olvide: - Invocar exit para terminar el hijo - Invocar waitpid en el padre para enterrar al hijo - Padre e hijo usan espacios de direcciones independientes así es que el padre no verá los cambios que el hijo haya hecho en la memoria. Use un pipe para que el hijo entregue sus resultados al padre. ==== Ejercicio ==== Resuelva la pregunta 1 partes a y b del [[http://users.dcc.uchile.cl/~lmateu/CC3301/controles/c3-142.pdf|control 3 de 2014/2]]. Pruebe su solución con el archivo [[http://users.dcc.uchile.cl/~lmateu/CC3301/download/mult.zip|mult.zip]]. ==== Cambiar el archivo ejecutable ==== Una vez que el hijo se inicia, frecuentemente lo que hace es invocar a algún comando, lo que hace llamando a una función de la familia //exec//. Esto no crea un proceso nuevo, sino que sustituye el binario del proceso. Un //exec// que funciona no retorna, sino que inicia un nuevo archivo ejecutable, sin cambiar la identificación del proceso (//pid//). Supongamos que se tienen las siguientes declaraciones previas: #include char *filename, *path, *s1, *s2, ...; char *argv[], *environ[]; La siguiente tabla muestra las distintas funciones para cambiar el archivo ejecutable. ^ Llamada ^ notas ^ | execl(path, s1, s2, ..., NULL); | ejecuta el programa indicado por path con parámetros s1, s2, ... | | execle(path, s1, s2, .., NULL, environ ); | igual al anterior + se traspasa ambiente indicado en environ | | execlp(filename, s1, s2, ..., NULL); | si filename no empieza por "/", se le busca en el PATH | | execv(path, argv); | el último elemento de argv[] debe ser NULL | | execve(path, argv, environ); | Llamada al sistema (sección 2) | | execvp(filename, argv); | si filename no empieza por "/", se le busca en el PATH | | execvpe(filename, argv, environ); | si filename no empieza por "/", se le busca en el PATH | El archivo ejecutable debe corresponder a un programa con encabezamiento: int main(int argc, char *argv[], char *envp[]); Pero usualmente no se declara el parámetro envp, porque también existe la función getenv para consultar las variables de ambiente (ver más abajo). === Ejemplo 2: invocar ls === #include #include #include #include #include /* * Ejemplo trivial de un fork/exec/wait */ int main() { pid_t pid; int status; printf("voy al ls\n"); printf("---------------\n"); fflush(stdout); /* Para asegurarme que lo anterior ya salga por la salida * prueben comentando el fflush y redirijan la salida a un archivo. */ pid = fork(); if (pid < 0) { fprintf(stderr, "falla fork!\n"); exit(1); } if (pid == 0) { /* soy el hijo */ execl("/bin/ls", "ls", "-l", 0); fprintf(stderr, "nunca debio ocurrir!\n"); exit(1); } else { waitpid(pid, &status, 0); /* espera la muerte del proceso pid */ printf("---------------\n"); return 0; } } === Ejemplo 3: more === El siguiente programa filtra la salida por medio de more: /* Esto se requiere para poder usar fdopen sin warnings */ #define _POSIX_C_SOURCE 1 #include #include #include #include #include #include #include void error(char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "Error: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); exit(1); } int main() { int fd[2]; pid_t pid; FILE *out; int status; int i; if (pipe(fd)!=0) error("Can't create pipe"); if ((pid= fork()) == -1) error("Can't fork"); if (pid==0) { /* es el hijo */ /* el siguiente truco pone al extremo del lectura del pipe como el stdin del nuevo proceso */ close(STDIN_FILENO); dup(fd[0]); /* aprovechamos que se usa el mismo fd recien liberado */ close(fd[0]); close(fd[1]); execlp("more", "more", NULL); fprintf(stderr, "nunca debio ocurrir!\n"); exit(1); } else { /* este es el padre */ close(fd[0]); /* no va a leer del pipe */ out= fdopen(fd[1], "w"); /* simula que el extremo de lectura del pipe fue abierto con fopen */ /* ahora generamos el output */ for(i= 1; i<=200; ++i) fprintf(out, "Linea numero %d\n", i); fclose(out); wait(&status); /* esperamos que el hijo muera */ return 0; } } === Ejercicio: ls | more === * Modifique la función pquicksort de modo que reciba el parámetro adicional p, correspondiente al número de procesos que se usarán para el ordenamiento. * Escriba la función ls_pipe_more() que lanza el comando ls de manera que su salida estándar alimente la entrada estándar del comando more. Esto es equivalente a invocar desde el shell de comandos ''ls | more''. Nota: La solución de este ejercicio aparece en [[start#pipes|esta página]]. ==== La función system ==== Una función la biblioteca estándar de C es system que permite invocar comandos como si los invocara el shell de comandos ''sh''. Use ''man 3 system'' para obtener información de esta función. Una implementación muy simple de esta función aparece en [[start#clase_12linuxejemplos_con_pipes|esta página]]. Con la función system es trivial implementar ls_pipe_more. La desventaja es que en ese caso se crea un proceso adicional para sh, el cual no es un proceso barato. ==== Variables de ambiente ==== El puntero environ en las funciones execle, execve y execvpe apunta a un arreglo de strings de la forma: name=value terminando con un puntero NULL. Ejemplo de variables de ambiente: HOME PATH TERM TZ LOGNAME Para buscar un nombre de variable en el environment se usa: #include char *p; p= getenv(name); El parámetro //name// apunta al nombre de la variable y //p// recibe el puntero a la línea //name=value// respectiva, o bien NULL si no existe. Para el resto de las funciones de la familia exec se preservan las mismas variables de ambiente que regían antes de la llamada a exec. En una llamada a fork, el proceso hijo hereda las mismas variables de ambiente del proceso hijo. Por esta razón al invocar un comando desde el shell, el proceso que se crea heredará las variables de ambiente del shell. ==== Información disponible durante la ejecución ==== #include #include pid_t pid, ppid; char *s; uid_t uid; gid_t guid; ^ Llamada ^ Descripción ^ | pid=getpid(); | retorna el pid propio | | ppid=getppid(); | retorna el pid del padre | | s=getlogin(); | retorna puntero al nombre del usuario, o NULL | | uid=getuid(); | retorna real UID | | uid=geteuid(); | retorna effective UID | | gid=getgid(); | real group ID | | gid=getegid(); | effective group ID | | s=ctermid(NULL); | puntero al nombre del terminal en un buffer estático | | (void)ctermid(termname); | copia nombre del terminal a termname[L_ctermid] | Dado un uid, es posible obtener un record de información asociada al usuario, tomada de lo que aparece en el archivo /etc/passwd: #include #include struct passwd *pwptr; pwptr=getpwuid(uid); La estructura a la cual apunta pwptr es: /* The passwd structure. */ struct passwd { char *pw_name; /* Username. */ char *pw_passwd; /* Password (encriptada). */ __uid_t pw_uid; /* User ID. */ __gid_t pw_gid; /* Group ID. */ char *pw_gecos; /* Real name. */ char *pw_dir; /* Home directory. */ char *pw_shell; /* Shell program. */ }; También existe la función pwptr=getpwnam(username); que busca por nombre y retorna un record al puntero respectivo. En ambos casos se retorna NULL si el usuario no existe. ==== El shell de comandos ==== Los shells de comandos (sh o csh) usan intensivamente fork/exec para lanzar los comandos ingresados por el usuario. También invocan pipe cuando se ingresan pipes de comandos. Casi todos los comandos que se ejecutan desde un shell son archivos ejecutables, pero existen excepciones. La más notable es el comando cd que debe ser reconocido como un comando interno que cambia la variable de ambiente PWD. Si fuese un archivo ejecutable que se lanza con fork/exec, cambiar la variable PWD cambiaría la variable del proceso que se creó para cd, y no el del shell, lo que lo haría completamente inútil. Otros ejemplos de comandos internos del shell son setenv (en csh) y set. ==== Hora y Fecha ==== #include time_t t; /* usualmente unsigned long */ t=time(NULL); /* Almacena en t el número de segundos desde 1/1/1970 */ (void)time(&t); /* forma alternativa */ La función anterior basta para calcular todo lo que se necesita, pero las siguientes funciones son útiles para simplificar esta tarea: struct tm { int tm_sec; /* Seconds. [0-60] (1 leap second) */ int tm_min; /* Minutes. [0-59] */ int tm_hour; /* Hours. [0-23] */ int tm_mday; /* Day. [1-31] */ int tm_mon; /* Month. [0-11] */ int tm_year; /* Year - 1900. */ int tm_wday; /* Day of week. [0-6] */ int tm_yday; /* Days in year.[0-365] */ int tm_isdst; /* DST. [-1/0/1]*/ long int tm_gmtoff; /* Seconds east of UTC. */ const char *tm_zone; /* Timezone abbreviation. */ }; struct tm *t; time_t now; now=time(NULL); t=localtime(&now); t=gmtime(&now); seconds=mktime(&t); /* conversion inversa */ /* Los campos tm_wday y tm_yday se ignoran y se actualizan en t */ s=ctime(&now); /* Genera string de la forma "Wed Jun 30 21:49:08 1993\n" */ s=asctime(&t); /* Genera string de la forma "Wed Jun 30 21:49:08 1993\n" */ La función strftime es una especie de sprintf para formatear tiempos (ver manual). Para medir intervalos de tiempo de procesador se usa: #include ticks=clock(); /* Tiempo medido desde algún instante de referencia */ /* Para transformar a segundos dividir por CLOCKS_PER_SEC */ === Ejemplo 4: mostrar la hora === hora.c #include #include int main() { time_t now; struct tm *t; now=time(NULL); t=localtime(&now); printf("Son las %02d:%02d:%02d del %d/%d/%d\n", t->tm_hour, t->tm_min, t->tm_sec, t->tm_mon+1, t->tm_mday, t->tm_year+1900); printf("Dia de la semana: %d\n", t->tm_wday+1); printf("Dia del anno: %d\n", t->tm_yday+1); printf(t->tm_isdst>0?"Horario de Verano\n":"Horario Normal\n"); return 0; }