针对对象
这篇文章我尽量写的细一些,主要针对于不太懂得嵌入式命令行到系统层过程的新手看,如果觉得有用可以关注一下,不定期写一些新手需要看的文章。
正文
引言
我们在做嵌入式设备的时候,经常会输入一些命令,例如: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的过程又怎是简简单单一篇文章就可以分析透彻与干净,但只需要抓住最终的关键函数,盘他!盘的圆润光滑就好了,盘的最后自己都觉得是这么一回事就行了。
以上有哪些没有写明白,或者有写错的位置,各位可以指出。谢谢。