Llamadas al Sistema

Todo procesador moderno posee al menos dos modos de funcionamiento distintos modo kernel (Protected Mode) y modo usuario (System Management Mode – SMM).

A grandes rasgos las diferencias entre estos dos modos son las siguientes:

• En modo kernel se encuentran disponibles todas las instrucciones y funcionalidades que la arquitectura del procesador es capaz de brindar, sin ningún tipo de restricciones. Es en este modo en el cual corre el kernel (núcleo) del sistema operativo. Por lo general es el kernel el único que corre en este modo.

• En modo usuario tan sólo un subconjunto de las instrucciones y funcionalidades que la arquitectura del procesador ofrece se encuentran disponibles. En este modo se ejecutan los procesos de los usuarios del sistema (todo proceso corriendo en el sistema pertenece a un usuario). Conocido también como userland.

Una de las responsabilidades de un S.O. es administrar los recursos del sistema y los procesos que corren en el mismo. Cada vez que un proceso necesite algún recurso del sistema (memoria, uso de un hardware específico, ejecutar otro proceso), será el S.O. el encargado de suministrárselo. Para esto deberá existir un medio de comunicación entre el S.O. y los procesos en ejecución.

Una de las maneras que existe para que los procesos y el sistema operativo puedan comunicarse son las llamadas al sistema.

A primer vista las llamadas al sistema lucen como simples funciones las cuales reciben sus parámetros de entrada y entregan un valor de salida, y de hecho lo son, solo que estas funciones son implementadas por el núcleo del S.O. Esto significa que será el S.O. quien recibirá los parámetros de entrada, ejecutará la función que le sea requerida, siempre y cuando sea posible y permitida, y devolverá los valores necesarios, así como también puede o no cambiar el estado de su estructura interna. De esto también podemos deducir que estas llamadas al sistema se ejecutarán en modo kernel. De hecho, el procesador está constantemente cambiando de modo usuario a modo kernel y viceversa (en cualquier S.O. multitarea, como lo es Linux, en el cual se requiere una mayor responsabilidad del S.O. para administrar los procesos, el procesador alterna entre ambos modos al menos unas cuantas miles de veces por segundo).

Ahora bien, ¿para qué son necesarias las llamadas al sistema?. Como dijimos antes, para que los procesos puedan comunicarse con el kernel del S.O. Pero seamos un poco más específicos, las llamadas al sistema nos brindan un medio para obtener recursos del S.O., obtener información del mismo, establecer o cambiar el seteo de los recursos que se ofrecen. Veamos algunos ejemplos de esto:

• En los sistemas UNIX (Linux es uno de ellos) todo los dispositivos son representados mediante archivos, y dado que es el S.O. el encargado de administrar el sistema de archivos, file system, (así como los dispositivos, por supuesto), es por medio de éste que podremos utilizar un dispositivo específico como puede ser una lectora de CD, una placa de video, el mouse o el teclado.

• Otro de los recursos administrados por el S.O. es la memoria. Cada vez que un proceso requiera de más cantidad de memoria, o desee liberar parte de la misma que tiene asignada, necesitará usar las llamadas al sistema para informar al núcleo del S.O.

• Hay cosas más simples que solo pueden ser accedidas mediante el S.O. como la hora actual, el nombre de un equipo, hasta el simple almacenamiento de datos en un archivo de texto.

Todas estas tareas no pueden realizarse sin la ayuda del S.O., entonces; ¿qué es lo que se puede hacer sin la ayuda del S.O.? La respuesta es nada, ya que para poder ejecutar un proceso, necesitamos del S.O., y sin un proceso corriendo no podemos hacer nada. Ahora, supongamos que tenemos a un proceso corriendo en el sistema, que es lo que este puede hacer sin emplear llamadas al sistema, la respuesta será virtualmente la misma. Este podrá ejecutar todas las instrucciones que la arquitectura del procesador permita que sean ejecutadas en modo usuario pero nada más. Si tenemos en cuenta que toda comunicación entre el procesador y el mundo exterior se hace mediante dispositivos periféricos (teclado, monitor, placa de red, etc.) y que es el S.O. el encargado de administrarlos, y por tanto, el único que puede con ellos comunicarse, rápidamente nos daremos cuenta hasta que punto somos dependientes del S.O. para poder llevar a cabo cualquier tipo de tarea.

Para resumir, si alguien nos preguntase que es todo lo que podemos hacer como usuarios de un S.O., podríamos responder que todo lo que uno puede hacer, se encuentra acotado por las instrucciones que la arquitectura del sistema en que trabajamos nos permite ejecutar en modo usuario, más las tareas que pueden ser realizadas por medio de llamadas al sistema. Eso es todo, chequeando estos dos parámetros podremos saber que es lo que un S.O. nos permite realizar.

El S.O. Linux posee más de 200 llamadas al sistema, las cuales se encuentran enumeradas en el archivo /usr/include/asm/unistd.h.

Cómo utilizar las llamadas al sistema:

Más del 90% del código de Linux se encuentra escrito en C y si bien una llamada al sistema puede invocarse utilizando cualquier lenguaje de programación, el lenguaje que nosotros utilizaremos será el C, dado que se trata del lenguaje nativo de dicho S.O. Podría parecer que analizar las mismas desde un lenguaje assembler es quizás una aproximación más pura, pero esto es algo falso, dado que Linux funciona sobre un gran número de arquitecturas diferentes, y para cada arquitectura existe una forma distinta de expresar en código máquina las llamadas al sistema, en C estos detalles resultan transparentes al ser un lenguaje de nivel superior menos relacionado con la arquitectura subyacente.

Llamadas al sistema en C:

Veamos ahora como sería la  llamada  implementada en C:

/* read.c – Llamada al sistema read en C */
#include <unistd.h>
int main(){
char buf[32];
int result;
result = read(0,buf,sizeof(buf));
return result;
}

Cada vez que el intérprete de comandos (shell) ejecuta un programa asocia al mismo tres descriptores de archivos conocidos como la entrada estándar (stdin), salida estándar (stdout) y salida de errores (stderr), cuyos valores son respectivamente 0, 1 y 2. Por tanto el valor 0 pasado como argumento a la función read indica que lea de la entrada estándar. Ésta puede ser tanto el teclado como un archivo de texto dependiendo de como uno ejecute el programa desde el shell.

Por la general para realizar una llamada al sistema desde C no se necesita más que incluir las directivas del preprocesador #include haciendo referencia a los archivos de cabecera (headers) mencionados en la correspondiente página del manual de la llamada y luego simplemente invocar la función que ejecuta la llamada. En este caso <unistd.h>.

La GNU C library, junto con las demás que provee, incluye funciones que llevan a cabo la tarea de pasar los parámetros al kernel y llamar a la interrupción adecuada, de manera transparente para el programador.

A continuación listaremos un programa llamado mi_date.c que imita al conocido comando UNIX date, y muestra la fecha y hora actual por pantalla:

/* mi_date.c – Muestra la fecha y la hora actual por pantalla */
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
void print_time () {
struct timeval tv;
struct tm* ptm;
char time_string[40];

/*Obtenemos la fecha y hora del día y la transformamos en un estructura tm*/
gettimeofday(&tv, NULL);
ptm = localtime(&tv.tv_sec);

/*Utilizando la estructura tm creamos un string con la informacion que deseamos*/
strftime(time_string, sizeof(time_string), “%d/%m/%Y %H:%M:%S” , ptm);
printf( “%s\n” ,time_string);
}

int main() {
print_time();
return 0;
}

Este programa utiliza la llamada al sistema gettimeofday para conseguir la fecha y hora actual, que son obtenidas por el kernel consultando el RTC (real time clock) de la máquina.

La función gettimeofday en nuestro ejemplo toma como primer parámetro un puntero a una estructura struct timeval, el segundo argumento es un puntero a una estructura timezone que aquí no es utilizada (NULL).

struct timeval {
__time_t tv_sec; /* Seconds */
__suseconds_t tv_usec; /* Microseconds */
};

La estructura timeval es instanciada por el kernel con los valores actuales de tiempo, la misma contiene la cantidad de segundos y microsegundos (timestamp) que han transcurrido desde el comienzo de la llamada UNIX Epoch, la medianoche del primero de enero de 1970. Como esta representación del tiempo no es muy cómoda para la gran mayoría de los humanos, utilizamos las funciones de la librería libc localtime y strftime, las cuales son utilizadas para ofrecer un formato más legible para la fecha y hora actual. Pueden consultarse las man pages de estas funciones con los comandos:

$ man 3 localtime
$ man 3 strftime

Veamos otro ejemplo de una llamada al sistema. Se trata de un programa que muestra por pantalla información acerca del sistema. La llamada al sistema que utiliza, sysinfo, establece los valores de una estructura de tipo struct sysinfo la cual se encuentra definida en /usr/include/linux/kernel.h.

/* stats.c – Muestra estadísticas del sistema respecto al uso de memoria y los procesos
en ejecución. */
#include <stdio.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <sys/sysinfo.h>

int main () {
const long minute = 60;
const long hour = minute * 60;
const long day = hour * 24;
const double megabyte = 1024 * 1024;
struct sysinfo si;

/* Obtenemos estadísticas del sistema */
sysinfo(&si);

/* Mostramos algunos valores interesantes contenidos en la estructura sysinfo. */
printf(“Tiempo que lleva el sist. en funcionamiento: %ld dias , %ld:%02ld:%02ld\n”,
si.uptime/day,
(si.uptime % day) / hour,
(si.uptime % hour) / minute,
si.uptime % minute);
printf(“Memoria RAM total: %5.1f Mb\n” , si.totalram / megabyte);
printf(“Memoria RAM libre: %5.1f Mb\n” , si.freeram / megabyte);
printf(“Cantidad de procesos corriendo: %d\n” , si.procs);
return 0;
}

Tipos de llamadas al sistema:

Si bien no existe ninguna definición formal de esto, las llamadas al sistema pueden ser agrupadas en ciertos grupos según las funcionalidades que las mismas ofrecen. Sin dar más detalles enumeraremos estos grupos.

NOTA:

Salvo para el caso de las llamadas asociadas al manejo de archivos, no existe en el diseño del núcleo nada que refleje explícitamente esta agrupación que haremos.

Acceso a archivos:

Estas llamadas tienen como objetivo el leer, escribir, abrir, cerrar, etc., archivos. Algunas de estas son:

open close
write read
lseek readdir
ioctl fsync
flock mmap

Como ya dijimos, un archivo, bajo Linux, puede estar representando muchas cosas distintas. Puede ser un simple archivo de datos, que almacena información, puede representar un dispositivo de entrada/salida, como el mouse o el teclado, o puede ser simplemente información almacenada en la memoria que utiliza el kernel para guardar información, como lo son los archivos contenidos en el directorio /proc.

Antes de poder usar cualquier archivo, un proceso necesita abrir el mismo, esto se lleva a cabo con la función open. La misma devuelve al proceso un descriptor de archivo (file descriptor) que en realidad no es más que un número que identifica unívocamente al archivo solicitado.

El resto de la funciones necesitarán este descriptor para saber sobre que archivo deben ejecutarse.

La función write escribe a un determinado archivo y la función read lee del mismo. La semántica de estas funciones depende del tipo de archivo con el cual se esté trabajando.

Por ejemplo, los archivos /dev/tty1, /dev/tty2, etc. representan consolas virtuales, como las que usamos para trabajar de ordinario con el bash por ejemplo. Escribir a /dev/tty1 significará mostrar por pantalla en esa consola los caracteres que escribamos. Escribir a un archivo normal del S.O. (normal por decir de alguna manera) significará guardar información en el disco rígido. Leer del archivo /dev/mouse (solo el root tiene permiso) devolverá, dependiendo del tipo de mouse con el cual trabajemos, una serie de bytes que representa las coordenadas de los movimientos del mouse. Leyendo el archivo /proc/interrupts obtendremos un listado de las interrupciones que está atendiendo el sistema, y qué driver se encarga de atenderla a cada una, además de otros datos.

Esta entrada fue publicada en Consultas. Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s