Procesos
Un proceso es un programa en ejecución que es lanzado y controlado por el sistema operativo.
Funciones
Proc* | bproc_exec (...) |
void | bproc_close (...) |
bool_t | bproc_cancel (...) |
uint32_t | bproc_wait (...) |
bool_t | bproc_finish (...) |
bool_t | bproc_read (...) |
bool_t | bproc_eread (...) |
bool_t | bproc_write (...) |
bool_t | bproc_read_close (...) |
bool_t | bproc_eread_close (...) |
bool_t | bproc_write_close (...) |
void | bproc_exit (...) |
Desde la perspectiva del programador, el multi-procesamiento es la posibilidad de iniciar e interactuar con otros procesos (hijos) desde proceso principal (padre). El sistema operativo puede ejecutar el proceso hijo en otro núcleo de la CPU (true multitasking) o en el mismo que el padre (context switch). Esta es una decisión del sistema en la que el programador no puede influir que dependerá del tipo de procesador y de su carga de trabajo. El efecto final será que ambos procesos (padre e hijo) se ejecutan en paralelo.
- Utiliza bproc_exec para lanzar un nuevo proceso desde la propia aplicación.
- Utiliza bproc_read para leer desde la salida estándar del proceso.
- Utiliza bproc_write para escribir en la entrada estándar del proceso.
1. Lanzando procesos
bproc_exec lanzará un proceso desde nuestro propio programa en C de forma similar a como lo hace el Terminal (Figura 1). En este caso, la E/S Estándar stdin
, stdout
y stderr
se redireccionará al objeto Proc mediante tuberías anónimas. Desde aquí, podemos utilizar bproc_write para escribir en el canal stdin
del hijo y bproc_read para leer desde su stdout
. Las reglas de lectura/escritura son las que rigen los pipes del sistema operativo y que podemos resumir en:
- Si el padre llama a
bproc_read
y el hijo no ha escrito nada (búfer vacío), el padre se bloqueará (esperará) hasta que haya información en el canal de salida del hijo. - Si el hijo termina y el padre está esperando para leer,
bproc_read
devolveráFALSE
y el padre continuará su ejecución. - Si el padre llama a
bproc_write
y el búfer de escritura está lleno, el padre se bloqueará (esperará) hasta que el hijo lea desde sustdin
y libere espacio en el canal. - Si el hijo termina y el padre está bloqueado por escritura,
bproc_write
devolveráFALSE
y el padre continuará su ejecución. - Algunos comandos o procesos (p.e
sort
) no se iniciarán hasta que se lea todo el contenido destdin
. En estos casos, el proceso padre deben usar bproc_write_close para indicar al hijo que la escritura en sustdin
ha concluido. - Cuando el padre llama a bproc_close, todos los canales de E/S se cerrarán y ambos procesos continuarán su ejecución de forma independiente. Para terminar la ejecución del proceso hijo (kill) utiliza bproc_cancel.
- bproc_wait detendrá al proceso padre hasta que el hijo finalice. Para evitar la sobrecarga del buffer de salida
stdout
del hijo, cerrar el canal mediante bproc_read_close. - bproc_finish comprobará, de forma no bloqueante, si el hijo ha terminado su ejecución.
2. Ejemplos multi-procesamiento
Veamos algunos ejemplos prácticos de IPC Inter-Process Communication utilizando los canales E/S estándar en procesos vinculados padre-hijo. En (Listado 1) volcaremos en un fichero la salida stdout
del proceso hijo. En (Listado 2) redirigiremos ambos canales, escribiremos en stdin
y leeremos de stdout
apoyándonos en archivos de disco. Por último, implementaremos un protocolo asíncrono donde el padre y el hijo intercambian peticiones y respuestas. En (Listado 4) mostramos el código del proceso hijo, en (Listado 3) el del proceso padre y en (Listado 5) el resultado de la comunicación, escrito por el proceso padre.
stdout
de un proceso y guardándolo en un archivo.
1 2 3 4 5 6 7 8 |
byte_t buffer[512]; uint32_t rsize; File *file = bfile_create("out.txt", NULL); Proc *proc = bproc_exec("dir C:\Windows\System32", NULL); while(bproc_read(proc, buffer, 512, &rsize, NULL) == TRUE) bfile_write(file, buffer, rsize, NULL, NULL); bproc_close(&proc); bfile_close(&file); |
Los comandos del shell no son portables en general. Los utilizamos solo como ejemplo.
stdin
y stdout
de un proceso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
byte_t buffer[512]; uint32_t rsize; File *fsrc = bfile_open("members.txt", ekFILE_READ, NULL); File *fdes = bfile_create("sorted_members.txt", NULL); Proc *proc = bproc_exec("sort", NULL); // Writes to stdin while (bfile_read(fsrc, buffer, 512, &rsize, NULL) == TRUE) bproc_write(proc, buffer, rsize, NULL, NULL); // Closes child stdin bproc_write_close(proc); // Reads child stdout while(bproc_read(proc, buffer, 512, &rsize, NULL) == TRUE) bfile_write(fdes, buffer, rsize, NULL, NULL); bfile_close(&fsrc); bfile_close(&fdes); bproc_close(&proc); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Proc *proc; uint32_t commands[] = { 326, 32, 778, 123, 889, 712, 1, 55, 75, 12 }; uint32_t exit_command = 0; uint32_t i; proc = bproc_exec("child", NULL); for (i = 0; i < 10; ++i) { uint32_t response; uint32_t time; // Send command to child bproc_write(proc, (byte_t*)&commands[i], sizeof(uint32_t), NULL); // Waits for child response bproc_read(proc, (byte_t*)&response, sizeof(uint32_t), NULL); bproc_read(proc, (byte_t*)&time, sizeof(uint32_t), NULL); bstd_printf("Child command %d in %d milliseconds.\n", response, time); } bproc_write(proc, (byte_t*)&exit_command, sizeof(uint32_t), NULL); bproc_close(&proc); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
for (;;) { uint32_t command; // Reads from standard input a command from parent. if (bstd_read((byte_t*)&command, sizeof(command), NULL) == TRUE) { if (command != 0) { // Waits random time (simulates processing). uint32_t timer = bmath_randi(1000, 2000); bthread_sleep(timer); // Writes to standard output the response to parent. bstd_write((const byte_t*)&command, sizeof(command), NULL); bstd_write((const byte_t*)&timer, sizeof(timer), NULL); } else { // Command 0 = Exit break; } } } |
1 2 3 4 5 6 7 8 9 10 |
Child command 326 in 1761 milliseconds. Child command 32 in 1806 milliseconds. Child command 778 in 1989 milliseconds. Child command 123 in 1909 milliseconds. Child command 889 in 1043 milliseconds. Child command 712 in 1153 milliseconds. Child command 1 in 1780 milliseconds. Child command 55 in 1325 milliseconds. Child command 75 in 1157 milliseconds. Child command 12 in 1426 milliseconds. |
bproc_exec ()
Lanza un nuevo proceso.
Proc* bproc_exec(const char_t *command, perror_t *error);
command | El comando a ejecutar (ruta y argumentos). p.e. |
error | Código de error si la función falla. Puede ser |
Retorna
Manejador del proceso hijo que podemos utilizar para comunicarnos con él. Si la función falla, retorna NULL
.
Observaciones
bproc_close ()
Cierra la comunicación con el proceso hijo y libera recursos.
void bproc_close(Proc **proc);
proc | Manejador del proceso. Será puesto a |
Observaciones
Si el proceso todavía se está ejecutando, esta función no lo finaliza. Solo cierra el canal de comunicación entre el padre y el hijo que continuarán ejecutándose de forma independiente. Como cualquier otro objeto, un proceso siempre debe cerrarse, incluso si ya ha terminado su ejecución. Ejemplos multi-procesamiento.
bproc_cancel ()
Fuerza la finalización del proceso.
bool_t bproc_cancel(Proc *proc);
proc | Manejador del proceso. |
Retorna
TRUE
si el proceso ha terminado. FALSE
si no.
bproc_wait ()
Espera hasta que el proceso hijo finalice.
uint32_t bproc_wait(Proc *proc);
proc | Manejador del proceso. |
Retorna
El valor de retorno del proceso hijo o UINT32_MAX
si hay algún error.
bproc_finish ()
Comprueba si el proceso hijo sigue en ejecución.
bool_t bproc_finish(Proc *proc, uint32_t *code);
proc | Manejador del proceso. |
code | El valor de salida del proceso (si ha terminado). Puede ser |
Retorna
TRUE
si el proceso hijo ha terminado, FALSE
si no.
Observaciones
Esta función retorna inmediatamente. No bloquea al proceso que la llama.
bproc_read ()
Lee datos desde la salida estándar del proceso (stdout
).
bool_t bproc_read(Proc *proc, byte_t *data, const uint32_t size, uint32_t *rsize, perror_t *error);
proc | Manejador del proceso. |
data | Búfer donde se escribirán los datos leídos. |
size | El número de bytes máximos a leer (tamaño del buffer). |
rsize | Recibe el número de bytes leídos realmente. Puede ser |
error | Código de error si la función falla. Puede ser |
Retorna
TRUE
si se han leído datos. FALSE
si ha ocurrido algún error.
Observaciones
Esta función bloqueará al proceso padre hasta que el hijo escriba en su stdout
. Si no hay datos en el canal y el hijo termina, retornará FALSE
con rsize = 0
y error = ekPROC_SUCCESS
. Ejemplos multi-procesamiento.
bproc_eread ()
Lee datos desde la salida de error del proceso (stderr
).
bool_t bproc_eread(Proc *proc, byte_t *data, const uint32_t size, uint32_t *rsize, perror_t *error);
proc | Manejador del proceso. |
data | Búfer donde se escribirán los datos leídos. |
size | El número de bytes máximos a leer. |
rsize | Recibe el número de bytes leídos realmente. Puede ser |
error | Código de error si la función falla. Puede ser |
Retorna
TRUE
si se han leído datos. FALSE
si ha ocurrido algún error.
Observaciones
Esta función bloqueará al proceso padre hasta que el hijo escriba en su stdout
. Si no hay datos en el canal y el hijo termina, retornará FALSE
con rsize = 0
y error = ekPROC_SUCCESS
. Ejemplos multi-procesamiento.
bproc_write ()
Escribe datos en el canal de entrada del proceso (stdin
).
bool_t bproc_write(Proc *proc, const byte_t *data, const uint32_t size, uint32_t *wsize, perror_t *error);
proc | Manejador del proceso. |
data | Búfer que contiene los datos a escribir. |
size | El número de bytes a escribir. |
wsize | Recibe el número de bytes escritos realmente. Puede ser |
error | Código de error si la función falla. Puede ser |
Retorna
TRUE
si se han escrito datos. FALSE
si ha ocurrido algún error.
Observaciones
Esta función bloqueará al proceso padre si no hay espacio en el búfer para completar la escritura. Cuando el proceso hijo lea si stdin
y libere espacio, se completará la escritura y el proceso padre continuará su ejecución. Ejemplos multi-procesamiento.
bproc_read_close ()
Cierra el canal stdout
del proceso hijo.
bool_t bproc_read_close(Proc *proc);
proc | Manejador del proceso. |
Retorna
TRUE
si el canal se ha cerrado. FALSE
si ya estaba cerrado.
Observaciones
Esta función permite ignorar la salida del proceso hijo, previniendo bloqueos por la saturación del canal. Lanzando procesos.
bproc_eread_close ()
Cierra el canal stderr
del proceso hijo.
bool_t bproc_eread_close(Proc *proc);
proc | Manejador del proceso. |
Retorna
TRUE
si el canal se ha cerrado. FALSE
si ya estaba cerrado.
Observaciones
Esta función permite ignorar la salida de errores del proceso hijo, previniendo bloqueos por la saturación del canal. Lanzando procesos.
bproc_write_close ()
Cierra el canal stdin
del proceso hijo.
bool_t bproc_write_close(Proc *proc);
proc | Manejador del proceso. |
Retorna
TRUE
si el canal se ha cerrado. FALSE
si ya estaba cerrado.
Observaciones
Algunos procesos necesitan leer todo el contenido de stdin
antes de comenzar el trabajo. Al cerrar el canal, el proceso hijo recibe la señal EOF
End-Of-File en stdin
. Lanzando procesos.
bproc_exit ()
Termina el proceso actual (el que llama) y todos sus hijos de ejecución.
void bproc_exit(const uint32_t code);
code | El código de salida del proceso. |