En orden para soportar múltiples sistemas de archivos, Linux contiene un nivel especial de interfaces del núcleo llamado VFS (Interruptor de Sistemas de Ficheros Virtuales). Esto es muy similar a la interfaz vnode/vfs encontrada en los derivados de SVR4 (originalmente venían de BSD y de las implementaciones originales de Sun).
La antememoria de inodos de Linux es implementada en un simple fichero,
fs/inode.c
, el cual consiste de 977 lineas de código. Es
interesante notar que no se han realizado muchos cambios en él durante
los últimos 5-7 años: uno todavía puede reconocer algún código
comparando la última version con, digamos, 1.3.42.
La estructura de la antememoria de inodos Linux es como sigue:
inode_hashtable
, donde cada inodo
es ordenado por el valor del puntero del superbloque y el
número de inodo de 32bit. Los inodos sin un superbloque
(inode->i_sb == NULL
) son añadidos a la lista
doblemente enlazada encabezada por anon_hash_chain
en
su lugar. Ejemplos de inodos anónimos son los conectores creados por
net/socket.c:sock_alloc()
, llamado por
fs/inode.c:get_empty_inode()
.
inode_in_use
), la cual
contiene los inodos válidos con i_count>0
y
i_nlink>0
. Los inodos nuevamente asignados por
get_empty_inode()
y get_new_inode()
son
añadidos a la lista inode_in_use
.
inode_unused
), la cual
contiene los inodos válidos con i_count = 0
.
sb->s_dirty
)
que contiene los inodos válidos con i_count>0
,
i_nlink>0
y i_state & I_DIRTY
. Cuando el inodo
es marcado como sucio, es añadido a la lista
sb->s_dirty
si el está también ordenado. Manteniendo una
lista sucia por superbloque de inodos nos permite rápidamente
sincronizar los inodos.
inode_cachep
. Tal como los objetos inodos son asignados
como libres, ellos son tomados y devueltos a esta antememoria SLAB.Los tipos de listas son sujetadas desde inode->i_list
, la tabla
hash desde inode->i_hash
. Cada inodo puede estar en una tabla
hash y en uno, y en sólo uno, tipo de lista (en_uso, sin_usar o sucia).
Todas estas listas están protegidas por un spinlock simple:
inode_lock
.
El subsistema de caché de inodos es inicializado cuando la función
inode_init()
es llamada desde
init/main.c:start_kernel()
. La función es marcada como
__init
, lo que significa que el código será lanzado
posteriormente. Se le pasa un argumento simple - el número de páginas
físicas en el sistema. Esto es por lo que la antememoria de inodos puede
configurarse ella misma dependiendo de cuanta memoria está disponible,
esto es, crea una tabla hash más grande si hay suficiente memoria.
Las únicas estadísticas de información sobre la antememoria de inodos es el
número de inodos sin usar, almacenados en
inodes_stat.nr_unused
y accesibles por los programas de usuario
a través de los archivos /proc/sys/fs/inode-nr
y
/proc/sys/fs/inode-state
.
Podemos examinar una de las listas desde gdb en un núcleo en funcionamiento de esta forma:
(gdb) printf "%d\n", (unsigned long)(&((struct inode *)0)->i_list)
8
(gdb) p inode_unused
$34 = 0xdfa992a8
(gdb) p (struct list_head)inode_unused
$35 = {next = 0xdfa992a8, prev = 0xdfcdd5a8}
(gdb) p ((struct list_head)inode_unused).prev
$36 = (struct list_head *) 0xdfcdd5a8
(gdb) p (((struct list_head)inode_unused).prev)->prev
$37 = (struct list_head *) 0xdfb5a2e8
(gdb) set $i = (struct inode *)0xdfb5a2e0
(gdb) p $i->i_ino
$38 = 0x3bec7
(gdb) p $i->i_count
$39 = {counter = 0x0}
Destacar que restamos 8 de la dirección 0xdfb5a2e8 para obtener la
dirección de struct inode
(0xdfb5a2e0) de acuerdo a la
definición de la macro list_entry()
de
include/linux/list.h
.
Para entender cómo trabaja la antememoria de inodos, déjanos seguir un tiempo de vida de un inodo de un fichero regular en el sistema de ficheros ext2, el cómo es abierto y cómo es cerrado:
fd = open("file", O_RDONLY);
close(fd);
La llamada al sistema open(2) es implementada en la función
fs/open.c:sys_open
y el trabajo real es realizado por la
función fs/open.c:filp_open()
, la cual está dividida en dos
partes:
open_namei()
: rellena la estructura nameidata
conteniendo las estructuras dentry y vfsmount.
dentry_open()
: dado dentry y vfsmount, esta función
asigna una nueva struct file
y las enlaza a todas
ellas; también llama al método específico del sistema de
ficheros f_op->open()
el cual fue inicializado en
inode->i_fop
cuando el inodo fue leído en
open_namei()
(el cual suministra el inodo a través
de dentry->d_inode
). La función open_namei()
interactúa con la antememoria dentry a
través de path_walk()
, el cual en el regreso llama a
real_lookup()
, el cual llama al método específico del sistema
de ficheros inode_operations->lookup()
. La misión de este
método es encontrar la entrada en el directorio padre con el nombre
correcto y entonces hace iget(sb, ino)
para coger el
correspondiente inodo - el cual nos trae la antememoria de inodos. Cuando el
inodo es leido, el dentry es instanciado por medio de d_add(dentry,
inode)
. Mientras estamos en él, nótese que en los sistemas de
ficheros del estilo UNIX que tienen el concepto de número de inodos en
disco, el trabajo del método lookup es mapear su bit menos significativo
al actual formato de la CPU, ej. si el número de inodos en la entrada del
directorio sin formato (específico del sistema de ficheros) está en
el formato de 32 bits little-endian uno haría:
unsigned long ino = le32_to_cpu(de->inode);
inode = iget(sb, ino);
d_add(dentry, inode);
Por lo tanto, cuando abrimos un fichero nosotros llamamos a
iget(sb, ino)
el cual es realmente iget4(sb, ino, NULL,
NULL)
, el cual hace:
inode_lock
. Si el inodo es encontrado, su cuenta
de referencia (i_count
) es incrementada; si era 0
anteriormente al incremento y el inodo no estaba sucio, es
quitado de cualquier tipo de lista (inode->i_list
)
en la que esté (tiene que estar en la lista
inode_unused
, por supuesto) e insertado en la lista
del tipo inode_in_use
; finalmente
inodes_stat.nr_unused
es decrementado.
iget4()
devolverá un inodo desbloqueado.
get_new_inode()
, pasándole el puntero al sitio de la
tabla hash donde debería de ser insertado.
get_new_inode()
asigna un nuevo inodo desde la antememoria
SLAB inode_cachep
, pero esta operación puede bloquear
(asignación GFP_KERNEL
), por lo tanto el spinlock
que guarda la tabla hash tiene que ser quitado. Desde que hemos
quitado el spinlock, entonces debemos de volver a buscar el
inodo en la tabla; si esta vez es encontrado, se devuelve
(después de incrementar la referencia por __iget
)
el que se encontró en la tabla hash y se destruye el nuevamente
asignado. Si aún no se ha encontrado en la tabla hash, entonces
el nuevo inodo que tenemos acaba de ser asignado y es el que va
a ser usado; entonces es inicializado a los valores requeridos y
el método específico del sistema de ficheros
sb->s_op->read_inode()
es llamado para propagar el
resto del inodo. Esto nos proporciona desde la antememoria de inodos
la vuelta al código del sistema de archivos - recuerda que
venimos de la antememoria de inodos cuando el método específico del
sistema de ficheros lookup()
llama a iget()
.
Mientras el método s_op->read_inode()
está leyendo
el inodo del disco, el inodo está bloqueado (i_state =
I_LOCK
); él es desbloqueado después de que el método
read_inode()
regrese y todos los que están esperando
por el hayan sido despertados.Ahora, veamos que pasa cuando cerramos este descriptor de ficheros. La
llamada al sistema close(2) está implementada en la función
fs/open.c:sys_close()
, la cual llama a do_close(fd, 1)
el cual rompe (reemplaza con NULL) el descriptor del descriptor de
ficheros de la tabla del proceso y llama a la función filp_close()
,
la cual realiza la mayor parte del trabajo. La parte interesante sucede en
fput()
, la cual chequea si era la última referencia al fichero, y si es
así llama a fs/file_table.c:_fput()
la cual llama a __fput()
en la
cual es donde sucede la interacción con dcache (y entonces con la memoria
intermedia de inodos - ¡recuerda que dcache es la memoria intermedia de
inodos Maestra!). El fs/dcache.c:dput()
hace dentry_iput()
la cual nos brinda la vuelta a la memoria intermedia de inodos a través de
iput(inode)
, por lo tanto déjanos entender fs/inode.c:iput(inode)
:
sb->s_op->put_inode()
, es llamada inmediatamente sin
mantener ningún spinlock (por lo tanto puede bloquear).
inode_lock
es tomado y i_count
es
decrementado. Si NO era la última referencia a este inodo
entonces simplemente chequeamos si hay muchas referencias a el
y entonces i_count
puede urdir sobre los 32 bits
asignados a el si por lo tanto podemos imprimir un mensaje
de peligro y regresar. Nótese que llamamos a printk()
mientras mantenemos el spinlock inode_lock
- esto está
bien porque printk()
nunca bloquea, entonces
puede ser llamado absolutamente en cualquier contexto (¡incluso
desde el manejador de interrupciones!).
EL trabajo realizado por iput()
en la última referencia del
inodo es bastante complejo, por lo tanto lo separaremos en una lista de
si misma:
i_nlink == 0
(ej. el fichero fué desenlazado mientras
lo manteníamos abierto) entonces el inodo es quitado de la tabla
hash y de su lista de tipos; si hay alguna página de datos
mantenida en la antememoria de páginas para este inodo, son borradas
por medio de truncate_all_inode_pages(&inode->i_data)
.
Entonces el método específico del sistema de archivos
s_op->delete_inode()
es llamado, el cual típicamente
borra la copia en disco del inodo. Si no hay un método
s_op->delete_inode()
registrado por el sistema de
ficheros (ej. ramfs) entonces llamamos a
clear_inode(inode)
, el cual llama
s_op->clear_inode()
si está registrado y si un inodo
corresponde a un dispositivo de bloques, esta cuenta de
referencia del dispositivo es borrada por
bdput(inode->i_bdev)
.
i_nlink != 0
entonces chequeamos si hay otros inodos
en el mismo cubo hash y si no hay ninguno, entonces si el inodo
no está sucio lo borramos desde su tipo de lista y lo añadimos
a la lista inode_unused
incrementando
inodes_stat.nr_unused
. Si hay inodos en el mismo cubo
hash entonces los borramos de la lista de tipo y lo añadimos a
la lista inode_unused
. Si no había ningún inodo
(NetApp .snapshot) entonces lo borramos de la lista de tipos y
lo limpiamos/destruimos completamente.El núcleo Linux suministra un mecanismo para los nuevos sistemas de ficheros para ser escritos con el mínimo esfuerzo. Los motivos históricos para esto son:
Déjanos considerar los pasos requeridos para implementar un sistema de
ficheros bajo Linux. El código para implementar un sistema de ficheros
puede ser un módulo dinámicamente cargado o estár estáticamente
enlazado en el núcleo, el camino es realizado por Linux
trasparentemente. Todo lo que se necesita es rellenar una estructura
struct file_system_type
y registrarla con el VFS usando la
función register_filesystem()
como en el siguiente ejemplo de
fs/bfs/inode.c
:
#include <linux/module.h>
#include <linux/init.h>
static struct super_block *bfs_read_super(struct super_block *, void *, int);
static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super);
static int __init init_bfs_fs(void)
{
return register_filesystem(&bfs_fs_type);
}
static void __exit exit_bfs_fs(void)
{
unregister_filesystem(&bfs_fs_type);
}
module_init(init_bfs_fs)
module_exit(exit_bfs_fs)
Las macros module_init()/module_exit()
aseguran que, cuando BFS
es compilado como un módulo, las funciones init_bfs_fs()
y
exit_bfs_fs()
se convierten en init_module()
y
cleanup_module()
respectivamente; si BFS está estáticamente
enlazado en el núcleo el código exit_bfs_fs()
lo hace
innecesario.
La struct file_system_type
es declarada en
include/linux/fs.h
:
struct file_system_type {
const char *name;
int fs_flags;
struct super_block *(*read_super) (struct super_block *, void *, int);
struct module *owner;
struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */
struct file_system_type * next;
};
Los campos anteriores son explicados de esta forma:
/proc/filesystems
y es usado como clave para encontrar
un sistema de ficheros por su nombre; este mismo nombre es
usado por el tipo de sistema de ficheros en mount(2),
y debería de ser único; (obviamente) sólo puede haber un
sistema de ficheros con un nombre dado. Para los módulos,
los nombres de los punteros al espacio de direcciones del
módulo no son copiados: esto significa que
cat /proc/filesystems puede fallar si el módulo fue
descargado pero el sistema de ficheros aún está registrado.
FS_REQUIRES_DEV
para sistemas de ficheros que sólo
pueden ser montados como dispositivos de bloque,
FS_SINGLE
para sistemas de ficheros que pueden tener
sólo un superbloque, FS_NOMOUNT
para los sistemas de
ficheros que no pueden ser montados desde el espacio de usuario
por medio de la llamada al sistema mount(2): ellos
pueden de todas formas ser montados internamente usando la
interfaz kern_mount()
, ej, pipefs.
FS_SINGLE
donde fallará en
get_sb_single()
, intentando desreferenciar un puntero
a NULL en fs_type->kern_mnt->mnt_sb
con
(fs_type->kern_mnt = NULL
).
THIS_MODULE
lo hace automáticamente.
FS_SINGLE
. Esto es establecido por
kern_mount()
(POR HACER: kern_mount()
debería de rechazar montar sistemas de ficheros si
FS_SINGLE
no está establecido).
file_systems
(ver fs/super.c
).
La lista está protegida por el spinlock read-write
file_systems_lock
y las funciones
register/unregister_filesystem()
modificada
por el enlace y desenlace de la entrada de la lista.El trabajo de la función read_super()
es la de rellenar los
campos del superbloque, asignando el inodo raiz e inicializando cualquier
información privada del sistema de ficheros asociadas por esta instancia
montada del sistema de ficheros. Por lo tanto, tipicamente el
read_super()
hará:
sb->s_dev
, usando la función de la antememoria
intermedia bread()
. Si se anticipa a leer unos pocos
más bloques de metadatos inmediatamente subsecuentes, entonces
tiene sentido usar breada()
para planificar el leer
bloque extra de forma asíncrona.
sb->s_op
para apuntar a la estructura
struct super_block_operations
. Esta estructura
contiene las funciones específicas del sistema de ficheros
implementando las operaciones como "leer inodo", "borrar
inodo", etc.
d_alloc_root()
.
sb->s_dirt
a 1 y marca la
antememoria conteniendo el superbloque como sucio (POR HACER: ¿porqué
hacemos esto? Yo lo hice en BFS porque MINIX lo hizo ...)Bajo Linux hay varios niveles de rodeos entre el descriptor de ficheros
del usuario y la estructura de inodos del núcleo. Cuando un proceso
realiza la llamada al sistema open(2), el núcleo devuelve un
entero pequeño no negativo el cual puede ser usado para operaciones de
E/S subsecuentes en este fichero. Cada estructura de fichero apunta a
dentry a través de file->f_dentry
. Y cada dentry apunta a un
inodo a través de dentry->d_inode
.
Cada tarea contiene un campo tsk->files
el cual es un puntero a
struct files_struct
definida en include/linux/sched.h
:
/*
* Abre la estructura tabla del fichero
*/
struct files_struct {
atomic_t count;
rwlock_t file_lock;
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* actualmente una matriz de descriptores de ficheros */
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
El file->count
es una cuenta de referencia, incrementada por
get_file()
(usualmente llamada por fget()
) y
decrementada por fput()
y por put_filp()
. La
diferencia entre fput()
y put_filp()
es que
fput()
hace más trabajo usualmente necesitado para ficheros
regulares, como la liberación de conjuntos de bloqueos, liberación de
dentry, etc, mientras que put_filp()
es sólo para manipular las
estructuras de tablas de ficheros, esto es, decrementa la cuenta, quita
el fichero desde anon_list
y lo añade a la free_list
,
bajo la protección del spinlock files_lock
.
El tsk->files
puede ser compartido entre padre e hijo si el
hilo hijo fue creado usando la llamada al sistema clone()
con
la bandera CLONE_FILES
establecida en los argumentos de las
banderas de clone. Esto puede ser visto en
kernel/fork.c:copy_files()
(llamada por do_fork()
) el
cual sólo incrementa el file->count
si CLONE_FILES
está establecido, en vez de la copia usual de la tabla de descriptores
de ficheros en la tradición respetable en el tiempo de los
clásicos fork(2) de UNIX.
Cuando un fichero es abierto, la estructura del fichero asignada para él
es instalada en el slot current->files->fd[fd]
y un bit
fd
es establecido en el bitmap
current->files->open_fds
. Todo esto es realizado bajo la
protección de escritura del spinlock read-write
current->files->file_lock
. Cuando el descriptor es cerrado un
bit fd
es limpiado en current->files->open_fds
y
current->files->next_fd
es establecido igual a fd
como una indicación para encontrar el primer descriptor sin usar la
próxima vez que este proceso quiera abrir un fichero.
La estructura de ficheros es declarada en include/linux/fs.h
:
struct fown_struct {
int pid; /* pid o -pgrp donde SIGIO debería de ser enviado */
uid_t uid, euid; /* uid/euid del proceso estableciendo el dueño */
int signum; /* posix.1b rt señal para ser enviada en ES */
};
struct file {
struct list_head f_list;
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
int f_error;
unsigned long f_version;
/* necesitado para este controlador tty, y quizás por otros */
void *private_data;
};
Déjanos mirar varios de los campos de struct file
:
sb->s_files
lista
de todos los ficheros abiertos en este sistema de ficheros, si
el correspondiente inodo no es anónimo, entonces
dentry_open()
(llamado por filp_open()
)
enlaza el fichero en esta lista;
b) fs/file_table.c:free_list
, conteniendo las
estructuras de ficheros sin utilizar;
c) fs/file_table.c:anon_list
, cuando una nueva
estructura de ficheros es creada por get_empty_filp()
es colocada en esta lista. Todas estas listas son
protegidas por el spinlock files_lock
.
open_namei()
(o más bien path_walk()
la cual
lo llama a él) pero el campo actual file->f_dentry
es
establecido por dentry_open()
para contener la dentry
de esta forma encontrada.
vfsmount
del sistema de ficheros conteniendo el fichero. Esto es
establecido por dentry_open()
, pero es encontrado como
una parte de la búsqueda de nameidata por open_namei()
(o
más bien path_init()
la cual lo llama a él).
file_operations
, el cual
contiene varios métodos que pueden ser llamados desde el
fichero. Esto es copiado desde inode->i_fop
que
es colocado aquí durante la búsqueda nameidata. Miraremos
los métodos file_operations
en detalle más tarde
en esta sección.
get_file/put_filp/fput
.
O_XXX
desde la llamada al
sistema open(2) copiadas allí (con ligeras
modificaciones de filp_open()
) por
dentry_open()
y después de limpiar O_CREAT
,
O_EXCL
, O_NOCTTY
, O_TRUNC
- no hay
sitio para almacenar estas banderas permanentemente ya que
no pueden ser modificadas por las llamadas
fcntl(2) F_SETFL
(o consultadas por
F_GETFL
).
dentry_open()
.
El punto de conversión es almacenar los accesos de lectura y
escritura en bits separados, por lo tanto uno los chequearía
fácilmente como (f_mode & FMODE_WRITE)
y
(f_mode & FMODE_READ)
.
long long
, esto es un valor de 64 bits.
SIGIO
(ver fs/fcntl.c:kill_fasync()
).
get_empty_filp()
. Si el fichero es un conector,
usado por netfilter ipv4.
fs/nfs/file.c
y chequeado en mm/filemap.c:generic_file_write()
.
event
global)
cuando cambia f_pos
.
file->f_dentry->d_inode->i_rdev
.
Ahora déjanos mirar en la estructura file_operations
la cual
contiene los métodos que serán llamados en los archivos. Déjanos
recalcar que es copiado desde inode->i_fop
donde es
establecido por el método s_op->read_inode()
. Se declara
en include/linux/fs.h
:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
};
THIS_MODULE
, los sistemas de ficheros puede felizmente
ignorarlos porque sus cuentas de módulos son controladas en
el tiempo de montaje/desmontaje, en cambio los controladores
necesitan controlarlo en tiempo de apertura/liberación.
fs/read_write.c:default_llseek()
, la cual hace lo
correcto (POR HACER: fuerza a todos aquellos que establecen a
NULL actualmente a usar default_llseek - que es el camino por el
que salvamos una if()
en llseek()
).
read(2)
.
Los sistemas de ficheros pueden usar
mm/filemap.c:generic_file_read()
para ficheros
regulares y fs/read_write.c:generic_read_dir()
(la cual simplemente devuelve -EISDIR
) para directorios
aquí.
mm/filemap.c:generic_file_write()
para ficheros
regulares e ignorarlo para directorios aquí.
FIBMAP
,
FIGETBSZ
, FIONREAD
son implementados por
niveles más altos y por lo tanto nunca leerán el método
f_op->ioctl()
.
dentry_open()
. Los sistemas de ficheros raramente
usan esto, ej. coda intenta almacenar el fichero localmente
en tiempo de apertura.
release()
a continuación). El único sistema de ficheros que lo utiliza es
en un cliente NFS para pasar a disco todas las páginas sucias.
Nótese que esto puede devolver un error el cual será retornado
al espacio de usuario que realizó la llamada al sistema
close(2).
file->f_count
llega a 0.
Aunque definido como un entero de retorno, el valor de retorno
es ignorado por VFS (ver fs/file_table.c:__fput()
).
file =
fget(fd)
) y bajar/subir el semáforo inode->i_sem
.
El sistema de ficheros Ext2 ignora el último argumento y
realiza lo mismo para fsync(2) y fdatasync(2).
file->f_flags & FASYNC
.
posix_lock_file()
), si tiene éxito pero el código de
bloqueo estandart POSIX falla, entonces nunca será desbloqueado
en un nivel dependiente del sistema de ficheros...
Bajo Linux, la información sobre los sistemas de ficheros montados es
mantenida en dos estructuras separadas - super_block
y
vfsmount
. El motivo para esto es que Linux permite montar el
mismo sistema de ficheros (dispositivo de bloque) bajo múltiples puntos
de montaje, lo cual significa que el mismo super_block
puede
corresponder a múltiples estructuras vfsmount
.
Déjanos mirar primero en struct super_block
, declarado en
include/linux/fs.h
:
struct super_block {
struct list_head s_list; /* Mantiene esto primero */
kdev_t s_dev;
unsigned long s_blocksize;
unsigned char s_blocksize_bits;
unsigned char s_lock;
unsigned char s_dirt;
struct file_system_type *s_type;
struct super_operations *s_op;
struct dquot_operations *dq_op;
unsigned long s_flags;
unsigned long s_magic;
struct dentry *s_root;
wait_queue_head_t s_wait;
struct list_head s_dirty; /* inodos sucios */
struct list_head s_files;
struct block_device *s_bdev;
struct list_head s_mounts; /* vfsmount(s) de este */
struct quota_mount_options s_dquot; /* Opciones Específicas de Diskquota */
union {
struct minix_sb_info minix_sb;
struct ext2_sb_info ext2_sb;
..... todos los sistemas de archivos necesitan sb-private ...
void *generic_sbp;
} u;
/*
* El siguiente campo es *sólo* para VFS. Los sistemas de ficheros
* no tienen trabajo alguno mirando en él. Has sido avisado.
*/
struct semaphore s_vfs_rename_sem; /* Truco */
/* El siguiente campo es usado por knfsd cuando convierte un
* manejador de ficheros (basado en el número de inodo) en
* una dentry. Tal como construye un camino en el árbol dcache
* desde el fondo hasta arriba, quizás exista durante algún
* tiempo un subcamino de dentrys que no están conectados al
* árbol principal. Este semáforo asegura que hay sólo
* siempre un camino libre por sistema de ficheros. Nótese que
* los ficheros no conectados (o otros no directorios) son
* permitidos, pero no los directorios no conectados.
*/
struct semaphore s_nfsd_free_path_sem;
};
Las diversos campos en la estructura super_block
son:
FS_REQUIRES_DEV
, esto es la i_dev
del
dispositivo de bloques. Para otros (llamados sistemas de
ficheros anónimos) esto es un entero MKDEV(UNNAMED_MAJOR,
i)
donde i
es el primer bit no establecido en
la matriz unnamed_dev_in_use
, entre 1 y 255 incluidos.
Ver fs/super.c:get_unnamed_dev()/put_unnamed_dev()
.
Ha sido sugerido muchas veces que los sistemas de ficheros
anónimos no deberían de usar el campo s_dev
.
lock_super()/unlock_super()
.
struct file_system_type
del
sistema de ficheros correspondiente. El método
read_super()
del sistema de ficheros no necesita ser
establecido como VFS fs/super.c:read_super()
, lo
establece para ti si el read_super()
que es específico
del sistema de ficheros tiene éxito, y se reinicializa a NULL si
es que falla.
super_operations
,
la cual contiene métodos específicos del sistema de ficheros
para leer/escribir inodos, etc. Es el trabajo del método
read_super()
del sistema de ficheros inicializar
s_op
correctamente.
read_super()
leer el inodo raiz desde el
disco y pasárselo a d_alloc_root()
para asignar la
dentry e instanciarlo. Algunos sistemas de ficheros dicen
"raiz" mejor que "/" y por lo tanto usamos la función más
genérica d_alloc()
para unir la dentry a un nombre, ej.
pipefs se monta a si mismo en "pipe:" como su raiz en vez de
"/".
inode->i_state & I_DIRTY
)
entonces su lista sucia específica del superbloque es
enlazada a través de inode->i_list
.
fs/file_table.c:fs_may_remount_ro()
el cual va a
través de la lista sb->s_files
y deniega el remontar
si hay ficheros abiertos para escritura (file->f_mode &
FMODE_WRITE
) o ficheros con desenlaces pendientes
(inode->i_nlink == 0
).
FS_REQUIRES_DEV
, esto apunta a la
estructura block_device describiendo el dispositivo en el que
el sistema de ficheros está montado.
vfsmount
, una por cada instancia montada de este
superbloque.
Las operaciones de superbloque son descritas en la estructura
super_operations
declarada en include/linux/fs.h
:
struct super_operations {
void (*read_inode) (struct inode *);
void (*write_inode) (struct inode *, int);
void (*put_inode) (struct inode *);
void (*delete_inode) (struct inode *);
void (*put_super) (struct super_block *);
void (*write_super) (struct super_block *);
int (*statfs) (struct super_block *, struct statfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*clear_inode) (struct inode *);
void (*umount_begin) (struct super_block *);
};
fs/inode.c:get_new_inode()
, y desde
iget4()
(y por consiguiente iget()
). Si un
sistema de ficheros quiere usar iget()
entonces
read_inode()
debe de ser implementado - en otro caso
get_new_inode()
fallará.
Mientras el inodo está siendo leido está bloqueado
(inode->i_state = I_LOCK
). Cuando la función regresa,
todos los que están esperando en inode->i_wait
son
despertados. El trabajo del método read_inode()
del
sistema de ficheros es localizar el bloque del disco que
contiene el inodo a ser leído y usar la funcion de la antememoria intermedia
bread()
para leerlo e inicializar varios campos de la
estructura de inodos, por ejemplo el inode->i_op
y
inode->i_fop
para que los niveles VFS conozcan qué
operaciones pueden ser efectuadas en el inodo o fichero
correspondiente.
Los sistemas de ficheros que no implementan
read_inode()
son ramfs y pipefs. Por ejemplo, ramfs
tiene su propia función de generación de inodos
ramfs_get_inode()
con todas las operaciones de inodos
llamándola cuando se necesita.
read_inode()
en que necesita localizar el bloque
relevante en el disco e interactuar con la antememoria intermedia llamando
a mark_buffer_dirty(bh)
. Este método es llamado en los
inodos sucios (aquellos marcados como sucios por
mark_inode_dirty()
) cuando el inodo necesita ser
sincronizado individualmente o como parte de la actualizacion
entera del sistema de ficheros.
inode->i_count
y
inode->i_nlink
llegan a 0. El sistema de ficheros borra
la copia en disco del inodo y llama a clear_inode()
en
el inodo VFS para "terminar con él con el perjuicio extremo".
brelse()
el bloque conteniendo el superbloque y
kfree()
cualesquiera bitmaps asignados para bloques
libres, inodos, etc.
sb-private
) y mark_buffer_dirty(bh)
. También
debería de limpiar la bandera sb->s_dirt
.
struct statfs
pasado como argumento, es el puntero del
núcleo, no un puntero del usuario, por lo tanto no necesitamos
hacer ninguna E/S al espacio de usuario. Si no está implementada
entonces statfs(2)
fallará con ENOSYS
.
clear_inode()
. Los sistemas que atacan datos privados
a la estructura del inodo (a través del campo
generic_ip
) deben liberarse aquí.
Por lo tanto, déjanos mirar qué pasa cuando montamos un sistema de
ficheros en disco (FS_REQUIRES_DEV
). La implementación de la
llamada al sistema mount(2) está en
fs/super.c:sys_mount()
que es justo un envoltorio que copia
las opciones, el tipo del sistema de ficheros y el nombre del dispositivo
para la función do_mount()
, la cual realiza el trabajo real:
do_mount()
llamando a get_fs_type()
y otra vez
por get_sb_dev()
llamando a get_filesystem()
si
read_super()
tuvo éxito. El primer incremento es para
prevenir la descarga del módulo mientras estamos dentro del
método read_super()
, y el segundo incremento es para
indicar que el módulo está en uso por esta instancia montada.
Obviamente, do_mount()
decrementa la cuenta antes de
regresar, por lo tanto, después de todo, la cuenta sólo crece en
1 después de cada montaje.
fs_type->fs_flags & FS_REQUIRES_DEV
es verdad, el superbloque es inicializado por una llamada a
get_sb_bdev()
, la cual obtiene la referencia al
dispositivo de bloques e interactúa con el método
read_super()
del sistema de ficheros para rellenar el
superbloque. Si todo va vien, la estructura super_block
es inicializada y tenemos una referencia extra al módulo del
sistema de ficheros y una referencia al dispositivo de bloques
subyacente.
vfsmount
es asignada y enlazada a
la lista sb->s_mounts
y a la lista global
vfsmntlist
. El campo vfsmount
de
mnt_instances
nos permite encontrar todas las instancias
montadas en el mismo superbloque que este. El campo
mnt_list
nos permite encontrar todas las instancias
para todos los superbloques a lo largo del sistema. El campo
mnt_sb
apunta a este superbloque y mnt_root
tiene una nueva referencia a la dentry sb->s_root
.Como un ejemplo simple del sistema de ficheros de Linux que no requiere
un dispositivo de bloque para montar, déjanos considerar pipefs desde
fs/pipe.c
. El preámbulo del sistema de ficheros es bastante
directo y requiere una pequeña explicación:
static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super,
FS_NOMOUNT|FS_SINGLE);
static int __init init_pipe_fs(void)
{
int err = register_filesystem(&pipe_fs_type);
if (!err) {
pipe_mnt = kern_mount(&pipe_fs_type);
err = PTR_ERR(pipe_mnt);
if (!IS_ERR(pipe_mnt))
err = 0;
}
return err;
}
static void __exit exit_pipe_fs(void)
{
unregister_filesystem(&pipe_fs_type);
kern_umount(pipe_mnt);
}
module_init(init_pipe_fs)
module_exit(exit_pipe_fs)
El sistema de ficheros es del tipo FS_NOMOUNT|FS_SINGLE
, lo que
significa que no puede ser montado desde el espacio de usuario y sólo
puede haber uno en el sistema. El fichero FS_SINGLE
también
significa que debe de ser montado a través de kern_mount()
después de que haya sido registrado con éxito a través de
register_filesystem()
, lo cual es exactamente lo que pasa en
init_pipe_fs()
. El único fallo en esta función es que si
kern_mount()
falla (ej. porque kmalloc()
falló en
add_vfsmnt()
) entonces el sistema de ficheros es dejado como
registrado pero la inicialización del módulo falla. Esto causará que
cat /proc/filesystems falle (justamente acabo de enviar un
parche a Linus mencionándole que esto no es un fallo real hoy
en día porque pipefs no puede ser compilado como módulo, debería de
ser escrito desde el punto de vista de que en el futuro pudiera ser
modularizado).
El resultado de register_filesystem()
es que
pipe_fs_type
es enlazado en la lista file_systems
, por
lo tanto uno puede leer /proc/filesystems
y encontrar
la entrada "pipefs" allí con la bandera "nodev" indicando que
FS_REQUIRES_DEV
no fue establecida.
El archivo /proc/filesystems
debería realmente de ser capaz de
soportar todas las nuevas banderas FS_
(y yo he hecho un parche
que lo hace) pero no puede ser realizado porque hará fallar a todas las
aplicaciones de usuario que lo utilicen. A pesar de que los interfaces del
núcleo Linux cambian cada minuto (sólo para mejor) cuando se refiere a
la compatibilidad del espacio de usuario, Linux es un sistema operativo
muy conservador que permite que muchas aplicaciones sean usadas durante
un largo periodo de tiempo sin ser recompiladas.
El resultado de kern_mount()
es que:
unnamed_dev_in_use
;
si no hay más bits entonces kern_mount()
fallará con
EMFILE
.
get_empty_super()
. La función
get_empty_super()
camina a través de las cabeceras de
las lista de superbloques por super_block
y busca
una entrada vacía, esto es s->s_dev == 0
. Si no
se encuentra dicho superbloque vacío entonces uno nuevo es
asignado usando kmalloc()
con la prioridad
GFP_USER
. El número máximo de superbloques en el
sistema es chequeado en get_empty_super()
, por lo tanto
empieza fallando, uno puede modificar el parámetro ajustable
/proc/sys/fs/super-max
.
pipe_fs_type->read_super()
, esto es
pipefs_read_super()
, es invocada, la cual asigna el
inodo y la dentry raiz sb->s_root
, y establece
sb->s_op
para ser &pipefs_ops
.
kern_mount()
llama a add_vfsmnt(NULL,
sb->s_root, "none")
la cual asigna una nueva estructura
vfsmount
y la enlaza en vfsmntlist
y
sb->s_mounts
.
pipe_fs_type->kern_mnt
es establecido a esta nueva
estructura vfsmount
y es devuelta. El motivo por el que
el valor de retorno de kern_mount()
es una estructura
vfsmount
es porque incluso los sistemas de ficheros
FS_SINGLE
pueden ser montados múltiples veces y
por lo tanto sus mnt->mnt_sb
deberían apuntar a la
misma cosa, que sería tonto devolverla desde múltiples
llamadas a kern_mount()
.Ahora que el sistema de ficheros está registrado y montado dentro del
núcleo podemos usarlo. El punto de entrada en el sistema de ficheros
pipefs es la llamada al sistema pipe(2), implementada por una
función dependiente de la arquitectura sys_pipe()
, pero el
trabajo real es realizado por un función portable
fs/pipe.c:do_pipe()
. Déjanos mirar entonces en do_pipe()
.
La interacción con pipefs sucede cuando do_pipe()
llama a
get_pipe_inode()
para asignar un nuevo inodo pipefs. Para este
inodo, inode->i_sb
es establecido al superbloque de pipefs
pipe_mnt->mnt_sb
, las operaciones del archivo i_fop
son
establecidas a rdwr_pipe_fops
y el número de lectores y
escritores (mantenidos en inode->i_pipe
) es establecido a 1. El
motivo por el que hay un campo de inodos separado i_pipe
en
vez de mantenerlo en la unión fs-private
es que pipes y FIFOs
comparten el mismo código y los FIFOs puede existir en otros sistemas
de ficheros los cuales usan otros caminos de acceso con la misma unión,
lo cual en C es muy malo y puede trabajar sólo con pura suerte. Por lo
tanto, sí, los núcleos 2.2.x trabajan por pura suerte y pararán de
trabajar tan pronto como tu retoques ligeramente los campos en el inodo.
Cada llamada al sistema pipe(2) incrementa una cuenta de
referencia en la instancia de montaje pipe_mnt
.
Bajo Linux, los pipes no son simétricos (pipes STREAM o
bidireccionales) esto es, dos caras del mismo fichero tienes diferentes
operaciones file->f_op
- la read_pipe_fops
y
write_pipe_fops
respectivamente. La escritura en la cara de
lectura devuelve un EBADF
y lo mismo si se lee en la cara de
escritura.
Como un simple ejemplo de un sistema de ficheros Linux en disco,
déjanos considerar BFS. El preámbulo del módulo de BFS está en
fs/bfs/inode.c
:
static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super);
static int __init init_bfs_fs(void)
{
return register_filesystem(&bfs_fs_type);
}
static void __exit exit_bfs_fs(void)
{
unregister_filesystem(&bfs_fs_type);
}
module_init(init_bfs_fs)
module_exit(exit_bfs_fs)
Una declaracion especial de la macro del sistema de ficheros
DECLARE_FSTYPE_DEV()
es usada, la cual establece el
fs_type->flags
a FS_REQUIRES_DEV
para significar que
BFS requiere un dispositivo de bloque real para ser montado.
La función de inicialización del módulo registra el sistema de ficheros con VFS y la función de limpieza (sólo presente cuando BFS está configurada para ser un módulo) lo desregistra.
Con el sistema de ficheros registrado, podemos proceder a montarlo, lo
cual invocará al método fs_type->read_super()
que es
implementado en fs/bfs/inode.c:bfs_read_super().
. El realiza lo
siguiente:
set_blocksize(s->s_dev, BFS_BSIZE)
: desde que nosotros
vamos a interactuar con la capa de dispositivos de bloque a
través de la antememoria intermedia, debemos inicializar unas pocas cosas,
esto es, establecer el tamaño del bloque y también informar a VFS
a través de los campos s->s_blocksize
y
s->s_blocksize_bits
.
bh = bread(dev, 0, BFS_BSIZE)
: leemos el bloque 0 del
dispositivo a través de s->s_dev
. Este bloque es el
superbloque del sistema.
BFS_MAGIC
y,
si es válido, es almacenado en el campo sb-private
s->su_sbh
(el cual es realmente
s->u.bfs_sb.si_sbh
).
kmalloc(GFP_KERNEL)
y limpiamos todos sus bits a 0,
excepto los dos primeros, los cuales estableceremos a 1 para
indicar que nunca deberemos asignar los inodos 0 y 1. El inodo
2 es la raiz y el correspondiente bit será establecido a 1 unas
pocas lineas después de cualquier forma - ¡el sistema de
ficheros debe de tener un inodo raiz válido en tiempo de
montaje!
s->s_op
, lo cual significa que
podemos desde este punto llamar a la memoria intermedia de inodos a través de
iget()
, lo cual resulta en la invocación de
s_op->read_inode()
. Esto encuentra el bloque que
contiene el inodo especificado (por inode->i_ino
y
inode->i_dev
) y lo lee. Si fallamos al obtener el inodo
raiz entonces liberamos el bitmap de inodos y descargaremos
la antememoria de superbloque a la antememoria intermedia y devolveremos NULL. Si
el inodo raiz fue leido correctamente, entonces asignamos una dentry con el
nombre /
(como convirtiéndolo en raiz) y lo
instanciamos con este inodo.
iput()
- no mantenemos
una referencia a él más tiempo del necesario.
s->s_dirt
(POR HACER: ¿Porqué hago esto?
Originalmente, lo hice porque lo hacía
minix_read_super()
pero ni minix ni BFS parecen
modificar el superbloque en el read_super()
).
fs/super.c:read_super()
.Después de que la función read_super()
regrese con éxito, VFS
obtiene la referencia al módulo del sistema de ficheros a través de
la llamada a get_filesystem(fs_type)
en
fs/super.c:get_sb_bdev()
y una referencia al dispositivo de
bloques.
Ahora, déjanos examinar qué pasa cuando hacemos una E/S en el
sistema de ficheros. Ya hemos examinado cómo los inodos son leidos
cuando iget()
es llamado y cómo son quitados en
iput().
Leyendo inodos, configura entre otras cosas,
inode->i_op
y
inode->i_fop
; abriendo un fichero propagará
inode->i_fop
en file->f_op
.
Déjanos examinar el camino de código de la llamada al sistema
link(2). La implementación de la llamada al sistema está en
fs/namei.c:sys_link()
:
getname()
la cual realiza el chequeo de errores.
path_init()/path_walk()
interactuando con dcache. El
resultado es almacenado en las estructuras old_nd
y nd
.
old_nd.mnt != nd.mnt
entonces "enlace a través de
dispositvos" EXDEV
es devuelto - uno no puede enlazar
entre sistemas de ficheros, en Linux esto se traduce en -
uno no puede enlazar entre múltiples instancias de un sistema
de ficheros (o, en particular entre sistemas de ficheros).
nd
por
lookup_create()
.
vfs_link()
es llamada, la cual
chequea si podemos crear una nueva entrada en el directorio e
invoca el método dir->i_op->link()
, que nos
trae atrás a la función específica del sistema de ficheros
fs/bfs/dir.c:bfs_link()
.
bfs_link()
, chequeamos si estamos intentando
enlazar un directorio, y si es así, lo rechazamos con un error
EPERM
. Este es el mismo comportamiento que el estándar
(ext2).
bfs_add_entry()
la cual va a través de todas las entradas buscando un slot
sin usar (de->ino == 0
) y, cuando lo encuentra, escribe
en el par nombre/inodo en el bloque correspondiente y lo
marca como sucio (a una prioridad no-superbloque).
inode->i_nlink
, actualizamos inode->i_ctime
y
marcamos este inodo como sucio a la vez que instanciamos la nueva
dentry con el inodo.Otras operaciones de inodos relacionadas como unlink()/rename()
etc. trabajan en una forma similar, por lo tanto no se gana mucho
examinándolas a todas ellas en detalle.
Linux soporta la carga de aplicaciones binarias de usuario desde disco. Más interesantemente, los binarios pueden ser almacenados en formatos diferentes y la respuesta del sistema operativo a los programas a través de las llamadas al sistema pueden desviarla de la norma (la norma es el comportamiento de Linux) tal como es requerido, en orden a emular los formatos encontrados en otros tipos de UNIX (COFF, etc) y también emular el comportamiento de las llamadas al sistema de otros tipos (Solaris, UnixWare, etc). Esto es para lo que son los dominios de ejecución y los formatos binarios.
Cada tarea Linux tiene una personalidad almacenada en su
task_struct
(p->personality
). Las
personalidades actualmente existentes (en el núcleo oficial o en el
parche añadido) incluyen soporte para FreeBSD, Solaris, UnixWare,
OpenServer y algunos otros sistemas operativos populares. El valor de
current->personality
es dividido en dos partes:
STICKY_TIMEOUTS
,
WHOLE_SECONDS
, etc.Cambiando la personalidad, podemos cambiar la forma en la que el sistema
operativo trata ciertas llamadas al sistema, por ejemplo añadiendo una
STICKY_TIMEOUT
a current->personality
hacemos que la
llamada al sistema select(2) preserve el valor del último
argumento (timeout) en vez de almacenar el tiempo no dormido. Algunos
programas defectuosos confían en sistemas operativos defectuosos (no
Linux) y por lo tanto suministra una forma para emular fallos en casos
donde el código fuente no está disponible y por lo tanto los fallos no
pueden ser arreglados.
El dominio de ejecución es un rango contiguo de personalidades implementadas por un módulo simple. Usualmente un dominio de ejecución simple implementa una personalidad simple, pero a veces es posible implementar personalidades "cerradas" en un módulo simple sin muchos condicionantes.
Los dominios de ejecución son implementados en
kernel/exec_domain.c
y fueron completamente reescritos para el
núcleo 2.4, comparado con el 2.2.x. La lista de dominios de ejecución
actualmente soportada por el núcleo, a lo largo del rango de
personalidades que soportan, está disponible leyendo el archivo
/proc/execdomains
. Los dominios de ejecución, excepto el
PER_LINUX
, pueden ser implementados como módulos
dinámicamente cargados.
La interfaz de usuario es a través de la llamada al sistema
personality(2), la cual establece la actual personalidad del
proceso o devuelve el valor de current->personality
si el
argumento es establecido a una personalidad imposible. Obviamente, el
comportamiento de esta llamada al sistema no depende de la
personalidad.
La interfaz del núcleo para el registro de dominios de ejecución consiste en dos funciones:
int register_exec_domain(struct exec_domain *)
: registra
el dominio de ejecución enlazándolo en una lista simplemente enlazada
exec_domains
bajo la protección de escritura del
spinlock read-write exec_domains_lock
. Devuelve 0 si
tiene éxito, distinto de cero en caso de fallo.
int unregister_exec_domain(struct exec_domain *)
:
desregistra el dominio de ejecución desenlazándolo desde la
lista exec_domains
, otra vez usando el spinlock
exec_domains_lock
en modo de escritura. Retorna 0 si
tiene éxito.El motivo por el que exec_domains_lock
es read-write es que
sólo las peticiones de registro y desregistro modifican la lista,
mientras haciendo cat /proc/filesystems llama
fs/exec_domain.c:get_exec_domain_list()
, el cual necesita sólo
acceso de lectura a la lista. Registrando un nuevo dominio de ejecución
define un "manejador lcall7" y un mapa de conversión de número de
señales. Actualmente, el parche ABI extiende este concepto a los
dominios de ejecución para incluir información extra (como opciones de
conector, tipos de conector, familia de direcciones y mapas de
números de errores).
Los formatos binarios son implementados de una forma similar, esto es,
una lista simplemente enlazada de formatos es definida en
fs/exec.c
y es protegida por un cierre read-write
binfmt_lock
. Tal como con exec_domains_lock
, el
binfmt_lock
es tomado para leer en la mayoría de las ocasiones
excepto para el registro/desregistro de los formatos binarios.
Registrando un nuevo formato binario intensifica la llamada al sistema
execve(2) con nuevas funciones
load_binary()/load_shlib()
. Al igual que la habilidad para
core_dump()
. El método load_shlib()
es usado sólo
por la vieja llamada al sistema uselib(2) mientras que el
método load_binary()
es llamada por el
search_binary_handler()
desde do_execve()
el cual
implementa la llamada al sistema execve(2).
La personalidad del proceso está determinada por el formato binario
cargado por el método del correspondiente formato load_binary()
usando algunas heurísticas. Por ejemplo, para determinar los binarios
UnixWare7, uno primero marca el binario usando la utilidad
elfmark(1), la cual establece la cabecera de ELF
e_flags
al valor mágico 0x314B4455, el cual es detectado en
tiempo de carga del ELF y current->personality
es establecido a
PER_SVR4. Si esta heurística falla entonces una más genérica como el
tratamiento de los caminos del intérprete ELF como
/usr/lib/ld.so.1
o /usr/lib/libc.so.1
para indicar un
binario SVR4, es usado y la personalidad es establecida a PER_SVR4. Uno
podría escribir un pequeño programa de utilidad que usara las
capacidades del ptrace(2) de Linux para, en un simple paso,
códificar y forzar a un programa funcionando a cualquier personalidad.
Una vez que la personalidad (y entonces current->exec_domain
)
es conocida, las llamadas al sistema son manejadas como sigue. Déjanos
asumir que un proceso realiza una llamada al sistema por medio de la
instrucción puerta lcall7. Esto transfiere el control a
ENTRY(lcall7)
de arch/i386/kernel/entry.S
porque fue
preparado en arch/i386/kernel/traps.c:trap_init()
. Después de
la apropiada conversión de la pila, entry.S:lcall7
obtiene el
puntero a exec_domain
desde current
y entonces un
desplazamiento del manejador lcall7 con el exec_domain
(el cual
es codificado fuertemente como 4 en código ensamblador, por lo tanto no
puedes desplazar el campo handler
a través de la declaración
en C de struct exec_domain
) y salta a él. Por lo tanto, en C,
se parecería a esto:
static void UW7_lcall7(int segment, struct pt_regs * regs)
{
abi_dispatch(regs, &uw7_funcs[regs->eax & 0xff], 1);
}
donde abi_dispatch()
es un envoltorio sobre la tabla de
punteros de función que implementa las llamadas al sistema de esta
personalidad uw7_funcs
.