Está en la página 1de 32

1.

3 Interfaces con el sistema operativo [SILB94]

1.3.1 Las llamadas al sistema

Ya 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 rdenes

Cuando 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 Sistema

Juan Carlos Prez y


Sergio Sez Sergio Sez ssaez@disca.upv.es
Juan Carlos Prez jcperez@disca.upv.es

5.1 Introduccin
Vamos 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:
o La zona del usuario, normalmente los tres primeros gigabytes de las
direcciones lgicas.
o 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:
o El cdigo de la zona del ncleo se ejecuta siempre en modo supervisor.
o 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:
o Facilidad de uso al acceder desde un lenguaje de alto nivel.
o Portabilidad entre arquitecturas: Linux-sparc, Linux-i386, etc.
o 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 <sys/time.h>
#include <sys/resource.h>

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,&divide_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

o 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.
o 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 5
Valor de salida
En el registro %eax se devuelve el cdigo de retorno de la llamada al sistema.
5.4 Ejemplo de invocacin

Ejemplo 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.


stdio.h

fstream.h

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:


o La que se enlaza de forma esttica (libc.a), y est incluida en el propio
programa ejecutable del usuario.
o 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.o
libc-start.o
set-init.o
sysdep.o
version.o
check_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


*/]) = 0
uname({sys="Linux", node="viver", ...}) =
0
brk(0) =
0x8053448
old_mmap(NULL, 4096,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-1, 0) = 0x40017000
...
open("/lib/i586/libc.so.6", O_RDONLY) =
3
read(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 ...
o El procesador pasa de modo usuario ("priviledge level" 3 en la arquitectura
x86) a modo supervisor ("priviledge level" 0).
o 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_call

La 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_fla
gs(%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 Se verifica si el proceso padre est monitorizando este proceso, en cuyo caso se ejecuta
#247 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, <0 =>
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 Se comprueba que el nmero de llamada pedido es vlido (si est dentro del rango). Si
#249 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 [syscall_call] Se llama a la funcin que sirve la llamada pedida y se guarda
#252
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 = 0x38

sys_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_exit

Al 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 Durante la ejecucin de la llamada al sistema el proceso ha podido cambiar de estado, y


#259 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 [restore_all] Si no hay trabajo pendiente y la instruccin no est siendo


#261 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 volver


Al 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_SI
NGLESTEP), %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 [syscall_exit_work] Se comprueba se est monitorizando a la tarea
#316 invocante. Si es as, se continua con la ejecucin, en caso contrario se salta a

work_pending [arch/i386/kernel/entry.S#L265]
#317 Si el proceso estaba siendo monitorizado, se invoca a do_syscall_trace y
#322 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 [work_resched] Se comprueba si la hace falta invocar al planificador antes de
#267
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".

#270 Tras ejecutar el planificador se comprueba si la tarea actual (posiblemente una distinta)
tiene trabajo pendiente, repitiendose el proceso desde work_resched (#266).

#280 [work_notifysig] Tras algunas comprobaciones, se invoca a la funcin


#287
do_notify_resume [arch/i386/kernel/signal.c#L642] que se encarga entre
otras cosas de comprobar si el proceso tiene seales pendientes.

#288 Finalmente 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 Resumen
1. 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.

También podría gustarte