Sistemas Operativos: Guía Completa de Procesos y Comunicaciones en Linux

Planificación Cooperativa

Ahora, cada tarea debe renunciar, por su propia voluntad, a su acceso a la CPU en algún momento de su funcionamiento. Esto suena como que estamos bloqueando el sistema operativo, pero si cada una de las tareas está escrita correctamente esto no será realmente así. La ventaja es que la tarea devuelve el control en un momento de su elección, por lo que puede controlar el almacenamiento de su propio contexto y no es necesario gastar memoria extra en el control central. La planificación cooperativa es poco probable que sea tan rápida en respuesta a los ajustados plazos como la planificación “pre-emptiva”. Sin embargo, necesita menos memoria y puede cambiar de tarea rápidamente. Esto es muy importante en los pequeños sistemas, como los basados en microcontroladores AVR.

¿Qué es el Kernel de un Sistema Operativo Linux?

El Kernel es un programa que se carga en memoria en el momento en el que el PC se inicia, y se encarga de administrar el sistema hasta que apagamos el computador; es decir, crea y controla procesos, gestiona la memoria, el sistema de archivos y mucho más.

¿Qué es el Directorio Personal en Linux?

El directorio personal es un directorio con un determinado nombre que se asigna a un usuario para que disponga sus archivos personales en él. Los directorios personales habitualmente son subdirectorios del directorio /home. Generalmente el nombre del directorio de cada usuario coincide con su nombre de usuario, aunque puede no ser así, y varios usuarios pueden estar trabajando en el mismo directorio. Cada usuario de Linux puede crear una estructura en árbol de subdirectorios y archivos tan compleja como desee bajo su directorio personal, pero normalmente nunca fuera de él. Así, si por ejemplo si somos el usuario Jorge, posiblemente nuestro directorio personal sea /home/Jorge. Dentro de este directorio podemos crear un árbol de directorios para organizar nuestros propios ficheros y documentos.

Comando para Cambiar de Directorio

cd es el comando para cambiar de directorio. Con el comando pwd sabemos en qué directorio nos encontramos.

Permisos de Archivos en Linux

En Linux cada fichero tiene permisos para 3 grupos de usuarios:

  • U: Usuario, hace referencia al dueño del archivo, cada archivo tiene un dueño en Linux.
  • G: Grupo, hace referencia al grupo al que pertenece el archivo, cada archivo pertenece a un grupo en Linux. De esta forma solo los usuarios pertenecientes a ese grupo tienen acceso al fichero.
  • A: Todos, hace referencia al resto de usuarios, son los permisos para los demás usuarios.

Los permisos que se pueden activar o desactivar son:

  • R: Lectura, permite la lectura del archivo o directorio.
  • W: Escritura, permite la escritura y modificación del archivo o directorio.
  • X: Ejecución, permite la ejecución del archivo y en caso de directorio, su acceso.

Por ejemplo: ‘chmod a+x listado.txt’, activaría para todos los usuarios el permiso de ejecución (x), de forma que cualquier usuario podría ejecutar el archivo.

Acceso a Dispositivos de Almacenamiento

En Linux hay un único sistema de ficheros, y si se conecta un CD o un USB en el sistema hay que montarlo sobre algún directorio del sistema. Existen algunos directorios predefinidos para esto, aunque en principio lo podemos montar donde queramos. Para montar un CD-ROM o DVD:

linux~# mount /dev/cdrom /mnt
linux~# mount /dev/dvd /montaje/dvd

Tuberías en el Manejo de Comandos

Consiste en “conectar” la salida estándar de un comando con la entrada estándar de otro. Para el ejemplo anterior esto se hace en la forma ‘ls | mail juan’. Con el operador de tubería (|) se pueden conectar tantos comandos como se desee.

Comando para Detener un Programa

Con el comando kill, que se utiliza para parar o matar un proceso.

Comando para Mostrar el Contenido de un Archivo

El comando empleado para ver el contenido de un archivo ASCII es ‘cat’. Para ver el contenido del archivo listado.txt, sería: ‘cat listado.txt’. Podemos verlo pantalla a pantalla con ‘cat listado.txt | less’.

Envío de Señales en Linux

Para mandar una señal a un proceso en ejecución se hace el comando kill. Los parámetros que toma el programa es el identificador de la señal a enviar y el ID de proceso del proceso destinatario. El sistema también Linux envía señales a los procesos en respuesta a las condiciones específicas. Por ejemplo, se puede enviar SIGBUS (error de bus), SIGSEGV (violación de segmentación) y SIGFPE (excepción de coma flotante) a un proceso que intenta realizar una operación ilegal. Un proceso también puede enviar una señal a otro proceso mediante la función kill() que funciona de forma similar al comando kill. Un uso común de este mecanismo es acabar con otro proceso mediante el envío de una señal SIGTERM o SIGKILL. Otro uso común es enviar un comando a un programa en ejecución. Están reservadas dos señales ‘definidas por el usuario’ para este propósito: SIGUSR1 y SIGUSR2. La señal SIGHUP a veces se utiliza para este fin, así, comúnmente para despertar un programa ocioso o hacer que un programa vuelva a leer sus archivos de configuración.

Redirección de E/S en el Entorno Shell

Los comandos de Linux tienen una entrada estándar (stdin, identificada con el número 0) y dos salidas estándar (stdout, identificada con el número 1 para la salida normal del comando, y stderr identificada con el número 2 para la salida de los mensajes de error que se puedan producir en su ejecución). Por defecto tanto la entrada como las salidas estándar de los comandos son la propia terminal, a no ser que por la propia naturaleza del comando se den en él los nombres de algunos ficheros que hagan el papel de entrada y de salida. Cuando utilizamos, por ejemplo, el comando ls, la salida de este comando se dirige hacia el terminal. Si queremos que la salida de este comando se dirija a un fichero llamado file, podemos hacerlo escribiendo ls > file. El operador (>) es uno de los llamados operadores de redirección y dirige la salida estándar hacia el fichero indicado a continuación; si este fichero no existe, se crea en ese momento. Otros operadores de redirección son el operador (<) que redirige la entrada estándar desde un determinado fichero, y el operador (>>) que redirige la salida estándar hacia otro fichero, pero en ‘modo append’ (añadiendo dicha salida al final del fichero, sin sobrescribir el contenido original). Por ejemplo, si cada vez que entramos en el sistema ejecutamos el comando ‘date >> archivo’, tendremos un fichero llamado archivo que va a contener la información sobre todas las veces que hemos entrado en el sistema.

¿Qué es un Identificador de Proceso?

Cada proceso en un sistema Linux se identifica por su identificador (ID) de proceso único, a veces conocido como PID. ID de proceso realmente son números de 16 bits que se asignan secuencialmente con Linux a medida que se crean nuevos procesos.

Procesos Activos en Linux

El comando ps muestra los procesos que se ejecutan en el sistema. La versión de ps de GNU/Linux tiene un montón de opciones, ya que trata de ser compatible con versiones de ps de otras variantes de UNIX. Estas opciones controlan qué procesos se muestran y la información que se muestra para cada uno. De forma predeterminada, la ejecución de ps muestra los procesos controlados por la terminal o la ventana de terminal desde la que se invoca ps.

Función system() en la Librería Estándar de C

La función system de la biblioteca estándar de C proporciona una manera fácil de ejecutar un comando desde un programa, de la misma manera que si el comando se ha escrito en un shell. De hecho, “system” crea un subproceso que ejecuta el Bourne shell estándar (/bin/sh) y pasa el control a la shell para su ejecución. La función “system” devuelve el valor de retorno del comando shell. Si la propia shell no se puede ejecutar, “system” devuelve 127, y si se produce otro error, “system” devuelve -1. Como la función “system” utiliza una shell para ejecutar el comando, está sujeto a las características, limitaciones y fallos de seguridad de la shell.

Función exec

Las funciones exec reemplazan el programa que se ejecuta en un proceso con otro programa. Cuando un programa llama a una función “exec”, este proceso cesa inmediatamente la ejecución de ese programa y comienza a ejecutar un nuevo programa desde el principio, suponiendo que la llamada a exec no se encuentre con un error. Como exec reemplaza el programa que inicia la llamada con otro, nunca retorna a menos que se produzca un error. Dentro de la familia de funciones “exec”, hay funciones que varían ligeramente en sus capacidades y cómo se ejecutan.

Por ejemplo: Las funciones que contienen la letra p en sus nombres (execvp y execlp) aceptan un nombre de programa y buscan un programa con ese nombre en la ruta de ejecución actual, las funciones que no contienen la p deben incluir la ruta completa del programa para ser ejecutado.

Mecanismo de Señales en Linux

Las señales son mecanismos para comunicar y manipular los procesos en Linux. Una señal es un mensaje especial enviado a un proceso. Las señales son asíncronas; cuando un proceso recibe una señal, procesa la señal de forma inmediata, sin terminar la función actual o incluso la línea actual de código.

Ejemplos de señales:

  • SIGBUS (error de bus)
  • SIGSEGV (violación de segmentación)
  • SIGFPE (excepción de coma flotante)

Un uso común de este mecanismo es acabar con otro proceso mediante el envío de una señal SIGTERM o SIGKILL. Otro uso común es enviar un comando a un programa en ejecución. Están reservadas dos señales “definidas por el usuario” para este propósito: SIGUSR1 y SIGUSR2. La señal SIGHUP a veces se utiliza para este fin, así, comúnmente para despertar un programa ocioso o hacer que un programa vuelva a leer sus archivos de configuración.

Señales para Terminar un Proceso

Un proceso también puede terminar de forma anormal, en respuesta a una señal. Por ejemplo, las señales mencionadas anteriormente SIGBUS, SIGSEV, y SIGFPE hacen que el proceso termine. Se emplean otras señales para terminar un proceso de forma explícita. La señal SIGINT se envía a un proceso cuando el usuario intenta acabar con ella tecleando Ctrl+C en la terminal. La señal SIGTERM es enviada con el comando kill. La disposición por defecto para ambos es dar por terminado el proceso. Al llamar a la función “abort”, un proceso se envía a sí mismo la señal SIGABRT, que termina el proceso y produce un fichero “core” para depuración. La señal de terminación más poderosa es SIGKILL, que pone fin a un proceso de inmediato y no puede ser bloqueada o manipulada por el programa.

Procesos Zombi

Un proceso zombi es un proceso que ha terminado, pero no se ha limpiado aún. Un proceso zombi se produce cuando un proceso hijo termina y el proceso padre no está llamando a wait. Podemos evitarlo haciendo que el proceso padre llamase a “wait3” o “wait4” de forma periódica, para limpiar a los hijos zombis.

Tipos de Comunicación de Procesos

En los sistemas LINUX podemos encontrar cinco tipos de comunicación entre procesos:

  • Memoria compartida: Permite a los procesos comunicarse con la simple lectura y escritura en una posición de memoria especificada.
  • Memoria asignada: Es similar a la memoria compartida, excepto que está asociado con un archivo en el sistema de ficheros.
  • Tuberías: Permiten la comunicación secuencial de un proceso con otro proceso relacionado.
  • FIFO: Son similares a las tuberías, excepto que permite a procesos no relacionados comunicarse debido a que al tubo se le da un nombre en el sistema de archivos.
  • Sockets: Permiten la comunicación entre procesos no relacionados, incluso en equipos diferentes.

Comunicación Padre-Hijo a través de Tubería

Una llamada a pipe crea los descriptores de archivos, que son válidos únicamente dentro de ese proceso y de sus hijos. Los descriptores de archivo de un proceso no se pueden pasar a procesos no relacionados, sin embargo, cuando el proceso llama a “fork”, los descriptores de archivos se copian en el nuevo proceso hijo. Una llamada a “fork” genera un proceso hijo. El hijo hereda los descriptores de archivo de la tubería. El padre escribe una cadena en la tubería, y el hijo lee por el otro extremo.

Funciones popen y pclose

La llamada a “popen” crea un proceso hijo que ejecuta el comando “sort”, sustituyendo a las llamadas “pipe”, “fork”, “dup2” y “execlp”. El segundo argumento, “w”, indica que este proceso quiere escribir en el proceso hijo. El valor de retorno de “popen” es un extremo del tubo, y el otro extremo está conectado a la entrada estándar del proceso hijo. Una vez finalizado el proceso de escritura, “pclose” cierra el flujo de datos del proceso hijo, espera a que el proceso termine, y devuelve su valor de estado.

Conexión Cliente-Servidor

Para crear una conexión entre dos sockets, el cliente llama a “connect”, especificando la dirección de un socket de servidor al que conectarse. Un cliente es el proceso que inicia la conexión, y un servidor es el proceso que espera para aceptar conexiones. El cliente llama a “connect” para iniciar una conexión desde un socket local al socket del servidor especificado por el segundo argumento. El tercer argumento es la longitud, en bytes, de la estructura de la dirección apuntada por el segundo argumento. Los formatos de dirección de socket difieren de acuerdo con el espacio de nombres de socket que se emplee.

Sockets Locales vs. de Internet

Los sockets locales de dominio UNIX sólo se pueden utilizar para comunicación entre dos procesos en un mismo equipo. Los sockets de dominio de internet, por otra parte, pueden ser utilizados para conectar los procesos de diferentes máquinas conectadas por una red de computadores. Los sockets que conectan procesos en el mismo equipo pueden utilizar el espacio de nombres local, representado por los sinónimos PF_LOCAL y PF_UNIX. Son los llamados sockets locales o sockets de dominio UNIX. Sus direcciones de sockets, especificadas mediante nombres de archivos, sólo se utilizan cuando se crean conexiones. Los sockets que conectan procesos a través de internet utilizan el espacio de nombres de internet representado por PF_INET. Los protocolos más comunes son los basados en TCP/IP. El protocolo de internet (IP), es un protocolo de bajo nivel, que mueve los paquetes a través de internet, se encarga de dividir y volver a unir los paquetes, si es necesario. Sólo garantiza la entrega con una política de ‘mejor esfuerzo’, por lo que los paquetes pueden desaparecer o ser reordenados durante el transporte. Cada equipo participante se especifica mediante una dirección IP única. El protocolo de control de transmisión (TCP), que reside en la parte superior de la capa IP, ofrece conexiones de transporte fiables y ordenadas. Permite establecer comunicaciones orientadas a la conexión entre ordenadores y asegura que la información se entrega de forma fiable y en orden.

Obtener una Dirección IP a partir de un Nombre de Host

Para convertir nombres de host legibles por los humanos, ya sea números en notación estándar de punto (por ejemplo, 10.0.0.1) o nombres DNS (como www.codesourcery.com) en direcciones IP de 32 bits, podemos emplear “gethostbyname”.

Función fork

Linux proporciona una función, fork, que crea un proceso secundario que es una copia exacta de su proceso padre. Cuando un programa llama a ‘fork’, se crea un proceso duplicado, llamado el proceso hijo. El proceso padre continúa ejecutando el programa desde el punto en que se llamó a ‘fork’. El proceso hijo también ejecuta el mismo programa en el mismo lugar. Entonces, ¿cómo se diferencian los dos procesos? En primer lugar, el proceso hijo es un nuevo proceso y por lo tanto tiene un nuevo ID de proceso, distinto del ID de proceso de su padre. Una forma para que un programa distinga si está en el proceso padre o en el proceso hijo es llamar a getpid. Sin embargo, la función ‘fork’ proporciona valores de retorno distintos para los padres y para el proceso hijo: un proceso ‘entra’ en la llamada a ‘fork’, y dos procesos salen con diferentes valores de retorno. El valor de retorno para el proceso padre es el ID del proceso hijo. El valor de retorno del proceso hijo es cero. Como no hay procesos que tengan ID cero, esto hace que sea fácil para el programa saber si se está ejecutando como proceso padre o hijo. El listado siguiente es un ejemplo del uso de fork para duplicar el proceso de un programa. Hay que tener en cuenta que el primer bloque de la instrucción if se ejecuta sólo en el proceso padre, mientras que la cláusula else se ejecuta en el proceso hijo.

Código

(Insertar código aquí)

Definición de Sockets

Un socket es un elemento de comunicación bidireccional que se puede utilizar para comunicarse con otro proceso en la misma máquina o con un proceso que se ejecuta en otras máquinas. Los sockets constituyen la única comunicación entre procesos que permite realizarla en diferentes ordenadores.