2. QNX进程管理基础

任务目标

在QNX Neutrino实时操作系统上,编写一个C程序演示多进程生命周期管理:使用fork()创建子进程,通过exec()族函数加载新程序,利用waitpid()回收子进程并检查其退出状态。程序需输出每个阶段的进程PID与状态变化,便于在QNX Shell中直接观察验证。

环境与依赖

  • QNX SDP 7.0或更高版本(含qcc编译器)
  • 目标机为x86_64或ARM架构的QNX Neutrino运行时
  • 无需额外库,仅依赖POSIX标准头文件

核心实现

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>

#define CHILD_COUNT 3

int main(void) {
    pid_t pids[CHILD_COUNT];
    int i;

    printf("父进程 PID=%d 开始\n", getpid());

    // 创建3个子进程
    for (i = 0; i < CHILD_COUNT; i++) {
        pid_t pid = fork();
        if (pid < 0) {
            perror("fork失败");
            exit(EXIT_FAILURE);
        }
        if (pid == 0) {
            // 子进程分支:执行外部命令
            char arg[8];
            snprintf(arg, sizeof(arg), "%d", i + 1);
            printf("子进程 PID=%d (来自fork #%d) 准备exec\n", getpid(), i + 1);
            execl("/bin/echo", "echo", "子进程", arg, "执行完毕", (char *)NULL);
            // 仅当exec失败时才会到达此处
            perror("exec失败");
            _exit(EXIT_FAILURE);
        }
        pids[i] = pid;
    }

    // 父进程回收所有子进程
    for (i = 0; i < CHILD_COUNT; i++) {
        int status;
        pid_t ret = waitpid(pids[i], &status, 0);
        if (ret == -1) {
            perror("waitpid失败");
            continue;
        }
        if (WIFEXITED(status)) {
            printf("回收子进程 PID=%d,退出码=%d\n", ret, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("回收子进程 PID=%d,被信号 %d 终止\n", ret, WTERMSIG(status));
        }
    }

    printf("父进程 PID=%d 结束\n", getpid());
    return EXIT_SUCCESS;
}

编译与运行

# 在QNX主机开发环境编译
qcc -o proc_mgmt proc_mgmt.c

# 将可执行文件复制到目标机(或直接在目标机编译)
# 在目标机QNX Shell中运行
./proc_mgmt

验证方法与判定标准

运行程序后,观察输出应满足以下条件即判定通过:

  • 父进程先打印启动信息,随后依次创建3个子进程。
  • 每个子进程在exec前打印自己的PID,exec后由/bin/echo输出对应文本(证明exec成功替换了进程映像)。
  • 父进程最终按创建顺序回收全部子进程,并打印每个子进程的PID与退出码0。
  • 程序正常退出,无fork失败或exec失败错误信息。

若需观察进程状态变化,可在程序运行期间于另一个Shell执行pidin | grep proc_mgmt,可看到父进程与子进程的瞬时状态(如RUN/WAIT),但注意子进程生命周期极短,需快速执行或适当增加延时才能捕获。

关键说明

  • fork()返回两次:父进程收到子进程PID,子进程收到0;返回-1表示失败。
  • 子进程必须使用_exit()而非exit(),避免刷新父进程的stdio缓冲区造成重复输出。
  • exec成功后原进程代码不再执行,因此perror("exec失败")仅在exec返回-1时触发。
  • 父进程使用waitpid精确等待指定子进程,避免回收顺序混乱。
时间 父进程 PID=100 fork() × 3 子进程1 PID=101 exec → /bin/echo echo 执行完毕 exit(0) 子进程2 PID=102 exec → /bin/echo echo 执行完毕 exit(0) 子进程3 PID=103 exec → /bin/echo echo 执行完毕 exit(0) waitpid() × 3 回收全部子进程 父进程 子进程 exec后