Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Practica02 SistemasOperativos 4CM3
Practica02 SistemasOperativos 4CM3
Practica 2
Administración de procesos/hilos
Grupo: 4CM3.
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:
Las principales llamadas al sistema en LINUX bajo este paradigma son open(), read(),
write(), close(), iseek(), unlink().
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.
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.
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.
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
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];
lseek(fd, 4, SEEK_SET);
11
close(fd);
if (unlink("ej.txt") == -1) {
fprintf(stderr, "Error al abrir el archivo\n"
return 1;
}
return 0;
}
Código 5
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 (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;
}
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