Esta pagina se ve mejor con JavaScript habilitado

Compilación del kernel en FreeBSD

 ·  🎃 kr0m

FreeBSD por defecto proporciona una imagen del kernel genérica, esta incluye una gran cantidad de drivers para soportar el mayor número de hardware posible, además incluye las funcionalidades mas ampliamente utilizadas así como deshabilitadas las menos comunes.

Compilando el kernel conseguiremos varios beneficios:

  • Mayor rapidez en el arranque del sistema, dado que el kernel sólo tiene que probar el hardware que realmente tiene el equipo.
  • Menor consumo de RAM ya que hemos eliminado las partes innecesarias.
  • Menor superficie de ataque al eliminar partes del código que ya no estarán presentes.
  • Habilitar el soporte para un hardware en concreto que en GENERIC viene deshabilitado.
  • Habilitar opciones exóticas que vienen deshabilitada por defecto.

En mi caso la motivación por la que recompilar el kernel viene dada por las ganas de experimentar con una imagen mínima para mi servidor casero.

El manual se compone de distintos apartados:


Instalación del código fuente del kernel:

Instalamos Git:

pkg install git

Las fuentes del SO se guardan en /usr/src, esto es un dataset ZFS.

zfs list

NAME                                         USED  AVAIL     REFER  MOUNTPOINT
zroot/usr/src                                827M  27.2G      827M  /usr/src

Por lo tanto si queremos partir desde cero debemos recrearlo:

umount /usr/src
zfs destroy zroot/usr/src
zfs create zroot/usr/src

Clonamos el repo en el directorio /usr/src:

git clone -o freebsd https://git.FreeBSD.org/src.git /usr/src

La rama principal del repositorio de Git corresponde con la CURRENT , nosotros queremos permanecer en la RELEASE, mas concretamente en la misma versión con la que fueron compiladas las core userlandtools de nuestro sistema, esto nos evitará muchos problemas extraños y difíciles de debugear(como que algunas conexiones de red fallen y otras no).

Consultamos la versión de las userlands:

freebsd-version -u

13.0-RELEASE-p11

Podemos ver las branches en esta web , la mas actual 13.0 RELEASE es:

releng/13.0

Cambiamos a la branch deseada:

cd /usr/src
git checkout releng/13.0

Con el tiempo querremos actualizar nuestro repositorio para mantener al día el código fuente del kernel, para ello podemos optar por dos métodos.

Manualmente:

cd /usr/src
git pull --rebase

Mediante freebsd-update:
Tan solo debemos asegurarnos de que esté configurado para actualizar también el código fuente del kernel, por defecto ya viene habilitado.

vi /etc/freebsd-update.conf

Components src world kernel

src indica que se deben actualizar tanto las fuentes del kernel como las de las core userlantools.


Identificación de hardware:

Lo primero que debemos hacer es saber de que hardware disponemos, esto podemos averiguarlo mediante los siguientes fichero/comandos:

/var/run/dmesg.boot
dmidecode
lspci -lv
kldstat

En mi caso se trata de un portátil DELL bastante viejo pero que sigue dando servicio:

Del XPS Studio XPS 1340 - A15
Sistema BIOS

A continuación pongo el hardware identificado vía /var/run/dmesg.boot, no pongo todo el hardware detectado, tan solo el que voy a utilizar, por ejemplo las tarjetas PCM-CIA, el lector de tarjetas SD, la tarjeta de sonido y la wifi no les doy uso así que no añadiré soporte en mi kernel:

CPU: Intel(R) Core(TM)2 Duo CPU     P9500  @ 2.53GHz (2533.32-MHz K8-class CPU)
ahci0: <NVIDIA MCP79 AHCI SATA controller> port 0x30e8-0x30ef,0x30dc-0x30df,0x30e0-0x30e7,0x30d8-0x30db,0x30c0-0x30cf mem 0xf0884000-0xf0885fff irq 19 at device 11.0 on pci0
ohci0: <nVidia nForce MCP79 USB Controller> mem 0xf0886000-0xf0886fff irq 20 at device 4.0 on pci0
ehci0: <NVIDIA nForce MCP79 USB 2.0 controller> mem 0xf0889000-0xf08890ff irq 21 at device 4.1 on pci0
ohci1: <nVidia nForce MCP79 USB Controller> mem 0xf0887000-0xf0887fff irq 22 at device 6.0 on pci0
nfe0: <NVIDIA nForce MCP79 Networking Adapter> port 0x30d0-0x30d7 mem 0xf0888000-0xf0888fff,0xf0889c00-0xf0889cff,0xf0889800-0xf088980f irq 18 at device 10.0 on pci0
miibus0: <MII bus> on nfe0
atkbd0: <AT Keyboard> irq 1 on atkbdc0
ada0: <Samsung SSD 870 EVO 1TB SVT02B6Q> ACS-4 ATA SATA 3.x device
cd0: <HL-DT-ST DVD+-RW GS20N A106> Removable CD-ROM SCSI device
da0: <WD 10EZEX External 1.75> Fixed Direct Access SPC-2 SCSI device

Compilación del kernel:

Primero comprobamos que imagen de kernel tenemos cargada actualmente:

uname -a

FreeBSD MightyMax.alfaexploit.com 13.1-RELEASE-p2 FreeBSD 13.1-RELEASE-p2 GENERIC amd64

Si necesitamos consultar algún parámetro del kernel GENERIC podemos hacerlo en el siguiente fichero:

/usr/src/sys/$(uname -m)/conf/GENERIC

Deshabilitar opciones del kernel tan solo implica que no se compilará dicha opción dentro del kernel, por defecto siempre se compilan TODOS los módulos incluso de opciones incluidas dentro del kernel, para evitar compilar módulos innecesarios FreeBSD soporta varios parámetros de compilación interesantes:

     MODULES_OVERRIDE
		   (str) Set to	a list of modules to build instead of all of
		   them.
     WITHOUT_MODULES
		   (str) Set to	a list of modules to exclude from the build.
		   This	provides a somewhat easier way to exclude modules you
		   are certain you will	never need than	specifying
		   MODULES_OVERRIDE.  This is applied after MODULES_OVERRIDE.

De este modo el tiempo de compilación se verá reducido drásticamente.

Como punto de partida vamos a utilizar la configuración MINIMAL que deja dentro del kernel el mínimo de opciones y externaliza como módulo el máximo posible:

/usr/src/sys/$(uname -m)/conf/MINIMAL

Para tener un kernel lo mas limpio posible tendremos que seguir los siguientes pasos:

  • Copiar MINIMAL y eliminar de la config todo lo que no necesitemos.
  • Compilar un kernel MINIMAL.
  • Mirar los módulos compilados para añadir dentro de nuestro kernel los del hardware mas utilizado.
  • Compilar nuestro kernel con la opción MODULES_OVERRIDE indicando los módulos del hardware menos utilizado.

Con esto obtenemos un kernel con soporte para el hardware mas utilizado dentro del kernel y algunos módulos del hardware menos utilizado.

Una de las tareas mas complicadas cuando compilamos el kernel es saber que funcionalidad corresponde con cada opción, para averiguarlo tenemos varias opciones:

Grepear el nombre en las configs:

grep -r aac /usr/src/sys/*

/usr/src/sys/amd64/conf/GENERIC:device		aac			# Adaptec FSA RAID

Consultar las notas de la arquitectura en concreto:

/usr/src/sys/conf/NOTES
/usr/src/sys/amd64/conf/NOTES

Consultar la información online del handbook, las páginas de man o la web de GhostBSD:
https://docs.freebsd.org/es/books/handbook/kernelconfig/
https://www.freebsd.org/cgi/man.cgi?manpath=FreeBSD+13.1-RELEASE+and+Ports
https://wiki.ghostbsd.org/index.php/Kernel

Googlear:

X freebsd module

Una buena forma de detectar dependencias es grepeando el código fuente del kernel, por ejemplo:

grep MODULE_DEPEND /usr/src/sys/fs/smbfs/smbfs_vfsops.c

MODULE_DEPEND(smbfs, netsmb, NSMB_VERSION, NSMB_VERSION, NSMB_VERSION);
MODULE_DEPEND(smbfs, libiconv, 1, 1, 2);
MODULE_DEPEND(smbfs, libmchain, 1, 1, 1);

Según sea una funcionalidad muy utilizada o poco utilizada la añadiremos a la imagen del kernel o la añadiremos a la variable de compilación MODULES_OVERRIDE.

Algunos módulos no se pueden compilar de forma específica si no que forman parte de un paquete, por ejemplo los algoritmos de control de congestión TCP o se compilan todos o ninguno, como podemos ver solo existe un módulo llamado cc no cc_cdg, cc_chd, cc_cubic… el módulo cc los incluye todos:

ls -la /usr/src/sys/modules/cc

total 33
drwxr-xr-x    9 root  wheel   10 Aug 12 04:48 .
drwxr-xr-x  442 root  wheel  444 Aug 12 04:48 ..
-rw-r--r--    1 root  wheel  432 Aug 12 04:48 Makefile
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_cdg
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_chd
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_cubic
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_dctcp
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_hd
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_htcp
drwxr-xr-x    2 root  wheel    3 Aug 12 04:48 cc_vegas

NOTA: Con algunos otros módulos como geom pasa igual.

Mientras tengamos corriendo el kernel GENERIC podremos consultar que opciones fueron incluidas en él:

sysctl kern.conftxt|grep aac

device	aac

O las opciones compiladas como módulo:

ls -la /boot/kernel/*.ko|grep aac

-r-xr-xr-x  1 root  wheel   123224 May 23 20:16 /boot/kernel/aac.ko

Procedemos con la compilación del kernel MINIMAL para que nos genere una imagen reducida y un buen puñado de módulos.

cd /usr/src
time make -j3 buildkernel KERNCONF=MINIMAL INSTKERNNAME=kernel.minimal

Lo instalamos:

make -j3 installkernel KERNCONF=MINIMAL INSTKERNNAME=kernel.minimal

Ahora podemos ver la configuración minimal en el fichero MINIMAL y los módulos en /boot/kernel.minimal/*.ko, ahora tan solo debemos deshabilitar todo lo innecesario de la config MINIMAL y decidir que módulos meteremos dentro de la imagen, que módulos fuera y que módulos eliminar.

Antes de tocar nada hacemos una copia de la configuración genérica:

cd /usr/src/sys/$(uname -m)/conf/
mkdir /root/kernel_configs
cp GENERIC /root/kernel_configs/

Y copiamos la MINIMAL a una config propia que llamaremos KR0M-MINIMAL:

cp MINIMAL /root/kernel_configs/
ln -s /root/kernel_configs/KR0M-MINIMAL KR0M-MINIMAL

ls -la KR0M-MINIMAL
lrwxr-xr-x  1 root  wheel  33 Aug 29 01:13 KR0M-MINIMAL -> /root/kernel_configs/KR0M-MINIMAL

Debemos tener en cuenta que el fichero DEFAULTS se incluye automáticamente cuando se ejecuta el comando buildkernel, no hay que editar el fichero, si no deshabilitar las opciones que no queramos en nuestra custom config, por ejemplo yo no necesito UART(puerto serie).

Editamos nuestra configuración dejándola del siguiente modo:

vi KR0M-MINIMAL

cpu		HAMMER
ident		KR0M-MINIMAL

nodevice	uart_ns8250					# No UART needed

device		acpi
device		atkbdc						# AT keyboard controller
device		atkbd						# AT keyboard
device		vt							# Virtual terminal console driver
device		vt_vga						# Virtual terminal console driver VGA mode 
device		vt_vbefb					# VBE framebuffer
device		loop						# Network loopback
device		ether						# Ethernet support
device		bpf							# Berkeley packet filter

device		acpi_wmi					# Interface for vendor specific WMI implementations
device		ahci						# Serial ATA Advanced Host Controller Interface driver
device		pci							# PCI/PCIe bus driver: ahci dep
device		ata			    			# Generic ATA/SATA controller driver: MCP79
device		scbus		    			# Common Access Method Storage subsystem: ata/ahci dep
device 		ada							# ATA Direct Access device driver
device 		da							# SCSI Direct Access device driver
device 		cd							# SCSI CD-ROM driver
device 		usb							# Universal Serial Bus
device		ehci						# USB Enhanced Host Controller
device		ohci						# OHCI USB Host Controller driver
device 		ctl							# CAM Target Layer
device 		cfumass						# USB Mass Storage Class Transport
device 		coretemp					# Intel Core on-die digital thermal sensor
device 		cpuctl						# To retrieve CPUID information and perform CPU firmware updates.
device 		cpufreq						# CPU frequency control framework
device		crypto						# User-mode access to hardware-accelerated cryptography
device		cryptodev					# User-mode access to hardware-accelerated cryptography
device 		firmware					# Interface for loading firmware images into the kernel
device 		miibus						# Media Independent Interface network bus: nForce MCP Ethernet dep
device 		nfe							# NVIDIA nForce MCP Ethernet driver
device 		iflib						# Network Interface	Driver Framework
device		mem							# Interface to the physical memory of the computer
device		umass						# USB Mass Storage Devices driver
device		pass						# CAM application passthrough driver





options 	SCHED_ULE					# ULE scheduler
options 	PREEMPTION					# Enable kernel thread preemption
options 	SMP							# Symmetric MultiProcessor Kernel
options 	INET						# InterNETworking
#options 	INET6						# IPv6 communications protocols: Iocage dep
options 	TCP_OFFLOAD					# TCP offload
options 	ZFS							# Zettabyte File System
options 	MD_ROOT						# MD is a potential root device
options 	COMPAT_FREEBSD11			# Compatible with FreeBSD11
options 	COMPAT_FREEBSD12			# Compatible with FreeBSD12
options 	SCSI_DELAY=5000				# Delay (in ms) before probing SCSI
options 	_KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions
options 	PRINTF_BUFR_SIZE=128		# Prevent printf output being interspersed.
options 	AUDIT						# Security event auditing
options 	CAPABILITY_MODE				# Capsicum capability mode
options 	CAPABILITIES				# Capsicum capabilities
options 	INCLUDE_CONFIG_FILE			# Include this file in kernel
options 	EARLY_AP_STARTUP			# Start Application Processors earlier during boot
options 	IOMMU						# input–output memory management unit 
options 	VESA						# Add support for VESA BIOS Extensions (VBE)
options 	MAC							# TrustedBSD MAC Framework


options		HZ=1000						# Set the timer granularity, recomended value by dummynet
options 	IPFIREWALL					# IPFW
options		DEVICE_POLLING				# Device Polling
options		SYSVMSG						# SYSV-style message queues
options     SYSVSEM         			# SYSV-style semaphores
options     SYSVSHM         			# SYSV-style shared memory
options		TMPFS						# Tmpfs file system
options 	ZSTDIO						# zstd-compressed kernel and user dumps: ZFS dep
options 	NFSCL						# Network Filesystem Client: ZFS dep
options 	NFSD						# Network Filesystem Server: ZFS dep
options 	NFSLOCKD					# Network Lock Manager: ZFS dep
options 	NFS_ROOT					# NFS usable as /, requires NFSCL: ZFS dep

En cuanto a los módulos he decido dejar estos:

  • accf_data : buffer incoming connections until data arrives.

  • accf_dns : buffer incoming DNS requests until the whole first request is present.

  • accf_http : buffer incoming connections until a certain complete HTTP requests arrive.

  • acl_nfs4: introduction to the POSIX.1e/NFSv4 ACL security API.

  • acl_posix1e: introduction to the POSIX.1e/NFSv4 ACL security API.

  • cc: TCP Congestion Control algorithms.

  • cd9660: ISO-9660 file system.

  • cd9660_iconv: ISO-9660 file system.

  • fdescfs: Provides access to the per-process file descriptor namespace in the global file system namespace.

  • fusefs: File system that is serviced by a userspace program.

  • mac_ntpd: Policy allowing ntpd to run as non-root user.

  • nullfs: The nullfs driver will permit the FreeBSD kernel to mount a loopback file system sub-tree.

  • opensolaris: ZFS requirement.

  • udf: Universal Disk Format.

  • udf_iconv: Universal Disk Format.

Compilamos nuestro kernel personalizado:

cd /usr/src
time make -j3 buildkernel KERNCONF=KR0M-MINIMAL INSTKERNNAME=kernel.kr0m-minimal MODULES_OVERRIDE=“accf_data accf_dns accf_http acl_nfs4 acl_posix1e cc cd9660 cd9660_iconv fdescfs fusefs mac_ntpd nullfs opensolaris udf udf_iconv”

Lo instalamos:

make -j3 installkernel KERNCONF=KR0M-MINIMAL INSTKERNNAME=kernel.kr0m-minimal MODULES_OVERRIDE=“accf_data accf_dns accf_http acl_nfs4 acl_posix1e cc cd9660 cd9660_iconv fdescfs fusefs mac_ntpd nullfs opensolaris udf udf_iconv”

Se ha definido la variable INSTKERNNAME para que no sobreescriba el kernel original, ya que luego es muy complicado mantener el sistema operativo actualizado con nuestro kernel custom corriendo y una versión actualizada de la imagen GENERIC como recomiendan aquí , de este modo freebsd-update actualizará /boot/kernel al margen de /boot/kernel.kr0m-minimal

Si fuese necesario podríamos afinar mas el proceso de compilación mediante el fichero /etc/make.conf

Arrancamos con el kernel nuevo:

nextboot -k kernel.kr0m-minimal
shutdown -r now

Comprobamos que imagen tenemos cargada:

uname -a

FreeBSD MightyMax.alfaexploit.com 13.1-RELEASE-p2 FreeBSD 13.1-RELEASE-p2 #19 releng/13.1-n250155-514a191356c-dirty: Thu Sep  1 13:01:03 CEST 2022     root@MightyMax.alfaexploit.com:/usr/obj/usr/src/amd64.amd64/sys/KR0M-MINIMAL amd64

Si funciona correctamente editamos la configuración del loader para que sea el kernel por defecto:

vi /boot/loader.conf

kernel=kernel.kr0m-minimal

NOTA: Los módulos del kernel normalmente se encuentran en /boot/KERNEL_NAME/*.ko a excepción de módulos de terceras partes como podría ser Nvidia o VirtualBox que se encuentran en /boot/modules/*.ko

El hardware donde se ha compilado el kernel es el siguiente:

sysctl hw.model hw.machine hw.ncpu

hw.model: Intel(R) Core(TM)2 Duo CPU     P9500  @ 2.53GHz
hw.machine: amd64
hw.ncpu: 2

Con 8Gb de RAM:

freecolor -m -o

             total       used       free     shared    buffers     cached
Mem:          7664       1307       6356          0          0          0
Swap:         2048          0       2048

A continuación la diferencia entre compilar un kernel GENERIC y compilar uno mínimo:

CUSTOM:
real	4m56.748s
user	8m54.733s
sys	0m33.359s
GENERIC:
real	30m56.621s
user	54m24.386s
sys	4m48.999s

La diferencia es abismal, hemos pasado de tardar 30m aproximadamente a unos 5m

En cuanto al tamaño también apreciamos diferencia:

CUSTOM:
du -h /boot/kernel.kr0m-minimal/kernel
6.8M	/boot/kernel.kr0m-minimal/kernel

du -sch /boot/kernel.kr0m-minimal/*.ko
209K	total
GENERIC:
du -h /boot/kernel/kernel 
17M	/boot/kernel/kernel

du -sch /boot/kernel/*.ko
80M	total

Troubleshooting:

El único problema con el que me he encontrado es con IOcage al haber deshabilitado IPv6, deshabilitar IPv6 en las jails no solventa nada, al arrancarlas sigue dando error quedando paradas.

Para poder arrancar las jails he tenido que “hackear” un poco el código fuente:

vi /usr/local/lib/python3.9/site-packages/iocage_lib/ioc_common.py

    #af_mapping = {
    #    'Internet': 'ipv4',
    #    'Internet6': 'ipv6'
    #}
    af_mapping = {
        'Internet': 'ipv4'
    }
vi /usr/local/lib/python3.9/site-packages/iocage_lib/ioc_start.py
        start_parameters = [
            x for x in net
            + [x for x in parameters if '1' in x]
            + [
                f'name=ioc-{self.uuid}',
                _sysvmsg,
                _sysvsem,
                _sysvshm,
                _exec_created,
                f'host.domainname={host_domainname}',
                f'host.hostname={host_hostname}',
                f'path={self.path}/root',
                f'securelevel={securelevel}',
                f'host.hostuuid={self.uuid}',
                f'devfs_ruleset={devfs_ruleset}',
                f'enforce_statfs={enforce_statfs}',
                f'children.max={children_max}',
                f'exec.prestart={exec_prestart}',
                f'exec.clean={exec_clean}',
                f'exec.timeout={exec_timeout}',
                f'stop.timeout={stop_timeout}',
                f'mount.fstab={self.path}/fstab',
                'allow.dying',
                f'exec.consolelog={self.iocroot}/log/ioc-'
                f'{self.uuid}-console.log',
                f'ip_hostname={ip_hostname}' if ip_hostname else '',
                'persist'
            ] if (x != '')]
	# CUSTOM CODE only this two additional lines:
        start_parameters.remove('ip6.saddrsel=1')
        start_parameters.remove('ip6=new')

Hay ocasiones en las que una actualización no actualizará el kernel pero si otras partes del sistema, este nivel de parcheo del sistema se puede consultar mediante el comando:

uname -r

13.0-RELEASE-p11

Para que nuestro custom kernel refleje correctamente el patchlevel del sistema debemos recompilarlo de nuevo, de este modo la imagen será generada con la información actualizada del patchlevel. Con el kernel GENERIC pasa lo mismo, si no cargamos la imagen nueva tampoco refleja correctamente el patchlevel.

Resumiendo

  • En todas las actualizaciones debemos recompilar el kernel para que cuadre con la versión de las core userlandtools y las fuentes tanto de las core userlandtools como del kernel.
  • Si además queremos reflejar correctamente el patchlevel de las core userlandtools también debemos reiniciar con el kernel recién compilado.

Si nuestro nuevo kernel tuviese problemas para arrancar siempre podremos cargar la imagen GENERIC o la imagen anterior desde el boot loader de FreeBSD, tan solo seleccionaríamos la opción “Escape to loader prompt” y teclear el nombre de la imagen.

Escape to loader prompt
boot kernel
boot kernel.kr0m.old

Puede darse el caso en el que el kernel arranque pero algunos comandos como ps, vmstat o conexiones de forma aparentemente random fallen, esto es debido a que las utilidades del sistema fueron compiladas para una versión del kernel muy distinta a la actual.

Para solventarlo debemos proceder de forma distinta dependiendo de si compilamos “world” y las core userland tools o utilizamos la versión binaria de estas:

  • Source: Recompilar e instalar las core userlandtools utilizando una versión de las fuentes a la par con la versión del kernel.
  • Binarios: Se debería actualizar mediante freebsd-update a una versión de FreeBSD que lleve los binarios compilados con la misma versión de kernel que estemos utilizando.

Podemos consultar la versión del kernel y las userlandtools del siguiente modo:

  • freebsd-version -r -> Kernel que está corriendo actualmente.
  • freebsd-version -k -> Kernel instalado pero que todavía no ha cargado porque no se ha reiniciado el equipo.
  • freebsd-version -u -> Versión de las core userlandtools.

Si no disponemos de una imagen GENERIC por la razón que sea podemos regenerarla del siguiente modo:

cd /usr/src
make -j3 kernel __MAKE_CONF=/dev/null SRCCONF=/dev/null

Este proceso solo tiene como requisitos no haber tocado la configuración GENERIC ni compilar con parámetros retocados en /etc/make.conf

También podemos consultar los parámetros con los que fué compilado nuestro kernel si este fué compilado con la opción INCLUDE_CONFIG_FILE habilitada(en GENERIC lo está):

sysctl kern.conftxt

kern.conftxt: options	CONFIG_AUTOGENERATED
ident	KR0M-MINIMAL
machine	amd64
cpu	HAMMER
options	NFS_ROOT
options	NFSLOCKD
options	NFSD
...

Dejo algunos enlaces de referencia que pueden resultar de ayuda:
https://docs.freebsd.org/en/books/handbook/kernelconfig/
https://docs.freebsd.org/en/books/handbook/mirrors/index.html#git

Si te ha gustado el artículo puedes invitarme a un RedBull aquí