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]
|