Comando kernel con llamadas al sistema Linux

Explore SCI y agregue sus propias llamadas

Las llamadas al sistema Linux:®— las usamos todos los días. Ahora bien, ¿sabe usted como se realiza una llamada al sistema desde el espacio del usuario hasta el kernel? Explore la interfaz de llamadas al sistema (SCI por su sigla en inglés) de Linux, aprenda cómo agregar nuevas llamadas al sistema (y las alternativas que existen para hacerlo) y descubra las utilidades relacionadas con la SCI. [Este artículo ha sido actualizado para reflejar los cambios de codificación para los kernels 2.6.18 y versiones posteriores.]

M. Tim Jones, Consultant Engineer, Emulex Corp.

M. Tim JonesM. Tim Jones es embedded software architect y autor de GNU/Linux Application Programming, AI Application Programming y BSD Sockets Programming from a Multilanguage Perspective. Sus antecedentes en el área de la ingeniería van desde el desarrollo de kernels para naves espaciales geosincronas hasta la arquitectura de sistemas incrustados y el desarrollo de protocolos de red. Tim es ingeniero consultor para Emulex Corp. en Longmont, Colorado.


Nivel de autor contribuyente en developerWorks

12-04-2010 (Primera publicación 12-04-2010)

Comuníquese con Tim

Tim es uno de los autores más populares y prolíficos. Navegue por todos los artículos de Timen developerWorks. Vea el perfil de Tim y comuníquese con él, con otros autores y con otros lectores en My developerWorks.

Una llamada al sistema es una interfaz entre una aplicación del espacio del usuario y un servicio que provee el kernel. Debido a que el servicio se provee en el kernel, no es posible realizar una llamada directa; en cambio, se debe cruzar el límite entre el espacio del usuario y el kernel. La manera de hacerlo difiere según la arquitectura específica. Por este motivo, me atendré a la arquitectura más común, i386.

En este artículo, exploraré la SCI de Linux, demostrará cómo agregar una llamada al sistema al 2.6.17 y a los kernel 2.6 anteriores, y utilizaré esta función desde el espacio del usuario. Además, investigaré algunas de las funciones que le resultarán útiles para el desarrollo de llamadas al sistema y las alternativas a las llamadas al sistema. Por último, analizaré algunos de los mecanismos secundarios relacionados con las llamadas al sistema, como por ejemplo el rastreo de su utilización desde un proceso dado. La interfaz de llamadas al sistema cambió en los kernels 2.6.18 y posteriores con el fin de simplificar la codificación. (Si usted está usando un kernel 2.6.18 o posterior, consulte la nota del recuadro "Using Linux kernels 2.6.18 and later (Cómo usar kernels 2.6.18 y posteriores de Linux).")

La SCI

La implementación de llamadas al sistema en Linux varía según la arquitectura, pero también puede variar dentro de una arquitectura dada. Por ejemplo, los procesadores x86 más antiguos usaban un mecanismo de interrupción para migrar desde un espacio de usuario hasta el espacio de kernel, pero los nuevos procesadores IA-32 ofrecen instrucciones que optimizan esta transición (con instrucciones sysenter y sysexit). Debido a que existen tantas opciones y a que el resultado final es tan complicado, me atendré a una discusión superficial sobre los detalles de la interfaz. Consulte la sección Recursos al final de este artículo para obtener los detalles más escabrosos.

No es necesario que usted comprenda acabadamente los detalles internos de la SCI para poder enmendarla, de manera que analizaré una versión simple del proceso de llamadas al sistema (ver Figura 1). Cada llamada al sistema se somete a un proceso de multiplexación al kernel a través de un único punto de ingreso. El registro eax se usa para identificar la llamada al sistema específica que se deberá invocar, la cual se especifica en la biblioteca C (en función de la llamada desde la aplicación del espacio de usuario). Cuando la biblioteca C ha cargado el índice de llamadas al sistema y cualquiera de los argumentos, se invoca una interrupción del software (interrupt 0x80), que provoca la ejecución (a través del controlador de interrupciones) de la función system_call. Esta función controla todas las llamadas al sistema, según lo identificado por los contenidos de eax. Después de realizar unas simples verificaciones, se invoca a la llamada real al sistema con system_call_table y el índice incluido en eax. Ante la devolución de la llamada al sistema, se alcanza eventualmente syscall_exit, y se llama a las transiciones de resume_userspace nuevamente en el espacio del usuario. La ejecución se reanuda en la biblioteca C, que luego retorna a la aplicación del usuario.

Figura 1. Flujo simplificado de una llamada al sistema con el método interrumpir
Simplified flow of a system call

En el núcleo de la SCI se encuentra la tabla de demultiplexación de llamadas al sistema. Esta tabla, que se muestra en la Figura 2, usa el índice provisto en el eax para identificar cuál es la llamada al sistema a invocar desde la tabla (sys_call_table). También se muestra un ejemplo de los contenidos de esta tabla y de las ubicaciones de estas entidades. (Para más información sobre la demultiplexación, ver la nota de recuadro, "Demultiplexación de llamadas al sistema.")

Figura 2. Tabla de llamadas al sistema y diversos enlaces
System call table and various linkages

Cómo agregar una llamada al sistema Linex

Demultiplexación de llamadas al sistema

Algunas llamadas al sistema son demultiplexadas nuevamente por el kernel. Por ejemplo, las llamadas del socket de Berkeley Software Distribution (BSD) (socket, bind, connect, etc.) se asocian con un único índice de llamadas al sistema (__NR_socketcall) pero son demultiplexadas en el kernel para la llamada adecuada a través de otro argumento. Vea la función ./linux/net/socket.c sys_socketcall.

El agregado de una nueva llamada al sistema es fundamentalmente un tema de procedimiento, si bien usted deberá prestar atención a algunas cuestiones. Esta sección analiza la construcción de unas pocas llamadas al sistema para demostrar su implementación y su uso por parte de una aplicación del espacio de usuario.

Usted deberá llevar a cabo tres pasos básicos para agregar una nueva llamada al sistema al kernel:

  1. Agregar la nueva función.
  2. Actualizar los archivos de encabezado.
  3. Actualizar la tabla de llamadas al sistema para la nueva función.

Nota: Este proceso ignora las necesidades del espacio de usuario, de las cuales me ocuparé más adelante.

Muy a menudo, usted crea un nuevo archivo para sus funciones. No obstante, por una cuestión de sencillez, yo agrego mis nuevas funciones a in archivo fuente existente. Las primeras dos funciones, que se muestran en el Listado 1, constituyen simples ejemplos de una llamada al sistema. El Listado 2 ofrece una función algo más complicada que usa argumentos indicadores.

Listado 1. Funciones simples de kernel para el ejemplo de llamada al sistema
asmlinkage long sys_getjiffies( void ) { return
(long)get_jiffies_64(); } asmlinkage long sys_diffjiffies 
( long ujiffies ) {
return (long)get_jiffies_64() - ujiffies; }

En el Listado 1, se brindan dos funciones para el monitoreo de jiffies. (Para más información acerca de los jiffies, ver nota de recuadro "Jiffies del kernel.") La primera función devuelve los jiffies actuales, mientras que la segunda devuelve la diferencia entre el valor actual y el valor que pasa el llamador. Observe el uso del modificador asmlinkage. Este macro (definido en linux/include/asm-i386/linkage.h) ordena al compilador que pase todos los argumentos de funciones de la pila.

Listado 2. Función final de kernel para el ejemplo de llamada al sistema
asmlinkage
long sys_pdiffjiffies ( long ujiffies, long __user *presult ) { long
cur_jiffies = (long)get_jiffies_64(); long result; int err = 0; if (presult) {
result = cur_jiffies - ujiffies; err = put_user ( result, presult ); } return
err ? -EFAULT : 0; }

Jiffies del kernel

El kernel de Linux mantiene una variable global denominada jiffies, que representa la cantidad de tics de cronómetro transcurridos desde el arranque de la máquina. Esta variable se inicia en cero y aumenta con cada interrupción del cronómetro. Se pueden leer los jiffies con la función get_jiffies_64, y luego se puede convertir este valor a milésimas de Segundo (msec) con jiffies_to_msecs o a microsegundos (usec) con jiffies_to_usecs. Las funciones globales y asociadas de los jiffies se ofrecen en ./linux/include/linux/jiffies.h.

El Listado 2 provee la tercera función. Esta función toma dos argumentos: un long y un indicador de un long que se define como __user. El macro __user simplemente ordena al compilador (a través de noderef) que el indicador debe seguir estando referenciado (debido q que no tiene sentido en el espacio de dirección actual). Esta función calcula la diferencia entre dos valores de jiffies, y luego proporciona el resultado al usuario a través de un indicador de espacio de usuario. La función put_user coloca el valor del resultado en el espacio de usuario en la ubicación especificada por presult. Si se produce un error durante la operación, el mismo será devuelto, y usted notificará, asimismo, al llamador del espacio de usuario.

Para el paso 2, yo actualizo los archivos de encabezado con el fin de hacer espacio para las nuevas funciones de la tabla de llamadas al sistema. Para ello, actualizo el archivo de encabezado linux/include/asm/unistd.h con los nuevos números de llamadas al sistema. Las actualizaciones se muestran en negrita en el Listado 3.

Listado 3. Actualizaciones a unistd.h para hacer lugar para las nuevas llamadas al sistema
#define __NR_getcpu 318 #define __NR_epoll_pwait 319
#define__NR_getjiffies 320#define __NR_diffjiffies 321#define__NR_pdiffjiffies 322#define NR_syscalls 323

Ahora tengo mis llamadas al sistema del kernel y los números que las representan. Todo lo que me queda hacer es obtener una equivalencia entre estos números (índices de la tabla) y las funciones mismas. Este es el paso 3, que consiste en actualizar la tabla de llamadas al sistema. Como se muestra en el Listado 4, actualizo el archivo linux/arch/i386/kernel/syscall_table.S para las nuevas funciones que poblarán los índices particulares que se muestran en el Listado 3.

Listado 4. Actualizar la tabla de llamadas al sistema con las nuevas Funciones
.long sys_getcpu .long sys_epoll_pwait
.long sys_getjiffies
/* 320 */
.long sys_diffjiffies.long sys_pdiffjiffies

Nota: El tamaño de esta tabla se define mediante la constante simbólica NR_syscalls.

En este momento, el kernel queda actualizado. Debo volver a compilar el kernel y hacer que la nueva imagen esté disponible para el arranque antes de verificar la aplicación del espacio de usuario.

Cómo leer y escribir en la memoria del usuario

El kernel de Linux ofrece diversas funciones que se pueden usar para mover argumentos de llamadas al sistema desde y hacia el espacio de usuario. Las opciones para ello incluyen funciones simples para tipos básicos (como por ejemplo get_user o put_user). Para mover bloques de datos tales como estructuras o arrays, se puede usar otro conjunto de funciones: copy_from_user y copy_to_user. El movimiento de cadenzas finalizadas en valores nulos posee sus propias llamadas: strncpy_from_user y strlen_from_user. Además, se puede verificar si un indicador de espacio de usuario es válido en toda la llamada con access_ok. Estas funciones se encuentran definidas en linux/include/asm/uaccess.h.

El macro access_ok se usa para validar un indicador de espacio de usuario para una operación dada. Esta función toma el tipo de acceso (VERIFY_READ o VERIFY_WRITE), el indicador al bloque de memoria del espacio de usuario, y el tamaño del bloque (en bytes). Cuando tiene éxito, la función devuelve cero:

int access_ok ( type, address, size );

El movimiento de tipos simples entre el kernel y el espacio de usuario (como por ejemplo ints o longs) se puede lograr fácilmente con get_user y put_user. Cada uno de estos macros toma un valor y un indicador a una variable. La función get_user mueve el valor que especifica la dirección del espacio de usuario (ptr) a la variable del kernel especificada (var). la función put_usermueve el valor que especifica la variable del kernel (var) a la dirección del espacio de usuario (ptr). Las funciones devuelven cero si se tiene éxito:

int get_user ( var, ptr ); int put_user(
                var, ptr );

Para mover objetos de mayor tamaño, como estructuras o arrays, se pueden usar las funciones copy_from_user y copy_to_user. Estas funciones mueven un bloque de datos complete entre el espacio de usuario y el kernel. La función copy_from_user mueve un bloque de datos desde el espacio de usuario al espacio de kernel, y copy_to_user mueve un bloque de datos desde el kernel hasta el espacio de usuario:

unsigned long copy_from_user ( void *to, const
void __user *from, unsigned long n ); unsigned long copy_to_user ( void *to,
const void __user *from, unsigned long n );

Por último, se puede copier una cadena finalizada en NULL desde el espacio de usuario hasta el kernel con la función strncpy_from_user. Antes de llamar a esta función, es posible obtener el tamaño de la cadena del espacio de usuario con una llamada al macrostrlen_user:

long strncpy_from_user ( char *dst, const char
                __user *src, long count ); strlen_user ( str );

Estas funciones brindan las funciones básicas de movimiento entre el kernel y el espacio de usuario. Existen algunas funciones adicionales (como por ejemplo aquellas que reducen la cantidad de verificaciones realizadas). Estas funciones se encuentran en uaccess.h.


Cómo usar la llamada al sistema

Cómo usar los kernels 2.6.18 y posteriores de Linux

Los macros _syscallN fueron eliminados en el kernel 2.6.18, de manera que en lugar de usarlos, se deberá usar la función syscall. Esta función brinda soporte a una cantidad arbitraria de argumentos (int syscall(int number, ...)). Consulte la sección Recursos par ver la página manual de esta llamada de función.

Ahora que el kernel se encuentra actualizado con algunas nuevas llamadas al sistema, veamos qué se necesita para usarlas desde una aplicación del espacio de usuario. Existen dos maneras de usar las nuevas llamadas al sistema del kernel. La primera es un método de conveniencia (que no es algo que a usted le convenga hacer en un código de producción), y la segunda es el método tradicional que requiere algo más de trabajo.

Con el primer método, usted llama a sus nuevas funciones según lo identificado por su índice en la función syscall. Con la función syscall, usted puede llamar a una llamada al sistema especificando su índice de llamadas y un conjunto de argumentos. Por ejemplo, la breve aplicación que se muestra en el Listado 5 llama a su sys_getjiffies usando su índice.

Listado 5. Cómo usar syscall para invocar a una llamada al sistema
#include <linux/unistd.h> #include <sys/syscall.h> 
#define__NR_getjiffies 320 int main() {
long jiffies; jiffies = syscall(__NR_getjiffies ); 
printf( "Current jiffies is %lx\n", jiffies ); 
return 0; }

Como puede ver, la función syscall incluye como primer argumento el índice de la tabla de llamadas al sistema que se debe usar. Si hubiera habido argumentos para pasar, los mismos se habrían proporcionado después del índice de llamadas. La mayoría de las llamadas al sistema incluyen una constante simbólica SYS_ para especificar su mapeo a los índices __NR_. Por ejemplo, usted invoca el índice __NR_getpid con syscall como:

syscall ( SYS_getpid )

La función syscall es específica de la arquitectura, pero usa un mecanismo para transferir el control al kernel. El argumento se basa en un mapeo de índices __NR a símbolos SYS_ provistos por /usr/include/bits/syscall.h (definidos cuando se construye libc). Nunca haga referencia directa a este archivo; use en cambio /usr/include/sys/syscall.h.

El método tradicional exige que usted cree llamadas a funciones que correspondan a las del kernel en términos del índice de llamadas al sistema (de manera que usted esté llamado al servicio de kernel correcto) y que los argumentos se correspondan. Linux ofrece un conjunto de macros para brindar esta capacidad. Los macros _syscallN se definen en /usr/include/linux/unistd.h y cuentan con el siguiente formato:

_syscall0 ( ret-type, func-name
)_syscall1( ret-type, func-name, arg1-type, arg1-name )
_syscall2( ret-type, func-name, arg1-type, 
                arg1-name, arg2-type, arg2-name )

Espacio de usuario y constantes __NR

Observe que en el Listado 6 he proporcionado las constantes simbólicas __NR. Las mismas se pueden encontrar en in /usr/include/asm/unistd.h (para llamadas al sistema estándar).

Los macros _syscall se definen con una profundidad de hasta seis argumentos (si bien aquí sólo se muestran tres).

Ahora bien, ésta es la manera en que se deben usar los macros _syscall para hacer que sus nuevas llamadas al sistema sean visibles en el espacio de usuario. El Listado 6 muestra una aplicación que usa cada una de las llamadas al sistema según lo definido por los macros _syscall.

Listado 6. Cómo usar el macro _syscall para el desarrollo de aplicaciones en el espacio de usuario
#include <stdio.h> #include 
<linux/unistd.h> #include <sys/syscall.h>
#define __NR_getjiffies 320 #define __NR_diffjiffies 321
#define __NR_pdiffjiffies 322 _syscall0( long, getjiffies);
_syscall1( long, diffjiffies, long, ujiffies ); _syscall2(
long, pdiffjiffies, long, ujiffies, long*, presult ); int main() { long
jifs, result; int err; jifs = getjiffies(); printf( "difference is
%lx\n", diffjiffies(jifs) ); err = pdiffjiffies( jifs,
&result ); if (!err) { printf( "difference is %lx\n", result ); } else {
printf( "error\n" ); } return 0; }

Observe que los índices __NR son necesarios en esta aplicación debido a que el macro _syscall usa el func-name para construir el índice __NR (getjiffies -> __NR_getjiffies). Pero el resultado es que usted puede llamar a las funciones del kernel por sus nombres, al igual que sucede con cualquier otra llamada al sistema.


Alternativas de interacción entre usuario y kernel

Las llamadas al sistema son una manera eficaz de solicitar servicios en el kernel. El problema más importante que tienen es que se trata de una interfaz estandarizada. Resultaría difícil hacer que su nueva llamada al sistema se agregara al kernel, por lo cual todos los agregados se realizan por otros medios. Si usted no tiene intención de introducir sus llamadas al sistema en la línea principal del kernel público de Linux, entonces las llamadas al sistema constituyen un modo conveniente y eficaz de disponer de los servicios del kernel en el espacio de usuario.

Otra manera de hacer que sus servicios sean visible en el espacio de usuario es a través del sistema de archivos /proc. El sistema de archivos /proc es un sistema de archivos virtuales sobre el cual se puede aplicar un directorio y archivos para usuarios, y luego brindar una interfaz en el kernel para sus nuevos servicios a través de una interfaz al sistema de archivos (de lectura, escritura, etc.).


Cómo rastrear las llamadas al sistema con strace

El kernel de Linux ofrece un modo útil de rastrear las llamadas al sistema que invoca un proceso (así como las señales que el proceso recibe). La utilidad se denomina strace y se ejecuta desde la línea de comandos, usando la aplicación que usted desea rastrear como argumento. Por ejemplo, si usted desea saber cuáles son las llamadas al sistema que se invocaron en el contexto del comando date, escriba el siguiente comando:

strace date

El resultado es un volcado de gran tamaño que muestra las diversas llamadas al sistema que se realizan en el contexto de una llamada al comando date. Usted verá la carga de las bibliotecas compartidas, el mapeo de memoria, y – al final del rastreo – la emisión de la información sobre fechas al standard-out:

... write(1, "Fri Feb 9 23:06:41 MST 2007\n", 29Fri Feb
9 23:06:41 MST 2007) = 29 munmap(0xb747a000, 4096) = 0 exit_group(0) = ? $

Este rastreo se logra en el kernel cuando la solicitud de llamada al sistema tiene un sistema especial de campos denominado syscall_trace, que provoca la invocación de esta función do_syscall_trace. Además, puede buscar las llamadas del rastreo como parte de la solicitud de llamadas al sistema en ./linux/arch/i386/kernel/entry.S (ver syscall_trace_entry).


Cómo ir más allá

Las llamadas al sistema constituyen un modo eficaz de pasar entre el espacio de usuario y el kernel para solicitar servicios en el espacio del kernel. Pero también se encuentran muy controladas, y resulta más sencillo simplemente agregar una nueva entrada al sistema de archivos /proc para proporcionar las interacciones entre usuario y kernel. Sin embargo, cuando la velocidad es importante, las llamadas al sistema constituyen una manera ideal de obtener el mayor rendimiento de su aplicación. Consulte la sección Recursos para profundizar aún más en la SCI.

Recursos

Aprender

Obtener los productos y tecnologías

  • Con el software de prueba de IBM, disponible para su descarga directa en developerWorks, construya su próximo proyecto de desarrollo en Linux.

Comentar

  • Involúcrese en la comunidad My developerWorks. Comuníquese con otros usuarios de developerWorks mientras explora los blogs, foros, grupos y wikis impulsados por desarrolladores.

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que se registra en developerWorks, se crea un perfil para usted. Información sobre su perfil (nombre, país/región y compañia) estará disponible al público y acompañará cualquiera de sus publicaciones. Puede actualizar su cuenta IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Linux
ArticleID=481930
ArticleTitle=Comando kernel con llamadas al sistema Linux
publish-date=04122010