Llamando al sistema (es)

Submitted by kernel-labs on Thu, 2006-04-13 09:06.
En este artículo vamos a estudiar cómo añadir una llamada al sistema, system call, a nuestro sistema operativo. Es una práctica bastante usada en las aproximaciones a la codificación del kernel Linux y bastante documentada, por ello, es obligado dar un repaso a todos estos conceptos. Entender el mecanismo de llamadas al sistema es una buena base para entender la filosofía de funcionamiento de un sistema Unix.

Syscalls

Los sistemas Unix, tradicionalmente proporcionan un interface para que las aplicaciones de espacio de usuario puedan comunicarse con el hardware. Esta interface son las system calls, conocidas en Linux simplemente como syscalls. Mediante estas llamadas, las aplicaciones podrán pedir al sistema operativo que realice tareas en su nombre. Por ejemplo, la función open() emitida por un proceso de usuario, estará indicando al kernel que abra algún fichero, el kernel que es el único con potestad para acceder al hardware (en este caso el disco), realizará la apertura del fichero que la aplicación le haya indicado en sus parámetros, es decir, el kernel se ejecutará en nombre del proceso de usuario, realizando la tarea que le ha sido pedida.

En realidad, las llamadas al sistema son casi el único punto de entrada que tienen los procesos de espacio de usuario al kernel. Es el principal mecanismo de comunicación de las aplicaciones con el kernel.

El puente API/syscall

Para que nuestra aplicación de espacio de usuario pueda emitir una syscall, es necesario poder realizar la llamada mediante un API que alguien nos proporcione. Este API lo proporciona la librería estándar de C de nuestro sistema, la libc o glibc en el caso de Linux. Una de las APIs más comunes del mundo Unix es la basada en el estándar POSIX. Este estándar aglutina un conjunto de estándares o recomendaciones del instituto IEEE, que intentan definir la base para la realización de sistemas operativos abiertos basados en Unix. Linux es considerado compatible POSIX, e intenta ajustarse al estándar SUSv3 (Single UNIX Specification Version 3 o UNIX 03) cuando es aplicable.

En la siguiente figura podemos ver la relación existente entre las aplicaciones, la librería de C y la llamada al kernel.



Hay que decir que el estándar POSIX se refiere a las APIs, no a las syscalls, es decir, define un comportamiento, pero no como lo debe hacer. Un sistema puede ser certificado como compatible POSIX, pues ofrece un apropiado conjunto de APIs a los programas, no importando como las correspondientes funciones han sido implementadas. Desde el punto de vista del programador de aplicaciones, la distinción entre una API y una syscall es irrelevante, lo único que importa es el nombre de la función, los tipos de los parámetros y el valor devuelto.

Por otro lado, también hay que destacar que el estándar POSIX recomienda que exista una correlación uno a uno entre la función de API y la llamada al sistema. Es decir, el API de una syscall debe ser igual al formato usado en la syscall del kernel. Pero no se garantiza que detrás de un API de una syscall se invoque la pertinente syscall, sino que puede que la funcionalidad de la misma se esté proporcionando desde la propia librería de C, sin invocar al kernel o que el resultado sea la combinación de varias syscalls del kernel. Lo ideal, en aras de la eficiencia y velocidad de ejecución es que la syscall exista realmente en el kernel.

System Call Handler

Cuando un proceso de espacio usuario invoca una system call, la CPU cambia a modo kernel y empieza la ejecución de la función del kernel invocada, en nombre del proceso. El método usado para invocar al kernel es la emisión de una interrupción software: se producirá una excepción y el procesador cambiará a modo kernel ejecutando un manejador de la excepción (exception handler) del kernel. Este manejador de la excepción en realidad es el System Call Handler, que es una sección de código en ensamblador, por tanto es dependiente de la arquitectura subyacente.

En la arquitectura 80x86 la emisión de esta interrupción software se puede realizar mediante dos métodos que comentaremos más adelante: instrucción int $0x80, o una técnica más moderna, instrucción sysenter.

Puesto que el kernel implementa muchas syscalls diferentes, el proceso de espacio de usuario debe indicar mediante el paso de un parámetro el syscall number, que es un número que identifica unívocamente a la syscall requerida. El syscall number es importante, cuando se asigna un número a una syscall en el kernel, este número no puede ser cambiado en un futuro, si no las aplicaciones compiladas ya existentes fallarán, llamarán a otra syscall no deseada. Por tanto cuando una syscall es eliminada del kernel, su syscall number no puede ser reciclado, sino que se le asigna una syscall especial sys_ni_syscall(), que devuelve un valor -ENOSYS, indicando que la syscall invocada no está implementada. Por otro lado hay que advertir, que raramente una syscall es eliminada del kernel.

Todas las syscalls devuelven un valor entero como valor de retorno. Las convenciones para este valor pueden variar, pero generalmente, en el kernel un valor positivo o 0 denota éxito en la ejecución de la syscall. Mientras que negativo denota un error ocurrido en la ejecución de la syscall. Cuando se produce un error, el valor devuelto es un código de error en negativo que debe ser devuelto al programa de aplicación en la variable global de espacio de usuario errno; debe encargase de ello la librería de C correspondiente.

El system call handler tiene una estructura similar a otros manejadores de excepciones, desarrolla las siguientes operaciones:
  • Guarda el contenido de la mayoría de los registros en la pila del kernel (kernel stack).
  • Da servicio a la system call invocando la correspondiente función en C llamada la system call service routine.
  • Cuando termina la rutina de servicio, los registros son recuperados de la kernel stack y la CPU vuelve a ejecutar el kernel, vuelve a modo kernel o el proceso de usuario, modo usuario.
El nombre de la rutina de servicio asociada con una determinada system call que emite el programa de usuario, nombre_syscall(), suele denominarse sys_nombre_syscall(). La asociación de cada system call number con su correspondiente rutina de servicio viene controlada por la system call dispatch table, la cual está almacenada en el array sys_call_table, la cual tiene una entrada por cada llamada al sistema, concretamente nr_syscalls entradas. Podemos encontrar tradicionalmente el sys_call_table en el fichero dependiente de arquitectura arch/i386/kernel/entry.S, pero a partir de la versión 2.6.12 del kernel, dicho array ha sido extraído del fichero entry.S e incorporado en un fichero independiente arch/i386/kernel/syscall_table.S (hay que observar que siempre estamos hablando de la arquitectura 80x86).

arch/i386/kernel/entry.S
        ...
        #define nr_syscalls ((syscall_table_size)/4)
        ...
        .section .rodata,"a"
        #include "syscall_table.S"

        syscall_table_size=(.-sys_call_table)

arch/i386/kernel/syscall_table.S
        ...
        ENTRY(sys_call_table)
                .long sys_restart_syscall
                .long sys_exit
                ...
                .long sys_unshare


Dos métodos para emitir una syscall

Existen dos métodos para la invocación de llamadas al sistema en el kernel Linux.
  1. Ejecutando la instrucción en lenguaje ensamblador int $0x80. Esta ha sido la única forma de cambiar de modo usuario a modo kernel en Linux en procesadores 80x86.
  2. Con la llegada de los procesadores Intel Pentium II, se introdujo una nueva instrucción especializada para la misma tarea sysenter. Esta instrucción es soportada por el kernel Linux a partir de la versión 2.6. No obstante se da soporte a ambos métodos, algo que podrá cambiar en el futuro.
De igual forma que existen dos métodos para entrar en modo kernel al invocar una syscall, existen dos métodos para salir de la syscall y retornar al modo usuario de la CPU.
  1. La tradicional instrucción ensamblador iret.
  2. La nueva instrucción a partir de los Pentium II de Intel sysexit.
Dejaremos el estudio pormenorizado de estas dos técnicas, para un segundo artículo más centrado en el código ensamblador y la manipulación del vector de interrupción 128, el cual es el system call handler.

Nuestra syscall práctica: runqueue()

Pasamos a la parte práctica del artículo. El método para añadir una nueva syscall al kernel Linux es bastante simple y mecánico. No tenemos que preocuparnos por la manipulación o comportamiento del system call handler. La dificultad radica en la implementación del trabajo útil de la syscall, ¿qué puede hacer nuestra syscall?. Puesto que este estudio es puramente didáctico no vamos a preocuparnos mucho por la utilidad de nuestra syscall, pero tampoco vamos a quedarnos con un “hola mundo, soy una syscall”. Proponemos acceder a alguna estructura constituyente de algún sistema importante del kernel y obtener algunos datos con fines estadísticos.

Nuestra syscall accederá a la estructura runqueue, como sabremos es la estructura de datos básica del planificador de procesos (scheduler). Esta estructura es definida en kernel/sched.c como se muestra en el cuadro siguiente. La runqueue tiene la lista de procesos en estado “runnable” (listos para ejecución) en un procesador dado, hay una runqueue por cada procesador y cada proceso del sistema estará sólo en una runqueue. Esta estructura mantiene información adicional por cada procesador, la cual será el objetivo de nuestra syscall.

#include <linux/cpu.h>

struct runqueue {
    spinlock_t      lock; /* spin lock para la protección de la runqueue */
    unsigned long  nr_running; /* número de runnable tasks en esta runqueue */
#ifdef CONFIG_SMP
    unsigned long  cpu_load[3];
#endif
    unsigned long  long nr_switches; /* contador de context switchs de este micro */
    unsigned long  nr_uninterruptible; /* número de tareas en estado uninterruptible */
    unsigned long      expired_timestamp;
    unsigned long long timestamp_last_tick;
    task_t            *curr, *idle;
    struct mm_struct  *prev_mm;
    prio_array_t      *active, *expired, arrays[2];
    int                best_expired_prio;
    atomic_t          nr_iowait;
#ifdef CONFIG_SMP
    struct sched_domain *sd;
    int active_balance;
    int push_cpu;
    task_t *migration_thread;
    struct list_head migration_queue;
#endif
#ifdef CONFIG_SCHEDSTATS
    struct sched_info rq_sched_info;
   
    unsigned long yld_exp_empty;
    unsigned long yld_act_empty;
    unsigned long yld_both_empty;
    unsigned long yld_cnt;
    unsigned long sched_switch;
    unsigned long sched_cnt;
    unsigned long sched_goidle;
    unsigned long ttwu_cnt;
    unsigned long ttwu_local;
#endif
};


Vamos a intentar acceder con nuestra llamada al sistema a tres sencillos datos almacenados en los campos de la estructura runqueue:
  • nr_running: contiene el número de tareas en estado de ejecución para este procesador, es decir, tareas en estado TASK_RUNNING.
  • expired_timestamp: timestamp del último cambio del active array y expired array. En scheduler del kernel 2.6 mantiene una array de procesos en estado TASK_RUNNING que cuando consumen su timeslice pasan a otro array idéntico de tareas expiradas. Cuando terminan todas las tareas su timeslice, se cambia el array de tareas expiradas y pasa a ser el array de tareas activas.
  • nr_uninterruptible: número de tareas en estado no interrumpible, TASK_UNINTERRUMPIBLE. Es decir, tareas esperando por un evento de entrada/salida no activables por la llegada de una señal

Por tanto al utilizar nuestra llamada al sistema, que denominaremos a partir de este momento runqueue(), obtendremos los datos antes comentados del espacio kernel al espacio de usuario de nuestra aplicación. Implementamos la syscall runqueue() en la próxima sección.

Pasos para la implementación de la nueva syscall

El registro en el kernel de nuestra syscall como si fuera una syscall oficial es trivial, los pasos a seguir son los siguientes:
  1. El primer paso a dar es añadir una entrada al final de la tabla de llamadas al sistema, syscall table. Esto debería de ser hecho en cada syscall table de cada arquitectura que soporte nuestra syscall, en la práctica real se intenta que todas las arquitecturas soporten todas las syscalls. En nuestro ejemplo, vamos a dar soporte para nuestra syscall sólo a la arquitectura 80x86, para simplificar. Añadimos por tanto al final del fichero kernel/syscall_table.S nuestra nueva syscall, nótese que usamos la nomenclatura oficial para denominar la syscall, esto es, el nombre de la syscall precedido con el prefijo sys_.

    arch/i386/kernel/syscall_table.S
        ...
        ENTRY(sys_call_table)
        .long sys_restart_syscall
        .long sys_exit
        ...
        .long sys_unshare
        .long sys_runqueue
  2. El segundo paso a dar, es identificar nuestra syscall por un número único, el syscall number. Para ello incrementaremos en uno el número de la última syscall disponible en el kernel sobre el que estamos trabajando. Estos números se encuentran definidos en los ficheros, por cada arquitectura, asm/unistd.h. Observesé que también tenemos que incrementar en uno la macro que mantiene el número total de syscall disponibles NR_syscalls.

    ...
    #define __NR_ppoll    309
    #define __NR_unshare  310
    #define __NR_runqueue 311

    #define NR_syscalls  312
    ...
  3. El último paso es incluir el código de nuestra syscall en algún punto del árbol de fuentes ya sea añadiendo un nuevo fichero al conjunto del kernel o incluyendo nuestra implementación en algún fichero ya existente. La primera opción obligará a modificar los makefiles del kernel para incluir el nuevo fichero fuente. Lo más habitual será añadir el código de nuestra syscall en algún punto del código ya existente. Esta segunda opción es la que decidimos, pero ¿dónde colocamos nuestro código?
Existe un fichero donde se almacena un buen conjunto de syscalls generales, kernel/sys.c, es un buen sitio si no sabemos donde colocar el código. Pero, observando el código del kernel vemos que la implementación de las syscall está repartida por todo el árbol, generalmente porque la syscall pertinente accede a recursos del código únicamente accesibles en ese punto. En nuestro ejemplo, el sentido común nos indica que sería un buen sitio para implementar la syscall runqueue() en el fichero kernel/sched.c, donde podemos acceder a la estructura que nos interesa con facilidad, en este mismo fichero se implementan otra syscalls relacionadas con el planificador de CPU, luego vamos por buen camino.

El siguiente cuadro muestra la implementación de nuestra syscall runqueue() que comentaremos a continuación.

/*
* sys_runqueue - Devuelve tres valores de la estructura runqueue.
*/
asmlinkage long sys_runqueue(unsigned long *ubuff, long len)
{
        struct runqueue *rq;
        unsigned long flags;
        unsigned long kbuff[3];

        /*
        * Si el buffer size del usuario
        * es distinto al nuestro devolvemos error.
        */
        if (len != sizeof(kbuff))
                return -EINVAL;

        /*
        * Delimitamos la region critica para
        * acceder al recurso compartido.
        */
        rq = task_rq_lock(current, &flags);

        kbuff[0] = rq->nr_running;
        kbuff[1] = rq->expired_timestamp;
        kbuff[2] = rq->nr_uninterruptible;

        task_rq_unlock(rq, &flags);

        if (copy_to_user(ubuff, &kbuff, len))
                return -EFAULT;

        return len;
}

Declaración de la función: las syscalls deben declararse usando una extensión del compilador gcc definida en la macro asmlinkage.

asmlinkage long sys_runqueue(unsigned long *ubuff, unsigned long len)
{
    ...
}

Esta macro se define en include/asm-i386/linkage.h como:

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

__attribute__regparm(0), básicamente se indica al compilador que la función busque sus parámetros en la pila, pues puede ocurrir que el compilador use registros para el paso de parámetros a funciones en algunas optimizaciones. Cuando se invoca a la syscall desde el espacio de usuario, los parámetros para dicha syscall se colocan en registros y se invoca la interrupción 0x80. El control pasa entonces a system_call en entry.S, cuya primera tarea es salvar los registros en la pila del kernel con la macro SAVE_ALL, más tarde invoca la función del kernel que implementa la syscall, la función sys_, a esta función con asmlinkage se le indica que busque los parámetros en la stack kernel, donde los encontrará.

Control de parámetros: es importante que nuestra syscall compruebe adecuadamente los parámetros llegados antes de satisfacer la petición del proceso de usuario. Esta comprobación puede ser de diversa índole, desde asegurarse de que el proceso tiene permisos para realizar lo que quiere (generalmente usando las capabilities), hasta comprobar si el dato recibido es correcto (si es un PID válido, un descriptor de fichero correcto, ...). Este control por tanto puede variar. Por otro lado, un control que se hace estrictamente obligatorio es controlar los punteros (direcciones de memoria) que el usuario proporciona. Si el usuario proporciona un puntero a un buffer de espacio de usuario para copiar datos en él, debemos asegurarnos de que esa dirección es válida, puesto que el kernel puede acceder a cualquier dirección podríamos estar escribiendo o leyendo datos sensibles del sistema, haciéndolos accesibles al proceso de usuario.

Los controles que se deben tener en cuenta son:
  1. Verificar que la dirección proporcionada por el proceso de usuario pertenece al espacio de direcciones del proceso de usuario, no pertenece al espacio de direcciones del kernel o el espacio de direcciones de otro proceso.
  2. Comprobar que la región de memoria (VMA) a la que apunta tiene los apropiados derechos de acceso, es decir, si se quiere leer en ella esté marcada para lectura, etc.
Puesto que el trasiego de datos entre el espacio kernel y el espacio usuario es común en drivers y llamadas al sistema, el kernel proporciona dos APIs para enviar datos en ambos sentidos: copy_to_user() y copy_from_user(). Estas macros realizan los controles pertinentes de la dirección de espacio usuario proporcionada.

En nuestra syscall, sys_runqueue(), realizamos dos controles. Uno para determinar que el tamaño del buffer proporcionado por el proceso de usuario es igual al tamaño de nuestro buffer y el otro control lo realiza la macro copy_to_user() como hemos explicado.

copy_to_user(ubuff, &kbuff, len)

Bloqueo del recurso: es importante destacar en el código que hemos bloqueado la estructura runqueue antes de acceder a ella, para ello hemos usado funciones que nos proporciona el propio código del planificador. Es común y necesario conseguir acceso exclusivo a este tipo de recursos, pues son compartidos por muchas partes del sistema y tenemos que mantenerlos consistentes. En especial, puesto que las syscall son interrumpibles, tenemos que tener mucho cuidado a la hora de acceder a este tipo de recursos.

El último paso

Una vez implementada nuestra flamante syscall sólo nos queda hacer uso de ella. Como ya hemos comentado el acceso a las llamadas al sistema desde un programa de usuario se realiza mediante el puente que nos proporciona la librería estándar de C. Existirá una función de librería por cada correspondiente syscall del kernel, pero obviamente no existirá una función de librería que de soporte a nuestra nueva syscall. Esta situación está prevista proporcionándose un conjunto de macros que nos permiten acceder a cualquier syscall, son los macros conocidos como _syscalln(), donde “n” es un entero entre 0 y 6. El número corresponde con el número de parámetros pasados a la syscall. Más específicamente la macro espera el tipo devuelto por la syscall, el nombre de la syscall y el tipo y nombres de las variables parámetros de la syscall.

De esta forma, la syscall open() podría invocarse con estas dos sentencias:

#define __NR_open 5
_syscall3(long, open, const char *, filename, int, flags, int, mode);


Nuestra syscall runqueue() debería ser invocada con:

#define __NR_runqueue 311
_syscall2(long, runqueue, unsigned long *, dst, long, len);


El código completo del proceso de espacio usuario podría ser:

/*
* test_sys_runqueue - runqueue() syscall test.
*
* 2006 kernel-labs.org Team.
*/
#include <stdio.h>
#include <errno.h>
#include <sys/syscall.h>
#include <unistd.h>

#define __NR_runqueue 311

_syscall2(long, runqueue, unsigned long *, dst, long, len);

int main(int argc, char **argv)
{
        long ret;
        unsigned long buf[3];

        printf("invocando syscall ..\n");

        if ((ret = runqueue(buf, sizeof(buf))) < 0){
                perror("ERROR");
                return -1;
        }

        printf("runnable tasks      : %lu\n", buf[0]);
        printf("time last array swap: %lu\n", buf[1]);
        printf("uninterruptibles    : %lu\n", buf[2]);

        return 0;
}


Por último presentamos el parche que implementa la syscall aplicado contra un árbol 2.6.16, al estilo habitual en la LKML:

arch/i386/kernel/syscall_table.S |    1
include/asm-i386/unistd.h        |    3 +-
kernel/sched.c                  |  34 +++++++++++++++++++++++++++++
3 files changed, 37 insertions(+), 1 deletion(-)
========================================================================
diff -uprN -X linux-2.6.16-vanilla/Documentation/dontdiff linux-2.6.16-vanilla/arch/i386/kernel/syscall_table.S linux-2.6.16/arch/i386/kernel/syscall_table.S
--- linux-2.6.16-vanilla/arch/i386/kernel/syscall_table.S      2006-03-20 06:53:29.000000000 +0100
+++ linux-2.6.16/arch/i386/kernel/syscall_table.S      2006-04-22 12:31:53.000000000 +0200
@@ -310,3 +310,4 @@ ENTRY(sys_call_table)
        .long sys_pselect6
        .long sys_ppoll
        .long sys_unshare              /* 310 */
+      .long sys_runqueue
diff -uprN -X linux-2.6.16-vanilla/Documentation/dontdiff linux-2.6.16-vanilla/include/asm-i386/unistd.h linux-2.6.16/include/asm-i386/unistd.h
--- linux-2.6.16-vanilla/include/asm-i386/unistd.h      2006-03-20 06:53:29.000000000 +0100
+++ linux-2.6.16/include/asm-i386/unistd.h      2006-04-22 12:33:50.000000000 +0200
@@ -316,8 +316,9 @@
#define __NR_pselect6          308
#define __NR_ppoll            309
#define __NR_unshare          310
+#define __NR_runqueue          311

-#define NR_syscalls 311
+#define NR_syscalls 312

/*
  * user-visible error numbers are in the range -1 - -128: see
diff -uprN -X linux-2.6.16-vanilla/Documentation/dontdiff linux-2.6.16-vanilla/kernel/sched.c linux-2.6.16/kernel/sched.c
--- linux-2.6.16-vanilla/kernel/sched.c 2006-03-20 06:53:29.000000000 +0100
+++ linux-2.6.16/kernel/sched.c 2006-04-25 20:04:42.000000000 +0200
@@ -6160,3 +6160,37 @@ void set_curr_task(int cpu, task_t *p)
}

#endif
+
+/*
+ * sys_runqueue - Devuelve tres valores de la estructura runqueue.
+ */
+asmlinkage long sys_runqueue(unsigned long *ubuff, long len)
+{
+      struct runqueue *rq;
+      unsigned long flags;
+      unsigned long kbuff[3];
+
+      /*
+        * Si el buffer size del usuario
+        * es distinto al nuestro devolvemos error.
+        */
+      if (len != sizeof(kbuff))
+              return -EINVAL;
+
+      /*
+        * Delimitamos la region critica para
+        * acceder al recurso compartido.
+        */
+      rq = task_rq_lock(current, &flags);
+
+      kbuff[0] = rq->nr_running;
+      kbuff[1] = rq->expired_timestamp;
+      kbuff[2] = rq->nr_uninterruptible;
+
+      task_rq_unlock(rq, &flags);
+
+      if (copy_to_user(ubuff, &kbuff, len))
+              return -EFAULT;
+
+      return len;
+}

Conclusiones

Hemos estudiado someramente la manera de incluir una syscall a nuestro núcleo Linux. El tema tiene mucho contenido y puede ser profundizado hasta muy bajo nivel, por ello la intención es continuar el artículo enfocándolo en el código a bajo nivel (ensamblador) y lograr así realizar pequeños trucos como la intercepción de syscalls o incluso aportar alguna modificación al mecanismo de syscalls.

Referencias

[1] “Linux Kernel Development, Second Edition”. Robert Love. 2005 Novell Press.
[2] “Adding A System Call” - http://fossil.wpi.edu/docs/howto_add_systemcall.html
[3] “Implementing Linux System Calls” - http://www.linuxjournal.com/article/3326
[4] “System Calls” - http://www.linuxjournal.com/article/1145
[5] “Linux System Calls” - http://www.linuxjournal.com/article/4048


Submitted by kernel-labs on Thu, 2006-09-21 11:15.
Nos preguntaban por correo qué es una interrupción software. Como es una pregunta en relación a este artículo, la respondemos aquí para hacerla disponible para todos los lectores interesados:

Cuando un elemento del sistema computador ya sea software o hardware requiere la asistencia del sistema operativo para que realize alguna tarea emitirá una petición al mismo, una service request. Esta petición la realizará mediante una "interrupción". Las interrupciones pueden ser:

- hardware: Es cuando un elemento periférico, es decir, un hardware pone un bit en un canal particular del hardware que provoca que la CPU en la cual el sistema operativo corre, señale que el dispositivo necesita atención. El resultado de este disparador es que el procesador termina la instrucción en ejecución, salva su actual estado y salta a una dirección de memoria que fue conectada con la interrupción de ese hardware en tiempo de inicialización del sistema operativo.

- software: muchos procesadores tienen un conjunto de instrucciones que emiten interrupciones las cuales provocan que el procesador se comporte como si la interrupción viniera de un elemento hardware. En Intel instrucciones que emiten estas interrupciones son INT y SYSTENTER.

Submitted by alumno on Tue, 2006-06-13 21:50.

Excelente artículo, muy actualizado y detallado; pero tengo un problema, espero alguien pueda ayudarme:

- Hice las modificaciones en los 3 ficheros, luego compile el kernel con make clean bzImage modules modules_install install. Todo bien.
- Cuando inicié con el nuevo kernel y traté de compilar el archivo usuario con "gcc -c test_sys_runqueue.c", pero me sale el error: "syntax error before 'runqueue'. La definición de datos no tiene tipo o clase de almacenamiento".

Gracias por sus comentarios.
David

Submitted by alumno on Wed, 2006-06-14 13:18.

Esta compilando sin el parámetro -I:
gcc -I /usr/src/linux/include/ -c test_sys_runqueue.c

Saludos
David

Submitted by odioregistrarme on Fri, 2007-06-22 17:50.

Hola, he incorporado la llamada al sistema a mi fichero sched.c y al compilar el nucleo me da un error.

Concretamente me dice esto:

kernel/sched.c:6842: aviso: asignación de tipo de puntero incompatible
kernel/sched.c:6844: error: puntero deferenciado a tipo de dato incompleto
kernel/sched.c:6845: error: puntero deferenciado a tipo de dato incompleto
kernel/sched.c:6846: error: puntero deferenciado a tipo de dato incompleto
kernel/sched.c:6848: aviso: al pasar el argumento 1 de `task_rq_unlock' de tipo de puntero incompatible
.
Las lineas que provocan el error son:
.

rq = task_rq_lock(current, &flags);

kbuff[0] = rq->nr_running;
kbuff[1] = rq->expired_timestamp;
kbuff[2] = rq->nr_uninterruptible;

task_rq_unlock(rq, &flags);
.
Lo cierto es que no entiendo por que pasa. Mi version de nucleo es 2.6.21.3 ¿Alguna solucion?

Gracias de antemano y felicidades por la pagina.

PD: Por supuesto, el codigo esta tal cual lo teneis en la web, no he cambiado nada.