Llamadas al sistema linux (linux system calls)

A la hora de programar en C hay 3 tipos de funciones a las que podemos recurrir. Las funciones ordinarias internas a tu programa (desarrollo propio al programa), las funciones de librería que son funciones ordinarias que residen en una librería externa a tu programa (desarrollo propio o ajeno al programa). Como por ejemplo las funciones de la librería estandar C (libc). Una llamada a estas funciones es igual a cualquier llamada a una función. Los argumentos son situados en los registros del procesador o en la pila. La ejecución es transferida al comienzo del código de la función, que tipicamente está cargada en una librería compartida. Y, por último, las llamadas al sistema (system call).

Una llamada al sistema está implementada en el núcleo de Linux. Cuando un programa llama a una función del sistema, los argumentos son empaquetados y manejados por el núcleo, el cual toma el control de la ejecución hasta que la llamada se completa. Una llamada al sistema no es una llamada a una función ordinaria, y se requiere un procedimiento especial para transferir el control al núcleo. Sin embargo la librería GNU C encapsula la llamadas al sistema con funciones de manera que pueden ser llamadas de manera sencilla y se confunden con llamadas a funciones ordinarias. Las funciones de entrada/salida como open y read son ejemplos de llamadas al sistema en Linux.

El conjunto de llamadas al sistema Linux forman el interfaz más básico entre los programas y el núcleo de Linux.

Existen llamadas al sistema que sólo pueden ser ejecutados por el superusuario en caso contrario fallarán. Además una función de librería puede invocar una o más funciones de librería o llamadas del sistema como parte de su implementación.

Linux tiene más de 300 diferentes llamadas al sistema. Un listado de las llamadas al sistema de tu versión del kernel de linux se puede encontrar en /usr/include/asm/unistd.h actualmente este fichero incluye a otros dos uno para arquitecturas de 32 bits y otro para arquitecturas de 64 bits (unistd_32.h y unistd_64.h respectivamente)

Para poder introducirnos en las llamadas al sistema es necesario conocer strace. Strace (system trace) es un comando de la shell donde puedes depurar las llamadas al sistema de cualquier programa. strace te informa sobre las llamadas al sistema que realiza un programa además de las señales que recibe. Es por tanto un herramienta muy valiosa de depuración.

Para utilizar strace basta con escribir strace seguido del nombre del programa que quieres analizar, por ejemplo

$ strace hostname

strace basicamente ejecuta el comando especificado hasta que este finaliza. Strace intercepta y registra las llamadas al sistema que el programa ha ejecutado y las señales recibidas por el proceso. strace envia a stderror o a un fichero especificado con la opción -o el nombre de la llamada al sistema, sus argumentos y el valor de retorno de la llamada. Por ejemplo:

execve(“/bin/hostname”, [“hostname”], [/\* 43 vars \*/]) = 0

La llamada al sistema es execve. Los parámetros que se pasan al programa son los que se encuentran entre parámetros “/bin/hostname”, [“hostname”], [/\* 43 vars \*/] y el valor de retorno aparece despues del igual (0)

Para señales se imprime el simbolo de la señal y una cadena explicativa rodeado de ‘—‘ y ‘+++’. Por ejemplo:

— SIGFPE (Floating point exception) @ 0 (0) —
+++ killed by SIGFPE +++

En el caso de SIGKILL podemos ver:

<unfinished …>

Recordemos que esta señal no se puede enmascarar.

A continuación voy a analizar un par de llamadas al sistema bastante comunes que pueden servir como ejemplo. El primero es access que sirve para comprobar los permisos sobre un determinado fichero. Access puede comprobar cualquier combinación de permisos lectura, escritura y ejecución.

La llamada a access tiene 2 argumentos el primero es la ruta al fichero cuyos permisos queremos comprobar el segundo es un campo de bits tipo OR que puede contener R_OK, W_OK, X_OK que corresponden a verificar los permisos de lectura, escritura y ejecución. El valor de retorno es 0 si tiene todos los permisos especificados por el segundo parámetro. Si el fichero existe pero no tiene todos los permisos especificados devuelve -1 y errno cambia a EACCES (o a EROFS si se ha solicitado los permisos de escritura para un fichero de sólo lectura).

Si el segundo argumento es F_OK sólo se comprueba la existencia del fichero. Si el fichero existe devuelve 0 si no existe devuelve -1 y fija errno a NOENT. Si el directorio donde está el fichero es inaccesible errno se fija a EACCESS. Veamoslo con un ejemplo:

#include <error.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char** argv)
{
if(argc!=2)
{
printf(“Usage: %s file-path\n”,argv[0]);
return 1;
}
char* path = argv[1];
int sc;
sc = access(path,F_OK);
if(sc == 0) printf(“%s existe\n”, path);
else printf(“%s no existe o es inaccesible\n”, path);
return 0;
}

Otro ejemplo de llamada a sistema es fsync. Fsync permite sincronizar el contenido de un fichero con su copia en disco. Linux utiliza sincronizaciones asincronas entre la copia del fichero en memoria y la copia en disco. Para asegurarse de que ambas copias contienen la misma información. El sistema se encarga de hacer esto automaticamente pero es posible hacerlo manualmente a través de esta llamada. Este es un ejemplo.

#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
const char* journal_filename = “journal.log”;
void write_journal_entry (char* entry)
{
int fd = open (journal_filename, O_WRONLY | O_CREAT | O_APPEND, 0660);
write (fd, entry, strlen (entry));
write (fd, “\n”, 1);
fsync (fd);
close (fd);
}

int main(int argc, int argv)
{
write_journal_entry(“Esto va directo a disco”);
return 0;
}

Si compilas el fichero anterior y al ejecutable lo llamamos sync en el directorio actual podemos ejecutar

$ traceroute sync

Y así ver como traceroute nos muestra la llamada al sistema, sync().

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *