linux reboot流程,从命令行到内核全解析

针对对象

这篇文章我尽量写的细一些,主要针对于不太懂得嵌入式命令行到系统层过程的新手看,如果觉得有用可以关注一下,不定期写一些新手需要看的文章。

正文

引言

我们在做嵌入式设备的时候,经常会输入一些命令,例如:cat test.txt来查看test.txt文本;又比如输入reboot, 来重启系统。从我们输入reboot到系统重启的整个过程都是如何操作的呢?如果你还不懂,那么可以继续往下面看。

由于笔者的项目环境为Android, 因此借用android工程进行说明,linux开发者不用担心,我会把区别的地方特地标出来

从命令行输入开始分析

大家都知道,在嵌入式设备字符终端中我们输入的每一个命令其实都是 一个二进制可执行的文件,将其编译然后将其所在的目录加入环境变量中,我们就可以在命令行的任何目录去直接输入文件名来运行这个程序(即命令)。
reboot命令也一样,它也是一个由C/C++语言编译的的可执行文件。那么要分析reboot的重启执行过程就需要得到reboot这个命令的源码。linux用户可以从busybox中得到reboot的源码。这里我是android工程,在安卓工程的 \system\core\reboot\reboot.c中,接下来对这个文件进行简单分析。

将reboot代码贴出如下

int main(int argc, char *argv[])
{
    int ret;
    size_t prop_len;
    char property_val[PROPERTY_VALUE_MAX];
    const char *cmd = "reboot";
    char *optarg = "";

    opterr = 0;
    do {
        int c;

        c = getopt(argc, argv, "p");

        if (c == -1) {
            break;
        }

        switch (c) {
        case 'p':
            cmd = "shutdown";
            break;
        case '?':
            fprintf(stderr, "usage: %s [-p] [rebootcommand]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    } while (1);

    if(argc > optind + 1) {
        fprintf(stderr, "%s: too many arguments\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (argc > optind)
        optarg = argv[optind];

    prop_len = snprintf(property_val, sizeof(property_val), "%s,%s", cmd, optarg);
    if (prop_len >= sizeof(property_val)) {
        fprintf(stderr, "reboot command too long: %s\n", optarg);
        exit(EXIT_FAILURE);
    }

    ret = property_set(ANDROID_RB_PROPERTY, property_val);
    if(ret < 0) {
        perror("reboot");
        exit(EXIT_FAILURE);
    }

    // Don't return early. Give the reboot command time to take effect
    // to avoid messing up scripts which do "adb shell reboot && adb wait-for-device"
    while(1) { pause(); }

    fprintf(stderr, "Done\n");
    return 0;
}

这里代码不复杂,主要看property_set(ANDROID_RB_PROPERTY, property_val)
这里有个宏,这个宏定义在include\cutils\android_reboot.h, 定义如下
/* Properties */
#define ANDROID_RB_PROPERTY “sys.powerctl”

函数property_set函数如下:

uint32_t property_set(const std::string& name, const std::string& value) {
    size_t valuelen = value.size();

    if (!is_legal_property_name(name)) {
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: bad name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (valuelen >= PROP_VALUE_MAX) {
        LOG(ERROR) << "property_set(\"" << name << "\", \"" << value << "\") failed: "
                   << "value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

   //省略中间代码
   
  	//主要看这个函数
    property_changed(name, value);

    return PROP_SUCCESS;
}

别的函数有兴趣可以深入看看具体实现,但这里我们需要知道的就是最后会执行property_changed(name, value); 这个函数

void property_changed(const std::string& name, const std::string& value) {
    // If the property is sys.powerctl, we bypass the event queue and immediately handle it.
    // This is to ensure that init will always and immediately shutdown/reboot, regardless of
    // if there are other pending events to process or if init is waiting on an exec service or
    // waiting on a property.
    // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
    // commands to be executed.
    if (name == "sys.powerctl") {
    
    	//其他都不重要,看这个就好了
        if (HandlePowerctlMessage(value)) {
            shutting_down = true;
            
        }
    }

    if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);

    if (waiting_for_prop) {
        if (wait_prop_name == name && wait_prop_value == value) {
            LOG(INFO) << "Wait for property took " << *waiting_for_prop;
            ResetWaitForProp();
        }
    }
}

HandlePowerctlMessage函数代码如下

bool HandlePowerctlMessage(const std::string& command) {
    、、、省略
    \\都不重要,只要看DoReboot函数
    auto shutdown_handler = [cmd, command, reboot_target,
                             run_fsck](const std::vector<std::string>&) {
        DoReboot(cmd, command, reboot_target, run_fsck);
        return 0;
    };
    、、、省略
    return true;
}

最后这个DoReboot函数中调用了RebootSystem
代码如下

static void __attribute__((noreturn))
RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
    LOG(INFO) << "Reboot ending, jumping to kernel";
    switch (cmd) {
        case ANDROID_RB_POWEROFF:
            reboot(RB_POWER_OFF);
            break;

        case ANDROID_RB_RESTART2:
            syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                    LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());
            break;

        case ANDROID_RB_THERMOFF:
            reboot(RB_POWER_OFF);
            break;
    }
    // In normal case, reboot should not return.
    PLOG(FATAL) << "reboot call returned";
    abort();
}

这么大堆代码终于到了关键性调用了
syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());

这里调用了系统函数syscall,这里有个知识点

几乎所有的应用层都是通过这个接口来进行系统调用,具体怎么调用,系统已经封装为库函数,我们不需要去管,只需要知道可以通过这个函数进行系统调用,使用了这个函数也就可以理解为从用户态跳入了内核层(我更喜欢称syscall调用为系统层)。

内核系统调用分析

当调用syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());
后进入内核的第一个函数就是在这里插入图片描述
可以查看这个宏SYSCALL_DEFINE4,这个宏定义在include\linux\syscall.h中,可以仔细看一下。我这里简单说下结论,用SYSCALL_DEFINEX定义的函数,
第一个参数为函数名后缀,如上图,则这个定义的函数名字为SyS_reboot。
第二个,第三个一起看,为类型加变量名。
可以发现一共有4组类型加变量名的 变量。所以DEFINE4 中的数字4就代表SyS_reboot有4个参数。同理SYSCALL_DEFINE2就是有2个参数,具体可以去看这个宏的定义。

到这里其实也不需要怎么分析,各位读者看看就应该知道是怎么回事了。下面我简单讲一下。

首先对应用层中syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str());传递进来的参数进行分析,判断校验
在这里插入图片描述
这里提一下,很多命令行输入reboot不生效,系统没有出现重启,有一部分原因是因为这里判断出错。各位如果有碰到需要多注意下,应用层的传递参数是否是别的值。

然后就调用kernel_restart(buffer);函数,这里需要注意,这里已经是内核重启函数的实现方式了,对于不同厂商,每个人都有定义自己的重启函数,也就是说有些为了特殊需要,可能并没有使用kernel_restart函数进行重启。
在这里插入图片描述
kernel_restart_prepare函数进行设备重启前的关闭。这边顺带提一下,如果我们的外设需要在复位的时候做一些工作,可以将shutdown函数注册到设备函数中,然后系统在接收到reboot命令后,最终遍历设备列表并执行shutdown函数,这个步骤就是在kernel_restart_prepare中实现的。
这里主要看一下machine_restart函数。
在这里插入图片描述
restart函数调用do_kernel_restart进行复位系统。多数厂商都在这个函数中进行自定义的重启方式。
这里给大家说个野路子
有时候因为一些qiqigaygay的原因,reboot经常出错。这时候我们可以考虑使用硬复位,也就是reset,例如使用开关电路控制电源芯片,然后将控制pin接入cpu的gpio管脚。然后我们在do_kernel_restart这个函数中将gpio管脚拉高拉低来断开电源。。。走投无路的办法,慎用慎用。

我们接着分析,
在这里插入图片描述
这里我们do_kernel_restart里面做的是发送一个通知,通知各个通过register_restart_handler注册的钩子函数,执行这个关机函数。我们可以使用sourceinsight搜索这个函数,最后我发现在我系统中是使用看门狗复位来实现重启的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里的打印都是我的跟踪打印,大家可以忽略。

最后我们发现是调用的看门狗的restart。然后进入看门狗驱动发现注册的函数
在这里插入图片描述
最后在imx2_wdt_restart中对看门狗进行溢出写入,然后看门狗引发系统复位。

到此解析结束。

写在最后

给新手们一个小建议,我们作为新手,或者说我们更多的是为了解决问题,因此我建议,佛系一点,活用打印,不求甚解。我们目的是知道整个的流程,然后针对专一的东西进行深入剖析即可。例如上述文章,reboot的过程又怎是简简单单一篇文章就可以分析透彻与干净,但只需要抓住最终的关键函数,盘他!盘的圆润光滑就好了,盘的最后自己都觉得是这么一回事就行了。

以上有哪些没有写明白,或者有写错的位置,各位可以指出。谢谢。

  • 25
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux中,reboot是一个系统调用,用于重新启动计算机。它可以通过命令行或者在代码中调用。reboot命令有几个选项可以使用,比如-d选项可以在重启系统时不将操作写入日志文件,-f选项可以强制重启操作系统,-n选项可以在重启操作系统前不同步硬盘/存储介质等。\[1\]在用户空间,一般的Linux操作系统提供了一些工具集合,如reboot、halt和poweroff命令,它们都与重启相关。内核空间根据执行路径的不同,提供了kernel_restart、kernel_halt和kernel_poweroff三个处理函数,响应用户空间的reboot请求。这些处理函数的处理流程大致相同,包括向关心reboot过程的进程发送notify事件、关闭所有的外部设备、关闭system core等。最后,调用machine相关的接口实现真正意义上的reboot。此外,内核还提供了其他途径的关机方法,如通过按键组合或向/proc文件写入命令。\[2\]\[3\] #### 引用[.reference_title] - *1* [Linux 命令(223)—— reboot 命令](https://blog.csdn.net/K346K346/article/details/128373667)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Linux电源管理系统休眠唤醒之 reboot>](https://blog.csdn.net/qq_39575672/article/details/129708512)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值