Tema: sysenter and syscall instructions

duilio - 23/7/2005 a las 22:29

Me gustaría reseñar una cosa interesante que encontré hace un tiempo.

Muchos sabrán que en Windows la entrada a modo kernel se hace usando la interrupción INT 2Eh, cargando previamente en eax el ID del servicio nativo que se quiere acceder.

O al menos se hacía de esa manera, ya que en la versión XP de Windows la secuencias de llamadas al sistema se arman usando trampolines que no utilizan la instrucción INT 2Eh si el procesador soporta la instrucción sysenter (Pentium IV) o syscall (Athlon).

Como bien se sabe, las interrupciones no son nada particular desde el punto de vista del procesador; la interrupción INT 2Eh o la INT 80h o cualquier otra, solo hacen que el procesador entre a modo kernel y salte a la dirección de memoria guardada en la tabla IDT (Interrupt Description Table) con el offset dado por el número de interrupción.

Que esto se haya utilizado para implementar llamadas al sistema, solo es un legado del 386, pero como se ve no se tiene en el procesador ningún soporte especial para llamadas al sistema (en concreto, paso de argumentos userspace->kernelspace). Todo esto hasta la llegada de las instrucciones sysenter y syscall. Con ellas el paso a modo kernel es más veloz (pues fueron creados para ello ex profeso), y el uso del viejo método mediante interrupciones en PIV y Athlon tiene una penalización en performance notable.

La pregunta es cómo se implementa esto. Si antes de terminar de leer esto ya programaron un experimento en assembler, verán que su programa aborta con una excepción de hardware "Illegal instruction". La forma en que se implementa el trampolín en Windows XP, es mapeando en el espacio del proceso, en la página 0x7FFE0300, un stub al cual hay que saltar cargando previamente en eax el ID del servicio requerido, y el stub se encarga de ejecutar sysenter. Hacemos algo como:

mov eax, 27h
mov edx, 7FFE0300h
call edx
retn 0Ch

y en 7FFE0300h nos espera el stub:

mov edx, esp
sysenter
retn

¿Y en Linux, qué onda?

Ésta característica está disponible en Linux a partir de su versión 2.5.53, gracias a éste parche

http://lwn.net/Articles/18414/

de Linus Torvalds. Aconsejable leer en particular la modificación al archivo entry.S para i386. Para quienes alguna vez hayan pensado que Torvalds no entiende de cosas de bajo nivel, por favor lean ésta lección magistral de assembler.

Para comprender las cuestiones de alto nivel sugiero leer:

http://kerneltrap.org/node/531/1998
http://glek.net/~taras/lwn.html (How to speed up system calls)

y para entender en particular el truco de Torvalds (problema de las syscalls con 6 o más argumentos):

http://lwn.net/Articles/18419/

En linux la página que se mapea (en sólo lectura) es la penúltima página del proceso, 0xffffe000 - 0xffffeffff (no se mapea la última para evitar futuros errores de wraparound).

En el segmento de texto, en 0xffffe400, se puede observar el código del trampolín:

push %ecx
push %edx
push %ebp
mov %esp,%ebp
sysenter

Ejemplito (tomado de http://www.win.tue.nl/~aeb/linux/lk/lk-4.html):

#include <stdio.h>

int pid;

int main() {
__asm__(
"movl $20, %eax \n"
"call 0xffffe400 \n"
"movl %eax, pid \n"
);
printf("pid is %d\n", pid);
return 0;
}

Como dije al principio, me pareció interesante, y esto porque muestra de una linda forma que en todas partes se cuecen habas.


Saludos,
Duilio.


[
Editado el 23/7/2005 por duilio]


ranto - 24/7/2005 a las 04:28

Muy bueno!
Me cope leyendo el thread en la lista del kernel. lkml.org. Me parecio muy interesante como fue el proceso de agregar ese feature.
Primero un tipo viene y muestra un benchmark y muestra que los P4 demoran mucho mas que los P3 para hacer las llamadas al sistema. (unas 4 veces mas)
De ahi explican cuales son los problemas que tiene el P4 y empiezan a discutir el tema de la nueva instruccion, sysenter.
Resulta que la instruccion sysenter pierde la direccion de retorno asi que el kernel solo puede volver a una direccion fija de memoria. de ahi proponen mapear el trampolin a una pagina fija.
hay muchisimos detalles, pero lo que me deslumbra es la metodologia que tienen los tipos para llegar a una buena solucion.
otro detalle, una de las razones por las que no se usa la ultima pagina de memoria es porque si por error un programa desreferencia -1, cae ahi. (no es tan popular como desreferenciar null, pero debe estar en el top 10).
Algunos deliraron con el tema de la pagina read-only compartida por todos los procesos y querian implementar algunas syscalls directamente en espacio de usuario, cosas asi. Esta bueno ver como se balancea la creatividad con la madurez en un proyecto como ese.

Muy buena resenia!

Tengo unas dudas de orientacion a objetos que me gustaria discutir con los que se prendan, en breve abro otro topic.
saludos


[Editado el 24/7/2005 por ranto]


duilio - 24/7/2005 a las 19:52

quote:


otro detalle, una de las razones por las que no se usa la ultima pagina de memoria es porque si por error un programa desreferencia -1, cae ahi. (no es tan popular como desreferenciar null, pero debe estar en el top 10).


Justamente, obtener un -1 es común en los casos límites, por wraparound. Otra forma de obtener un -1 es pasándote de rosca con un acarreo con signo (posible cuando la lo que controla el acarreo es una variable).

quote:
Algunos deliraron con el tema de la pagina read-only compartida por todos los procesos y querian implementar algunas syscalls directamente en espacio de usuario, cosas asi.


Bueno, por ejemplo Haiku hace algo de esto. Por ser un microkernel, parte de las llamadas al sistema las implementan servicios en espacio de usuario. El modo de entrada al kernel se implementa utilizando el método clásico de interrupción: se carga en eax el número de servicio, en ecx el número de argumentos, en edx un puntero al arreglo de argumentos, y se produce la interrupción INT 99.

Los detalles, muy bien explicados, pueden verse aquí:
http://haiku-os.org/learn.php?mode=nsl_view&id=22

Observar cómo no se salta directamente desde la IDT, sino que se pasa por un manejador global de excepciones, i386_handle_trap(), que decodifica y llama al manejador que corresponda (esto añade capas extras de procesamiento comparado con un kernel monolítico, pero en un microkernel lo que prima es la flexibilizdad).

Para el caso de la INT 99, si se pasa el saneamiento de argumentos, el manejador a llamar será syscall_dispatcher(). syscall_dispatcher() decodifica (otra capa de decodificación commparado con un kernel monolítico) el ID de servicio, y con ella llama al user_service() apropiado (en espacio de usuario).

La flexibilidad se pone de manifiesto cuando por ejemplo queremos agregar una llamada al sistema on the fly, que es lo que hacen los servicios al registrarse. (Ver register_generic_syscall() aquí: http://svn.berlios.de/viewcvs/haiku/haiku/trunk/src/system/ke rnel/syscalls.c?rev=12897&view=markup)

Actualmente Haiku no utiliza las instrucciones syscall o sysenter en las plataformas que las soportan, es decir que allí el SO tendrá una penalización en los tiempos de llamada al sistema.

Es interesante pensar las implicancias arquitecturales de utilizar dichas dos instrucciones dentro de un microkernel, como Haiku.


Saludos,
Duilio.


[
Editado el 24/7/2005 por duilio]


Este tema viene de: www.exactas.org
http://www.exactas.org/

Dirección de este sitio:
http://www.exactas.org//modules.php?op=modload&name=XForum&file=viewthread&fid=12&tid=400