我正在为自定义设备的嵌入式 Linux 应用程序编写 C++ 程序。我经常通过
popen
和 pclose
运行 Linux 终端命令。如果我只需要检查命令是否执行成功,我会检查 pclose
返回值。在其他情况下,如果我需要检查该命令的终端输出,我会在程序运行时使用 fgets
。这一直运行得很好,直到我偶然发现了以下情况。
现在我需要将数据写入闪存,该闪存使用 MTD 设备驱动程序映射到 Linux 内核。要将数据写入 MTD,我需要首先运行
flash_erase
,然后运行 nandwrite
。如果我在设备的控制台上手动运行这些命令,它们就可以正常工作。这两个命令都会在终端中打印有关擦除/写入进度的信息。
我的 C++ 程序必须运行这两个命令才能更新闪存内容。另外,我的设备有一个 GUI,我想从这些命令收集进度信息并向用户显示某种进度条。
我现在面临的实际问题是,当我运行
nandwrite
命令时,fgets
会卡住,直到执行结束。执行完毕后,立即释放之前所有的终端内容。我以类似方式运行的任何其他命令都没有发生同样的事情,甚至是flash_erase
。使用 flash_erase
,我能够在命令运行时获得部分输出并捕获输出并在 GUI 上以百分比形式向用户显示。
请检查以下程序。它完全正常工作,并且准确地显示了我刚才描述的问题。
#include <iostream>
#include <string>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <algorithm>
using namespace std;
bool run_terminal_command(string cmd);
string get_timestamp();
int main() {
run_terminal_command("flash_erase /dev/mtd4 0 188");
run_terminal_command("nandwrite /dev/mtd4 flash_content.bin");
return 0;
}
bool run_terminal_command(string cmd) {
bool ret_val = false;
cout << get_timestamp() << "running command: " << cmd << endl;
FILE *process_handle = popen(cmd.c_str(), "r");
if (process_handle) {
char buffer[256]{};
cout << get_timestamp() << "just before loop ..." << endl;
while (fgets(buffer, sizeof(buffer) - 1, process_handle)) {
string line(buffer);
line.erase(remove(line.begin(), line.end(), '\r'), line.end());
line.erase(remove(line.begin(), line.end(), '\n'), line.end());
cout << get_timestamp() << "this is from fgets: " << line << endl;
}
int res = pclose(process_handle);
// check return from calling pclose itself
if (res != -1) {
// check return from the command, whether or not it exit status is zero
if (WEXITSTATUS(res) == 0) {
cout << get_timestamp() << "success" << endl << endl;
ret_val = true;
}
else {
cout << "WEXITSTATUS is not zero" << endl;
}
}
else {
cout << "pclose error" << endl;
}
}
else {
cout << "process handle error" << endl;
}
return ret_val;
}
string get_timestamp() {
auto now = chrono::high_resolution_clock::now();
auto tick_ms = chrono::time_point_cast<chrono::milliseconds>(now).time_since_epoch().count();
std::time_t now_time_t = chrono::system_clock::to_time_t(now);
std::tm now_tm = *localtime(&now_time_t);
stringstream s_timestamp;
s_timestamp << setw(2) << setfill('0') << now_tm.tm_hour;
s_timestamp << ":";
s_timestamp << setw(2) << setfill('0') << now_tm.tm_min;
s_timestamp << ":";
s_timestamp << setw(2) << setfill('0') << now_tm.tm_sec;
s_timestamp << ".";
s_timestamp << setw(3) << setfill('0') << (tick_ms % 1000);
return ">>> [" + s_timestamp.str() + "] ";
}
以下是上述同一程序的输出。请注意,为了简短起见,故意省略了一些终端信息。
在
flash_erase
执行中,时间在just before loop ...
线之后不断流逝。
但是对于
nandwrite
来说,just before loop ...
线和第一条this is from fgets
线之间有31秒的间隙。这 31 秒代表运行此特定文件的总时间 nandwrite
。
root@host:~# ./test_fgets
>>> [11:34:38.177] running command: flash_erase /dev/mtd4 0 188
>>> [11:34:38.180] just before loop ...
>>> [11:34:38.318] this is from fgets: Erasing 4 Kibyte @ 0 *** ommited ***
>>> [11:34:38.432] this is from fgets: ing 4 Kibyte @ 6000 *** ommited ***
>>> [11:34:38.546] this is from fgets: g 4 Kibyte @ c000 -- *** ommited ***
>>> [11:34:38.660] *** ommited ***
>>> [11:34:41.566] this is from fgets: % complete Erasing 4 *** ommited ***
>>> [11:34:41.680] this is from fgets: 94 % complete Erasing 4 *** ommited ***
>>> [11:34:41.775] this is from fgets: 0 -- 97 % complete *** ommited ***
>>> [11:34:41.777] success
>>> [11:34:41.777] running command: nandwrite /dev/mtd4 flash_content.bin
>>> [11:34:41.779] just before loop ...
>>> [11:35:12.183] this is from fgets: Writing data to block 0 at offset 0x0
>>> [11:35:12.184] this is from fgets: Writing data to block 1 at offset 0x1000
>>> [11:35:12.184] this is from fgets: Writing data to block 2 at offset 0x2000
>>> [11:35:12.184] *** ommited ***
>>> [11:35:12.549] this is from fgets: Writing data to block 91 at offset 0x5b000
>>> [11:35:12.550] this is from fgets: Writing data to block 92 at offset 0x5c000
>>> [11:35:12.550] this is from fgets: Writing data to block 93 at offset 0x5d000
>>> [11:35:12.550] this is from fgets: Writing data to block 94 at offset 0x5e000
>>> [11:35:12.550] success
root@host:~#
为什么
nandwrite
只在命令结束时才一次性向fgets
释放信息?
我正在运行一个使用 Yocto 项目基于
poky
构建的自定义发行版。该工具链也是由 Yocto 构建的,基于 arm-linux-gnueabi
。
我希望获得一些指导,以便更好地了解正在发生的事情。
谢谢你。
我还测试了其他方法,例如通过
O_NONBLOCK
设置fcntl
标志,使用select
和read
代替fgets
,甚至使用fork
和execlp
运行命令,但对于所有他们的行为是相同的。正确设置后,非阻塞工作不会按预期阻塞,但终端信息仅在命令完成后对read
可用。所以我认为这不是阻塞问题。
原来是和缓冲有关的东西。将
setvbuf
设置为 _IONBUF
还不够,因此通过 posix_openpt
使用 pseuterminal 即可。
下面是我的函数的一个版本,适合与 pseuterminal 一起使用。它是针对我的
nandwrite
应用程序进行硬编码的。不过,可以实现参数解析...
bool run_terminal_nandwrite(string filepath) {
bool ret_val = false;
char buffer[256]{};
int main_fd;
char name[100];
main_fd = posix_openpt(O_RDWR | O_NOCTTY);
if (main_fd == -1) {
cout << "error: posix_openpt" << endl;
}
else if (grantpt(main_fd) == -1 || unlockpt(main_fd) == -1) {
cout << "error: grantpt/unlockpt" << endl;
}
else if (ptsname_r(main_fd, name, sizeof(name)) != 0) {
cout << "error: ptsname_r" << endl;
}
else {
pid_t pid = fork();
if (pid == -1) {
cout << "error: fork" << endl;
}
else if (pid == 0) {
// child process
int child_fd = open(name, O_RDWR);
if (child_fd == -1) {
cout << "error: open child" << endl;
}
else {
dup2(child_fd, STDIN_FILENO);
dup2(child_fd, STDOUT_FILENO);
dup2(child_fd, STDERR_FILENO);
close(child_fd);
execlp("nandwrite",
"nandwrite",
"/dev/mtd4",
filepath.c_str(),
(char *) NULL);
cout << "error: execlp" << endl;
exit(-1);
}
}
else {
// parent process
struct pollfd fds;
fds.fd = main_fd;
fds.events = POLLIN;
int status;
while (true) {
int ret = poll(&fds, 1, 1000);
if (ret == -1) {
cout << "error: poll" << endl;
break;
}
else if (ret == 0) {
// timeout
continue;
}
else if (fds.revents & POLLIN) {
ssize_t bytes = read(main_fd, buffer, sizeof(buffer) - 1);
if (bytes > 0) {
string line(buffer);
line.erase(remove(line.begin(), line.end(), '\r'), line.end());
line.erase(remove(line.begin(), line.end(), '\n'), line.end());
cout << get_timestamp() << "this is from fgets: " << line << endl;
}
else if (bytes == 0) {
// end of process
break;
}
else {
cout << "error: read" << endl;
break;
}
}
pid_t child_result = waitpid(pid, &status, WNOHANG);
if (child_result == 0) {
// child still running
}
else if (child_result == -1) {
// child error
cout << "error: waitpid" << endl;
break;
}
else {
// child has exited
if (WEXITSTATUS(status) == 0) {
cout << get_timestamp() << "success" << endl << endl;
ret_val = true;
}
else {
cout << "error: WEXITSTATUS" << endl;
}
break;
}
}
}
}
return ret_val;
}