您的位置:首页 > 其它

嵌入式复位流程与优化(2)

2016-04-17 20:49 190 查看

背景

在前一篇《嵌入式复位流程研究与优化》中,主要针对之前复位流程中无脑关闭FD进行了优化。这几日,出现了一次复位流程未能走完的问题,一直阻塞在umount中不能出来。经过优化后,即使卡在umount中,也不会像以前那样控制台断开了(因为没有关闭控制台对应的套接字),保留了现场。经过对内核态堆栈的分析,我们认为应当是阻塞在了工作队列(work queue)上,在umount中,需要刷新cache到一个工作队列上,并等待该工作队列中的工作全部处理完,但是很遗憾,我们没有等到。板卡复位后,通过对日志的分析,发现有一个任务一直在占用CPU。目前,该问题我们可以在实验室稳定复现,可以证明时该问题导致了复位流程阻塞,未能执行完,导致复位不成功。

由于umount阻塞的问题不止一次出现,且原因并不相同,考虑到该问题严重影响设备稳定性,我们不能仅解决引起umount阻塞的问题,我们必须寻求办法来进行规避,使阻塞在umount中的问题,在任何情况下都不能出现。

分析与解决

解决该问题最直接且暴力的方法,就是另起一个线程,等待一定时间后直接无脑复位。但我一直认为这种修改比较别扭,未采用。主要原因如下:

(1) 需要pthread_create创建一个线程,还需要设置一大堆参数,优先级设置的不合适不定出现什么问题,麻烦;

(2) pthread_create需要指定一个入口函数,目前我们没有这样线程的入口函数。而且在新平台架构下,复位函数被绑定到了板卡配置的一个reset_func函数指针上。目前复位函数本身,包含了文件系统同步,而这个线程入口函数,则不能含有文件系统的同步。新写一个函数是肯定的了,而这个函数由需要直接操作寄存器,怎么写都觉得别扭。

直到今日翻《APUE》时,偶然看到了其中对于慢速I/O读超时的处理方法。书中的例子主要针对的是read函数,使用alarm函数来实现。alarm函数向系统注册一个alarm定时器,该定时器超时后,会向进程发送SIGALRM信号。在SIGALRM信号中,我们只需要使其跳出阻塞的系统调用即可。信号处理函数,会打断系统调用的执行,在处理完信号后,系统调用是否会重试,依赖于具体的操作系统。在我们的这种场景下,我们需要系统调用被打断后,不能重试,否则又会陷入到阻塞态。这里,我们就要使用setjmp以及longjmp,书中之前就多次提到这两个函数配合信号使用,达到打断系统调用的目的。先写了一个demo做试验,代码如下:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>

static jmp_buf env_alrm;

void sig_alrm(int signo)
{
printf("receive sig alarm.\n");
longjmp(env_alrm, 1);
}

int main()
{
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
{
printf("fail to regist sig_alrm.\n");
}
if(setjmp(env_alrm) != 0)
{
printf("saved by alarm.\n");
return 0;
}
alarm(10);
while(1)
{
sleep(1);
}

}


代码十分简单,在main函数中,通过setjmp设置jump点,在收到alarm信号后,即可还原到该点,然后从main函数中退出。代码在调用alarm注册后,直接陷入无限循环。我们期望,alarm信号可以把这段代码从无限循环中拯救出来。运行结果如下:

$ ./test4.o
receive sig alarm.
saved by alarm.


代码最终正常退出。这段代码对应到我们的项目中,main函数就可以理解成reboot_hook函数,在该函数内仅执行文件系统同步的功能,并不执行复位操作,我们可以通过alarm注册,保证该函数在一定时间内一定会返回,不会导致再次卡死在复位函数中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: