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:
- Un nuevo número de dispositvo sin nombre (anónimo) es asignado estableciendo un bit en el bitmap unnamed_dev_in_use; si no hay más bits entonces kern_mount() fallará con EMFILE.
- Una nueva estructura superbloque es asignada por medio de 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.
- Un método específico del sistema de ficheros 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.
- Entonces 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.
- El 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.