1.3 Interfaces con el sistema operativo [SILB94]
1.3.1 Las llamadas al sistemaYa se ha comentado que el sistema
operativo es una interfaz que oculta las peculiaridades del
hardware. Para ello ofrece una serie de servicios que constituyen
una mquina virtual ms fcil de usar que el hardware bsico. Estos
servicios se solicitan mediante llamadas al sistema.La forma en que
se realiza una llamada al sistema consiste en colocar una serie de
parmetros en un lugar especfico (como los registros del
procesador), para despus ejecutar una instruccin del lenguaje
mquina del procesador denominada trap (en castellano, trampa). La
ejecucin de esta instruccin mquina hace que el hardware guarde el
contador de programa y la palabra de estado del procesador (PSW,
Processor Status Word) en un lugar seguro de la memoria, cargndose
un nuevo contador de programa y una nueva PSW. Este nuevo contador
de programa contiene una direccin de memoria donde reside una parte
(un programa) del sistema operativo, el cual se encarga de llevar a
cabo el servicio solicitado. Cuando el sistema operativo finaliza
el servicio, coloca un cdigo de estado en un registro para indicar
si hubo xito o fracaso, y ejecuta una instruccin return from trap,
esta instruccin provoca que el hardware restituya el contador de
programa y la PSW del programa que realiz la llamada al sistema,
prosiguindose as su ejecucin.Normalmente los lenguajes de alto
nivel tienen una (o varias) rutinas de biblioteca por cada llamada
al sistema. Dentro de estos procedimientos se asla el cdigo
(normalmente en ensamblador) correspondiente a la carga de
registros con parmetros, a la instruccin trap, y a obtener el cdigo
de estado a partir de un registro. La finalidad de estos
procedimientos de biblioteca es ocultar los detalles de la llamada
al sistema, ofreciendo una interfaz de llamada al procedimiento.
Como una llamada al sistema depende del hardware (por ejemplo, del
tipo de registros del procesador), la utilizacin de rutinas de
biblioteca hace el cdigo portable.El nmero y tipo de llamadas al
sistema vara de un sistema operativo a otro. Existen, por lo
general, llamadas al sistema para ejecutar ficheros que contienen
programas, pedir ms memoria dinmica para un programa, realizar
labores de E/S (como la lectura de un carcter de un terminal),
crear un directorio, etc. Ejemplos de rutinas de biblioteca que
realizan llamadas al sistema en un entorno del sistema operativo
C-UNIX son: read, write, malloc, exec, etc. .1.3.2 El intrprete de
rdenesCuando un usuario se conecta a un ordenador se inicia un
intrprete de rdenes (en entornos UNIX llamados shells). El
intrprete de rdenes es un programa que muestra un indicador
(prompt) formado por algunos caracteres, que pueden incluir el
directorio de trabajo (un posible indicador en MS DOS es C:\>),
que indica al usuario que es posible introducir una orden. El
usuario escribir una orden , por ejemplo C:\> copy fich fich2 y
pulsar la tecla return. En un entorno UNIX, o MS DOS, la primera
palabra es el nombre de un fichero que contiene un programa, siendo
el resto de la lnea una serie de argumentos, separados por
espacios, que toma dicho programa. Una excepcin a esto son las
rdenes internas, que el intrprete implementa como rutinas suyas, y
que no tienen, por tanto, un programa asociado guardado en disco.
El intrprete de rdenes realiza entonces una o varias llamadas al
sistema para ejecutar dicho programa. Cuando el programa finalice
(al realizar una llamada al sistema exit) el control vuelve al
programa que lo lanz (el intrprete de rdenes), mostrando ste otra
vez el indicador, y repitindose el ciclo.As pues, el intrprete de
rdenes es un programa que sirve de interfaz entre el sistema
operativo y un usuario, utilizndolo este ltimo para ejecutar
programas. A diferencia de un programador, un "usuario final"
realiza todas las llamadas al sistema indirectamente, a travs de
las llamadas al sistema de los programas que ejecuta. En muchos
sistemas se ha optado por sustituir el intrprete de rdenes por un
programa que utiliza ventanas. En estos programas aparecen iconos
que el usuario puede seleccionar mediante un ratn. Cuando el
usuario selecciona un icono que representa un programa se realizan
llamadas al sistema para ejecutar un fichero, asociado al icono,
que contiene el programa. Por lo tanto, se sustituye la interfaz
del usuario para ejecutar programas, pero la interfaz con el
sistema operativo no cambia.Es el momento de hacer una aclaracin.
Existen una serie de programas muy importantes (como traductores,
editores de texto, ensambladores, enlazadores e intrpretes de
rdenes) que ayudan al programador a realizar sus programas, y que
vienen en el lote con cualquier sistema operativo. Estos programas,
que forman parte de los programas de sistema o software de
sistemas, utilizan llamadas al sistema, pero no son parte del
sistema operativo. El sistema operativo es el cdigo que acepta
llamadas al sistema, y realiza un procesamiento para satisfacer
dichas llamadas. El sistema operativo es el programa de sistema ms
importante.
Tema 5. Llamadas al SistemaJuan Carlos Prez y Sergio Sez Sergio
SezJuan Carlos [email protected]@disca.upv.es
5.1 IntroduccinVamos a llevar a cabo el estudio detallado del
mecanismo mediante el cual Linux implementa las llamadas al sistema
en una arquitectura x86. Un sistema operativo est definido por los
servicios que ofrece en relacin con el acceso a entrada/salida,
procesos, manejo de memoria, envo de seales, tiempo, etc. A los
servicios del S.O. se accede a travs de las llamadas al sistema:
open, read, fork, mmap, kill, time, etc.
La llamada al sistema la invoca un proceso de usuario (o mejor
dicho un proceso en modo usuario) y es servida por el ncleo
(tpicamente el mismo proceso en modo ncleo). Una llamada al sistema
implica pasar o saltar del cdigo del usuario al cdigo del ncleo.
Este salto conlleva un cambio en el modo del funcionamiento del
procesador. El procesador debe pasar de modo usuario (acceso
restringido a los recursos) a modo supervisor o privilegiado
(recordemos que no tiene nada que ver con el superusario root).
5.2 Estructura de un proceso en Linux Un proceso en Linux,
aunque tiene un modelo de memoria plano, est estructurado en dos
zonas de memoria: La zona del usuario, normalmente los tres
primeros gigabytes de las direcciones lgicas. La zona del ncleo,
que ocupa el cuarto gigabyte. La zona del ncleo de Linux est
"mapeada" en todos los procesos del sistema. Esta estructura
coincide con los dos modos del procesador que utiliza Linux: El
cdigo de la zona del ncleo se ejecuta siempre en modo supervisor.
El cdigo presente en la zona del usuario se ejecuta en modo
usuario.
5.3 Interfaz con el sistema operativo Se pueden distinguir dos
puntos de vista en la interfaz ofrecida por las llamadas al
sistema: 1. La interfaz ofrecida por el ncleo del sistema
operativo. Es una interfaz definida a nivel de lenguaje
ensamblador. Depende directamente del "hardware" sobre el cual se
est ejecutando el S.O.: Registros del procesador, cmo se cambia de
modo y se salta del cdigo de usuario al cdigo del ncleo (jump,
call, trap, int, sysenter ...), etc. 2. La interfaz ofrecida al
programador o usuario (API). MSDOS Documentada a nivel de
ensamblador: Interrupcin a la que hay que llamar, valores de los
registros que hay que cargar y valores de retorno. UNIX Funciones
estndar en lenguaje C. P.ej. int setpriority (int which, int who,
int prio);
5.3.1 Interfaz ofrecida al programador (API) Todas las
implementaciones de UNIX disponen de unas bibliotecas de usuario
que esconden la implementacin concreta de las llamadas al sistema
(la interfaz real ofrecida por el S.O.) y ofrecen al programador
una interfaz C que presenta las siguientes ventajas: Facilidad de
uso al acceder desde un lenguaje de alto nivel. Portabilidad entre
arquitecturas: Linux-sparc, Linux-i386, etc. Portabilidad entre
diferentes versiones de UNIX: estndar POSIX. El estndar POSIX se
define slo a nivel de interfaz, no a nivel de implementacin real.
De hecho, hay sistemas no-UNIX, como Windows NT, que ofrecen una
interfaz POSIX. Todas las llamadas al sistema se encuentran
documentadas en la seccin 2 del manual en lnea de UNIX: # man 2
setpriority GETPRIORITY(2) Linux Programmer's Manual GETPRIORITY(2)
NAME getpriority, setpriority - get/set program scheduling priority
SYNOPSIS #include #include int getpriority(int which, int who); int
setpriority(int which, int who, int prio); DESCRIPTION The
scheduling priority of the process, process group, or user, as
indicated by which and who is obtained with the getpriority() call
and set with the setpriority() call. The value which is one of
PRIO_PROCESS, PRIO_PGRP, or PRIO_USER, and who is interpreted
relative to which (a process identifier for PRIO_PROCESS, process
group identifier for PRIO_PGRP, and a user ID for PRIO_USER). A
zero value for who denotes (respectively) the calling process, the
process group of the calling process, or the real user ID of the
calling process. Prio is a value in the range -20 to 19 (but see
the Notes below). The default priority is 0; lower priorities cause
more favor- able scheduling. The getpriority() call returns the
highest priority (lowest numerical value) enjoyed by any of the
specified processes. The setpriority() call sets the priorities of
all of the specified processes to the specified value. Only the
superuser may lower priorities. RETURN VALUE Since getpriority()
can legitimately return the value -1, it is necessary to clear the
external variable errno prior to the call, then check it afterwards
to determine if a -1 is an error or a legitimate value. The
setpriority() call returns 0 if there is no error, or -1 if there
is. ERRORS EINVAL which was not one of PRIO_PROCESS, PRIO_PGRP, or
PRIO_USER. ESRCH No process was located using the which and who
values specified. In addition to the errors indicated above,
setpriority() may fail if: EPERM A process was located, but its
effective user ID did not match either the effective or the real
user ID of the caller, and was not privileged (on Linux: did not
have the CAP_SYS_NICE capability). But see NOTES below. EACCES The
caller attempted to lower a process priority, but did not have the
required privilege (on Linux: did not have the CAP_SYS_NICE
capability). Since Linux 2.6.12, this error only occurs if the
caller attempts to set a process priority outside the range of the
RLIMIT_NICE soft resource limit of the target process; see
getrlimit(2) for details. CONFORMING TO SVr4, 4.4BSD (these
function calls first appeared in 4.2BSD), POSIX.1-2001. NOTES A
child created by fork(2) inherits its parent's nice value. The nice
value is preserved across execve(2). ... SEE ALSO nice(1), fork(2),
capabilities(7), renice(8) Linux 2002-09-20 GETPRIORITY(2)
5.3.2 Interfaz ofrecida por el ncleo La versin 2.6 del ncleo de
Linux para la arquitectura x86 utiliza dos posibles puertas de
entrada en el ncleo: int 0x80 Sistema original basado en una
interrupcin software. sysenter Llamada al sistema rpida disponible
desde el Pentium II. Para utilizar un mecanismo concreto, la
librera libc y el ncleo tienen que estar de acuerdo en su
disponibilidad. Para resolver el problema, el ncleo de Linux genera
una pequea librera que se enlaza con los ejecutables al hacer
execve (est alojada en una pgina de memoria fija), y que le ofrece
a la librera libc la funcin __kernel_vsyscall. Dicha funcin utiliza
el mejor mecanismo disponible. arch/i386/kernel/vsyscall-int80.S
[10-17] 10 .text 11 .globl __kernel_vsyscall 12 .type
__kernel_vsyscall,@function 13 __kernel_vsyscall: 14
.LSTART_vsyscall: 15 int $0x80 16 ret 17
.LEND_vsyscall:arch/i386/kernel/vsyscall-sysenter.S [10-22,30-39]
10 .text 11 .globl __kernel_vsyscall 12 .type
__kernel_vsyscall,@function 13 __kernel_vsyscall: 14
.LSTART_vsyscall: 15 push %ecx 16 .Lpush_ecx: 17 push %edx 18
.Lpush_edx: 19 push %ebp 20 .Lenter_kernel: 21 movl %esp,%ebp 22
sysenter... 30 .globl SYSENTER_RETURN /* Symbol used by entry.S. */
31 SYSENTER_RETURN: 32 pop %ebp 33 .Lpop_ebp: 34 pop %edx 35
.Lpop_edx: 36 pop %ecx 37 .Lpop_ecx: 38 ret 39 .LEND_vsyscall:
Dependiendo del mecanismo de entrada utilizado, la salida del ncleo
se realiza mediante iret o sysexit, respectivamente.
Estudiaremos la versin basada en la interrupcin software int
0x80, disponible en cualquier procesador de la arquitectura x86.
Recurdese que el manejador para la interrupcin software se instala
en la funcin trap_init() [arch/i386/kernel/traps.c#L995]. El nmero
de la interrupcin software a utilizar esta definido por la
constante SYSCALL_VECTOR
[include/asm-i386/mach-default/irq_vectors.h#L25].
arch/i386/kernel/traps.c [995-996,1009-1017,1032,1037-1040] 995
void __init trap_init(void) 996 {...1009
set_trap_gate(0,÷_error);1010
set_intr_gate(1,&debug);1011 set_intr_gate(2,&nmi);1012
set_system_intr_gate(3, &int3); /* int3-5 can be called from
all */1013 set_system_gate(4,&overflow);1014
set_system_gate(5,&bounds);1015
set_trap_gate(6,&invalid_op);1016
set_trap_gate(7,&device_not_available);1017
set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);...1032
set_system_gate(SYSCALL_VECTOR,&system_call);...1037
cpu_init();1038 1039 trap_init_hook();1040 }Parmetros de entrada
Cdigo del servicio requerido en el registro %eax. En el fichero
include/asm-i386/unistd.h aparecen listadas todas las llamadas al
sistema que ofrece Linux con su correspondiente nmero. Si la
llamada al sistema espera algn parmetro, debern estar en los
siguientes registros del procesador. %ebx arg 1%ecx arg 2%edx arg
3%esi arg 4%edi arg 5Valor de salida En el registro %eax se
devuelve el cdigo de retorno de la llamada al sistema.
5.4 Ejemplo de invocacinEjemplo de invocacin de la llamada al
sistema int nice(int); Detalle de los anillos de prioridad: En la
figura se observa la utilizacin de los anillos o niveles de
prioridad de la arquitectura x86 por parte del ncleo de Linux.
Detalle del mapa de memoria de un proceso:
En la figura se muestra la utilizacin del espacio de direcciones
por parte de un proceso de usuario. El cdigo del usuario puede
utilizar las direcciones lgicas desde 0 a 3GB y el cdigo del ncleo
est situado desde el 3GB al 4GB. Flujo simplificado de una llamada
al sistema utilizando la entrada mediante int.
5.5 Biblioteca de llamadas al sistema La biblioteca que contiene
todas las llamadas al sistema es la libc. Esta biblioteca se
encarga de ocultar los detalles de la interfaz de las llamadas al
sistema del ncleo en forma de funciones en C. Dichas funciones
trasladan los parmetros que reciben, normalmente a traves de la
pila, en los registros del procesador apropiados, invocan al S.O.,
recogen el cdigo de retorno (asignndolo tpicamente a la variable
errno), etc. En algunos casos concretos, las llamadas al sistema
que ofrece la biblioteca no se corresponden con las que ofrece el
ncleo. Existen casos en los que dos llamadas al sistema de la
biblioteca coinciden con la misma llamada al sistema "real" (P.Ej.
waitpid y wait4 invocan ambas a la llamada al sistema wait4).
Incluso algunas supuestas llamadas al sistema ofrecidas por la
biblioteca son implementadas completamente por ella misma, con lo
que el ncleo del S.O. no llega a invocarse. Con estos mecanismos,
la biblioteca es capaz de ofrecer una interfaz estndar, p. ej.
POSIX, aunque las llamadas al sistema que ofrece el ncleo del S.O.
no coincidan exactamente con dicho estndar. En esta asignatura nos
centraremos nicamente en las llamadas al sistema "reales".
Inicialmente, en Linux, la libc la mantenan Linus Torvalds et al..
Actualmente, se utiliza la biblioteca de GNU (glibc). El cdigo de
la biblioteca NO pertenece al ncleo del S.O., sino que est en el
espacio de direcciones del usuario. Por lo tanto, el cdigo de las
bibliotecas se ejecuta todava en modo usuario.
5.5.1 La biblioteca de llamadas glibc Existen dos versiones de
esta biblioteca con idntica funcionalidad: La que se enlaza de
forma esttica (libc.a), y est incluida en el propio programa
ejecutable del usuario. La de enlace dinmico (libc.so) que se
incorpora al proceso de usuario slo cuando es necesario, y su cdigo
es compartido por todos los procesos que la utilizan. La biblioteca
GNU C es realmente compleja pues los mismos fuentes se pueden
compilar sobre multitud de sistemas UNIX y sobre diferentes
arquitecturas. El cdigo de la mayora de las llamadas al sistema se
genera en tiempo de compilacin, dependiendo su valor del S.O. y la
arquitectura para la cual se estn compilando las funciones. O sea,
el cdigo en ensamblador que realiza la llamada al sistema no existe
en un fichero sino que se crea y compila a partir de unos ficheros
de especificaciones del S.O. y la arquitectura de destino.
El resultado de compilar los fuentes de la libc son los ficheros
libc.a y libc.so, versin esttica y dinmica de la biblioteca
respectivamente. Dentro de los ficheros de biblioteca estn
empaquetados, como si de un fichero arj o zip se tratara, los
bloques de cdigo mquina de todas las funciones. La orden ar(1) se
utiliza para trabajar con bibliotecas (crear, listar, aadir y
borrar los ficheros que contienen las funciones). Con la siguiente
orden podemos mostrar todos los ficheros contenidos en la
biblioteca:# ar t /usr/lib/libc.a
init-first.olibc-start.oset-init.osysdep.oversion.ocheck_fds.o...
con la opcin x en lugar de t se puede extraer un fichero de la
biblioteca. Para extraer el fichero setpriority.o, que contiene la
llamada al sistema setpriority(2) la orden sera:
# ar x /usr/lib/libc.a setpriority.o
Con la utilidad objdump(1) se puede desensamblar cualquier cdigo
objeto o ejecutable. El siguiente fragmento de cdigo representa la
llamada al sistema setpriority. # objdump -d setpriority.o
setpriority.o: file format elf32-i386
Disassembly of section .text:
00000000 : 0: 53 push %ebx 1: 8b 54 24 10 mov 0x10(%esp),%edx 5:
8b 4c 24 0c mov 0xc(%esp),%ecx 9: 8b 5c 24 08 mov 0x8(%esp),%ebx d:
b8 61 00 00 00 mov $0x61,%eax 12: cd 80 int $0x80 14: 5b pop %ebx
15: 3d 01 f0 ff ff cmp $0xfffff001,%eax 1a: 0f 83 fc ff ff ff jae
1c 20: c3 ret Se puede observar como la funcin de biblioteca coge
los tres parmetros de la pila y los coloca en los registros %ebx,
%ecx y %edx respectivamente. Igualmente, la funcin coloca en el
registro %eax el cdigo [include/asm-i386/unistd.h#L105] de la
llamada al sistema (setpriority 0x61 = 97) Con la utilidad
strace(1) se puede ver cmo los programas hacen uso de las llamadas
al sistema. Acepta como parmetro el nombre de un ejecutable y lo
ejecuta volcando en la salida estndar de error todas las llamadas
al sistema que ste realiza con sus respectivos parmetros. # strace
ls execve("/bin/ls", ["ls"], %[/* 34 vars */]) =
0uname({sys="Linux", node="viver", ...}) = 0brk(0) =
0x8053448old_mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0x40017000...open("/lib/i586/libc.so.6", O_RDONLY) = 3read(3,
"\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\264\313"..., 1024)
= 1024...open(".", O_RDONLY|O_NONBLOCK|0x10000) = 3...write(1,
"11-introduccion\t\t21-arranque\t."..., 81) = 81..._exit(0)
5.5.2 Cdigo de retorno En la arquitectura x86, Linux devuelve el
cdigo de retorno de la llamada al sistema en el registro %eax.
Cuando la llamada no ha tenido xito, el valor devuelto es negativo.
Si es negativo, la biblioteca copia dicho valor sobre una variable
global llamada errno y devuelve -1 como valor de retorno de la
funcin Aun as, algunas llamadas realizadas con xito pueden devolver
un valor negativo. Actualmente, el rango de errores que puede
devolver una llamada al sistema se encuentra entre -1 y -4095
(0xfffff001). La biblioteca debe ser capaz de determinar cundo el
valor devuelto es un error y tratarlo de forma adecuada. . La
rutina syscall_error es la encargada de hacerlo. La variable errno
contiene el cdigo de error de la ltima llamada que fall. Una
llamada que se realice con xito no modifica errno. Ms informacin
con:# man 3 errno. La variable errno est declarada en la propia
biblioteca.
5.6 Entrando en el ncleo La int 0x80 produce un salto a la zona
de cdigo del sistema operativo. Concretamente se salta a la funcin
system_call [arch/i386/kernel/entry.S#L240]. La interrupcin 0x80 se
asocia con la funcin system_call al inicializar Linux en la lnea
arch/i386/kernel/traps.c#L1032 de la funcin trap_init() invocada
desde la funcin start_kernel. En el proceso de salto ... El
procesador pasa de modo usuario ("priviledge level" 3 en la
arquitectura x86) a modo supervisor ("priviledge level" 0). Se
cambia el puntero de pila para que apunte a la pila del ncleo del
proceso y se guardan en dicha pila algunos registros (SS, ESP,
EFLAGS, CS, EIP). En la arquitectura x86 cada nivel de privilegio
tiene un puntero de pila distinto por motivos de seguridad.
Evidentemente la instruccin int necesita muchos ciclos de reloj
para completarse, de ah que se incorporar la instruccin sysenter
para poder implementar una entrada rpida en el sistema.
5.6.1 Dentro de system_callLa implementacin se encuentra en el
fichero entry.S arch/i386/kernel/entry.S [240-261] 240 # system
call handler stub 241 ENTRY(system_call) 242 pushl %eax # save
orig_eax 243 SAVE_ALL 244 GET_THREAD_INFO(%ebp) 245 # system call
tracing in operation 246 testb
$(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp) 247 jnz
syscall_trace_entry 248 cmpl $(nr_syscalls), %eax 249 jae
syscall_badsys 250 syscall_call: 251 call *sys_call_table(,%eax,4)
252 movl %eax,EAX(%esp) # store the return value 253 syscall_exit:
254 cli # make sure we don't miss an interrupt 255 # setting
need_resched or sigpending 256 # between sampling and the iret 257
movl TI_flags(%ebp), %ecx 258 testw $_TIF_ALLWORK_MASK, %cx #
current->work 259 jne syscall_exit_work 260 restore_all: 261
RESTORE_ALL#242 [system_call] Primero guarda el cdigo de la llamada
al sistema en la pila del ncleo.
#243 A continuacin es importante guardar todos los registros del
procesador con la macro SAVE_ALL [arch/i386/kernel/entry.S#L84].
arch/i386/kernel/entry.S [84-98] 84 #define SAVE_ALL \ 85 cld; \ 86
pushl %es; \ 87 pushl %ds; \ 88 pushl %eax; \ 89 pushl %ebp; \ 90
pushl %edi; \ 91 pushl %esi; \ 92 pushl %edx; \ 93 pushl %ecx; \ 94
pushl %ebx; \ 95 movl $(__USER_DS), %edx; \ 96 movl %edx, %ds; \ 97
movl %edx, %es; 98
#244 Se pone el puntero a la estructura thread_info de la tarea
actual en el registro %ebp usando la macro GET_THREAD_INFO
[include/asm-i386/thread_info.h#L119].
#246#247 Se verifica si el proceso padre est monitorizando este
proceso, en cuyo caso se ejecuta el cdigo syscall_trace_entry
[arch/i386/kernel/entry.S#L302]. Para verificar algunas condiciones
sobre el estado del proceso actual se accede al campo flags de su
estructura thread_info a travs del registro %ebp y el offset
TI_flags. Para acceder a los campos de la estructura struct
thread_info [include/asm-i386/thread_info.h#L28] se utilizan
desplazamientos fijos que se calculan en tiempo de compilacin
mediante el fichero arch/i386/kernel/asm-offsets.c.
arch/i386/kernel/asm-offsets.c [24-25,47-54,66] 24 void foo(void)
25 {... 47 OFFSET(TI_task, thread_info, task); 48
OFFSET(TI_exec_domain, thread_info, exec_domain); 49
OFFSET(TI_flags, thread_info, flags); 50 OFFSET(TI_status,
thread_info, status); 51 OFFSET(TI_cpu, thread_info, cpu); 52
OFFSET(TI_preempt_count, thread_info, preempt_count); 53
OFFSET(TI_addr_limit, thread_info, addr_limit); 54
OFFSET(TI_restart_block, thread_info, restart_block);... 66
}include/asm-i386/thread_info.h [28-34,47] 28 struct thread_info {
29 struct task_struct *task; /* main task structure */ 30 struct
exec_domain *exec_domain; /* execution domain */ 31 unsigned long
flags; /* low level flags */ 32 unsigned long status; /*
thread-synchronous flags */ 33 __u32 cpu; /* current CPU */ 34
__s32 preempt_count; /* 0 => preemptable, BUG */... 47 };El
cdigo de syscall_trace_entry [arch/i386/kernel/entry.S#L247]
informa al proceso padre de que su hijo est realizando una llamada
al sistema, antes y despus de servirla, mediante la invocacin de la
funcin do_syscall_trace. arch/i386/kernel/entry.S [300-311] 300 #
perform syscall exit tracing 301 ALIGN 302 syscall_trace_entry: 303
movl $-ENOSYS,EAX(%esp) 304 movl %esp, %eax 305 xorl %edx,%edx 306
call do_syscall_trace 307 movl ORIG_EAX(%esp), %eax 308 cmpl
$(nr_syscalls), %eax 309 jnae syscall_call 310 jmp syscall_exit
311
#248#249 Se comprueba que el nmero de llamada pedido es vlido
(si est dentro del rango). Si no es vlido, se ejecuta el cdigo
syscall_badsys [arch/i386/kernel/entry.S#L333] que retorna
inmediatamente con el cdigo de error ENOSYS saltando a
resume_userspace [arch/i386/kernel/entry.S#L167].
arch/i386/kernel/entry.S [167-175,333-335] 167
ENTRY(resume_userspace) 168 cli # make sure we don't miss an
interrupt 169 # setting need_resched or sigpending 170 # between
sampling and the iret 171 movl TI_flags(%ebp), %ecx 172 andl
$_TIF_WORK_MASK, %ecx # is there any work to be done on 173 #
int/exception return? 174 jne work_pending 175 jmp restore_all...
333 syscall_badsys: 334 movl $-ENOSYS,EAX(%esp) 335 jmp
resume_userspace
#250#252 [syscall_call] Se llama a la funcin que sirve la
llamada pedida y se guarda el cdigo de retorno en la posicin de la
pila donde est situado el registro %eax. Se almacena el valor del
registro %eax en la posicin que ocupa dicho registro en la pila del
ncleo (EAX(%esp) [arch/i386/kernel/entry.S#L60]) para que al
ejecutar el cdigo de la macro RESTORE_ALL
[arch/i386/kernel/entry.S#L125] el valor se situe en el registro
%eax antes de volver a espacio de usuario (que es lo que espera la
biblioteca libc). Al comienzo del fichero entry.S se han definido
constantes que permiten acceder a los registros almacenados en la
pila utilizando el registro %esp arch/i386/kernel/entry.S [54-68]
54 EBX = 0x00 55 ECX = 0x04 56 EDX = 0x08 57 ESI = 0x0C 58 EDI =
0x10 59 EBP = 0x14 60 EAX = 0x18 61 DS = 0x1C 62 ES = 0x20 63
ORIG_EAX = 0x24 64 EIP = 0x28 65 CS = 0x2C 66 EFLAGS = 0x30 67
OLDESP = 0x34 68 OLDSS = 0x38sys_call_table
[arch/i386/kernel/entry.S#L574] es un vector de direcciones de
salto que contiene las direcciones de la funciones que sirven cada
una de las llamadas al sistema. Se utiliza el registro %eax como
ndice, teniendo en cuenta que cada direccin ocupa 4 bytes.
5.6.2 Recogida de parmetros En C normalmente se utiliza la pila
para pasar parmetros entre funciones. Antes de realizar la llamada
a una funcin se insertan en la pila todos los parmetros luego se
invoca a la funcin y sta lee los parmetros de la pila. Estructura
de la pila en el x86 al llamar a una funcin.
En nuestro caso, la macro SAVE_ALL
[arch/i386/kernel/entry.S#L84] guarda los registros (que es donde
se reciben los parmetros de las llamadas) en la pila (push), por lo
que si luego llamamos a una funcin C, sta se comportar como si otra
funcin en C le hubiera pasado los parmetros. El orden en el que se
insertan en la pila es importante. Los ltimos en insertarse en la
pila son los primeros parmetros en la declaracin de la funcin en
C.
5.7 Ejecucin de la llamada al sistema Siguiendo con el ejemplo
de la llamada setpriority, cuando se ejecute la instruccin 251 call
*sys_call_table(,%eax,4)se llamar a la funcin C cuya direccin se
encuentra en la entrada 97 [include/asm-i386/unistd.h#L105] de la
tabla sys_call_table [arch/i386/kernel/entry.S#L436]. Recordemos
que en el cdigo de la biblioteca libc se asigno el valor 0x61(97)
al registro %eax antes de ejecutar la instruccin int 0x80: ... d:
b8 61 00 00 00 mov $0x61,%eax 12: cd 80 int $0x80...y que el tamao
de cada direccin es de 32 bits, o sea 4 bytes. La funcin invocada
es sys_setpriority(int which, int who, int niceval)
[kernel/sys.c#L244], que se encargar de cambiar la prioridad del
proceso o processo indicados. kernel/sys.c [244-263,295-296] 244
asmlinkage long sys_setpriority(int which, int who, int niceval)
245 { 246 struct task_struct *g, *p; 247 struct user_struct *user;
248 int error = -EINVAL; 249 250 if (which > 2 || which < 0)
251 goto out; 252 253 /* normalize: avoid signed division (rounding
problems) */ 254 error = -ESRCH; 255 if (niceval < -20) 256
niceval = -20; 257 if (niceval > 19) 258 niceval = 19; 259 260
read_lock(&tasklist_lock); 261 switch (which) { 262 case
PRIO_PROCESS: 263 if (!who)... 295 return error; 296 } Los
parmetros que el usuario le paso a la funcin setpriority de la
biblioteca libc han pasado por los registros %ebx, %ecx y %edx,
luego se han copiado en la pila del ncleo y ese valor de la pila,
en el lenguaje C, se ve ahora como los parmetros which, who y
niceval de la funcin sys_setpriority.
5.8 Saliendo del ncleo: syscall_exitAl finalizar la ejecucin de
la llamada al sistema se ejecuta el cdigo que se encuentra en
syscall_exit [arch/i386/kernel/entry.S#L253]. Este cdigo lleva a
cabo algunas comprobaciones antes de volver a modo usuario. #254
[syscall_exit] Se deshabilitan las interrupciones.
#257#259 Durante la ejecucin de la llamada al sistema el proceso
ha podido cambiar de estado, y por lo tanto, haberse marcado la
necesidad de planificar de nuevo, atender seales, etc. Este cdigo
comprueba si hay trabajo pendiente o se est monitorizando el
proceso invocante, saltando a syscall_exit_work
[arch/i386/kernel/entry.S#L314] si as fuera.
#260#261 [restore_all] Si no hay trabajo pendiente y la
instruccin no est siendo monitorizada, la llamada al sistema
termina ejecutando el cdigo de restore_all. Este cdigo consiste
nicamente en la macro RESTORE_ALL [arch/i386/kernel/entry.S#L125].
Esta macro restaura los registros almacenados con SAVE_ALL, ignora
el ndice de la llamada al sistema insertado en la pila y ejecuta la
instruccin de retorno de interrupcin iret. arch/i386/kernel/entry.S
[99-106,108-111,125-128] 99 #define RESTORE_INT_REGS \ 100 popl
%ebx; \ 101 popl %ecx; \ 102 popl %edx; \ 103 popl %esi; \ 104 popl
%edi; \ 105 popl %ebp; \ 106 popl %eax... 108 #define RESTORE_REGS
\ 109 RESTORE_INT_REGS; \ 110 1: popl %ds; \ 111 2: popl %es; \...
125 #define RESTORE_ALL \ 126 RESTORE_REGS \ 127 addl $4, %esp; \
128 1: iret; \Es importante destacar que al ejecutar la instruccin
pop %eax en realidad se est almacenando el cdigo de retorno de la
llamada al sistema en el registro %eax (colocado ah por las
instrucciones del tipo mov %eax, EAX(%esp))
5.9 Ejecutando el trabajo pendiente antes de volverAl finalizar
una llamada al sistema o una interrupcin hay que comprobar si
existe trabajo pendiente antes de volver a modo usuario. Las
condiciones que se deben comprobar en el campo flags de la tarea
actual son: Si el proceso se estaba monitorizando Se debe invocar a
la funcin do_syscall_trace(). Viene indicado en el campo flags por
los indicadores: _TIF_SYSCALL_TRACE, _TIF_SYSCALL_AUDIT y/o
_TIF_SINGLESTEP . Si hay peticiones de planificacin pendientes Se
debe invocar a la funcin schedule(). Viene indicado en el campo
flags por el indicador: _TIF_NEED_RESCHED. Si hay seales pendientes
Se debe invocar a la funcin do_notify_resume(). Viene indicado en
el campo flags por el indicador: _TIF_SIGPENDING.
Salir del ncleo al terminar una llamada al sistema comparte
mucho cdigo con el retorno de una interrupcin. Diagrama de flujo
del proceso de salida del ncleo.
Si el campo flags (accedido mediante el offset TI_flags) de la
estructura thread_info de la tarea actual indica que hay trabajo
pendiente antes de volver al espacio de usuario, entonces se
ejecuta el cdigo de syscall_exit_work
[arch/i386/kernel/entry.S#L314]. arch/i386/kernel/entry.S [314-322]
314 syscall_exit_work: 315 testb
$(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl 316
jz work_pending 317 sti # could let do_syscall_trace() call 318 #
schedule() instead 319 movl %esp, %eax 320 movl $1, %edx 321 call
do_syscall_trace 322 jmp resume_userspace#315#316
[syscall_exit_work] Se comprueba se est monitorizando a la tarea
invocante. Si es as, se continua con la ejecucin, en caso contrario
se salta a work_pending [arch/i386/kernel/entry.S#L265]
#317#322 Si el proceso estaba siendo monitorizado, se invoca a
do_syscall_trace y despus se continua con el trabajo pendiente, si
lo hubiera, en resume_userspace.
Si el campo TI_flags indica que hay trabajo pendiente
(independientemente de si se est monitorizando la tarea invocante o
no) se acaba ejecutando el cdigo work_pending
[arch/i386/kernel/entry.S#L265]. arch/i386/kernel/entry.S [265-288]
265 work_pending: 266 testb $_TIF_NEED_RESCHED, %cl 267 jz
work_notifysig 268 work_resched: 269 call schedule 270 cli # make
sure we don't miss an interrupt 271 # setting need_resched or
sigpending 272 # between sampling and the iret 273 movl
TI_flags(%ebp), %ecx 274 andl $_TIF_WORK_MASK, %ecx # is there any
work to be done other 275 # than syscall tracing? 276 jz
restore_all 277 testb $_TIF_NEED_RESCHED, %cl 278 jnz work_resched
279 280 work_notifysig: # deal with pending signals and 281 #
notify-resume requests 282 testl $VM_MASK, EFLAGS(%esp) 283 movl
%esp, %eax 284 jne work_notifysig_v86 # returning to kernel-space
or 285 # vm86-space 286 xorl %edx, %edx 287 call do_notify_resume
288 jmp restore_all#266#267 [work_resched] Se comprueba si la hace
falta invocar al planificador antes de volver al espacio de
usuario. Si no se pasa a work_notifysig
[arch/i386/kernel/entry.S#L280] para ver si hay seales
pendientes.
#269 Se invoca a la funcin de planificacin. Posiblemente el
proceso que salga de dicha funcin sea un proceso diferente al
actual. Esto se tratar con el debido detalle en el tema 7 "Gestin
de procesos".
#270Tras ejecutar el planificador se comprueba si la tarea
actual (posiblemente una distinta) tiene trabajo pendiente,
repitiendose el proceso desde work_resched (#266).
#280#287 [work_notifysig] Tras algunas comprobaciones, se invoca
a la funcin do_notify_resume [arch/i386/kernel/signal.c#L642] que
se encarga entre otras cosas de comprobar si el proceso tiene
seales pendientes.
#288Finalmente se salta a restore_all donde se restauran todos
los registros mediante la macro RESTORE_ALL.
Se puede observar que la ejecucin ms frecuente de system_call
[arch/i386/kernel/entry.S#L241] (la llamada es correcta, no est
siendo monitorizada y no hay trabajo pendiente) est optimizada para
no tomar absolutamente ningn salto, a excepcin de la propia llamada
a la funcin. Los procesadores actuales basan la mayor parte de sus
prestaciones en la ejecucin segmentada de instrucciones, y las
instrucciones de salto suelen forzar el vaciado de la unidad de
ejecucin si la condicin de salto no se predice correctamente. El
cdigo de Linux intenta minimizar el nmero de saltos dentro del
flujo de control ms frecuente.
5.10 Resumen1. La biblioteca mete en los registros del
procesador los parmetros de la llamada. 2. Se produce la
interrupcin software (trap) 0x80. El descriptor de interrupcin.
0x80 apunta a la rutina system_call. 3. Se guardan el registro
%eax. El resto de los registros con SAVE_ALL. 4. Se verifica que el
nmero de llamada al sistema corresponde con una llamada vlida. 5.
Se salta a la funcin que implementa el servicio pedido:call
*sys_call_table(,%eax,4)
6. Si mientras se atenda la llamada al sistema se ha producido
algn evento que requiera llamar al planificador, se llama en este
punto. 7. Si el proceso al que se va a retornar tiene seales
pendientes, se le envan ahora. 8. Se restauran los valores de los
registros con RESTORE_ALL y se vuelve de la interrupcin software.
9. La biblioteca comprueba si la llamada ha producido algn error, y
en este caso guarda en la variable errno el valor de retorno.