Está en la página 1de 16

INSTITUTO POLITÉCNICO NACIONAL

ESCUELA SUPERIOR DE CÓMPUTO


(ESCOM)

Asignatura: Sistemas Operativos.

Profesor: David Araujo Diaz.

Practica 2
Administración de procesos/hilos

Integrantes del equipo:

Buendia Velazco Abel.


No. de lista: 2.

Carpio Becerra Erick Gustavo.


No. de lista: 4.

Domínguez Páez Alejando Yael.


No. de lista: 9.

Portela Nájera Jesús Bambino.


No. de lista: 23.

Velázquez Diaz Luis Francisco.


No. de lista: 36.

Grupo: 4CM3.

Ciclo escolar 2023-B


Objetivo
Entender y hacer uso de las llamadas al sistema de Linux que permiten crear procesos.

Descripción
Una llamada al sistema es, en términos simples, una función que permite a los procesos
comunicarse con el kernel (núcleo) del sistema operativo, para interés de esta práctica,
el kernel de Linux. En otras palabras, provee la interfaz que comunica a los procesos
con el sistema operativo. Las llamadas al sistema son indispensables para todos los
servicios y procesos del sistema que requieran recursos.
Las llamadas al sistema se vuelven protagonistas cuando algún programa requiere la
realización de una tarea fuera de su alcance, entre las que se enlistan, pero no se limitan
a: escritura y lectura de archivos, gestión de memoria del sistema o interacción con
hardware interno o externo.
En Linux, realizar una llamada al sistema involucra transferir el control desde el modo
de usuario sin privilegios, hacia el modo de uso privilegiado del kernel. Las librerías del
sistema operativo se encargan de recibir y procesar los argumentos de las llamadas
del sistema.
Las llamadas al sistema varían de arquitectura a arquitectura, pero por lo normal, se
presentan como rutinas escritas en lenguaje C y C++, aunque para tareas relativas al
uso de bajo nivel de hardware se requiere de scripts en assembley language.
El desarrollo de esta actividad estará basado en el marco de este contexto.
A continuación, se enlistan los pasos que suele involucrar una llamada al sistema:
1. Un programa, y sus procesos realizan una llamada al sistema; se introducen los
parámetros de la llamada a un pila.
2. Posteriormente tiene lugar la llamada al procedimiento de biblioteca.
3. El código se presenta para su lectura en registry.
4. Después, una instrucción TRAP (interrupción para buscar información en otra
ubicación) es ejecutada para alternar de modo usuario a modo kernel para, así,
inicializar la ejecución en una dirección fija dentro del núcleo del sistema.
5. Se utiliza la tabla de apuntadores a manejadores de llamadas al sistema para
realizar la examinación del número de llamada al sistema, mediante código del
kernel.
6. Se ejecuta el manejador de llamadas al sistema.
7. El control se regresa al procedimiento de biblioteca, ubicado en el espacio de
usuario.
8. El procedimiento regresa al programa de usuario.

1
9. Al finalizar el programa de usuario, se limpia la pila en la que se apilaron los
parámetros.
Las llamadas al sistema se clasifican principalmente en 6 categorías:

Llamadas al sistema para control de procesos:


En este tipo de llamadas se realizan tareas de creación, terminación y control de
procesos primordialmente.
Las principales llamadas al sistema en LINUX bajo este paradigma son fork(), exit(),
exec(),wait(), kill(), getpid().
➢ fork(): Utilizada para crear un proceso hijo en segundo plano a partir de un
proceso padre. Cuando se ejecuta, el proceso hijo inicializado tendrá su propia ID
de proceso y su espacio de direcciones, aunado a esto, será una copia exacta del
padre.
➢ exit(): Utilizada por un programa para finalizar su ejecución. Posterior a esta
llamada, el sistema operativo reclama los recursos que fueron utilizados por el
proceso antes de la llamada exit().
➢ exec(): Utilizada para reemplazar el código de un proceso con el código de otro.
Se le suele dar uso posteriormente de la llamada a fork(), con la finalidad de que
el proceso hijo ejecute un proceso diferente al que ejecuta el proceso padre.
➢ wait(): Utilizada para que un proceso padre espere a que uno o más de sus
procesos hijos terminen de ejecutarse antes de proceder con su propia ejecución.
➢ kill(): Utilizada para enviar señales, casi siempre de interrupción a la ejecución,
a procesos, o en su defecto, forzarle a realizar ciertas acciones.
➢ getpid(): Utilizado para obtener el PID (process id) del proceso actual.

Llamadas al sistema para administración de archivos:

Este tipo de llamadas manejan la manipulación de archivos, es decir, crearlos, leerlos,


escribirlos, etc.

Las principales llamadas al sistema en LINUX bajo este paradigma son open(), read(),
write(), close(), iseek(), unlink().

➢ open(): Utilizada para abrir un archivo existente, o en su defecto, la creación de


uno nuevo. Esta específica llamada al sistema se limita a abrir el archivo, para
realizar otras acciones con este se requieren de otras llamadas al sistema.
➢ read(): Utilizada para abrir archivos en modo lectura. Recibe como argumentos
la cantidad de datos a leer y la ubicación dentro del archivo desde el que se leen.
El retorno de la llamada será el número de bytes leídos.

2
➢ write(): Utilizada para abrir archivos en modo escritura. Esta llamada al sistema
permite el usuario editar un archivo. La cantidad de datos a escribir y la ubicación
dentro del archivo en el que se leen son los parámetros recibidos como
argumentos de la función. El retorno de la llamada será el número de bytes
escritos. Es importante mencionar también que no es posible que múltiples
procesos ejecuten la llamada write() en el mismo archivo simultáneamente.
➢ close(): Utilizada para cerrar un archivo anteriormente abierto. Una vez cerrado
el archivo, se liberan los recursos del sistema antes asignados a este archivo.
➢ Iseek(): Utilizada comúnmente para la lectura o escritura de datos en una
ubicación específica en un archivo, ya que permite modificar la posición del
puntero en un archivo abierto.
➢ unlink(): Utilizada para eliminar un archivo, se diferencia de rm en que solo puede
eliminar archivos de uno en uno. Es importante mencionar que esta acción no es
reversible.

Llamadas al sistema para la administración de directorios


Los directorios constituyen un tipo diferenciado de archivos en el contexto del sistema
operativo basado en UNIX. La información almacenada en ellos establece la
correspondencia entre los nombres de los archivos contenidos en el directorio.
Las llamadas al sistema para la administración de estos permiten a los procesos
interactuar directamente con el sistema de archivos del SO, para así crear, borrar, listar,
mover y renombrar directorios.
Entre las llamadas al sistema en LINUX en el marco de este paradigma se enlistan:
mkdir(), rmdir(), opendir(),closedir(), readdir(),opendir(),getcwd(), chdir().
➢ mkdir(): Utilizada para crear un nuevo directorio. El argumento de la función
recibe el nombre del nuevo dierctorio.
➢ rmdir(): Utilizada para eliminar un directorio vacío. El argumento de la función
recibe le nombre del directorio vacío a eliminar.
➢ opendir(): Utilizada para abrir un directorio para ser leído. El retorno de la función
es un descriptor de archivo.
➢ closedir(): Utilizada para cerrar un directorio anteriormente abierto con la
llamada opendir(). Posterior a la llamada se liberan los recursos asignados al
directorio recién cerrado.
➢ readdir(): Utilizada para leer la siguiente entrada del directorio anteriormente
abierto con la llamada opendir(). El retorno de esta función es una estructura de
datos que contiene la información relacionada a la entrada del directorio.
➢ chdir(): Utilizada para modificar el directorio de trabajo actual del proceso. La
función recibe como argumento el nombre del nuevo directorio.

3
➢ getwcwd(): Utilizada para obtener el directorio de trabajo actual del proceso.
Llamadas al sistema para manejo de dispositivos:
El manejo de dispositivos consiste en la manipulación de los dispositivos, por ejemplo,
leer desde sus buffers, escribir a sus buffers, etc.
La llamada al sistema en LINUX en el marco de este paradigma es: ioctl().
➢ ioctl(): Utilizada para el control de entrada y salida I/O, permite a una aplicación
controlar o comunicarse con un driver de dispositivo, fuera de los usuales
read/write para datos.

Llamadas al sistema para mantenimiento de la información:


Este tipo de llamadas se encargan de manejar la información y su transito a través del
sistema operativo y el programa de usuario.
Las llamadas al sistema en LINUX en el marco de este paradigma son: getpid(), alarm(),
sleep().
➢ getpid(): Utilizada para obtener el ID de proceso del proceso realizando la
llamada.
➢ alamr(): Utilizada para inicializar una alarma para la entrega de una señal. La
función retorna los segundos que restan hasta que cualquier alarma agendada
anteriormente retorne su señal, de no haber alarma agendada anteriormente, su
retorno será 0.
➢ sleep(): Utilizada para suspender la ejecución de cualquier proceso en ejecución
por cierto intervalo de tiempo. Importante mencionar que, durante este tiempo,
a otro proceso se le da la oportunidad de ejecutarse.

Llamadas al sistema para la comunicación:

Este tipo de llamadas al sistema son específicamente utilizadas para comunicaciones


inter-proceso.
La comunicación interproceso en sistemas basados en UNIX se basan en dos modelos.
1. Paso de mensajes: Consiste en el intercambio de mensajes entre varios
procesos.
2. Memoria compartida: Los procesos comporten una región de memoria para
comunicarse.

4
Las llamadas al sistema en LINUX en el marco de este paradigma son: pipe(), shmget(),
mmap().
➢ pipe(): Utilizada para comunicar diferentes procesos de LINUX, por lo que es
ampliamente utilizada para la comunicación inter-procesos.
➢ shmget(): Utilizada para la comunicación basada en memoria compartida, la
llamada permite acceder la memoria compartida y acceder a los mensajes con
la finalidad de comunicar los procesos.
➢ mmap(): Utilizada para ubicar o eliminar ficheros o dispositivos en memoria, por
lo que se utiliza para comunicación basada en memoria compartida.

Pruebas de escritorio, código fuente y pruebas de pantalla


Para objetos prácticos y con la finalidad de no transgredir en el área de la materia
comprendida por prácticas subsecuentes; en esta práctica se limitó la experimentación
a las llamadas al sistema para el control de procesos, la administración de archivos y
administración de directorios.
Como se mencionó en el marco teórico de este documento, dado que las llamadas a
sistema suelen presentarse en forma de rutinas en C y C++, las actividades
experimentales se realizaron en el lenguaje C, basándose en las prestaciones de POSIX:
la API que permite realizar las peticiones al sistema, sin necesidad de administración
interna del sistema operativo.
Llamadas al sistema para la administración de procesos.
Las funciones fork(), getpid() y getppid() permitieron realizar una sencilla prueba que
demuestra el funcionamiento de la llamada a creaciones de procesos hijo.
Dado que getpid() nos devuelve el identificador del proceso actual y getppid() devuelve
el identificador del proceso padre del proceso actual, es sencillo identificar la relación
entre procesos padre y los procesos hijo creados mediante fork().
Al asumir que el proceso hijo inicializado con fork() comparte todas las características
menos PID y localización en memoria, es pertinente asumir que estos cambios deberían
ser mínimos en un sistema UNIX con fines de optimización, como se muestra:

5
Evidentemente, en este ejemplo escrito la PID de los procesos se asumió fue 100 y 101,
en pro de la comprensión de este, pero, como se verá a continuación con la prueba de
pantalla, este valor es más bien dependiente de la arquitectura del SO.
#include<unistd.h>
#include<stdio.h>
int main(){
int pid;
pid=fork();
pid==0?printf("[Proceso HIJO]\tPID del Padre de este proceso:
%5d\tPID de este proceso: %5d\n", getppid(), getpid())
:printf("[Proceso PADRE]a\tPID del Padre de este proceso:
%5d\tPID de este proceso: %5d\tPID del proceso hijo:
%5d\n",getppid(),getpid(),pid);
return 0;
}
Código 1

En el código 1, se inicializa una variable tipo entero para posteriormente utilizarla para
almacenar el retorno de la función fork() al ser invocada. La variable entonces almacena
el PID cuando se encuentra en el proceso padre y 0 cuando se encuentra en el proceso
hijo. Es por este motivo que el operador ternario considera estas dos posibilidades para
las salidas en consola, lo que permitió observar el comportamiento de los PID para cada
caso.

Evidenciado por la similitud de los PID entre padre e hijo, principalmente en la segunda
línea del output del programa, se confirma lo planteado en la prueba de escritorio.

6
Ahora bien, partiendo de esta prueba de fork() y getpid() fue posible poner a prueba la
llamada al sistema wait() mediante ajustes menores, los cuales, permitieron observar
al proceso padre esperando que finalizara la ejecución de los procesos hijos.
Dado el valor de retorno de la llamada wait(), se planteó inicializar una variable status
para realizar el display del estado del proceso hijo cuando se cumpliera que el PID fuera
el del proceso padre, y posterior a una llamada a la función sleep para hacer aún más
evidente el efecto de wait(), una vez completada la ejecución del proceso hijo, el retorno
de la función wait() debió ser 0.

#include<unistd.h>
#include<sys/wait.h>
#include<stdio.h>
int main(){
int pid, status;
pid=fork();
if(pid==0){
sleep(1);
printf("[Proceso HIJO]\tPID del Padre de este proceso: %5d\tPID
de este proceso: %5d\n", getppid(), getpid());
}
else{
printf("[Proceso PADRE]\tPID del Padre de este proceso: %5d\tPID
de este proceso: %5d\tPID del proceso hijo:
%5d\n",getppid(),getpid(),pid);
pid=wait(&status);
printf("[Proceso PADRE]\tfinalizo el proceso hijo: %5d\t su
estado es: %5d\n",pid,status);
}
return 0;
}
Código 2

7
En el output de este script se observa evidentemente que el valor del estado del proceso
hijo es 0, evidenciando de manera práctica que el proceso finalizó y que el proceso padre
lo esperó.
Por otro lado, se plantea la prueba para la llamada exit() con prácticamente el mismo
fundamento que la anterior. Por otro lado, permite observar también que el retorno de
la función wait() será diferente de 0 cuando la ejecución del proceso no finaliza (debido
a la función exit()).

#include<unistd.h>
#include<sys/wait.h>
#include<stdio.h>
int main(){
int pid, status;
pid=fork();
if(pid==0){
sleep(1);
printf("[Proceso HIJO]\tPID del Padre de este proceso: %5d\tPID
de este proceso: %5d\n", getppid(), getpid());
_exit(3);
}
else{
printf("[Proceso PADRE]\tPID del Padre de este proceso: %5d\tPID
de este proceso: %5d\tPID del proceso hijo:
%5d\n",getppid(),getpid(),pid);
pid=wait(&status);

8
printf("[Proceso PADRE]\tfinalizo el proceso hijo: %5d\t su
estado es: %5d\n",pid,status);
}
return 0;
}
Código 3

La función exit(3) causa la terminación del proceso hijo antes de que pueda finalizar
normalmente, por lo que esta incapacidad de terminar debería verse también reflejada
en la salida del último printf, en donde se hace el display de la variable status.

En la última línea del output de este script observamos que el estado indicado es 768,
dado que la función wait retornará un valor mayor a 128 siempre que no haya sido capaz
de finalizar el proceso que se esperaba.
Finalmente, en lo respectivo al comando execvp(), se plantea una prueba en la que un
proceso hijo sea creado y mediante esta llamada al sistema se reemplace su argumento
con los comandos ls con la opción -l.

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(){
int pid;

pid = fork();

9
if(pid == 0){
char *args[] = {"ls","-l", NULL};
execvp(args[0],args);
}
else{
wait(NULL);
printf("Proceso hijo terminado.\n");
}
}
Código 4

De realizarse correctamente, el output mostraría los contenidos del directorio en el que


se encuentra el scrpit, y finalmente el proceso padre finalizaría la ejecución denotando
que el proceso hijo con el argumento proporcionado por la llamada al sistema execvp()
ha finalizado.

En este ejemplo el comando “ls” es ejecutado en el proceso hijo mediante la función


excevp(). La salida delcomando “ls”, el cual enlista los contenidos del directorio actual,
es impreso en el output de la terminal. Una vez que el proceso hijo es completado (se
enlistan todos los archivos en la dirección), el proceso padre imprime un mensaje que
indica que el proceso hijo ha sido completado exitosamente.
Llamadas al sistema para la administración de archivos.
Para este tipo de llamadas al sistema se planteó un código que comprendiera todas las
funciones enlistadas en el marco teórico de este reporte, dada la correlación de estas
funciones y la facilidad con la que es posible utiliza una con la otra:
Variables:
• fd: un entero utilizado para representar el descriptor de archivo.
• buf: una cadena de caracteres utilizada para almacenar los datos leídos del
archivo.
Procedimiento:
1. Se llama a la función open() para crear un archivo llamado ‘ej.txt’ con permisos
de lectura y escritura.

10
2. Si open() devuelve ‘-1’ se imprime un mensaje de error y el main retorna ‘1’.
3. Se llama a la función write() para escribir la cadena “Hola mundo!\n” en el archivo
.txt.
4. Se llama a la función lseek() para mover el indicador de posición del archivo al
principio de este mismo.
5. Se llama a la función read() para leer 13 bytes de datos del archivo y
almacenarlos en el buffer (cadena buf).
6. Se imprime en consola el contenido del buffer.
7. Se llama a la función close() para cerrar el descriptor de archivo.
8. Se llama a la función unlink() para eliminar el archivo ‘ej.txt.’.
9. Si unlink() devuelve ‘-1’, se imprime un mensaje de error en consola y el main
retornará ‘1’.
10. El main retorna ‘0’.

Resultado esperado
El programa debería crear un archivo llamado ‘ej.txt’, escribir en él la cadena “Hola
mundo!\n”, leer los 13 bytes indicados en el archivo, imprimir en consola “Hola
mundo!\n”, cerrar el archivo, eliminar el archivo, y finalizar la ejecución con un valor de
retorno de 0.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd;
char buf[1024];

fd = open("ej.txt", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);


if (fd == -1) {
fprintf(stderr, "Error al abrir el archivo\n"
return 1;
}

write(fd, "Hola mundo!", 12);

lseek(fd, 4, SEEK_SET);

read(fd, buf, 8);


printf("Lectura del archivo: %s\n", buf);

11
close(fd);

if (unlink("ej.txt") == -1) {
fprintf(stderr, "Error al abrir el archivo\n"
return 1;
}

return 0;
}
Código 5

Se observa la salida en consola esperada, si se comenta la fracción del script encargada


de borrar el archivo de texto, podemos inspeccionar que este efectivamente es creado
y se escribe en él la cadena de texto esperada.

Llamadas al sistema para la administración de directorios.


Nuevamente, se plantea un script en c en el que se comprenden llamadas a las
funciones listadas en la descripción de la práctica en relación a la administración de
directorios.
Procedimiento:
1. Se crea un directorio llamado “ejemplo_dir” mediante la llamada al sistema
mkdir().
2. Se accede al directorio utilizando la llamada a sistema opendir().
3. Se leen las entradas al directorio utilizando la llamada al sistema readdir().

12
4. Se cierra el directorio utilizando la llamada al sistema closedir().
5. Se modifica el directorio operativo actual mediante la llamada al sistema chdir().
6. Mediante el uso de la llamada getcwd() se obtiene el directorio activo actual.
7. Finalmente, se remueve el directorio mediante la llamada a sistema rmdir().
Resultado esperado:
Al ser ejecutado, este código debería mostrar en consola las entradas al directorio
“ejemplo_dir” y el directorio de trabajo actual, para posteriormente remover dicho
directorio.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

int main() {
char *dir_name = "ejemplo_dir";
DIR *dir_ptr;
struct dirent *dir_entry;

if (mkdir(dir_name, 0755) == -1) {


perror("mkdir fallo");
exit(EXIT_FAILURE);
}

if ((dir_ptr = opendir(dir_name)) == NULL) {


perror("opendir fallo");
exit(EXIT_FAILURE);
}

while ((dir_entry = readdir(dir_ptr)) != NULL) {


printf("Entrada al directorio: %s\n", dir_entry->d_name);
}

if (closedir(dir_ptr) == -1) {
perror("closedir fallo");
exit(EXIT_FAILURE);
}

if (chdir(dir_name) == -1) {
perror("chdir fallo");
exit(EXIT_FAILURE);
}

char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) == NULL) {
perror("getcwd fallo");
exit(EXIT_FAILURE);

13
}
printf("Directorio de trabajo actual: %s\n", cwd);

if (rmdir(dir_name) == -1) {
perror("rmdir fallo");
exit(EXIT_FAILURE);
}

return 0;
}

Nuevamente la salida es la esperada, pues no se presentan errores y la salida en


consola es la esperada, fue posible observar las entradas a ambos directorios mediante
las funciones anteriormente mencionadas, y la obtención del directorio de trabajo actual
(donde se encuentra el script).

Conclusiones
La comprensión de las llamadas a sistema en sistemas operativos basados en UNIX es
esencial para la formación de un ingeniero en sistemas computacionales. Las pruebas
de escritorio y de pantalla de las principales funciones de administración de procesos,
archivos y directorios han demostrado la importancia de comprender cómo funcionan
estas llamadas y cómo se pueden utilizar para administrar eficazmente un sistema
operativo UNIX.
El potencial de adquirir este conocimiento y ponerlo en práctica es altísimo; al entender
las llamadas a sistema, un ingeniero en sistemas puede optimizar el rendimiento del
sistema operativo, mejorar la seguridad y resolver problemas de manera más rápida y
eficiente. Por otro lado, esta comprensión es fundamental para el desarrollo de software
que se ejecuta en sistemas operativos basados en UNIX, ya que las llamadas a sistema
son la interfaz entre el software y el sistema operativo, o en otras palabras, el puente
de comunicación entre el kernel y los procesos del sistema.
Además de las llamadas a sistema, es importante que un ingeniero en sistemas
computacionales comprenda los estándares y especificaciones relevantes para
sistemas operativos basados en UNIX.
La comprensión de POSIX es crucial para el desarrollo de software multiplataforma que
se ejecuta en diferentes sistemas operativos basados en UNIX, lo que permite a los
desarrolladores aprovechar las características de cada sistema operativo sin tener que
preocuparse por las diferencias entre ellos, esto fue posible observarlo en la facilidad

14
con la que se ejecutaron las llamadas a sistema utilizando el lenguaje C en un sistema
operativo Linux.

Referencias
[1]
“Wait Command in Linux,” Linuxhint.com, 2018. https://linuxhint.com/wait_command_linux/
(accessed Mar. 21, 2023).

[2]
“Llamadas al Sistema para Gestión de Procesos,” W3.ual.es, 2023.
https://w3.ual.es/~jjfdez/SOA/pract6.html (accessed Mar. 21, 2023).

[3]
https://www.facebook.com/grokkeepcoding, “¿Qué es POSIX? | KeepCoding Tech
School,” KeepCoding Tech School, Feb. 25, 2022. https://keepcoding.io/blog/que-es-
posix/#:~:text=POSIX%20es%20el%20acr%C3%B3nimo%20de,hacia%20m%C3%A1s%20de
%20un%20sistema. (accessed Mar. 21, 2023).

[4]
“Linux system call in Detail,” GeeksforGeeks, Jun. 20, 2021.
https://www.geeksforgeeks.org/linux-system-call-in-detail/ (accessed Mar. 21, 2023).

15

También podría gustarte