【RT-Thread学习笔记】如何优雅地退出QEMU模拟器?

1 问题场景 相信很多人也跟我一样,刚接触rt-thread不久,正在学习rt-thread的路上,然而学习一款嵌入式实时操作系统,没有一个硬件开发板,在我之前的认知里面,这应该很难把rtos的内核代码调试起来吧?
直到了解了rt-thread,我才知道原来有qemu模拟器这么个东西。
所以我很快就参考相关教程,把qemu给装起来了,结合rt-thread编译bsp的方法,很快我选择的qemu-vexpress-a9固件很快就编译出来了。
看了bsp目录下有好几个启动脚本:
bsp/qemu-vexpress-a9$ ls -al *.sh -rwxr-xr-x 1 recan system 168 sep  6 10:43 qemu-dbg.sh -rwxr-xr-x 1 recan system 187 oct 22 17:41 qemu-nographic.sh -rwxr-xr-x 1 recan system 166 sep  6 10:43 qemu.sh 我逐个尝试,发现在我的环境下,只有./qemu-nographic.sh能够跑起来。
bsp/qemu-vexpress-a9$ ./qemu-nographic.sh  qemu-system-arm: -no-quit is only valid for gtk and sdl, ignoring option warning: image format was not specified for 'sd.bin' and probing guessed raw.          automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.          specify the 'raw' format explicitly to remove the restrictions. alsa lib confmisc.c:767:(parse_card) cannot find card '0' alsa lib conf.c:4732:(_snd_config_evaluate) function snd_func_card_driver returned error: no such file or directory alsa lib confmisc.c:392:(snd_func_concat) error evaluating strings alsa lib conf.c:4732:(_snd_config_evaluate) function snd_func_concat returned error: no such file or directory alsa lib confmisc.c:1246:(snd_func_refer) error evaluating name alsa lib conf.c:4732:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5220:(snd_config_expand) evaluate error: no such file or directory alsa lib pcm.c:2642:(snd_pcm_open_noupdate) unknown pcm default alsa: could not initialize dac alsa: failed to open `default': alsa: reason: no such file or directory alsa lib confmisc.c:767:(parse_card) cannot find card '0' alsa lib conf.c:4732:(_snd_config_evaluate) function snd_func_card_driver returned error: no such file or directory alsa lib confmisc.c:392:(snd_func_concat) error evaluating strings alsa lib conf.c:4732:(_snd_config_evaluate) function snd_func_concat returned error: no such file or directory alsa lib confmisc.c:1246:(snd_func_refer) error evaluating name alsa lib conf.c:4732:(_snd_config_evaluate) function snd_func_refer returned error: no such file or directory alsa lib conf.c:5220:(snd_config_expand) evaluate error: no such file or directory alsa lib pcm.c:2642:(snd_pcm_open_noupdate) unknown pcm default alsa: could not initialize dac alsa: failed to open `default': alsa: reason: no such file or directory audio: failed to create voice `lm4549.out'    \ | / - rt -     thread operating system  / | \     4.0.4 build nov  5 2021  2006 - 2021 copyright by rt-thread team lwip-2.1.2 initialized! [i/sal.skt] socket abstraction layer initialize success. [i/sdio] sd card capacity 65536 kb. [i/sdio] switching card to high speed failed! hello rt-thread 99, 99 1, 2 1, 2 1, 2 msh /> 不过问题来了,我想重新编译源码,再次运行新的代码,怎么办呢?如何才能退出这个qemu命令行控制台?
2 尝试解决 2.1 牛刀小试 大家都知道,linux退出一个控制台启动的程序,使用ctrl+c就可以把它退出来,我试了一下,发现它压根就不认ctrl+c,只是一直输出一些乱码符号。
2.2 我放大招 既然ctrl+c不能,那我用killall-9xxx总可以吧?难不成你还能逃脱linux内核对你的管控?
于是另开一个控制台,直接killall-9qemu-system-arm,结果一试,的确可以退出qemu(连进程都退出来了)。
但是问题来了,退出qemu之后,这个控制台感觉乱来了,我一瞧回车,它都不好好换行了,你看看! 
这就很让人难受了,控制台没法用了,而且这个时候敲命令进去还不能回显,也不知道你敲对了没有,只好退出命令行,重新登入,控制台得以恢复。
2.3 黔驴技穷 上面的这种情况,显示是我不能接受的,这个我倒是想了一下,qemu不可能不支持退出吧,会不会什么启动参数我搞错了,于是qemu-system-arm-h,找了几个看似跟这个问题相关的参数:
qemu-system-arm -h ... -no-quit        disable sdl window close capability ... -no-reboot      exit instead of rebooting ... -no-shutdown    stop before shutdown 于是在qemu-nographic.sh添加来尝试:
if [ ! -f sd.bin ]; then dd if=/dev/zero of=sd.bin bs=1024 count=65536 fi   qemu-system-arm -m vexpress-a9 -smp cpus=2 -kernel rtthread.bin -nographic -sd sd.bin -no-shutdown -no-quit -no-reboot 运行之后,同样在另一个控制台使用killall-9qemu-system-arm退出,发现有的时候退出qemu的控制台可以好好的,有的时候换行问题依然存在,没有找到规律,实在没办法,就不了了之了。
3 终极方案 3.1 发现新大陆 直到今天,我偶然翻到rt-thread的官方文档,对rt-thread smart版本的介绍的时候,有一个章节是介绍使用qemu模拟环境进行代码调试运行的,里面居然提到了如何退出qemu! 
word天呐,那种感觉简直像是发现新大陆一样。 马上登入qemu开发环境做测试,果然,操作竟是如此的丝滑,爽就一个字! 
真的像是历史难题被解决的那种感觉。
3.2 扒一扒到底谁让qemu退出了 第一感觉是不是rt-thread的finsh组件处理了这个ctrl+a,x? 于是找了finsh的关键代码:
void finsh_thread_entry(void *parameter) {     int ch;       /* normal is echo mode */ #ifndef finsh_echo_disable_default     shell->echo_mode = 1; #else     shell->echo_mode = 0; #endif   #if !defined(rt_using_posix) && defined(rt_using_device)     /* set console device as shell device */     if (shell->device == rt_null)     {         rt_device_t console = rt_console_get_device();         if (console)         {             finsh_set_device(console->parent.name);         }     } #endif   #ifdef finsh_using_auth     /* set the default password when the password isn't setting */     if (rt_strlen(finsh_get_password()) == 0)     {         if (finsh_set_password(finsh_default_password) != rt_eok)         {             rt_kprintf(finsh password set failed.\n);         }     }     /* waiting authenticate success */     finsh_wait_auth(); #endif       rt_kprintf(finsh_prompt);       while (1)     {         ch = (int)finsh_getchar();         if (ch stat = wait_spec_key;             continue;         }         else if (shell->stat == wait_spec_key)         {             if (ch == 0x5b)             {                 shell->stat = wait_func_key;                 continue;             }               shell->stat = wait_normal;         }         else if (shell->stat == wait_func_key)         {             shell->stat = wait_normal;               if (ch == 0x41) /* up key */             { #ifdef finsh_using_history                 /* prev history */                 if (shell->current_history > 0)                     shell->current_history --;                 else                 {                     shell->current_history = 0;                     continue;                 }                   /* copy the history command */                 memcpy(shell->line, &shell->cmd_history[shell->current_history][0],                        finsh_cmd_size);                 shell->line_curpos = shell->line_position = strlen(shell->line);                 shell_handle_history(shell); #endif                 continue;             }             else if (ch == 0x42) /* down key */             { #ifdef finsh_using_history                 /* next history */                 if (shell->current_history history_count - 1)                     shell->current_history ++;                 else                 {                     /* set to the end of history */                     if (shell->history_count != 0)                         shell->current_history = shell->history_count - 1;                     else                         continue;                 }                   memcpy(shell->line, &shell->cmd_history[shell->current_history][0],                        finsh_cmd_size);                 shell->line_curpos = shell->line_position = strlen(shell->line);                 shell_handle_history(shell); #endif                 continue;             }             else if (ch == 0x44) /* left key */             {                 if (shell->line_curpos)                 {                     rt_kprintf(\b);                     shell->line_curpos --;                 }                   continue;             }             else if (ch == 0x43) /* right key */             {                 if (shell->line_curpos line_position)                 {                     rt_kprintf(%c, shell->line[shell->line_curpos]);                     shell->line_curpos ++;                 }                   continue;             }         }           /* received null or error */         if (ch == '\0' || ch == 0xff) continue;         /* handle tab key */         else if (ch == '\t')         {             int i;             /* move the cursor to the beginning of line */             for (i = 0; i line_curpos; i++)                 rt_kprintf(\b);               /* auto complete */             shell_auto_complete(&shell->line[0]);             /* re-calculate position */             shell->line_curpos = shell->line_position = strlen(shell->line);               continue;         }         /* handle backspace key */         else if (ch == 0x7f || ch == 0x08)         {             /* note that shell->line_curpos >= 0 */             if (shell->line_curpos == 0)                 continue;               shell->line_position--;             shell->line_curpos--;               if (shell->line_position > shell->line_curpos)             {                 int i;                   rt_memmove(&shell->line[shell->line_curpos],                            &shell->line[shell->line_curpos + 1],                            shell->line_position - shell->line_curpos);                 shell->line[shell->line_position] = 0;                   rt_kprintf(\b%s  \b, &shell->line[shell->line_curpos]);                   /* move the cursor to the origin position */                 for (i = shell->line_curpos; i line_position; i++)                     rt_kprintf(\b);             }             else             {                 rt_kprintf(\b \b);                 shell->line[shell->line_position] = 0;             }               continue;         }           /* handle end of line, break */         if (ch == '\r' || ch == '\n')         { #ifdef finsh_using_history             shell_push_history(shell); #endif             if (shell->echo_mode)                 rt_kprintf(\n);             msh_exec(shell->line, shell->line_position);               rt_kprintf(finsh_prompt);             memset(shell->line, 0, sizeof(shell->line));             shell->line_curpos = shell->line_position = 0;             continue;         }           /* it's a large line, discard it */         if (shell->line_position >= finsh_cmd_size)             shell->line_position = 0;           /* normal character */         if (shell->line_curpos line_position)         {             int i;               rt_memmove(&shell->line[shell->line_curpos + 1],                        &shell->line[shell->line_curpos],                        shell->line_position - shell->line_curpos);             shell->line[shell->line_curpos] = ch;             if (shell->echo_mode)                 rt_kprintf(%s, &shell->line[shell->line_curpos]);               /* move the cursor to new position */             for (i = shell->line_curpos; i line_position; i++)                 rt_kprintf(\b);         }         else         {             shell->line[shell->line_position] = ch;             if (shell->echo_mode)                 rt_kprintf(%c, ch);         }           ch = 0;         shell->line_position ++;         shell->line_curpos++;         if (shell->line_position >= finsh_cmd_size)         {             /* clear command line */             shell->line_position = 0;             shell->line_curpos = 0;         }     } /* end of device read */ } 通读代码之后,发现它并没有处理这个ctrl+a,x输入,那么到底是谁接管了这个指令呢? 看到qemu退出的时候,有提示``,这个关键字给了我线索,于是我开始怀疑是qemu自己接管的这个命令,于是下面的一顿操作终于把它揪出来了。
bsp/qemu-vexpress-a9$ whereis qemu-system-arm qemu-system-arm: /usr/bin/qemu-system-arm /usr/share/man/man1/qemu-system-arm.1.gz bsp/qemu-vexpress-a9$   bsp/qemu-vexpress-a9$ cp /usr/bin/qemu-system-arm . bsp/qemu-vexpress-a9$  bsp/qemu-vexpress-a9$ grep -rsn terminated binary file qemu-system-arm matches bsp/qemu-vexpress-a9$  bsp/qemu-vexpress-a9$ hexdump -c qemu-system-arm | grep -n terminated 699798:00b2b4a0  4d 55 3a 20 54 65 72 6d  69 6e 61 74 65 64 0a 0d  |mu: terminated..| bsp/qemu-vexpress-a9$ bsp/qemu-vexpress-a9$ hexdump -c qemu-system-arm > hexdump.log bsp/qemu-vexpress-a9$ bsp/qemu-vexpress-a9$ head -699797 hexdump.log | tail -1  00b2b490  73 20 68 65 6c 70 0a 0d  00 43 2d 25 63 00 51 45  |s help...c-%c.qe| bsp/qemu-vexpress-a9$  bsp/qemu-vexpress-a9$ head -699798 hexdump.log | tail -1 00b2b4a0  4d 55 3a 20 54 65 72 6d  69 6e 61 74 65 64 0a 0d  |mu: terminated..| bsp/qemu-vexpress-a9$ 大致的流程就是对可执行文件qemu-system-arm进行grep检索,发现居然找到了terminated这个关键log,证明这行退出的log正在qemu-system-arm打出来的,这也就从侧面证实了这个退出命令是被它接管了,并且处理了,然后才退出的。
4 经验教训 这个问题真的困扰了我至少2个月,每次一用qemu,我就吐槽这个问题,没想到居然还是rt-thread的指导文档拯救了我。
所以啊,凡事先查查别人已经整理好的问题,真的会事半功倍!
各位老铁,rt-thread的文档中心,给我撸起来!!!
5 更多分享 架构师李肯
一个专注于嵌入式iot领域的架构师。有着近10年的嵌入式一线开发经验,深耕iot领域多年,熟知iot领域的业务发展,深度掌握iot领域的相关技术栈,包括但不限于主流rtos内核的实现及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其实现、底层汇编及编译原理、编译优化及代码重构、主流iot云平台的对接、嵌入式iot系统的架构设计等等。拥有多项iot领域的发明专利,热衷于技术分享,有多年撰写技术博客的经验积累,连续多月获得rt-thread官方技术社区原创技术博文优秀奖,荣获csdn博客专家、csdn物联网领域优质创作者、2021年度csdn&rt-thread技术社区之星、rt-thread官方嵌入式开源社区认证专家、rt-thread 2021年度论坛之星top4、华为云云享专家(嵌入式物联网架构设计师)等荣誉。坚信【知识改变命运,技术改变世界】!
欢迎关注我的github仓库01workstation,日常分享一些开发笔记和项目实战,欢迎指正问题。
同时也非常欢迎关注我的专栏;有问题的话,可以跟我讨论,知无不答,谢谢大家。

传统媒体新时代,人工智能彰显媒体匠心
构思精巧的太阳能磁悬浮电动机
单声道数字音频放大器AD87589
安捷伦推出最快速的PXI向量信号发生器
中兴事件背后的全球集成电路产业链全景图(二)
【RT-Thread学习笔记】如何优雅地退出QEMU模拟器?
人工智能与人类生活的加速融合 正成为大众对科技发展的最大期待
iPhone8销量惨淡:iPhone8销量低下原因竟然是它,十一月上市的iPhoneX能否拯救苹果销量?
Dialog在蓝牙世界大会上展出的产品及其新的性能特点的介绍
智能环网柜功能
最大的敌人是数据边界?边缘计算时代来临
微软对外确认了其在Xbox One上没有适配VR设备的计划
金属加工液机床表面残留物的解决方法
2020年新加坡将持续保持其在智慧城市投资的领先地位
腾讯建立国内首个单柜数据中心,帮助沙漠变“绿”
打破冥顽须悟空!华为因何仍能淡定前行
中国电信成功实现了5G DU和AAU之间的承载测试
安森美半导体推出用于更小更纤薄智能手机的可调谐射频元件,具备可靠天线性能
俄罗斯开始批量生产基于MIPS架构的自主研发PC处理器
我国传感器产业的发展应从工艺技术和应用两大方向进行突破