1. El último eslabón
El tema 21 terminó en un punto preciso. El kernel había creado el proceso 1 y ejecutado en él el programa del initramfs; ese programa montó la partición raíz y, sin crear otro proceso, reemplazó su contenido por el de /sbin/init. La última línea que mostramos del registro era la de ese programa presentándose.
En ese momento el kernel ya hizo todo lo que le corresponde. Sabe administrar procesos, memoria, archivos y dispositivos. No sabe, ni debe saber, cómo se llama el equipo, qué discos hay que montar además de la raíz, qué dirección de red usar, qué servicios debe ofrecer esa máquina ni cómo se identifica a un usuario. Nada de eso es mecanismo: es política, y la decide el espacio de usuario, como planteamos en el tema 10.
El kernel delega todo eso en un único programa, el primero que ejecuta, y no vuelve a tomar iniciativa alguna. Desde aquí hasta el apagado se limita a lo que describimos en el tema 11: responder a llamadas al sistema, interrupciones y excepciones.
Lo de este tema se aplica a las distribuciones que usan systemd, que son casi todas. En WSL depende de la configuración: en las instalaciones recientes systemd está activado, y en las anteriores el proceso 1 es un programa propio de WSL. Lo sabrás con el primer comando de la sección siguiente. En un contenedor, el proceso 1 suele ser directamente la aplicación.
2. El proceso número 1
El proceso init es el primer proceso de usuario que crea el kernel. Tiene siempre el identificador 1, es el antecesor de todos los demás procesos y permanece en ejecución hasta que el sistema se apaga. Su tarea es llevar el sistema al estado de funcionamiento y mantenerlo en él.
$ ps -o pid,ppid,comm -p 1,2
PID PPID COMMAND
1 0 systemd
2 0 kthreadd
$ ls -l /sbin/init
lrwxrwxrwx 1 root root 22 abr 12 10:31 /sbin/init -> ../lib/systemd/systemd
La columna PPID indica el proceso padre, el que creó a cada uno. Los dos primeros procesos tienen un padre con número 0, que no corresponde a ningún proceso: los creó el kernel directamente. El 1 es el antecesor de todos los programas de usuario. El 2, kthreadd, es el antecesor de los hilos del kernel que vimos en el tema 11. Todo lo demás desciende de uno de ellos.
El segundo comando muestra que /sbin/init, el nombre tradicional que el kernel busca, es un enlace al ejecutable de systemd. El árbol completo se ve con pstree:
$ pstree -pT
systemd(1)─┬─NetworkManager(912)
├─cron(1042)
├─dbus-daemon(905)
├─gdm3(1120)───gdm-session-wor(1890)───…
├─sshd(1101)
├─systemd(2011)─┬─gnome-shell(2204)
│ └─gnome-terminal-(3310)───bash(4102)───pstree(7655)
├─systemd-journal(388)
├─systemd-logind(930)
└─systemd-udevd(431)
Cada proceso del sistema tiene una línea de ascendencia que termina en el 1. La del comando que acabamos de ejecutar es: pstree, hijo de la shell, hija de la terminal, hija de un segundo systemd que administra la sesión del usuario, hijo del primero.
El proceso 1 es un programa de usuario corriente, en el anillo 3, que hace llamadas al sistema como cualquier otro. Pero el kernel le da un trato que no da a ningún otro proceso:
| Particularidad | Motivo |
|---|---|
No puede terminar. Si lo hace, el kernel entra en pánico con el mensaje Attempted to kill init! | Sin él no hay nadie que mantenga el sistema ni que lo apague ordenadamente. |
Está protegido de las señales. Solo recibe aquellas para las que instaló un manejador; kill -9 1 no tiene efecto, ni siquiera como administrador. | Evitar que se lo finalice por error. |
| Adopta a los huérfanos. Cuando un proceso termina antes que sus hijos, estos pasan a ser hijos del proceso 1. | Todo proceso debe tener un padre que recoja su resultado al terminar. |
La primera fila es el pánico que mencionamos en el tema 18, en otra de sus variantes. La tercera se entiende mejor con el ciclo de vida de los procesos, que vemos en el tema 26.
3. De init a systemd
Durante treinta años, el proceso 1 de los sistemas Unix fue un programa pequeño llamado simplemente init, en una versión que se conoce como SysV init por el Unix del que proviene. Su método era directo: ejecutaba, uno tras otro y en orden alfabético, los scripts de shell de un directorio. Cada script iniciaba un servicio.
El diseño era fácil de entender, y tenía limitaciones que se volvieron serias cuando los equipos empezaron a tener decenas de servicios y dispositivos que se conectan y desconectan en marcha:
| SysV init | systemd | |
|---|---|---|
| Un servicio se describe con | Un script de shell, con su lógica propia | Un archivo de texto declarativo |
| Orden de inicio | Secuencial, fijado por un número en el nombre del script | En paralelo, calculado a partir de las dependencias declaradas |
| Después de iniciar un servicio | Se desentiende de él | Lo vigila, y puede reiniciarlo si termina |
| Detener un servicio | Confía en que el script sepa qué procesos finalizar | Conoce con certeza todos los procesos del servicio |
| Mensajes de los servicios | Cada servicio con su archivo y su formato | Reunidos en un registro común |
| Estados del sistema | Niveles de ejecución, numerados del 0 al 6 | Targets, con nombre |
systemd apareció en 2010 y en pocos años fue adoptado por casi todas las distribuciones. Tomó ideas de launchd, el equivalente de macOS. La cuarta fila de la tabla se apoya en un mecanismo del kernel de Linux que permite agrupar procesos de manera que ninguno pueda salirse del grupo; es el mismo sobre el que se construyen los contenedores del tema 23.
La adopción fue polémica. systemd no se limita a iniciar servicios: incluye el registro, la gestión de dispositivos, la de sesiones y la de red, entre otras cosas. Sus críticos ven en eso una ruptura con la tradición de Unix de programas pequeños que hacen una sola cosa. Algunas distribuciones usan alternativas más simples, como OpenRC en Alpine. Aquí estudiamos systemd porque es el que encontrarás.
4. Las unidades
systemd administra más que servicios. Todo lo que maneja se describe con un mismo tipo de archivo, y cada cosa administrada se llama unidad. El tipo se reconoce por la extensión del nombre:
| Tipo | Representa | Ejemplo |
|---|---|---|
.service | Un programa que se inicia y se supervisa. | cron.service |
.target | Un punto de referencia que agrupa otras unidades. | multi-user.target |
.mount | Un sistema de archivos que debe montarse. | boot-efi.mount |
.device | Un dispositivo detectado por el kernel. | dev-nvme0n1p2.device |
.socket | Un punto de conexión. El servicio asociado se inicia cuando alguien se conecta. | ssh.socket |
.timer | Una tarea que se ejecuta a una hora o cada cierto tiempo. | apt-daily.timer |
Los archivos están en tres lugares, que se consultan en orden de prioridad:
| Directorio | Contenido | Quién escribe allí |
|---|---|---|
/etc/systemd/system | Unidades y ajustes propios de este equipo. Tienen prioridad. | El administrador |
/run/systemd/system | Unidades generadas al arrancar. Desaparecen al apagar. | El propio sistema |
/usr/lib/systemd/system | Las unidades que traen los paquetes instalados. | El gestor de paquetes |
La separación resuelve un problema práctico. Si el administrador modificara los archivos del tercer directorio, la siguiente actualización del paquete borraría sus cambios. Los cambios se ponen en el primero, que ninguna actualización toca.
5. Anatomía de un servicio
Un archivo de unidad es un texto dividido en secciones, cada una con pares de nombre y valor. Este es el de cron, el servicio de tareas programadas que apareció en la lista del tema 10:
$ systemctl cat cron.service
# /usr/lib/systemd/system/cron.service
[Unit]
Description=Regular background program processing daemon
Documentation=man:cron(8)
After=remote-fs.target nss-user-lookup.target
[Service]
EnvironmentFile=-/etc/default/cron
ExecStart=/usr/sbin/cron -f -P $EXTRA_OPTS
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
| Sección | Línea | Significado |
|---|---|---|
[Unit] | Description | El texto que se muestra en los listados y en el registro. |
After | Si esas otras unidades van a iniciarse, esta debe esperar a que lo hagan. | |
[Service] | ExecStart | El comando que inicia el servicio. Es la línea esencial. |
Restart | Qué hacer si el programa termina: aquí, volver a iniciarlo si terminó con error. | |
[Install] | WantedBy | A qué target se asocia el servicio cuando se lo habilita para el arranque. |
El archivo no dice cómo iniciar el servicio, verificar que arrancó o detenerlo: dice qué programa es y bajo qué condiciones debe ejecutarse. De lo demás se ocupa systemd, igual para todos. Es lo que significa «declarativo» en la tabla de la sección 3.
El estado de una unidad se consulta con systemctl status, que no requiere privilegios:
$ systemctl status cron
● cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-10-12 09:14:07 -03; 2h 31min ago
Docs: man:cron(8)
Main PID: 1042 (cron)
Tasks: 1 (limit: 18912)
Memory: 1.2M (peak: 2.9M)
CPU: 84ms
CGroup: /system.slice/cron.service
└─1042 /usr/sbin/cron -f -P
Las dos palabras resaltadas responden preguntas distintas, y confundirlas es el error más común al empezar:
enabled / disabled | active / inactive | |
|---|---|---|
| Responde a | ¿Se iniciará en el próximo arranque? | ¿Está en ejecución ahora? |
| Se cambia con | systemctl enable y disable | systemctl start y stop |
| Efecto | Permanente | Inmediato; no sobrevive al reinicio |
Un servicio puede estar habilitado y detenido, o en marcha sin estar habilitado. Quien inicia un servicio con start y olvida enable lo descubre en el siguiente reinicio. El resto de la salida se lee con lo visto en otros temas: el PID del proceso principal, el tiempo de CPU acumulado —el que el kernel contabiliza en cada tic, según el tema 19— y, en CGroup, la lista de todos los procesos que pertenecen al servicio.
6. Los targets: el arranque como un grafo
systemd no sigue una lista. Cada unidad declara de cuáles depende y después de cuáles debe iniciarse, y con esas declaraciones se forma un grafo. El arranque consiste en recorrerlo, iniciando a la vez todo lo que no tenga dependencias pendientes. En un equipo con varios núcleos, decenas de servicios se inician en paralelo.
Hay que distinguir dos clases de relación, que son independientes:
| Directiva | Clase | Significado |
|---|---|---|
Requires= | Necesidad | Esta unidad necesita a la otra. Si la otra falla, esta no se inicia. |
Wants= | Necesidad | Conviene iniciar también la otra, pero si falla se continúa igual. |
After=, Before= | Orden | Si las dos van a iniciarse, en qué orden. No obliga a iniciar la otra. |
Para dar estructura al grafo existen los targets: unidades que no ejecutan nada y sirven como puntos de referencia. Alcanzar un target significa que todo lo asociado a él está iniciado. Son los estados por los que pasa el sistema durante el arranque:
$ systemctl get-default # ¿hasta dónde arranca este equipo?
graphical.target
$ systemctl list-dependencies graphical.target | head -6
graphical.target
● ├─gdm.service
● ├─power-profiles-daemon.service
● └─multi-user.target
● ├─cron.service
● ├─NetworkManager.service
Ahora se entiende la línea WantedBy=multi-user.target del archivo de cron: al habilitarlo, queda anotado entre las unidades que ese target quiere iniciar. Y se entiende el parámetro systemd.unit=rescue.target del tema 21: le indica a systemd que se detenga en un target distinto del predeterminado, uno que solo incluye lo indispensable.
Los targets reemplazan a los niveles de ejecución de SysV init, y se conserva la equivalencia por compatibilidad:
| Nivel antiguo | Target | Estado del sistema |
|---|---|---|
| 0 | poweroff.target | Apagado. |
| 1 | rescue.target | Recuperación: un solo usuario, sin red ni servicios. |
| 3 | multi-user.target | Sistema completo, sin entorno gráfico. |
| 5 | graphical.target | Sistema completo, con entorno gráfico. |
| 6 | reboot.target | Reinicio. |
7. La secuencia del espacio de usuario
Con esos elementos, esto es lo que ocurre entre que systemd empieza a ejecutarse y aparece la pantalla de acceso. Aunque buena parte sucede en paralelo, las fases tienen un orden:
- systemd se inicia. Lee los archivos de unidad de los tres directorios y construye el grafo de dependencias a partir del target predeterminado. proceso 1
- Genera unidades a partir de la configuración clásica. Por cada línea de
/etc/fstab, el archivo que enumera los sistemas de archivos del equipo, crea una unidad.mount. proceso 1 - Inicia el registro y la gestión de dispositivos.
systemd-journaldempieza a recoger mensajes, ysystemd-udevda atender los avisos del kernel. servicios - Se completa la detección del hardware. udev recorre los dispositivos ya detectados y carga los módulos que faltan, usando los alias que vimos en el tema 24. El initramfs solo había cargado los necesarios para llegar al disco. servicios
- Se comprueba y se monta el resto de los sistemas de archivos. La raíz, que el kernel montó en solo lectura, se vuelve a montar con escritura. servicios
- Se alcanza
sysinit.targety luegobasic.target. Quedan establecidos el nombre del equipo, la hora, los puntos de conexión y los temporizadores. proceso 1 - Se inician los servicios, en paralelo: red, tareas programadas, acceso remoto y los que cada equipo tenga habilitados. servicios
- Se alcanza
multi-user.target. En las terminales de texto queda esperando un programa que pide usuario y contraseña. proceso 1 - Se inicia el gestor de pantalla y se alcanza
graphical.target. Aparece la pantalla de inicio de sesión. servicios - Un usuario se identifica. Se inicia un segundo systemd, propio de ese usuario, que administra los programas de su sesión. sesión
El paso 5 cierra un cabo del tema 21: el parámetro ro hacía montar la raíz en solo lectura precisamente para que en este punto pudiera verificarse antes de permitir escrituras. Y el paso 10 explica el segundo systemd que apareció en la salida de pstree.
A partir de ahí el sistema está en régimen. systemd no termina: queda dormido, esperando que un servicio finalice, que se conecte un dispositivo, que venza un temporizador o que alguien le dé una orden con systemctl.
8. Experimento: un servicio propio
La mejor forma de entender una unidad es escribir una. El servicio será un programa mínimo, que anuncia su inicio y luego escribe una línea cada diez segundos:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
setvbuf(stdout, NULL, _IOLBF, 0); /* vaciar el búfer en cada línea */
printf("iniciado con PID %d\n", (int)getpid());
for (int n = 1; ; n++) {
sleep(10);
printf("sigo aqui (%d)\n", n);
}
}
La línea resaltada es una aplicación directa del tema 15. Un servicio no tiene terminal: systemd conecta su salida estándar al registro del sistema. Como el destino no es una terminal, la biblioteca de C usaría el búfer completo, y los mensajes quedarían retenidos en la memoria del programa durante horas. Con setvbuf pedimos el modo por líneas. Es un error real y frecuente al convertir un programa en servicio: «funciona, pero no registra nada».
El archivo de unidad tiene lo mínimo:
[Unit]
Description=Servicio de ejemplo del tema 22
[Service]
ExecStart=/usr/local/bin/latido
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
$ gcc -Wall -Wextra -g latido.c -o latido
$ sudo cp latido /usr/local/bin/
$ sudo cp latido.service /etc/systemd/system/
$ sudo systemctl daemon-reload # que systemd relea los archivos de unidad
$ sudo systemctl start latido
$ journalctl -u latido -n 3 --no-pager
oct 12 11:52:10 equipo systemd[1]: Started latido.service - Servicio de ejemplo del tema 22.
oct 12 11:52:10 equipo latido[7731]: iniciado con PID 7731
oct 12 11:52:20 equipo latido[7731]: sigo aqui (1)
$ ps -o pid,ppid,comm -C latido
PID PPID COMMAND
7731 1 latido
El programa está en ejecución sin terminal y sin que ningún usuario lo haya lanzado directamente. Su padre es el proceso 1. Y lo que escribe con printf aparece en el registro, con la fecha, el nombre del equipo y el PID añadidos por el sistema: el programa no tuvo que ocuparse de nada de eso.
Falta ver la supervisión. Finalizamos el proceso por la fuerza, simulando un fallo:
$ sudo kill -9 7731
$ journalctl -u latido -n 5 --no-pager
oct 12 11:53:02 equipo systemd[1]: latido.service: Main process exited, code=killed, status=9/KILL
oct 12 11:53:02 equipo systemd[1]: latido.service: Failed with result 'signal'.
oct 12 11:53:04 equipo systemd[1]: latido.service: Scheduled restart job, restart counter is at 1.
oct 12 11:53:04 equipo systemd[1]: Started latido.service - Servicio de ejemplo del tema 22.
oct 12 11:53:04 equipo latido[7790]: iniciado con PID 7790
El kernel avisó al proceso 1 de que su hijo había terminado, y por qué. systemd lo anotó, esperó los dos segundos indicados en RestartSec y lo inició de nuevo, con otro PID. Es la diferencia central con SysV init: el servicio no solo se inicia, se mantiene.
Por último, la habilitación para el arranque, y la limpieza:
$ sudo systemctl enable latido
Created symlink /etc/systemd/system/multi-user.target.wants/latido.service → /etc/systemd/system/latido.service.
$ sudo systemctl disable --now latido # deshabilitar y detener
Removed "/etc/systemd/system/multi-user.target.wants/latido.service".
$ sudo rm /etc/systemd/system/latido.service /usr/local/bin/latido
$ sudo systemctl daemon-reload
La salida de enable revela que habilitar un servicio no tiene ningún misterio: es crear un enlace al archivo de unidad en un directorio cuyo nombre es el del target seguido de .wants. En el próximo arranque, al construir el grafo, systemd encontrará el enlace y sabrá que ese target quiere ese servicio.
9. El registro: journalctl
En el experimento usamos journalctl sin presentarlo. Es el comando que consulta el registro del sistema, que mantiene el servicio systemd-journald. Ese servicio reúne en un solo lugar mensajes de tres orígenes:
- Los del kernel: los mismos que muestra
dmesg. - La salida estándar y de errores de cada servicio.
- Los que los programas envían expresamente al registro.
Los guarda en un formato binario con índices, lo que permite filtrar con rapidez por servicio, por fecha, por gravedad o por arranque:
| Comando | Muestra |
|---|---|
journalctl -b | Todo lo registrado desde el arranque actual. |
journalctl -b -1 | Lo del arranque anterior. |
journalctl --list-boots | La lista de arranques de los que hay registro. |
journalctl -k | Solo los mensajes del kernel. |
journalctl -u cron | Solo los de una unidad. |
journalctl -p err | Solo los de gravedad «error» o superior. |
journalctl --since "10 min ago" | Los de un intervalo de tiempo. |
journalctl -f | Los nuevos, a medida que llegan. |
journalctl -n 20 | Los últimos veinte. |
Las opciones se combinan: journalctl -b -u NetworkManager -p warning muestra las advertencias de ese servicio en este arranque. Para leer el registro completo hace falta ser administrador o pertenecer al grupo adm; cada usuario puede ver sin restricciones los mensajes de sus propios programas.
La relación con dmesg, que usamos desde el tema 11, se resume así:
dmesg | journalctl | |
|---|---|---|
| Lee de | El búfer de mensajes, en la memoria del kernel | Archivos en /var/log/journal |
| Contiene | Solo mensajes del kernel | Kernel y espacio de usuario |
| Tras un reinicio | Se pierde | Se conserva |
| Si el búfer se llena | Los mensajes nuevos pisan a los más antiguos | No aplica: ya están en el disco |
| Disponible | Desde el instante cero, antes de que exista ningún proceso | Desde que se inicia el servicio de registro, que recoge también lo anterior |
La tercera fila tiene una consecuencia práctica de primer orden. Si el equipo se colgó y hubo que reiniciarlo, dmesg ya no sirve: muestra el arranque nuevo. Los últimos mensajes del kernel antes del problema están en journalctl -k -b -1, siempre que hayan llegado a escribirse en el disco.
10. Leer el arranque
Con dmesg reconstruimos en el tema 21 la parte del kernel. El registro permite continuar esa lectura donde la dejamos. Pedimos las marcas de tiempo en segundos desde el arranque, como las del kernel, y filtramos los hitos:
$ journalctl -b -o short-monotonic --no-pager | grep -E 'systemd\[1\]: (systemd 2|Reached target|Startup)'
[ 2.391005] equipo systemd[1]: systemd 255.4-1ubuntu8 running in system mode (…)
[ 5.321114] equipo systemd[1]: Reached target local-fs.target - Local File Systems.
[ 5.792872] equipo systemd[1]: Reached target sysinit.target - System Initialization.
[ 5.871251] equipo systemd[1]: Reached target basic.target - Basic System.
[ 8.287140] equipo systemd[1]: Reached target network-online.target - Network is Online.
[ 8.290307] equipo systemd[1]: Reached target multi-user.target - Multi-User System.
[ 8.292226] equipo systemd[1]: Reached target graphical.target - Graphical Interface.
[ 8.324418] equipo systemd[1]: Startup finished in 4.512s (firmware) + 2.210s (loader) + 2.391s (kernel) + 5.933s (userspace) = 15.046s.
La primera línea es la última que mostramos en el tema 21: los dos registros empalman. Las siguientes son los cuatro targets del diagrama de la sección 6, alcanzados en orden, y la final es el resumen que informa systemd-analyze. Este equipo tardó unos seis segundos en ir de systemd a un sistema completo: es la cifra de userspace.
El salto entre basic.target y multi-user.target llama la atención: casi dos segundos y medio en los que no aparece ningún hito. Para saber en qué se fueron hay dos comandos:
$ systemd-analyze blame | head -5
2.104s NetworkManager-wait-online.service
1.312s snapd.seeded.service
843ms plymouth-quit-wait.service
512ms dev-nvme0n1p2.device
271ms NetworkManager.service
$ systemd-analyze critical-chain
graphical.target @5.901s
└─multi-user.target @5.899s
└─NetworkManager-wait-online.service @3.790s +2.104s
└─NetworkManager.service @3.512s +271ms
└─basic.target @3.480s
| Comando | Informa | Advertencia |
|---|---|---|
systemd-analyze blame | Cuánto tardó cada unidad en iniciarse, de mayor a menor. | Los tiempos no se suman: las unidades se inician en paralelo. |
systemd-analyze critical-chain | La cadena de dependencias que determinó el tiempo total. Tras @, cuándo se inició cada unidad; tras +, cuánto tardó. | Es la que hay que mirar para acortar el arranque. |
La cadena crítica da la respuesta; sus tiempos se cuentan desde que systemd empezó, no desde el encendido. De los seis segundos, dos los consumió un servicio cuyo único trabajo es esperar a que la red esté conectada, para que los servicios que la necesitan no empiecen antes. No es un defecto, pero es una decisión: en un equipo que no necesita la red al arrancar, deshabilitarlo ahorra un tercio del tiempo. Es el margen de ajuste que anunciamos en el tema 21.
11. Cuando un servicio falla
La tabla de fallos del tema 21 terminaba con una fila que dejamos para este: la raíz se montó, el sistema de inicio arrancó, y algo posterior salió mal. Según la gravedad, hay tres situaciones:
| Situación | Síntoma | Causa habitual |
|---|---|---|
| Falla un servicio no esencial | El sistema arranca con normalidad; algo no funciona. | Un error de configuración de ese servicio. |
| Falla algo imprescindible | El mensaje «You are in emergency mode» y una petición de la contraseña de administrador. | Casi siempre, una línea de /etc/fstab que nombra un disco o una partición que ya no existe. |
| Termina el proceso 1 | Pánico del kernel: Attempted to kill init! | El sistema de inicio dañado, o un parámetro init= que apunta a un programa que termina. |
El modo de emergencia es el caso que más asusta y uno de los más fáciles de resolver. systemd no pudo montar un sistema de archivos declarado como necesario, y en lugar de seguir con un sistema incompleto se detiene y ofrece una shell. Se corrige o se comenta la línea errónea de /etc/fstab y se reinicia.
Para el primer caso, que es el cotidiano, el procedimiento tiene tres pasos:
$ systemctl --failed # 1. ¿qué unidades fallaron?
UNIT LOAD ACTIVE SUB DESCRIPTION
● nginx.service loaded failed failed A high performance web server
$ systemctl status nginx # 2. ¿cómo terminó?
× nginx.service - A high performance web server
Active: failed (Result: exit-code) since …
Process: 2210 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=1/FAILURE)
$ journalctl -u nginx -b -n 3 --no-pager # 3. ¿qué dijo antes de terminar?
oct 12 09:14:09 equipo nginx[2210]: nginx: [emerg] unexpected "}" in /etc/nginx/nginx.conf:41
oct 12 09:14:09 equipo nginx[2210]: nginx: configuration file /etc/nginx/nginx.conf test failed
oct 12 09:14:09 equipo systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
El tercer paso da la causa con archivo y número de línea. Lo que el servicio escribió en su salida de errores, que en una terminal habría pasado ante los ojos de alguien, quedó guardado: nadie miraba en el momento del arranque, y por eso existe el registro. Cuando ni siquiera el registro alcanza, queda el recurso del tema 16: ejecutar a mano el comando de ExecStart bajo strace.
12. El apagado
Apagar es también tarea del proceso 1, y es el arranque recorrido al revés. Importa hacerlo en orden por una razón que conocemos del tema 7: el kernel mantiene en memoria datos que todavía no escribió en el disco, y los servicios pueden tener trabajo a medio guardar.
- Alguien pide el apagado, con
systemctl poweroffo desde el entorno gráfico. systemd toma como objetivopoweroff.target. proceso 1 - Detiene las unidades en orden inverso al de inicio. A cada servicio le envía una señal que le pide terminar, y le da un plazo para guardar su estado. servicios
- Finaliza por la fuerza a los que no terminaron dentro del plazo. Es el origen del mensaje «A stop job is running…» que a veces demora un apagado. proceso 1
- Desmonta los sistemas de archivos, lo que obliga a escribir en el disco todo lo pendiente. proceso 1
- Hace la llamada al sistema
reboot, con la indicación de apagar o de reiniciar. kernel - El kernel detiene los dispositivos y le pide al firmware que corte la alimentación, mediante las tablas ACPI que recibió durante el arranque. firmware
La secuencia empieza y termina en el mismo lugar que el arranque: en el firmware. Y explica por qué no hay que apagar un equipo cortando la corriente. Sin los pasos 2 a 4, lo que estaba en la memoria y no en el disco se pierde, y un sistema de archivos interrumpido a mitad de una modificación puede quedar en un estado incoherente.
13. Cierre del bloque
Con este tema queda completa la cadena que dibujamos al comenzar el tema 20. Cada eslabón resuelve una limitación del anterior y es reemplazado por el siguiente:
| Eslabón | Está en | Sabe hacer | Entrega el control a | Tema |
|---|---|---|---|---|
| Firmware | Un chip de la placa | Inicializar el hardware; leer un archivo de una partición FAT | El cargador de arranque | 20 |
| Cargador | La partición EFI | Leer el sistema de archivos de Linux; cargar dos archivos en memoria | El kernel | 21 |
| Kernel | /boot | Administrar la máquina; ejecutar un programa | El init del initramfs | 21 |
| initramfs | La memoria | Cargar módulos; encontrar y montar la raíz | El init del sistema | 21 |
| systemd | La partición raíz | Montar, configurar, iniciar y vigilar los servicios | Nadie: permanece | 22 |
La última fila es la única distinta. Los cuatro primeros eslabones son andamios: hacen su parte y desaparecen. El quinto se queda, y es el primero que no se ejecuta con privilegio total. Con él termina el arranque y empieza el funcionamiento normal, el que estudiamos en los bloques 3 y 4.
El recorrido deja ver además un patrón que se repite en cada paso. El firmware tiene sus propios drivers; el cargador, los suyos; el initramfs, un espacio de usuario en miniatura. Cada etapa es una versión reducida de un sistema operativo, construida para poder cargar una versión más completa. Arrancar es reconstruir la máquina extendida del tema 2 una capa por vez, partiendo de un procesador que solo sabe buscar su primera instrucción en una dirección fija.
Hasta aquí estudiamos un sistema, Linux, y su manera de hacer las cosas. El bloque siguiente amplía la mirada: qué tipos de sistemas operativos existen y de qué otras maneras puede construirse un kernel.
14. Resumen
- Una vez que ejecuta el primer programa, el kernel no toma más iniciativas. Todo lo que falta para tener un sistema utilizable lo hace el espacio de usuario.
- El proceso 1 es el antecesor de todos los procesos de usuario. No puede terminar, está protegido de las señales y adopta a los procesos huérfanos.
- SysV init ejecutaba scripts en secuencia. systemd describe los servicios de forma declarativa, los inicia en paralelo según sus dependencias y los supervisa.
- Todo lo que systemd administra es una unidad: servicios, targets, montajes, dispositivos, puntos de conexión y temporizadores.
- Un archivo de servicio indica, en esencia, qué programa ejecutar, qué hacer si termina y a qué target asociarlo.
- Que un servicio esté habilitado y que esté activo son cosas independientes: la primera se refiere al próximo arranque y la segunda al momento actual.
- Los targets son puntos de referencia que agrupan unidades. El arranque pasa por
sysinit,basic,multi-usery, en un escritorio,graphical. - La salida estándar de un servicio va al registro del sistema, y conviene que no quede retenida en un búfer.
journalctlconsulta el registro, que reúne los mensajes del kernel y de los servicios y se conserva entre reinicios;dmesgsolo muestra los del kernel desde el arranque actual.systemd-analyze blameycritical-chainindican en qué se va el tiempo del arranque.- Ante un servicio que falla:
systemctl --failed,systemctl statusyjournalctl -u. - El apagado es el arranque al revés: detener los servicios, desmontar los discos y pedir al kernel que apague.
15. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Por qué el kernel no se ocupa de configurar la red o de iniciar los servicios?
Porque no son mecanismos, sino decisiones propias de cada equipo, y no requieren privilegios de hardware. El kernel ofrece los medios, y delega esas decisiones en programas de usuario, empezando por el primero que ejecuta.
2. ¿Qué indica que los procesos 1 y 2 tengan como padre al 0?
Que no los creó ningún otro proceso, sino el kernel directamente. El 1 es el antecesor de los procesos de usuario y el 2, el de los hilos del kernel.
3. Menciona tres particularidades del proceso 1.
No puede terminar: si lo hace, el kernel entra en pánico. Solo recibe las señales para las que instaló un manejador, de modo que no se lo puede finalizar con kill -9. Y adopta a los procesos cuyo padre terminó antes que ellos.
4. ¿Qué limitaciones de SysV init resolvió systemd?
El inicio secuencial, que desaprovechaba los equipos con varios núcleos; la falta de supervisión, por la que un servicio caído quedaba caído; la dificultad para saber qué procesos pertenecían a un servicio; y la dispersión de los mensajes en archivos distintos.
5. ¿Por qué las unidades propias se guardan en /etc/systemd/system y no en /usr/lib/systemd/system?
Porque el segundo directorio pertenece a los paquetes: una actualización sobrescribiría los cambios. El primero es del administrador, tiene prioridad y ninguna actualización lo modifica.
6. Un servicio aparece como disabled y active (running). ¿Qué significa?
Que está en ejecución ahora, porque alguien lo inició, pero no se iniciará en el próximo arranque. Para que lo haga hay que habilitarlo con systemctl enable.
7. ¿Qué diferencia hay entre Requires= y After=?
Requires= expresa necesidad: esta unidad no puede funcionar sin la otra. After= expresa solo orden: si las dos se inician, esta va después. Son independientes, y a menudo se usan juntas.
8. ¿Qué es un target, y a qué mecanismo antiguo reemplaza?
Una unidad que no ejecuta nada y sirve como punto de referencia: alcanzarla significa que todas las unidades asociadas están iniciadas. Reemplaza a los niveles de ejecución numerados de SysV init.
9. ¿Qué hace exactamente systemctl enable?
Crea un enlace al archivo de la unidad en el directorio .wants del target indicado en su línea WantedBy. En el arranque siguiente, systemd encuentra el enlace e incluye la unidad en el grafo.
10. ¿Por qué latido.c llama a setvbuf?
Porque la salida estándar de un servicio no es una terminal, y la biblioteca de C usaría el búfer completo: los mensajes quedarían en la memoria del proceso hasta acumular varios kilobytes. Con el modo por líneas, cada mensaje llega al registro en el momento.
11. Al finalizar latido con kill -9, vuelve a aparecer con otro PID. ¿Quién lo hace, y cómo se enteró?
systemd, que es su proceso padre. El kernel avisa al padre cuando un hijo termina; al tener la unidad Restart=always, systemd lo inicia de nuevo después del tiempo indicado en RestartSec.
12. El equipo se colgó y hubo que reiniciarlo. ¿Dónde buscas los últimos mensajes del kernel anteriores al problema?
En journalctl -k -b -1, que muestra los mensajes del kernel del arranque anterior. dmesg lee un búfer en memoria, que se perdió con el reinicio.
13. ¿Por qué los tiempos de systemd-analyze blame suman más que la duración del arranque?
Porque las unidades se inician en paralelo, y sus tiempos se superponen. Para saber qué determinó la duración total hay que mirar critical-chain, que muestra la cadena de dependencias más larga.
14. ¿Qué puede pasar si se apaga un equipo cortando la corriente?
Los servicios no guardan su estado, y los datos que el kernel tenía en memoria sin escribir se pierden. Un sistema de archivos interrumpido a mitad de una modificación puede quedar incoherente.
Para practicar
- Averigua qué programa es el proceso 1 de tu sistema y adónde apunta
/sbin/init. Sigue conpstree -ps $$la ascendencia de tu shell. - Ejecuta
sudo kill -9 1y comprueba conps -p 1que no ocurrió nada. Explica por qué. - Elige un servicio en ejecución, míralo con
systemctl caty localiza las líneasExecStart,RestartyWantedBy. ¿Dónde está su archivo de unidad? - Cuenta las unidades de cada tipo con
systemctl list-units --type=service --no-legend | wc -l, cambiando el tipo pormount,socketytimer. - Realiza el experimento de la sección 8 completo. Luego quita la línea de
setvbuf, vuelve a instalar el programa y observa cuánto tarda en aparecer el primer mensaje enjournalctl -u latido -f. - En
latido.service, cambiaRestart=alwaysporRestart=on-failure. Finaliza el proceso primero conkill -9y luego consystemctl stop. ¿En qué caso se reinicia? - Reconstruye el arranque de tu equipo con el comando de la sección 10 y anota cuándo se alcanzó cada target.
- Ejecuta
systemd-analyze critical-chain. ¿Qué unidad determina la duración de tu arranque? ¿Es un trabajo o una espera? - Compara
sudo dmesg | head -3conjournalctl -k -b | head -5. Luego consultajournalctl --list-boots: ¿de cuántos arranques conserva registro tu sistema? - Busca los errores del arranque actual con
journalctl -b -p err. Elige uno e intenta determinar qué servicio lo produjo y si es grave.