C++:获取命令终端数据时出现 fgets 问题

问题描述 投票:0回答:1

我正在为自定义设备的嵌入式 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
可用。所以我认为这不是阻塞问题。

c++ embedded-linux popen fgets pclose
1个回答
0
投票

原来是和缓冲有关的东西。将

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;

}

© www.soinside.com 2019 - 2024. All rights reserved.