一、版本说明

嵌入式Linux 下面的reboot命令看似简单,但出问题时定位起来发现别有洞天。

下面就按在shell下执行reboot命令之后程序的执行过程进行解析。

Busybox:1.23.2                        ——制作跟文件系统,/sbin/reboot程序的由来

Libc:2.6.1                                  ——标准C库

Linux kernel:2.6.35                 ——内核版本

 
二、流程简介

         如图所示是reboot的简要流程图。

 

 普通的reboot是通过busybox为入口,进入halt_main函数,然后给init进程发送SIGTERM信号,init进程接收到信号后给其他进程发送终止信号,最后调用C库函数reboot,reboot通过系统调用sys_reboot进入内核,内核将整个系统重启。其中在shell中执行reboot –f则通过halt_main直接调用C函数reboot,不经过init进程。
三、代码详解
1.reboot命令端

执行reboot命令,busybox检查当前命令为reboot,进入函数halt_main,

reboot,halt和poweroff都会进入这个函数,不同的命令发送的信号和执行的操作不同。

现只分析reboot的情况。
代码如下

    int halt_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
    int halt_main(int argc UNUSED_PARAM, char **argv)
    {
        static const int magic[] = {
            RB_HALT_SYSTEM,
            RB_POWER_OFF,
            RB_AUTOBOOT
        };
        static const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM };
     
        int delay = 0;
        int which, flags, rc;
     
        /* Figure out which applet we're running */
        for (which = 0; "hpr"[which] != applet_name[0]; which++)
            continue;
     
        /* Parse and handle arguments */
        opt_complementary = "d+"; /* -d N */
        /* We support -w even if !ENABLE_FEATURE_WTMP,
         * in order to not break scripts.
         * -i (shut down network interfaces) is ignored.
         */
        flags = getopt32(argv, "d:nfwi", &delay);
     
        sleep(delay);
     
        write_wtmp();
     
        if (flags & 8) /* -w */
            return EXIT_SUCCESS;
     
        if (!(flags & 2)) /* no -n */
            sync();
     
        /* Perform action. */
        rc = 1;
        if (!(flags & 4)) { /* no -f */
    //TODO: I tend to think that signalling linuxrc is wrong
    // pity original author didn't comment on it...
            if (ENABLE_FEATURE_INITRD) {
                /* talk to linuxrc */
                /* bbox init/linuxrc assumed */
                pid_t *pidlist = find_pid_by_name("linuxrc");
                if (pidlist[0] > 0)
                    rc = kill(pidlist[0], signals[which]);
                if (ENABLE_FEATURE_CLEAN_UP)
                    free(pidlist);
            }
            if (rc) {
                /* talk to init */
                if (!ENABLE_FEATURE_CALL_TELINIT) {
                    /* bbox init assumed */
                    rc = kill(1, signals[which]);
                } else {
                    /* SysV style init assumed */
                    /* runlevels:
                     * 0 == shutdown
                     * 6 == reboot */
                    execlp(CONFIG_TELINIT_PATH,
                            CONFIG_TELINIT_PATH,
                            which == 2 ? "6" : "0",
                            (char *)NULL
                    );
                    bb_perror_msg_and_die("can't execute '%s'",
                            CONFIG_TELINIT_PATH);
                }
            }
        } else {
            rc = reboot(magic[which]);
        }
     
        if (rc)
            bb_perror_nomsg_and_die();
        return rc;
    }

该函数判断reboot是否带了 -f 参数,如果带了,直接调用reboot调用C函数库

如果没带,则通过

kill(1, signals[which]);

给init进程发送SIGTERM信号。

2.init进程端

init进程初始化函数init_main将部分信号进行重定义

            bb_signals_recursive_norestart(0
                + (1 << SIGINT)  /* Ctrl-Alt-Del */
                + (1 << SIGQUIT) /* re-exec another init */
    #ifdef SIGPWR
                + (1 << SIGPWR)  /* halt */
    #endif
                + (1 << SIGUSR1) /* halt */
                + (1 << SIGTERM) /* reboot */
                + (1 << SIGUSR2) /* poweroff */
    #if ENABLE_FEATURE_USE_INITTAB
                + (1 << SIGHUP)  /* reread /etc/inittab */
    #endif
                , record_signo);

    void record_signo(int signo)
    {
        bb_got_signal = signo;
    }
     

将SIGUSR1(halt) SIGUSR2(poweroff) SIGTERM(reboot)信号存入全局变量bb_got_signal中。
在init_main的最后进入一个while(1)循环,不断检查信号和等待子进程的退出

其中check_delayed_sigs就是用来检查这个全局变量的,如下:

        while (1) {
            int maybe_WNOHANG;
     
            maybe_WNOHANG = check_delayed_sigs();
     
            /* (Re)run the respawn/askfirst stuff */
            run_actions(RESPAWN | ASKFIRST);
            maybe_WNOHANG |= check_delayed_sigs();
     
            /* Don't consume all CPU time - sleep a bit */
            sleep(1);
            maybe_WNOHANG |= check_delayed_sigs();
     
            /* Wait for any child process(es) to exit.
             *
             * If check_delayed_sigs above reported that a signal
             * was caught, wait will be nonblocking. This ensures
             * that if SIGHUP has reloaded inittab, respawn and askfirst
             * actions will not be delayed until next child death.
             */
            if (maybe_WNOHANG)
                maybe_WNOHANG = WNOHANG;
            while (1) {
                pid_t wpid;
                struct init_action *a;
     
                /* If signals happen _in_ the wait, they interrupt it,
                 * bb_signals_recursive_norestart set them up that way
                 */
                wpid = waitpid(-1, NULL, maybe_WNOHANG);
                if (wpid <= 0)
                    break;
     
                a = mark_terminated(wpid);
                if (a) {
                    message(L_LOG, "process '%s' (pid %d) exited. "
                            "Scheduling for restart.",
                            a->command, wpid);
                }
                /* See if anyone else is waiting to be reaped */
                maybe_WNOHANG = WNOHANG;
            }
        } /* while (1) */

而里面的while(1)一般会阻塞在waitpid中,那么信号检查是不是会有问题?

    WNOHANG        如果没有可用的子进程退出状态,立即返回而不是阻塞

但maybe_WNOHANG的值应该是0,不是WNOHANG(=1)感觉还是会阻塞。我这样理解的,因为所有的用户进程都是init进程的子进程,我判断前面执行reboot时也是一个子进程,halt_main发送完信号后就会退出,init接收到信号而且waitpid成功,然后跳出循环检查信号。

下面看一下信号的处理部分

    static int check_delayed_sigs(void)
    {
        int sigs_seen = 0;
     
        while (1) {
            smallint sig = bb_got_signal;
     
            if (!sig)
                return sigs_seen;
            bb_got_signal = 0;
            sigs_seen = 1;
    #if ENABLE_FEATURE_USE_INITTAB
            if (sig == SIGHUP)
                reload_inittab();
    #endif
            if (sig == SIGINT)
                run_actions(CTRLALTDEL);
            if (sig == SIGQUIT) {
                exec_restart_action();
                /* returns only if no restart action defined */
            }
            if ((1 << sig) & (0
    #ifdef SIGPWR
                + (1 << SIGPWR)
    #endif
                + (1 << SIGUSR1)
                + (1 << SIGUSR2)
                + (1 << SIGTERM)
            )) {
                halt_reboot_pwoff(sig);
            }
        }
    }

判断为SIGTERM进入halt_reboot_pwoff函数

    static void halt_reboot_pwoff(int sig)
    {
        const char *m;
        unsigned rb;
     
        /* We may call run() and it unmasks signals,
         * including the one masked inside this signal handler.
         * Testcase which would start multiple reboot scripts:
         *  while true; do reboot; done
         * Preventing it:
         */
        reset_sighandlers_and_unblock_sigs();
     
        run_shutdown_and_kill_processes();
     
        m = "halt";
        rb = RB_HALT_SYSTEM;
        if (sig == SIGTERM) {
            m = "reboot";
            rb = RB_AUTOBOOT;
        } else if (sig == SIGUSR2) {
            m = "poweroff";
            rb = RB_POWER_OFF;
        }
        message(L_CONSOLE, "Requesting system %s", m);
        pause_and_low_level_reboot(rb);
        /* not reached */
    }

reset_sighandlers_and_unblock_sigs函数将信号重置回默认处理。

    static void reset_sighandlers_and_unblock_sigs(void)
    {
        bb_signals(0
            + (1 << SIGUSR1)
            + (1 << SIGUSR2)
            + (1 << SIGTERM)
            + (1 << SIGQUIT)
            + (1 << SIGINT)
            + (1 << SIGHUP)
            + (1 << SIGTSTP)
            + (1 << SIGSTOP)
            , SIG_DFL);
        sigprocmask_allsigs(SIG_UNBLOCK);
    }

run_shutdown_and_kill_processes函数给所有进程发送SIGTERM信号并执行sync(保存数据)

延时后再次发送SIGKILL信号,这里说明一下为什么要发送SIGKILL信号,一般的SIGINT和SIGTERM信号都可以屏蔽或转作他用,SIGKILL信号是不可被屏蔽的,

这样告诉其他进程必须终止。

    static void run_shutdown_and_kill_processes(void)
    {
        /* Run everything to be run at "shutdown".  This is done _prior_
         * to killing everything, in case people wish to use scripts to
         * shut things down gracefully... */
        run_actions(SHUTDOWN);
     
        message(L_CONSOLE | L_LOG, "The system is going down NOW!");
     
        /* Send signals to every process _except_ pid 1 */
        kill(-1, SIGTERM);
        message(L_CONSOLE | L_LOG, "Sent SIG%s to all processes", "TERM");
        sync();
        sleep(1);
     
        kill(-1, SIGKILL);
        message(L_CONSOLE, "Sent SIG%s to all processes", "KILL");
        sync();
        /*sleep(1); - callers take care about making a pause */
    }

最终进入函数pause_and_low_level_reboot,起一个轻量级进程执行reboot标准C函数

    static void pause_and_low_level_reboot(unsigned magic)
    {
        pid_t pid;
     
        /* Allow time for last message to reach serial console, etc */
        sleep(1);
     
        /* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS)
         * in linux/kernel/sys.c, which can cause the machine to panic when
         * the init process exits... */
        pid = vfork();
        if (pid == 0) { /* child */
            reboot(magic);
            _exit(EXIT_SUCCESS);
        }
        while (1)
            sleep(1);
    }

到这里busybox里面的内容全部处理完。

3.标准C函数reboot
前面执行reboot -f 就是直接执行的这个函数

reboot函数比较简单,直接进行系统调用进入内核。(0xffe1dead  feeldead这个魔术还是比较有意思的)

其中参数howto为RB_AUTOBOOT=0x01234567

sysdeps/unix/sysv/linux/reboot.c

    int
    reboot (int howto)
    {
      return INLINE_SYSCALL (reboot, 3, (int) 0xfee1dead, 672274793, howto);
    }

4.内核系统调用

kernel/sys.c

    SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
            void __user *, arg)
    {
    。。。
     
        mutex_lock(&reboot_mutex);
        switch (cmd) {
        case LINUX_REBOOT_CMD_RESTART:
            kernel_restart(NULL);
            break;
     
        case LINUX_REBOOT_CMD_CAD_ON:
            C_A_D = 1;
            break;
     
        case LINUX_REBOOT_CMD_CAD_OFF:
            C_A_D = 0;
            break;
     
        case LINUX_REBOOT_CMD_HALT:
            kernel_halt();
            do_exit(0);
            panic("cannot halt");
     
        case LINUX_REBOOT_CMD_POWER_OFF:
            kernel_power_off();
            do_exit(0);
            break;
     
    。。。
     
        default:
            ret = -EINVAL;
            break;
        }
        mutex_unlock(&reboot_mutex);
        return ret;
    }

进入

case LINUX_REBOOT_CMD_RESTART:
kernel_restart(NULL);
break;
调用kernel_restart函数

——>machine_restart

    void machine_restart(char *cmd)
    {
        machine_shutdown();
        if (ppc_md.restart)
            ppc_md.restart(cmd);
    #ifdef CONFIG_SMP
        smp_send_stop();
    #endif
        printk(KERN_EMERG "System Halted, OK to turn off power\n");
        local_irq_disable();
        while (1) ;
    }

这个函数之后就与具体的架构有关系了。

下面是powerpc P1020芯片的复位

ppc_md.restart(cmd);的函数原型在/arch/powerpc/platforms/85xx中定义

    define_machine(p2020_rdb_pc) {
        .name            = "P2020RDB-PC",
        .probe            = p2020_rdb_pc_probe,
        .setup_arch        = mpc85xx_rdb_setup_arch,
        .init_IRQ        = mpc85xx_rdb_pic_init,
    #ifdef CONFIG_PCI
        .pcibios_fixup_bus    = fsl_pcibios_fixup_bus,
    #endif
        .get_irq        = mpic_get_irq,
        .restart        = fsl_rstcr_restart,
        .calibrate_decr        = generic_calibrate_decr,
        .progress        = udbg_progress,
    };

    void fsl_rstcr_restart(char *cmd)
    {
        local_irq_disable();
        if (rstcr)
            /* set reset control register */
            out_be32(rstcr, 0x2);    /* HRESET_REQ */
     
        while (1) ;
    }

最终cpu往寄存器Reset control register(0x000E_00B0)中写2

也就是往管脚HRESET_REQ发出了一个信号,该信号应该与HRESET硬复位管脚相连

这样就实现了CPU的复位

Linux reboot全过程的更多相关文章

  1. linux reboot命令

    命令简介: 该命令用来重启Linux系统.相当于Windows系统中的restart命令. 命令语法: /sbin/reboot [-n] [-w] [-d] [-f] [-i] 或 reboot [ ...

  2. Linux reboot与init 6区别

    Reboot与init 6的区别 - flyingcloud_2008的专栏 - CSDN博客https://blog.csdn.net/flyingcloud_2008/article/detail ...

  3. Linux Reboot And Poweroff Command

    1.Environment:RedHat Linux Enterprise 6+Vmware Workstation 12 Pro 2.Linux shutdown and restart comma ...

  4. [linux] reboot和shutdown-r的区别

    google看看: 先搜英文的资料 http://askubuntu.com/questions/441969/what-is-the-difference-between-reboot-and-sh ...

  5. Linux系统启动全过程

    分为两部分,第一部分是硬件本身需要加载的信息,之后才是加载Linux相关信息,因为有装有双系统的机器嘛 1.计算机加电 2.BIOS开始运行,检测硬件:cpu.内存.硬盘等 3.BIOS读取CMOS存 ...

  6. 在Red Hat Linux服务器端假设NSF Server来进行Linux系统安装全过程

            本教程讲述了通过在Red Hat Linux服务器端假设NSF Server来进行Linux系统安装的过程,并详细介绍了如何制作网络启动盘的细节.演示直观,讲解通俗易懂,特别适合初学者 ...

  7. linux reboot ,shutdown,halt区别

    reboot ,shutdown,halt区别 重启   reboot  和 shutdown -r now  效果是一样的都是重启 区别在于reboot 是重启时,删除所有的进程,为不是平稳的终止他 ...

  8. Linux reboot后数据库无法自动启动

    需将以下由N改为Y orcl:/data/oracle_db/product/13.2.0/db_1:Y Last login: Thu Aug 27 16:36:19 2015 from 10.10 ...

  9. linux启动全过程

    参考: http://www.staroceans.org/e-book/linux-bootstrap-1.html 1. MBR里的内容属于grub grub-2.02\grub-core\boo ...

  10. linux下c程序调用reboot函数实现直接重启【转】

    转自:http://www.blog.chinaunix.net/uid-20564848-id-73878.html linux下c程序调用reboot函数实现直接重启 当然你也可以直接调用syst ...

随机推荐

  1. [转]快速搭建简单的LBS程序——地图服务

    很多时候,我们的程序需要提供需要搭建基于位置的服务(LBS),本文这里简单的介绍一下其涉及的一些基本知识. 墨卡托投影 地图本身是一个三维图像,但在电脑上展示时,往往需要将其转换为二维的平面图形,需要 ...

  2. 即时通讯技术文集(第23期):IM安全相关文章(Part12) [共15篇]

    为了更好地分类阅读 52im.net 总计1000多篇精编文章,我将在每周三推送新的一期技术文集,本次是第23 期. [- 1 -] 理论联系实际:一套典型的IM通信协议设计详解(含安全层设计) [链 ...

  3. asp.net core Theme 中间件

    asp.net core中自定义视图引擎,继承接口 IViewLocationExpander public class ThemeViewLocationExpander : IViewLocati ...

  4. 使用R语言可视化地图数据

    在科研工作中,经常会遇到需要在地图上展示数据的情况,今天分享一下如何使用R语言可视化地图数据 # 先安装和导入R包,这里使用了本地地图数据,所以只导入maps包即可 # install.package ...

  5. C++之OpenCV入门到提高007:调整图像亮度和对比度

    一.介绍 今天是这个系列<C++之 Opencv 入门到提高>的第七篇文章.这篇文章也不难,介绍如何调整图像的亮度.图像本质上也是数据,既然是数据,我们就可以针对图像的每个像素进行增加或者 ...

  6. 为了解决服务启动慢的问题,我为什么要给Apollo和Spring提交PR?

    最近在整理之前记录的工作笔记时,看到之前给团队内一组服务优化启动耗时记录的笔记,简单整理了一下分享出来.问题原因并不复杂,主要是如何精准测量和分析,优化后如何定量测量优化效果,说人话就是用实际数据证明 ...

  7. Cuckoo Hash

    最近在看APSI中,涉及到了一些hash技术(朴素hash,置换hash,布谷鸟hash),Hash 技术是 PSI 协议中优化通信复杂度和计算杂度的重要工具之一,现在系统的认识一下! 更多关于哈希函 ...

  8. CICD:持续集成、持续交付、持续部署-基础概念

    一.简介 CI / CD的采用改变了开发人员和测试人员如何发布软件. 最初是瀑布模型,后来是敏捷开发,现在是DevOps,这是现代开发人员构建出色的产品的技术路线.随着DevOps的兴起,出现了持续集 ...

  9. oracle11gRAC升级到19C

    升级oracle11g集群到19.3 前述环境检查: [root@qhdb1 ~]# crsctl status res -t ------------------------------------ ...

  10. C# 深度学习框架 TorchSharp 原生训练模型和图像识别-手写数字识别

    目录 开始使用 Torch 准备 下载数据集 显示图片 加载数据集 定义网络 优化模型参数 训练模型 保存和加载模型 使用模型识别图片 教程名称:使用 C# 入门深度学习 作者:痴者工良 教程地址:h ...