4

The book "How Linux Works" says the general shutdown procedure (independent of init system) is something like this:

  1. init asks every process to shut down cleanly.
  2. If a process doesn’t respond after a while, init kills it, first trying a TERM signal.
  3. If the TERM signal doesn’t work, init uses the KILL signal on any stragglers.
  4. The system locks system files into place and makes other preparations for shutdown.
  5. The system unmounts all filesystems other than the root.
  6. The system remounts the root filesystem read-only.
  7. The system writes all buffered data out to the filesystem with the sync program.
  8. The final step is to tell the kernel to reboot or stop with the reboot(2) system call. This can be done by init or an auxiliary program such as reboot, halt, or poweroff.

How can sync write its buffers if the filesystem is read-only?

  • Remounting an fs read only prevents any file-level write requests and open() calls with rw mode from processes, thus no further data and fs structure modification is possible. The buffering is laying between block device drivers and fs drivers, so if the system has any dirty buffers it should write them to the underlying media – Serge May 31 '16 at 20:50
  • 2
    Notice that root file system is / and not necessarily the same as second bolded file system in the list. Also I think by sync program it means sync(2) system call. See here wiki.debian.org/ReadonlyRoot and here man7.org/linux/man-pages/man2/sync.2.html – coffeMug May 31 '16 at 20:55
  • @Serge Looks good! Could you make that an answer and maybe elaborate a bit on block device drivers and fs drivers part? Maybe an example? – Aaron May 31 '16 at 20:59
  • @coffeMug Step 5 unmounts all fs except root, step 6 remounts root, what could the fs in step 7 be other than root? – Aaron May 31 '16 at 21:01
4

You're right to be surprised: that order doesn't make sense. If a book presents it that way, it's sloppy and misleading.

Unmounting a filesystem, or mounting it read-only, writes all the data to the disk. When the umount command or mount -o remount,ro returns, all the data is written to the disk and sync has nothing left to do. It's pointless to call sync before (the data will be written by the umount operation anyway) and pointless to call it after (it won't do anything).

I think this wasn't true in some ancient Unix systems, where you had to call sync before unmounting. Calling it after was still pointless.

If you look beyond filesystems, there may be cases where sync does something. For example, I think that on Linux sync ensures that the metadata of RAID arrays are written to the disk. This is useful even in the absence of any filesystem mounted read-write.

  • Thanks, makes sense. Those steps are indeed exact copies from the book. – Aaron Jun 1 '16 at 9:20
2

Remounting an fs read only prevents any file-level write requests and open() calls with rw mode from processes, thus no further data and fs structure modification is possible. The buffering is laying between block device drivers and fs drivers, so if the system has any dirty buffers it should write them to the underlying media.

The typical stack looks this way:

  • process
  • kernel file io API
  • fs driver, e.g. ext3fs
  • blkdev abstraction layer, an API, a number of useful primitives, default behaviors, e.t.c. part of the kernel. Also, this layer manages buffers and disk caches as well as provides swapping to the kernel.
  • block device driver, e.g. scsi linux subsystem.
  • storage device

There are also loops possible at different levels. For example a file could be used as a loop device backing storage, LUKS device encryption, e.t.c.

1

Here is part of the code which does the shutdown (System V style implementation):

/*
 *  Kill all processes, call /etc/init.d/halt (if present)
 */
void fastdown()
{
    int do_halt = (down_level[0] == '0');
    int i;
#if 0
    char cmd[128];
    char *script;

    /*
     *  Currently, the halt script is either init.d/halt OR rc.d/rc.0,
     *  likewise for the reboot script. Test for the presence
     *  of either.
     */
    if (do_halt) {
        if (access(HALTSCRIPT1, X_OK) == 0)
            script = HALTSCRIPT1;
        else
            script = HALTSCRIPT2;
    } else {
        if (access(REBOOTSCRIPT1, X_OK) == 0)
            script = REBOOTSCRIPT1;
        else
            script = REBOOTSCRIPT2;
    }
#endif

    /* First close all files. */
    for(i = 0; i < 3; i++)
        if (!isatty(i)) {
            close(i);
            open("/dev/null", O_RDWR);
        }
    for(i = 3; i < 20; i++) close(i);
    close(255);

    /* First idle init. */
    if (kill(1, SIGTSTP) < 0) {
        fprintf(stderr, "shutdown: can't idle init: %s.\r\n", strerror(errno));
        exit(1);
    }

    /* Kill all processes. */ 
    fprintf(stderr, "shutdown: sending all processes the TERM signal...\r\n");
    kill(-1, SIGTERM);
    sleep(sltime ? atoi(sltime) : 3);
    fprintf(stderr, "shutdown: sending all processes the KILL signal.\r\n");
    (void) kill(-1, SIGKILL);

#if 0
    /* See if we can run /etc/init.d/halt */
    if (access(script, X_OK) == 0) {
        spawn(1, cmd, "fast", NULL);
        fprintf(stderr, "shutdown: %s returned - falling back "
                "on default routines\r\n", script);
    }
#endif

    /* script failed or not present: do it ourself. */
    sleep(1); /* Give init the chance to collect zombies. */

    /* Record the fact that we're going down */
    write_wtmp("shutdown", "~~", 0, RUN_LVL, "~~");

    /* This is for those who have quota installed. */
#if defined(ACCTON_OFF)
# if (ACCTON_OFF > 1) && (_BSD_SOURCE || (_XOPEN_SOURCE && _XOPEN_SOURCE < 500))
    /* This is an alternative way to disable accounting, saving a fork() */
    if (acct(NULL))
        fprintf(stderr, "shutdown: can not stop process accounting: %s.\r\n", strerror(errno));
# elif (ACCTON_OFF > 0)
    spawn(1, "accton", "off", NULL);
# else
    spawn(1, "accton", NULL);
# endif
#endif
    spawn(1, "quotaoff", "-a", NULL);

    sync();
    fprintf(stderr, "shutdown: turning off swap\r\n");
    spawn(0, "swapoff", "-a", NULL);
    fprintf(stderr, "shutdown: unmounting all file systems\r\n");
    spawn(0, "umount", "-a", NULL);

    /* We're done, halt or reboot now. */
    if (do_halt) {
        fprintf(stderr, "The system is halted. Press CTRL-ALT-DEL "
                "or turn off power\r\n");
        init_reboot(BMAGIC_HALT);
        exit(0);
    }

    fprintf(stderr, "Please stand by while rebooting the system.\r\n");
    init_reboot(BMAGIC_REBOOT);
    exit(0);
}

As you can see first the process kill part is, then we have:

sync();
fprintf(stderr, "shutdown: turning off swap\r\n");
spawn(0, "swapoff", "-a", NULL);
fprintf(stderr, "shutdown: unmounting all file systems\r\n");
spawn(0, "umount", "-a", NULL);

Which writes the data to disk using sync. Then turns off swap and un-mount all the file systems. Then actual halt or reboot happens.

Description of sync from man page:

sync() causes all pending modifications to file system metadata and cached file data to be written to the underlying filesystems.

The book might be a bit old or explaining other implementation of shutdown. Reading the code and man pages is also a very good way to learn how Linux works.

  • That code is very useful, I wish I could upvote you again. Where did you get it from? – Aaron Jun 1 '16 at 13:15
  • 1
    Here you can find the source code of shutdown as well as some other guys: download.savannah.gnu.org/releases/sysvinit – coffeMug Jun 1 '16 at 13:21
  • 1
    Look into sysvinit package. – coffeMug Jun 1 '16 at 13:21
  • The book might be a bit old — But that is alright, since the program that you are analyzing is not current, either. Ubuntu hasn't used it in just shy of a decade, for example. ☺ – JdeBP Jun 1 '16 at 15:48
  • Actually the code is from fairly recent Gentoo distro. – coffeMug Jun 1 '16 at 16:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.