1 問題場(chǎng)景
相信很多人也跟我一樣,剛接觸RT-Thread不久,正在學(xué)習(xí)RT-Thread的路上,然而學(xué)習(xí)一款嵌入式實(shí)時(shí)操作系統(tǒng),沒有一個(gè)硬件開發(fā)板,在我之前的認(rèn)知里面,這應(yīng)該很難把RTOS的內(nèi)核代碼調(diào)試起來(lái)吧?
直到了解了RT-Thread,我才知道原來(lái)有QEMU模擬器這么個(gè)東西。
所以我很快就參考相關(guān)教程,把QEMU給裝起來(lái)了,結(jié)合RT-Thread編譯bsp的方法,很快我選擇的qemu-vexpress-a9固件很快就編譯出來(lái)了。
看了bsp目錄下有好幾個(gè)啟動(dòng)腳本:
-
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
我逐個(gè)嘗試,發(fā)現(xiàn)在我的環(huán)境下,只有./qemu-nographic.sh
能夠跑起來(lái)。
-
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 />
不過(guò)問題來(lái)了,我想重新編譯源碼,再次運(yùn)行新的代碼,怎么辦呢?如何才能退出這個(gè)QEMU命令行控制臺(tái)?
2 嘗試解決
2.1 牛刀小試
大家都知道,Linux退出一個(gè)控制臺(tái)啟動(dòng)的程序,使用CTRL+C
就可以把它退出來(lái),我試了一下,發(fā)現(xiàn)它壓根就不認(rèn)CTRL+C
,只是一直輸出一些亂碼符號(hào)。

2.2 我放大招
既然CTRL+C
不能,那我用killall-9xxx
總可以吧?難不成你還能逃脫Linux內(nèi)核對(duì)你的管控?
于是另開一個(gè)控制臺(tái),直接killall-9qemu-system-arm
,結(jié)果一試,的確可以退出QEMU(連進(jìn)程都退出來(lái)了)。
但是問題來(lái)了,退出QEMU之后,這個(gè)控制臺(tái)感覺亂來(lái)了,我一瞧回車,它都不好好換行了,你看看!

這就很讓人難受了,控制臺(tái)沒法用了,而且這個(gè)時(shí)候敲命令進(jìn)去還不能回顯,也不知道你敲對(duì)了沒有,只好退出命令行,重新登入,控制臺(tái)得以恢復(fù)。

2.3 黔驢技窮
上面的這種情況,顯示是我不能接受的,這個(gè)我倒是想了一下,QEMU不可能不支持退出吧,會(huì)不會(huì)什么啟動(dòng)參數(shù)我搞錯(cuò)了,于是qemu-system-arm-h
,找了幾個(gè)看似跟這個(gè)問題相關(guān)的參數(shù):
-
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添加來(lái)嘗試:
-
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
運(yùn)行之后,同樣在另一個(gè)控制臺(tái)使用killall-9qemu-system-arm
退出,發(fā)現(xiàn)有的時(shí)候退出QEMU的控制臺(tái)可以好好的,有的時(shí)候換行問題依然存在,沒有找到規(guī)律,實(shí)在沒辦法,就不了了之了。
3 終極方案
3.1 發(fā)現(xiàn)新大陸
直到今天,我偶然翻到RT-Thread的官方文檔,對(duì)RT-Thread Smart版本的介紹的時(shí)候,有一個(gè)章節(jié)是介紹使用QEMU模擬環(huán)境進(jìn)行代碼調(diào)試運(yùn)行的,里面居然提到了如何退出QEMU!

Word天吶,那種感覺簡(jiǎn)直像是發(fā)現(xiàn)新大陸一樣。 馬上登入QEMU開發(fā)環(huán)境做測(cè)試,果然,操作竟是如此的絲滑,爽就一個(gè)字!

真的像是歷史難題被解決的那種感覺。
3.2 扒一扒到底誰(shuí)讓QEMU退出了
第一感覺是不是RT-Thread的Finsh組件處理了這個(gè)CTRL+A,X
? 于是找了Finsh的關(guān)鍵代碼:
-
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 < 0)
-
{
-
continue;
-
}
-
/*
-
* handle control key
-
* up key : 0x1b 0x5b 0x41
-
* down key: 0x1b 0x5b 0x42
-
* right key:0x1b 0x5b 0x43
-
* left key: 0x1b 0x5b 0x44
-
*/
-
if (ch == 0x1b)
-
{
-
shell->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 < shell->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 < shell->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 < shell->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 <= shell->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 < shell->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 < shell->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 */
-
}
通讀代碼之后,發(fā)現(xiàn)它并沒有處理這個(gè)CTRL+A,X
輸入,那么到底是誰(shuí)接管了這個(gè)指令呢? 看到QEMU退出的時(shí)候,有提示``,這個(gè)關(guān)鍵字給了我線索,于是我開始懷疑是QEMU自己接管的這個(gè)命令,于是下面的一頓操作終于把它揪出來(lái)了。
-
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$
大致的流程就是對(duì)可執(zhí)行文件qemu-system-arm進(jìn)行g(shù)rep檢索,發(fā)現(xiàn)居然找到了Terminated
這個(gè)關(guān)鍵log,證明這行退出的log正在qemu-system-arm打出來(lái)的,這也就從側(cè)面證實(shí)了這個(gè)退出命令是被它接管了,并且處理了,然后才退出的。

4 經(jīng)驗(yàn)教訓(xùn)
這個(gè)問題真的困擾了我至少2個(gè)月,每次一用QEMU,我就吐槽這個(gè)問題,沒想到居然還是RT-Thread的指導(dǎo)文檔拯救了我。
所以啊,凡事先查查別人已經(jīng)整理好的問題,真的會(huì)事半功倍!
各位老鐵,RT-Thread的文檔中心,給我擼起來(lái)?。?!
5 更多分享
架構(gòu)師李肯
一個(gè)專注于嵌入式IoT領(lǐng)域的架構(gòu)師。有著近10年的嵌入式一線開發(fā)經(jīng)驗(yàn),深耕IoT領(lǐng)域多年,熟知IoT領(lǐng)域的業(yè)務(wù)發(fā)展,深度掌握IoT領(lǐng)域的相關(guān)技術(shù)棧,包括但不限于主流RTOS內(nèi)核的實(shí)現(xiàn)及其移植、硬件驅(qū)動(dòng)移植開發(fā)、網(wǎng)絡(luò)通訊協(xié)議開發(fā)、編譯構(gòu)建原理及其實(shí)現(xiàn)、底層匯編及編譯原理、編譯優(yōu)化及代碼重構(gòu)、主流IoT云平臺(tái)的對(duì)接、嵌入式IoT系統(tǒng)的架構(gòu)設(shè)計(jì)等等。擁有多項(xiàng)IoT領(lǐng)域的發(fā)明專利,熱衷于技術(shù)分享,有多年撰寫技術(shù)博客的經(jīng)驗(yàn)積累,連續(xù)多月獲得RT-Thread官方技術(shù)社區(qū)原創(chuàng)技術(shù)博文優(yōu)秀獎(jiǎng),榮獲CSDN博客專家、CSDN物聯(lián)網(wǎng)領(lǐng)域優(yōu)質(zhì)創(chuàng)作者、2021年度CSDN&RT-Thread技術(shù)社區(qū)之星、RT-Thread官方嵌入式開源社區(qū)認(rèn)證專家、RT-Thread 2021年度論壇之星TOP4、華為云云享專家(嵌入式物聯(lián)網(wǎng)架構(gòu)設(shè)計(jì)師)等榮譽(yù)。堅(jiān)信【知識(shí)改變命運(yùn),技術(shù)改變世界】!
歡迎關(guān)注我的github倉(cāng)庫(kù)01workstation,日常分享一些開發(fā)筆記和項(xiàng)目實(shí)戰(zhàn),歡迎指正問題。
同時(shí)也非常歡迎關(guān)注我的專欄;有問題的話,可以跟我討論,知無(wú)不答,謝謝大家。
-
模擬器
+關(guān)注
關(guān)注
2文章
894瀏覽量
44453 -
RT-Thread
+關(guān)注
關(guān)注
32文章
1412瀏覽量
41994 -
qemu
+關(guān)注
關(guān)注
0文章
57瀏覽量
5679
發(fā)布評(píng)論請(qǐng)先 登錄
RT-Thread榮獲2025優(yōu)秀開源項(xiàng)目 | 新聞速遞

RT-Thread BSP全面支持玄鐵全系列RISC-V 處理器 | 技術(shù)集結(jié)

揭秘RT-Thread上的AUTOSAR CP系統(tǒng)

RT-Thread審核團(tuán)招募: 深度參與開源RTOS社區(qū)治理與演進(jìn)

如何將RT-Thread移植到NXP MCUXPressoIDE上

RT-Thread上CAN實(shí)踐

【S32K146 RT-thread】之 SPI驅(qū)動(dòng)適配

開源共生 商業(yè)共贏 | RT-Thread 2024開發(fā)者大會(huì)報(bào)名啟動(dòng)!

【成都】9月21日RT-Thread巡回線下培訓(xùn)-OpenMV機(jī)器視覺

【大連】9月7日RT-Thread巡回線下培訓(xùn)-OpenMV機(jī)器視覺

【武漢】9月7日RT-Thread巡回線下培訓(xùn)-RTduino-10分鐘上手嵌入式

【QEMU系列】不用開發(fā)板運(yùn)行RT-Thread指南-ARM架構(gòu)

2024 RT-Thread全球巡回 線下培訓(xùn)火熱來(lái)襲!

【好書推薦】RT-Thread設(shè)備驅(qū)動(dòng)開發(fā)指南

評(píng)論