Google PlusFacebookTwitter

Kexec et l’appel système (syscall) shutdown(8)

By on Jan 21, 2014 in Linux | 0 comments

Share On GoogleShare On FacebookShare On Twitter

Dans le doute tu rebootes, pas si facile avec kexec !

Ça vous est forcement arrivé, ce moment de solitude, mais si, vous savez, celui qui vous rend perplexe lorsque vous exécutez un simple init 6 ou reboot. Celui où vous vous attendez à voir le BIOS (qui met quinze plombes à charger) de votre serveur apparaître à l’écran alors qu’en fait ce n’est qu’une séquence de démarrage de votre noyau.

C’est bon, ça vous revient à l’esprit ? Vous vous souvenez donc ce que vous avez fait par la suite, un reboot -f bien moche qui passe outre l’init (d’ici, j’arrive à voir la honte sur votre visage). Vous n’êtes quand même pas allés jusqu’au reset matériel ?

J’avoue, je l’ai fait… Ne me regardez pas comme ça ! 😯

L’explication du pourquoi du comment

Pourquoi les commandes init 6, reboot et shutdown -r now ne fonctionnent pas alors que la commande reboot -f permet, elle de redémarrer le système ?

En soit, il n’y a quasiment pas de différence entre ces trois commandes, elles appellent toutes les trois les scripts de gestion des services.

Pour ce qui est de la commande reboot -f, c’est un peu différent. L’option -f indique au système de ne pas passer par l’init, ce qui signifie que les processus seront arrêtés de manière brutale via un SIGKILL. Qui dit SIGKILL dit possible perte de données, imaginez un SIGKILL sur une base de données MySQL en InnoDB… j’espère que vous avez une sauvegarde.

L’appel système utilisé par la commande reboot -f est reboot(2).

Un début de piste fait son apparition, l’init

Je travaille sous Debian GNU/Linux Wheezy (au travail ainsi qu’à la maison), je vais donc prendre cette distribution comme exemple.

Comme vu plus haut, lorsque la commande reboot est exécutée, elle appelle shutdown qui envoie un signal (6 dans notre cas) à l’init. Jusque là rien de nouveau allez-vous me dire, certes, certes…

L’init définit le runlevel dans lequel il se trouve puis lance /etc/init.d/rc avec le bon runlevel en paramètre pour que ce dernier puisse effectuer des tâches différentes, en fonction de l’état dans lequel se trouve le système. Si l’on regarde le script rc d’un peu plus près on peut voir ceci :

#
# Check if we are able to use make like booting. It require the
# insserv package to be enabled. Boot concurrency also requires
# startpar to be installed.
#
CONCURRENCY=makefile
test -s /etc/init.d/.depend.boot || CONCURRENCY="none"
test -s /etc/init.d/.depend.start || CONCURRENCY="none"
test -s /etc/init.d/.depend.stop || CONCURRENCY="none"

<...>

startup() {
   eval "$(startpar -p 4 -t 20 -T 3 -M $1 -P $previous -R $runlevel)"

<...>

case "$runlevel" in
   0|6)
       ACTION=stop
       ;;
     S)
       ACTION=start
       ;;
     *)
       ACTION=start
       ;;
esac

La commande startpar fait partie du paquet sysvinit-utils. startpar permet de paralléliser le lancement des services au démarrage (ainsi qu’à l’arrêt) du serveur. Pour ce faire, rc vérifie qu’au moins un des trois fichiers suivants existe et ne soit pas vide :

  1. /etc/init.d/.depend.boot
  2. /etc/init.d/.depend.start
  3. /etc/init.d/.depend.stop

Le fichier .depend.stop nous confirme que la dernière action exécutée par startpar est kexec :

TARGETS = kexec-load urandom virtualbox-guest-utils apache2 exim4 atd yum-updatesd isc-dhcp-server tftpd-hpa sendsigs rsyslog umountnfs.sh rpcbind nfs-common hwclock.sh networking umountfs umountroot halt kexec reboot
sendsigs: virtualbox-guest-utils exim4 atd apache2 isc-dhcp-server kexec-load
rsyslog: sendsigs exim4 atd yum-updatesd apache2 isc-dhcp-server tftpd-hpa
umountnfs.sh: virtualbox-guest-utils rsyslog sendsigs exim4 atd apache2 isc-dhcp-server kexec-load
rpcbind: umountnfs.sh
nfs-common: umountnfs.sh
hwclock.sh: rsyslog atd nfs-common
networking: umountnfs.sh rpcbind exim4 yum-updatesd apache2 isc-dhcp-server
umountfs: virtualbox-guest-utils umountnfs.sh rpcbind exim4 networking atd yum-updatesd urandom hwclock.sh apache2 isc-dhcp-server kexec-load
umountroot: umountfs
halt: umountroot
kexec: umountroot kexec-load
reboot: umountroot kexec

Une solution \o/

Pour empêcher l’exécution de kexec, il faut indiquer au paquet kexec-tools de ne pas gérer le redémarrage du serveur. Si on regarde le code qui gère le service kexec-load, on peut voir ceci :

#! /bin/sh
### BEGIN INIT INFO
# Provides:             kexec-load
# Required-Start:
# Required-Stop:        $local_fs $remote_fs kexec
# Should-Stop:          autofs
# Default-Start:
# Default-Stop:         6
# Short-Description: Load kernel image with kexec
# Description:
### END INIT INFO

<...>

test -r /etc/default/kexec && . /etc/default/kexec

<...>

do_stop () {
        test "$LOAD_KEXEC" = "true" || exit 0
        test -x /sbin/kexec || exit 0
        test "x`cat /sys/kernel/kexec_loaded`y" = "x1y" && exit 0

        if [ -f $NOKEXECFILE ]
        then
                /bin/rm -f $NOKEXECFILE
                exit 0
        fi

        test "$USE_GRUB_CONFIG" = "true" && get_grub_kernel

        REAL_APPEND="$APPEND"

        test -z "$REAL_APPEND" && REAL_APPEND="`cat /proc/cmdline`"
        log_action_begin_msg "Loading new kernel image into memory"
        if [ -z "$INITRD" ]
        then
                kexec -l "$KERNEL_IMAGE" --append="$REAL_APPEND"
        else
                kexec -l "$KERNEL_IMAGE" --initrd="$INITRD" --append="$REAL_APPEND"
        fi
        log_action_end_msg $?
}

Le fichier /etc/default/kexec est sourcé au début du fichier, dans ce dernier se trouve la variable LOAD_KEXEC. Si cette dernière est définie à true alors kexec sera exécuté lors de l’arrêt du service. Pour passer cette valeur à false, soit on édite directement le fichier soit on passe par dpkg-reconfigure. Passons par la bonne méthode :

# dpkg-reconfigure kexec-tools

Voici ce que debconf vous posera comme question (répondez No) :

Reconfigure du paquet kexec-tools

Essayez, allez, faites-le, je sais que vous en mourrez d’envie ! :mrgreen:

# reboot

En théorie c’est censé fonctionner. Vous aurez l’immense joie de voir apparaître le magnifique logo de votre constructeur…

Pas envie de commenter cette foutue ligne ?

Commenter cette ligne est une solution plus que correcte mais elle peut ne pas convenir à tout le monde. L’autre solution serait de faire l’équivalent de la commande init 6 mais à la main. Ça donnerait quelque chose comme ceci (à adapter en fonction des services présents sur le serveur bien sûr) :

# /etc/init.d/apache2 stop
# /etc/init.d/mysql stop
# /etc/init.d/libvirt-guest stop
# sync ; sync
# reboot -f

Le sync est très important, il permet de synchroniser le cache (buffer) sur le disque et donc de s’assurer de la consistance des données. C’est chiant, c’est long mais ça fonctionne…

Édite du 28/01/2014

Sous Debian GNU/Linux il existe la commande coldreboot (qui n’est qu’un script shell), cette dernière passe outre kexec :

#!/bin/sh
NOKEXECFILE=/tmp/no-kexec-reboot

/bin/rm -f $NOKEXECFILE
touch $NOKEXECFILE
/sbin/reboot $*

Le man coldboot donne la description suivante :

DESCRIPTION
coldreboot is a script that forces a cold reboot regardless of whether kexec is enabled or not in /etc/default/kexec. coldreboot takes the same arguments as /sbin/reboot and passes them on to /sbin/reboot later.

Liens

The following two tabs change content below.

Gaëtan Trellu (goldyfruit)

Technical Operation Manager chez Ormuco
Autodidacte en informatique, depuis 2005 je parcours l’écosystème Unix à la recherche de nouvelles connaissances et de nouvelles rencontres.

CC BY 4.0 Kexec et l’appel système (syscall) shutdown(8) par Gaëtan Trellu (goldyfruit) est sous Licence Creative Commons Internationale Attribution 4.0.