FreeBSD posee compatibilidad con Linux a nivel binario. Esto permite instalar y ejecutar la mayoría de los binarios de Linux en FreeBSD sin necesidad de tener que modificar o adaptarlos previamente. Se sabe incluso que en algunas situaciones los binarios para Linux se ejecutan de manera más eficiente en FreeBSD que en Linux. Sin embargo, algunos binarios que utilizan características específicas de Linux no son soportados bajo FreeBSD. Por ejemplo, binarios que utilizan llamas al sistema específicas de i386 como habilitar el modo 8086 virtual. Recientemente se ha incorporado el soporte para binarios Linux de 64 bit.
Cuando el kernel FreeBSD carga un archivo ELF, busca dentro del mismo si se trata de un binario FreeBSD o Linux. Basándose en ésto toma acciones apropiadas en una tabla para el binario. Esta tabla contiene algunos parámetros y punteros que permiten ejecutar el binario. Cuando el programa hace una llamada al sistema operativo (syscall), el kernel busca la función correcta dentro de esta tabla. Esto se hace para todo binario, ya sea FreeBSD o Linux, por ende idealmente no existe emulación alguna (sobrecarga) sino mas bien un simple mapeo de syscalls. Algún comportamiento específico es algo diferente entre algunas llamadas al sistema de Linux y FreeBSD, por lo que se puede llegar a necesitar cierta traducción adicional antes de invocar a la syscall FreeBSD subyacente.
Esta es la razón por la cual una gran cantidad de programas para Linux son ejecutados en FreeBSD a la misma velocidad que si fueran programas FreeBSD.
Por defecto, las librerías de Linux y el modo de compatibilidad se encuentran deshabilitados. Es posible instalar las librerías manualmente, o a través del port emulators/linux_base-c6. Este artículo explica cómo instalar y configurar dicho port, y cómo verificar su funcionamiento.
Antes de comenzar es necesario instalar los módulos del kernel FreeBSD que proveen soporte para la compatibilidad con Linux:
root@fbsd10:~ # kldload linux root@fbsd10:~ # kldload linux64
Luego verificar que hayan sido cargados correctamente:
root@fbsd10:~ # kldstat | grep linux 5 1 0xffffffff81e13000 31620 linux.ko 6 2 0xffffffff81e45000 2781 linux_common.ko 7 1 0xffffffff81e48000 2c910 linux64.ko
Ahora es posible compilar el port que provee las librerías Linux necesarias:
root@fbsd10:~ # portmaster emulators/linux_base-c6
O de manera alternativa:
# pkg install emulators/linux_base-c6
Este port contiene los paquetes de una instalación casi mínima de CentOS 6 de 32 bits. Estos paquetes, en conjunto con los módulos del kernel, proveen la base para el entorno de compatibilidad con Linux. Está diseñado para proveer un buena experiencia de uso al aprovechar la configuración de FreeBSD para la pieza de software Linux correspondiente, siempre que sea posible. Este port está sólo disponible para las arquitecturas i386/amd64.
En este caso, instalar con portmaster o pkg es lo mismo, pues no se compila nada sino que se descargan los paquetes RPM que contienen los binarios precompilados, desde el sitio de CentOS.
Al finalizar la instalación se debe habiltiar la conpatibilidad con Linux editando el archivo /etc/rc.conf
:
root@fbsd10:~ # nano /etc/rc.conf
linux_enable="YES"
Dependiendo de la versión de FreeBSD será posible incrementar la versión de Linux emulada de acuerdo a la variable compat.linux.osrelease
en /etc/sysctl.conf
.
Si se utiliza la última versión estable de FreeBSD, no es necesario modificar dicha variable:
root@fbsd10:~ # sysctl compat.linux.osrelease compat.linux.osrelease: 2.6.32
Ejecución de un binario Linux
La raíz de la instalación mínima de CentOS 6 se encuentra en en directorio /compat/linux/
. Dentro de la misma es conveniente crear un directorio /usr/local/bin
donde almacenar binarios Linux (en caso de que luego sea necesario hacer un chroot
a la misma para algún binario que así lo requiera):
root@fbsd10:~ # cd /compat/linux/usr root@fbsd10:/compat/linux/usr # mkdir -p local/bin root@fbsd10:/compat/linux/usr # cd local/bin
A modo de ejemplo, traer un binario desde un sistema GNU/Linux utilizando scp (cp sobre SSH):
root@fbsd10:/compat/linux/usr/local/bin # scp -P 2222 root@debian7:/usr/local/bin/zpaq .
file
reporta que se trata de un binario ELF de 64 bit linkeado estáticamente para GNU/Linux 2.6.32:
root@fbsd10:/compat/linux/usr/local/bin # file zpaq zpaq: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=030a674fbb576c85f5cd28c4af1016f36f995867, stripped
Veamos si funciona...
root@fbsd10:/compat/linux/usr/local/bin # ./zpaq zpaq v7.05 journaling archiver, compiled May 4 2015 zpaq archiver for incremental backups with rollback capability. http://mattmahoney.net/zpaq Usage: zpaq {add|extract|list} archive[.zpaq] files... -options... Files... may be directory trees. Default is the whole archive. Archive may be "" to test compression or comparison. * and ? in archive match numbers or digits in a multi-part archive. Part 0 is the index. If present, no other parts are needed to add or list. Commands (a,x,l) and options may be abbreviated if not ambiguous. -all [N] Extract/list versions in N [4] digit directories. -key [password] AES-256 encrypted archive [prompt without echo]. -noattributes Ignore/don't save file attributes or permissions. -not files... Exclude. * and ? match any string or char. -only files... Include only matches (default: *). -summary Be brief. -test Do not write to disk. -threads N Use N threads (default: 4). -to out... Rename files... to out... or all to out/all. -until N Roll back archive to N'th update or -N from end. -until 2016-04-28 18:19:51 Set date, roll back (UT, default time: 235959). add options: -force Add files even if the date is unchanged. -nodelete Do not mark unmatched files as deleted. -method L Compress level L (0..5 = faster..better, default 1). LB Use 2^B MB blocks (0..11, default 04, 14, 26..56). i Index (file metadata only). -fragment N Set average dedupe fragment size = 2^N KiB (default: 6). extract options: -force Overwrite existing files (default: skip). list (compare files) options: -force Compare file contents instead of dates (slower). -not =[+-#?^] Exclude by comparison result. -summary [N] Show N largest files/dirs only (default: 20).
Funciona perfectamente.
Binarios linkeados de forma dinámica
Pero ¿qué pasa con los binarios linkeados de forma dinámica?
Tal como indicaba la salida de file
, este binario está linkeado de forma estática (no depende de librerías compartidas):
root@fbsd10:/compat/linux/usr/local/bin # ldd zpaq ldd: zpaq: not a dynamic ELF executable
Veamos qué sucede al intentar ejecutar un binario linkeado de forma dinámica, por ejemplo la herramienta pwd
en un sistema GNU/Linux de 64 bits:
root@linux64:~# which pwd /bin/pwd root@linux64:~# ldd /bin/pwd linux-vdso.so.1 => (0x00007ffea9904000) libc.so.6 => /lib/libc.so.6 (0x00007f673a307000) /lib64/ld-linux-x86-64.so.2 (0x00007f673a67a000) root@linux64:~# ldd /lib64/ld-linux-x86-64.so.2 statically linked root@linux64:~# ldd /lib/libc.so.6 /lib64/ld-linux-x86-64.so.2 (0x00007fc8c8f67000) linux-vdso.so.1 => (0x00007fff306dc000)
Se observa que el binario pwd
linkea dinámicamente a las librerías linux-vdso.so.1
(virtual ELF dynamic shared object), libc.so.6
(standard C library) y ld-linux-x86-64.so.2
(Linux dynamic linker/loader). A su vez, el loader está linkeado estáticamente (obviamente, sino quién carga al loader) y la libc está linkeada dinámicamente al loader y al vdso (como todo binario linkeado de forma dinámica). Para más información consultar las siguientes manpages desde un sistema GNU/Linux:
man vdso man ld man ld-linux man libc
De aquí se desprende que, para que pwd
funcione, se requieren dichas librerías dinámicas.
Desde el sistema FreeBSD que cuenta con el modo de compatibilidad habilitado, traer el binario de pwd
del host Linux de 64 bits:
root@fbsd10:/compat/linux/usr/local/bin # scp -P 2222 root@linux64.linuxito.com:/bin/pwd .
root@fbsd10:/compat/linux/usr/local/bin # ll total 942 -rwxr-xr-x 1 root wheel 31968 Apr 28 15:28 pwd* -rwxr-x--- 1 root wheel 1648736 Apr 12 15:19 zpaq*
Se observa que se trata efectivamente de un binario ELF de 64 bits:
root@fbsd10:/compat/linux/usr/local/bin # file pwd pwd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=f50215e5dc10c9e4ebbedcb8b5ce42875b1a8b93, stripped
Al listar las dependencias con librerías compartidas utilizando ldd
, éste reporta error al no poder localizar el linker/loader:
root@fbsd10:/compat/linux/usr/local/bin # ldd pwd pwd: ELF interpreter /lib64/ld-linux-x86-64.so.2 not found, error 2 pwd: signal 6
Este error ocurre porque la instalación de CentOS 6 no cuenta con el directorio /lib64
:
root@fbsd10:/compat/linux/usr/local/bin # ll /compat/linux total 20 drwxr-xr-x 2 root wheel 60 Apr 12 15:05 bin/ drwxr-xr-x 24 root wheel 61 Apr 12 15:05 etc/ drwxr-xr-x 8 root wheel 115 Apr 12 15:05 lib/ drwxr-xr-x 2 root wheel 2 Apr 12 15:03 mnt/ drwxr-xr-x 2 root wheel 2 Apr 12 15:03 opt/ drwxr-xr-x 2 root wheel 2 Apr 12 15:03 proc/ drwxr-xr-x 2 root wheel 57 Apr 12 15:05 sbin/ drwxr-xr-x 2 root wheel 2 Apr 12 15:03 selinux/ drwxr-xr-x 2 root wheel 2 Apr 12 15:03 srv/ drwxr-xr-x 2 root wheel 2 Apr 12 15:03 sys/ drwxr-xr-x 13 root wheel 14 Apr 12 15:18 usr/ drwxr-xr-x 14 root wheel 15 Apr 12 15:05 var/
Entonces, crear dicho directorio y traer el linker/loader de 64 bits desde el host Linux:
root@fbsd10:/compat/linux/usr/local/bin # cd /compat/linux root@fbsd10:/compat/linux # mkdir lib64 root@fbsd10:/compat/linux # cd lib64/
root@fbsd10:/compat/linux/lib64 # scp -P 2222 root@linux64.linuxito.com:/lib64/ld-linux-x86-64.so.2 .
Ahora ldd
funciona inmediatamente, aunque no se encuentra la libc:
root@fbsd10:/compat/linux/usr/local/bin # ldd pwd pwd: linux_vdso.so.1 => (0x00007ffffffff000) libc.so.6 => not found
Notar que no fue necesario ejecutar un chroot
dentro de /compat/linux
para que ldd
encuentre a la (recién traída) librería ld-linux-x86-64.so.2
, la cual no se encuentra dentro del directorio /lib64
en el sistema de archivos raíz del host FreeBSD, sino que se encuentra dentro de /compat/linux/lib64
.
Esto se debe a que, cuando el kernel FreeBSD detecta un binario Linux, tiene en cuenta algunas consideraciones adicionales respecto a archivos y directorios. Si el programa busca un archivo o directorio /A
, el kernel primero busca a /compat/linux/A
, y si no lo encuentra, busca luego por /A
. Este comportamiento es el que permite ejecutar de manera transparente los binarios Linux sin necesidad de utilizar chroot
.
Ahora bien, si buscamos la libc faltante, ésta se encuentra dentro de /compat/linux/lib/
:
root@fbsd10:/compat/linux/usr/local/bin # find /compat/linux/ -name libc.so.6 /compat/linux/lib/i686/nosegneg/libc.so.6 /compat/linux/lib/libc.so.6
El problema es que se trata de una versión de 32 bits:
root@fbsd10:/compat/linux/usr/local/bin # file /compat/linux/lib/libc.so.6 /compat/linux/lib/libc.so.6: symbolic link to libc-2.12.so root@fbsd10:/compat/linux/usr/local/bin # file /compat/linux/lib/libc-2.12.so /compat/linux/lib/libc-2.12.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=76ea73f60dc47ac24b37eb54b41354c5419e8cce, for GNU/Linux 2.6.18, not stripped
Y, claramente, el binario Linux pwd
de 64 bits requiere una libc de 64 bits.
Traer la libc de 64 bits desde el host Linux de 64 bits:
root@fbsd10:/compat/linux/usr/local/bin # cd /compat/linux/lib64 root@fbsd10:/compat/linux/lib64 # scp -P 2222 root@linux64.linuxito.com:/lib/libc.so.6 .
root@fbsd10:/compat/linux/lib64 # ll total 92 -rwxr-xr-x 1 root wheel 128744 Apr 28 15:30 ld-linux-x86-64.so.2* -rwxr-xr-x 1 root wheel 1478056 May 2 10:40 libc.so.6* root@fbsd10:/compat/linux/lib64 # file libc.so.6 libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ba804939a2d595d44953e0a655bb5339c47ff9e4, for GNU/Linux 2.6.18, stripped
Pero al verificar nuevamente, la libc sigue sin encontrarse:
root@fbsd10:/compat/linux/lib64 # cd /compat/linux/usr/local/bin/ root@fbsd10:/compat/linux/usr/local/bin # ldd pwd pwd: linux_vdso.so.1 => (0x00007ffffffff000) libc.so.6 => not found
Si recuerdan la salida de ldd
(en el host Linux de 64 bits desde donde proviene este binario), pwd
busca la libc dentro del directorio /bin
. Por ende, no queda otra alternativa que pisar el link simbólico dentro de /compat/linux/lib
para que libc.so.6
apunte a la versión de 64 bits que se acaba de traer desde el host Linux de 64 bits:
root@fbsd10:/compat/linux/usr/local/bin # cd /compat/linux/lib root@fbsd10:/compat/linux/lib # rm libc.so.6 root@fbsd10:/compat/linux/lib # ln -s ../lib64/libc.so.6 libc.so.6 root@fbsd10:/compat/linux/lib # ll libc.so.6 lrwxr-xr-x 1 root wheel 18 May 2 10:44 libc.so.6@ -> ../lib64/libc.so.6
Ahora ldd
encuentra la libc correcta:
root@fbsd10:/compat/linux/lib # cd /compat/linux/usr/local/bin/ root@fbsd10:/compat/linux/usr/local/bin # ldd pwd pwd: linux_vdso.so.1 => (0x00007ffffffff000) libc.so.6 => /lib/libc.so.6 (0x0000000800827000) /lib64/ld-linux-x86-64.so.2 (0x0000000800607000)
Y es posible ejecutar el programa sin inconvenientes:
root@fbsd10:/compat/linux/usr/local/bin # ./pwd /compat/linux/usr/local/bin
¡Éxito! Gracias a este modo de compatibilidad se logra correr un binario proveniente de un sistema GNU/Linux de 64 bits sin necesidad de recompilar o siquiera hacer un rebranding del ELF. El efecto secundario es que ya no se podrá correr binarios de 32 bits porque la libc apunta a una versión de 64 bits. Para solucionar esto es necesario implementar el soporte multilib.
Conclusiones
Cuando se necesitan ejecutar binarios Linux de 64 bits en un sistema FreeBSD, el port emulators/linux_base-c6
no es de utilidad, pues es una instalación mínima de CentOS 6 de 32 bits (al menos hasta el momento no existe un port para emular un sistema GNU/Linux de 64 bits), por ende todas las librerías corresponden a la versión compilada para 32 bits.
En tal caso, no queda otra alternativa que traer uno manualmente todas las librerías necesarias desde un sistema de 64 bits, respetando la jerarquía de directorios dentro de /compat/linux
.
Para un cierto binario en un sistema Linux (php5-fpm
por poner un ejemplo), es posible obtener todas las librerías dinámicas de las cuales depende utilizando el siguiente comando:
# tar czvf libs_php5-fpm.tar.gz $(ldd /usr/sbin/php5-fpm | cut -d'>' -f2 | sed -e 's/[\ \t]*//g' | cut -d'(' -f1)
El archivo libs_php5-fpm.tar.gz
contendrá todas las librerías dinámicas de las cuales depende php5-fpm
. El problema es que a su vez se debe realizar esta tarea recursivamente para cada una de las librerías.
Por otro lado, en sistemas Debian y derivados es posible recopilar todos los binarios que incluye un determinado paquete ejecutando:
# tar czvf php-fpm.tar.gz $(file $(dpkg -L php5-fpm) | grep -vi directory | cut -d':' -f1)
Alternativamente uno podría traer absolutamente todas las librerías de una instalación mínima de la distribución de su preferencia, y así armar una especie de Linux base, como es lo que intenta lograr el port emulators/linux_base-c6
.
Lo más interesante de este modo de compatibilidad Linux en FreeBSD, es que habilita a crear jails Linux, logrando así paravirtualización entre sistemas operativos heterogéneos, algo impensado en otras soluciones de contenedores.
Referencias
Configuring Linux® Binary Compatibility
The FreeBSD-linuxulator explained (for users)