Sistema de Ficheros
Introducción
Una vez tenemos construida la ToolChain para compilar contra PowerPC, el empaquetado del sistema de ficheros con las herramientas y aplicaciones construido por BuildRoot, y el Kernel configurado, construido e insertado en el Atlas, en vez de generar directamente una imagen del sistema de ficheros lista para empotrar, resulta mucho más práctico en tiempo de investigación y desarrollo desempaquetar la jerarquía creada por BuildRoot en un directorio local a cada estación de trabajo y exportar dicha jerarquía por el protocolo de red NFS como sistema de ficheros raiz del router Linux cliente. Evidentemente, esta alternativa no sirve para realizar pruebas de rendimiento de cara al producto final por el ruido inyectado al trabajar con un sistema de ficheros remoto, pero resulta imprescindible para poder grabar la memoria Flash y programar cómodamente contra la plataforma.
Desempaquetamos, con permisos de administración, el TAR generado por BuildRoot formado por el sistema de ficheros raiz en un directorio local a la máquina de trabajo. Se construye así la jerarquía de ficheros y directorios que importaremos desde el router de forma remota vía NFS para montarlo como sistema de ficheros raiz. También servirá como punto inicial para las imágenes de los distintos sistemas de ficheros que crearemos y empotraremos en la Flash para su posterior evaluación.
Puesta a punto
Antes de asentar el sistema de ficheros, es necesario realizar pequeños ajustes sobre algunos ficheros de configuración del sistema operativo para adaptarlo a las decisiones de diseño tomadas:
Modificamos el fichero /etc/inittab cambiando de orden los mandatos /bin/mount -o remount,rw /
y /bin/mount -t proc proc /proc
entre sí. A continuación, añadimos el dropbear para que sea lanzado cada vez que se arranque el router.
El fichero, inicialmente, tiene el siguiente contenido:
# Startup the system null::sysinit:/bin/mount -o remount,rw / null::sysinit:/bin/mount -t proc proc /proc null::sysinit:/bin/mount -a null::sysinit:/bin/hostname -F /etc/hostname null::sysinit:/sbin/ifconfig lo 127.0.0.1 up null::sysinit:/sbin/route add -net 127.0.0.0 netmask 255.0.0.0 lo # now run any rc scripts ::sysinit:/etc/init.d/rcS # Set up a couple of getty's tty1::respawn:/sbin/getty 38400 tty1 tty2::respawn:/sbin/getty 38400 tty2 # Put a getty on the serial port #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100
Y una vez que se aplican todos los cambios anteriormente mencionados, queda de la siguiente manera:
# Startup the system null::sysinit:/bin/mount -t proc proc /proc null::sysinit:/bin/mount -o remount,rw / null::sysinit:/bin/mount -a null::sysinit:/bin/hostname -F /etc/hostname null::sysinit:/sbin/ifconfig lo 127.0.0.1 up null::sysinit:/sbin/route add -net 127.0.0.0 netmask 255.0.0.0 lo null::respawn:/usr/sbin/dropbear # now run any rc scripts ::sysinit:/etc/init.d/rcS # Set up a couple of getty's tty1::respawn:/sbin/getty 38400 tty1 tty2::respawn:/sbin/getty 38400 tty2 # Put a getty on the serial port #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 console::respawn:/sbin/getty -L console 115200 vt100
Llegados a este punto, hemos de definir una contraseña de root. Para ello, empleamos la aplicación openssl tal y como se explica a continuación.
Si queremos crear una contraseña de tipo DES, hacemos lo siguiente:
> openssl passwd <contraseña>
En cambio, si lo que queremos es una contraseña de tipo MD5, entonces ejecutamos lo siguiente:
> openssl passwd -1 <contraseña>
Sea cuál sea la contraseña que se elija, se copia y se pega en el lugar correspondiente dentro del fichero /etc/shadow, de tal forma que si el fichero tenía inicialmente el siguiente contenido:
root::10933:0:99999:7::: bin:*:10933:0:99999:7::: daemon:*:10933:0:99999:7::: adm:*:10933:0:99999:7::: lp:*:10933:0:99999:7::: sync:*:10933:0:99999:7::: shutdown:*:10933:0:99999:7::: halt:*:10933:0:99999:7::: uucp:*:10933:0:99999:7::: operator:*:10933:0:99999:7::: nobody:*:10933:0:99999:7::: default::10933:0:99999:7:::
Ahora pasa a tener el siguiente:
root:<contraseña_encriptada>:10933:0:99999:7::: bin:*:10933:0:99999:7::: daemon:*:10933:0:99999:7::: adm:*:10933:0:99999:7::: lp:*:10933:0:99999:7::: sync:*:10933:0:99999:7::: shutdown:*:10933:0:99999:7::: halt:*:10933:0:99999:7::: uucp:*:10933:0:99999:7::: operator:*:10933:0:99999:7::: nobody:*:10933:0:99999:7::: default::10933:0:99999:7:::
Ya sólo nos queda habilitar el login del ROOT por el terminal CONSOLE porque viene desactivado por omisión, esto se hace efectivo permitiendo dicho interfaz en el archivo /etc/securetty:
console
NFS
Lo primero que hay que hacer es dotar de soporte NFS al kernel del router para que pueda actuar de cliente hacia el servidor NFS que instalamos en la estación de trabajo, la implementación en espacio de núcleo del mismo nos servirá:
> sudo apt-get install nfs-kernel-server
Ahora, le indicamos al sevidor el directorio que debe exportar agregando la siguiente línea al fichero /etc/exports:
/NFSroot 138.100.9.0/24(rw,sync,no_root_squash,subtree_check)
En este caso, se indica que la jerarquía exportada con permisos de lectura y escritura a la red del laboratorio 138.100.9.0/24 cuelga del directorio local /NFSroot.
Se fuerza el reprocesado de este archivo de configuración con el siguiente mandato:
> exportfs -ra
Desde este momento, cualquiera de las máquinas del laboratorio puede montar remotamente la jerarquía previamente retocada siendo el router el que lo haga como sistema de ficheros raiz a través de la siguiente cadena de argumentos al kernel:
> console=ttyCPM0,9600n8n root=/dev/nfs ip=138.100.9.219:138.100.9.117:138.100.8.125:255.255.248.0:atlas50linux:eth0:off nfsroot=/NFSroot/ rw
Se detallan a continuación estos parámetros de arranque accesibles desde el BootLoader y almacenados en la memoria I2C que posee el Atlas 50:
- console=ttyCPM0,9600n8n
enlaza el interfaz CONSOLA de entrada/salida con el controlador del puerto serie de administración configurándolo a 9600 baudios, 8 bits de datos y ninguno de parada, paridad impar y sin control de flujo.
- root=/dev/nfs
indica al Kernel que debe cargar el directorio raiz a través del protocolo NFS.
- ip=138.100.9.219:138.100.9.117:138.100.8.125:255.255.248.0:atlas50linux:eth0:off
establece respectivamente las direcciones IP del cliente (router), servidor NFS (estación de trabajo), puerta de acceso por si el servidor estuviera en otra red, máscara de red, nombre de la máquina cliente (router), interfaz de red del cliente, y si debe autoconfigurar una dirección IP asignada dinámicamente mediante otros protocolos: DHCP, BOOTP, …
- nfsroot=/NFSroot/
define la jerarquía que debe importar del servidor NFS (estación de trabajo).
- rw
indica permisos de lectura y escritura sobre la jerarquía a montar.
JFFS2
Comenzamos descargando e instalando el paquete de herramientas para el manejo de tecnologías Flash:
> sudo apt-get install mtd-utils
Posteriormente nos dirigimos al directorio donde cuelga la jerarquía exportada por NFS y creamos una imagen JFFS2 de ella:
> sudo mkfs.jffs2 --pad=14024704 -b -e 128 -m none -o /JFFS2.img
Explicamos cada uno de los parámetros según el orden de aparición en la sentencia previa:
- tamaño de la imagen alineado a 14024704 bytes correspondientes a la partición DATA completa según muestra el siguiente cuadro:
[ 11.570436] physmap-flash flash: Using OF partition information [ 11.641696] Creating 4 MTD partitions on "flash": [ 11.666634] 0x00000000-0x00080000 : "BIOS" -> 0.5 MiB [ 11.716849] 0x00080000-0x00280000 : "KERNEL" -> 2 MiB [ 11.768129] 0x00280000-0x00fe0000 : "DATA" -> 13.375 MiB [ 11.817339] 0x00fe0000-0x01000000 : "NVRAM" -> 0.125 MiB = 128 KiB -> capacidad total = 16 MiB
- BIG endianess determinado por el hardware.
- tamaño del bloque físico de borrado definido por la memoria a 128KiB.
- imagen sin comprimir.
- construir la imagen con nombre JFFS2.img en el mismo nivel.
El tamaño de página o máximo tamaño de cada nodo se queda en 4 KiB, por omisión.
El siguiente paso consiste en incrustar la imagen creada en la partición DATA de la Flash obligatoriamente desde el router, para ello basta con volcar el contenido del archivo imagen hacia el interface asociado a la tercera partición de la memoria:
[root@uclibc /]# cat JFFS2.img > /dev/mtdblock2 [root@uclibc /]# cat JFFS2.img > /dev/mtd2 no escribe información válida.
Comprobamos que JFFS2 requiere de un interfaz orientado a bloques para poder ser montado como nos advertía BuildRoot:
[root@uclibc /]# mount -t jffs2 /dev/mtd2 /mnt mount: mounting /dev/mtd2 on /mnt failed: Invalid argument [root@uclibc /]# mount -t jffs2 /dev/mtdblock2 /mnt [root@uclibc /]# cd /mnt [root@uclibc mnt]# ls bin home lib mnt root share usr dev include linuxrc opt sbin sys var etc info man proc scripts tmp [root@uclibc mnt]# df Filesystem 1k-blocks Used Available Use% Mounted on rootfs 301898572 4617316 281945612 2% / /dev/root 301898572 4617316 281945612 2% / tmpfs 30488 104 30384 0% /tmp /dev/mtdblock2 13696 12812 884 94% /mnt
Cabe destacar que este proceso de estampado habría sido muy laborioso sin la inestimable ayuda de NFS.
Finalmente, falta por modificar en el BootLoader los parámetros pasados al kernel para que tome el sistema de ficheros raiz desde la partición DATA en vez del directorio remoto exportado por NFS:
> console=ttyCPM0,9600n8n root=31:2 rootfstype=jffs2 rw
Destacamos el uso de major/minor para describir el interfaz orientado a bloques tras el cual permanece la imagen construida, al hacerse imprescindible este método de identificación en los primeros instantes de arranque del núcleo.
[root@uclibc /]# ls -la /dev | grep mtd crw-r----- 1 root root 90, 0 May 9 2008 mtd0 crw-r----- 1 root root 90, 2 May 9 2008 mtd1 crw-r----- 1 root root 90, 4 May 9 2008 mtd2 crw-r----- 1 root root 90, 6 May 9 2008 mtd3 brw-r----- 1 root root 31, 0 May 9 2008 mtdblock0 brw-r----- 1 root root 31, 1 May 9 2008 mtdblock1 brw-r----- 1 root root 31, 2 May 9 2008 mtdblock2 brw-r----- 1 root root 31, 3 May 9 2008 mtdblock3
UBIFS
Lo primero de todo es descargarse los fuentes de la versión adaptada a UBIFS de las herramientas para manejar dispositivos Flash:
> git clone git://git.infradead.org/mtd-utils.git mtd-utils_src
Instalamos varias bibliotecas en la estación de trabajo necesarias por la utilidad constructora de imágenes UBIFS:
> sudo apt-get install zlib1g-dev > sudo apt-get install liblzo2-dev > sudo apt-get install uuid-dev
Algunas de las herramientas descargadas correrán en nuestra WorkStation y otras en la plataforma Atlas, de tal manera que realizamos una compilación nativa de todas las utilidades y una compilación cruzada selectiva para aquellas cuya ejecución se limita al router: ubinfo y ubiformat.
> cd mtd-utils_src > make > make ubinfo CROSS=powerpc-linux- > make ubiformat CROSS=powerpc-linux-
Lógicamente, es necesario haber incluido previamente en la variable de entorno PATH las localizaciones tanto del compilador nativo para la estación de desarrollo como del compilador cruzado para la arquitectura PowerPC.
En este momento, debemos mover los ejecutables ubinfo y ubiformat al directorio exportado por NFS para usarlos desde el router. Listamos el conjunto de sistemas de ficheros soportados por el núcleo empotrado y verificamos la existencia de UBIFS:
[root@uclibc ~]# cat /proc/filesystems nodev sysfs nodev rootfs nodev bdev nodev proc nodev debugfs nodev sockfs nodev pipefs nodev futexfs nodev tmpfs nodev eventpollfs nodev devpts nodev ramfs nodev nfs nodev cifs nodev jffs2 nodev autofs nodev fuse fuseblk nodev fusectl nodev rpc_pipefs ubifs
Activamos el montaje del pseudo-sistema de ficheros SYSFS (requerido por UBINFO) en cada arranque del router cuya misión es exportar al espacio de usuario información acerca de las estructuras internas del Sistema Operativo:
# /etc/fstab: static file system information. # # <file system> <mount pt> <type> <options> <dump> <pass> /dev/root / ext2 rw,noauto 0 1 proc /proc proc defaults 0 0 devpts /dev/pts devpts defaults,gid=5,mode=620 0 0 tmpfs /tmp tmpfs defaults 0 0 none /sys sysfs defaults 0 0
Observamos que la capa UBI está incorporada al Kernel pero aún no tiene dispositivos asociados:
[default@uclibc /]$ ./ubinfo UBI version: 1 Count of UBI devices: 0 UBI control device major/minor: 10:63
Este sistema de ficheros se divide en dos capas software, UBIFS apoyado sobre UBI, motivo por el cual hemos de generar dos imágenes:
- primero la imagen UBIFS de la jerarquía de ficheros y directorios según la configuración típica para memorias NOR Flash
> sudo ./mkfs.ubifs -r /NFSroot -m 1 -e 130944 -c 107 -o /UBIFS.img
- NFSroot es la carpeta contenedora de la jerarquía cuya imagen construimos con nombre UBIFS.img
- Establecemos el tamaño de la unidad mínima de entrada/salida a 1 byte según corresponde a la tecnlogía NOR Flash.
- Definimos un tamaño de 139044 bytes para cada bloque de borrado lógico resultado de restar 128 bytes al tamaño de cada bloque físico de borrado según recomienda la documentación.
- Establecemos un valor máximo para el contador de bloques de borrado a 107 resultante de dividir la capacidad de la partición DATA entre el tamaño de cada bloque lógico de borrado: floor(13.375 * 1024 * 1024 bytes / 130944 bytes) = 107
- después, la imagen UBI con una configuración de volúmenes lógicos dada que contendrá a la primera
> ./ubinize -o ubiDINAMICO.img -m 1 -p 128KiB ubinizeDINAMICO.cfg
Se construye así la imagen UBI con nombre ubiDINAMICO.img orientada a las características físicas de la memoria:
- unidad mínima de entrada/salida de 1 byte correspondiente a la tecnlogía NOR Flash.
- tamaño del bloque de borrado físico de 128 KiB según la memoria concreta que tratamos.
La configuración de ubinizeDINAMICO.cfg declara un volumen lógico con las siguientes características:
[ubifs-volume] mode=ubi image=UBIFS.img vol_id=0 vol_size=12MiB vol_type=dynamic vol_name=rootfs vol_flags=autoresize vol_alignment=1
- Establecemos la etiqueta ROOTFS con el identificador número 0 para el volumen de tipo dinámico en cuestión, es decir, su contenido puede variar en el tiempo.
- Indicamos que la imagen del sistema de ficheros fuente reside en el archivo UBIFS.img previamente construido.
- Definimos un tamaño máximo para el volumen de 12 MiB con la marca de AUTORESIZE que extenderá el mismo hasta completar la partición MTD sobre la que se monta en el siguiente arranque.
Ya estamos en disposición de formatear e insertar la imagen UBI en la partición DATA de la Flash a través de la herramienta ubiformat proyectada sobre el interfaz orientado a caracteres correspondiente:
[root@uclibc ~]# ./ubiformat /dev/mtd2 -e 1 -f ../ubiDINAMICO.img ubiformat: mtd2 (NOR), size 14024704 bytes (13.4 MiB), 131072 eraseblocks of 131072 bytes (128.0 KiB), min. I/O size 1 bytes libscan: scanning eraseblock 106 -- 100 % complete ubiformat: 107 eraseblocks have valid erase counter, mean value is 0 ubiformat: flashing eraseblock 57 -- 100 % complete ubiformat: formatting eraseblock 106 -- 100 % complete
La opción -e 1 obliga la restauración de los contadores de bloques borrados al valor inicial, y la opción -f fuerza el empotrado de la imagen UBI en la partición Flash. Tras este paso completado con éxito debe establecerse a nivel de núcleo el enlace entre el sistema de ficheros raiz y el volumen recién incrustado, esto se hace efectivo configurando en el BootLoader la siguiente cadena de argumentos al kernel que explicamos a continuación:
> console=ttyCPM0,9600n8n ubi.mtd=2 root=ubi0:rootfs rootfstype=ubifs rw
- ubi.mtd=2 asocia la capa UBI a la tercera partición de la memoria.
- root=ubi0:rootfs define el sistema de ficheros raiz como el volumen etiquetado con ROOTFS perteneciente a la capa UBI.
- rootfstype=ubifs declara el tipo del sistema de ficheros raiz.
- rw indica unos permisos de montaje de lectura y escritura.
Finalmente, creamos los interfaces necesarios para gestionar independientemente cada una de las capas desde las aplicaciones de usuario compiladas para el Atlas 50:
- interfaz UBIcontrol que gestiona íntegramente la capa UBI
[root@uclibc dev]# mknod UBIcontrol c 10 63 [root@uclibc dev]# ls -la UBI* crw-r--r-- 1 root root 10, 63 Jan 14 2009 UBIcontrol [root@uclibc ~]# ./ubinfo -a UBI version: 1 Count of UBI devices: 1 UBI control device major/minor: 10:63 Present UBI devices: ubi0 ubi0: Volumes count: 1 Logical eraseblock size: 130944 Total amount of logical eraseblocks: 107 (14011008 bytes, 13.4 MiB) Amount of available logical eraseblocks: 0 (0 bytes) Maximum count of volumes 128 Count of bad physical eraseblocks: 0 Count of reserved physical eraseblocks: 0 Current maximum erase counter value: 83 Minimum input/output unit size: 1 bytes Character device major/minor: 238:0 Present volumes: 0 Volume ID: 0 (on ubi0) Type: dynamic Alignment: 1 Size: 103 LEBs (13487232 bytes, 12.9 MiB) State: OK Name: rootfs Character device major/minor: 238:1
- interfaz ubi0 para la administración del dispositivo lógico concreto UBI que nace de la partición Flash:
[root@uclibc ~]# mknod /dev/ubi0 c 238 0 [root@uclibc ~]# ls -la /dev/ubi* crw-r--r-- 1 root root 238, 0 Jan 14 2009 /dev/ubi0
Este dispositivo lógico asociado a la partición DATA (/dev/mtd2) contiene el volumen UBIFS montado que se hace visible al espacio de usuario a través de un interfaz orientado a caracteres con major 238 y minor 1, sin embargo, no es necesario crear el nodo salvo acceso en bruto a ROOTFS.
Comparativa JFFS2 vs UBIFS
La segunda versión de Journalling Flash FileSystem (JFFS2), se monta directamente sobre un interfaz Flash orientado a bloques (/dev/mtdblock) de tal manera que delega la gestión de los mismos a la capa genérica del Sistema Operativo encargada de ello. Esta decisión de diseño si bien, es totalmente válida, no consigue aprovechar todas las características de la memoria, ya que esta capa de adminstración de bloques, a pesar de realizar labores interesantes de caching y secuenciación, no entiende de tecnologías hardware.
En cambio, el sistema de ficheros UBIFS utiliza una capa obligatoria intermedia de volúmenes lógicos que abstrae la complejidad tecnológica Flash. Esta capa denominada UBI se monta sobre un interfaz Flash bruto orientado a caracteres (/dev/mtd) y está especialmente diseñada para sacar el máximo partido al hardware de almacenamiento subyacente. Hay que dejar claro que UBI no es una Flash Translation Layer(FTL), es decir, no es una capa de emulación de dispositivo de bloques sobre el driver de la memoria Flash, lo que aleja a UBIFS de los sistemas de ficheros tradicionales. Por este motivo, UBIFS no puede montarse sobre aquellas memorias Flash que implementen en su circuitería alguna FTL con el objeto de hacerse automáticamente visibles al Sistema Operativo.
A continuación resumo las características de cada sistema con sus ventajas e inconvenientes:
- Escalabilidad: El tiempo de montaje y consumo de memoria de UBIFS resulta independiente del tamaño de la memoria admitiendo un rango muy amplio. Esto no se cumple en JFFS2, lo que hace a UBIFS más escalable además de por el hecho de estar dividido en dos capas autónomas.
- Velocidad de montaje: Aunque la capa UBI depende del tamaño de la memoria para inicializarse, UBIFS no ha de barrerla completamente en la operación de montaje, a diferencia de JFFS2.
- Escritura retardada: Este mecanismo mejora significativamente el rendimiento de UBIFS frente a la escritura directa de JFFS2.
- Tolerancia ante apagones: Este aspecto es considerado tanto en JFFS2 como UBIFS mediante el mecanismo de journalling que ambos implementan. Sin embargo, y como se ha comentado previamente, JFFS2 ha de escanear la memoria entera mientras que UBIFS sólo tiene que examinar el registro de operaciones, haciéndolo más eficiente en tiempo.
- Rendimiento en la entrada/salida: Incluso con el soporte para escritura retardada desactivado, UBIFS proporciona un rendimiento cercano al de JFFS2, pero resulta muy difícil competir contra él ya que mantiene sus estructuras en memoria mientras que UBIFS las vuelca a la memoria. En cambio, el mecanismo de journalling en UBIFS es más eficiente porque evita el trasiego de datos que realiza JFFS2.
- Compresión al vuelo: ambos sistemas UBIFS y JFFS2 permiten compresión del contenido del sistema de ficheros, pero sólo el primero permite compresión selectiva por inodo aplicando el algoritmo ZLIB, LZO o ninguno.
- Recuperabilidad: UBIFS permite la reconstrucción íntegra de sus tablas de índices mediante un barrido completo de la Flash. JFFS2 realiza esta acción en cada montaje alojándolas en RAM.
- Integridad: Tanto JFFS2 como UBIFS ofrecen integridad de sus contenidos mediante mecanismos de CRC pero sólo en el último se puede deshabilitar esta opción para aumentar el rendimiento.