找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3831

积分

0

好友

498

主题
发表于 1 小时前 | 查看: 1| 回复: 0

system()fork() + exec() 都是 Linux/Unix 中用于在程序中执行其他程序的常用方法,但它们在实现机制、使用场景、安全性和灵活性上有显著差异。作为程序员,了解这两种进程控制方式的异同,对于编写稳健、安全的代码至关重要。

相同点

  • 目的:在程序中启动一个新程序。
  • 进程关系:最终都会产生一个新的进程(通过 fork 创建子进程),在子进程中加载并执行目标程序。
  • 父子进程:调用进程(父进程)与新程序(子进程)之间存在父子关系。

不同点

1. 实现机制

特性 system() fork() + exec()
本质 一个封装好的库函数 两个独立的系统调用组合
内部实现 内部调用 fork()exec()waitpid() 开发者手动调用 fork(),然后在子进程中调用 exec 族函数(如 execlexecvp 等),父进程可选择 wait() 或不等待
Shell 参与 启动一个 /bin/sh 来解析命令字符串 直接执行目标程序,不经过 Shell(除非手动调用 sh -c

2. 灵活性与控制

特性 system() fork() + exec()
参数传递 只能通过字符串传递完整命令行,由 Shell 解析 可精确控制传递给新程序的 argv 数组,无 Shell 解析干扰
标准 I/O 重定向 可在命令字符串中直接使用 Shell 重定向(如 > file 需要在 fork 后、exec 前手动用 dup2 等系统调用重定向
环境变量 继承当前环境,可通过命令字符串修改 可通过 execenvp 参数精确控制环境变量,或使用 putenvsetenv 修改
信号处理 在调用期间会阻塞 SIGCHLD,忽略 SIGINT 和 SIGQUIT 完全由开发者控制信号处理逻辑
父子进程同步 自动等待子进程结束(同步执行) 可选择同步(调用 wait)或异步(不等待,父进程继续运行)

3. 安全性

方面 system() fork() + exec()
命令注入风险 如果命令字符串包含用户输入,可能被注入恶意命令(如 ; rm -rf / 无 Shell 解析,参数直接传递给程序,更安全
权限 以父进程权限运行,但引入 Shell 增加了攻击面 以父进程权限运行,但无 Shell,攻击面更小

4. 返回值与错误处理

情况 system() fork() + exec()
返回值 返回 Shell 的退出状态(需要解析),失败时返回 -1 需要自行检查每个系统调用的返回值
错误检测 难以区分是 forkexec 还是命令本身失败 可以精确知道哪个环节出错(fork 失败、exec 失败、子进程异常等)

5. 性能

方面 system() fork() + exec()
开销 较大,因为多启动了一个 Shell 进程 较小,直接加载目标程序

使用场景

  • 使用 system()
    • 执行简单的 Shell 命令(如 ls -lcp a b),且无需精细控制。
    • 需要 Shell 通配符、管道、重定向等特性。
    • 对安全性和错误处理要求不高。
  • 使用 fork() + exec()
    • 需要精确控制子进程的输入输出、环境、参数。
    • 执行带用户输入的程序,避免命令注入。
    • 需要异步执行(不等待子进程)。
    • 编写守护进程、网络服务等需要精细管理子进程的场景。

示例对比

使用 system()

int system_cmdExec(const char *cmd)
{
    if (!cmd)
    {
        DEBUG_PRINT("error bad parameter");
        return -1;
    }

    if (0 == cmdBlackListCheck(cmd))
    {
        DEBUG_PRINT("cmd is illegal");
        return -1;
    }

    DEBUG_PRINT("run cmd:%s", cmd);

    int ret = 0;

    /* system() */
    ret = system(cmd);
    if ( 0 != ret)
    {
        DEBUG_PRINT("system():run cmd:%s failed, ret:%d[%d(%s)]", cmd, ret, errno, strerror(errno));
        return 0;
    }

    DEBUG_PRINT("run cmd:%s OK", cmd);

    return 0;
}

使用 fork() + exec()

int fork_exec_cmdExec(const char *cmd)
{
    sigset_t mask = {0};
    sigset_t oldMask = {0};
    struct sigaction ign = {0};
    struct sigaction oldInt = {0};
    struct sigaction oldQuit = {0};
    pid_t pid = 0;
    int status = 0;

    if (0 == cmdBlackListCheck(cmd))
    {
        DEBUG_PRINT("cmd is illegal");
        return -1;
    }

    /* check if shell is avalaible */
    if (!cmd)
    {
        DEBUG_PRINT("check if shell is available");

        if (access("/bin/sh", X_OK) == 0)
        {
            DEBUG_PRINT("shell is available");
            return 1;
        }
        else
        {
            DEBUG_PRINT("shell is not available");
            return 0;
        }
    }

    DEBUG_PRINT("run cmd:%s", cmd);

    /* signal process (keep it same with system()'s behavior) */
    /* 1. block SIGCHILD to prevents child processes from being
     * reclaimed by other signal handlers before waitpid
     */
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, &oldMask);

    /* 2. Ignore SIGINT and SIGQUIT temporarily to prevent them
     * from breaking the parent process‘s wait
     */
    ign.sa_handler = SIG_IGN;
    sigemptyset(&ign.sa_mask);
    ign.sa_flags = 0;
    sigaction(SIGINT, &ign, &oldInt);
    sigaction(SIGQUIT, &ign, &oldQuit);

    /* create child */
    pid = fork();
    if (-1 == pid)
    {
        DEBUG_PRINT("create child failed, will restore signal set");
        status = 1;
        goto restore;
    }

    if (0 == pid)
    {
        /* child */
        /* Restores the signal mask and handler set before the parent
         * process (the child process inherits a copy of the parent process)
         */
        sigprocmask(SIG_SETMASK, &oldMask, NULL);
        sigaction(SIGINT, &oldInt, NULL);
        sigaction(SIGQUIT, &oldQuit, NULL);

        /* create array of parameters */
        char *argv[] = {"sh", "-c", (char *)cmd, NULL};
        execve("/bin/sh", argv, environ);

        /* if exec failed, child exit and report error */
        DEBUG_PRINT("execve failed");
        _exit(127);/* typical error code f shell cannot execute */
    }

    /* parent process: wait child finish */
    while (waitpid(pid, &status, 0) == -1)
    {
        if (EINTR != errno)
        {
            /* no EINTR error, exit loop */
            DEBUG_PRINT("no EINTR error, exit loop");
            status = -1;
            break;
        }

        /* if ENTIR, continue to wait */
        DEBUG_PRINT("EINTR error, continue wait");
    }

    DEBUG_PRINT("run cmd:%s OK", cmd);

restore:
    /* restore original signal */
    sigaction(SIGINT, &oldInt, NULL);
    sigaction(SIGQUIT, &oldQuit, NULL);
    sigprocmask(SIG_SETMASK, &oldMask, NULL);

    if (-1 == status)
    {
        return -1;
    }

    /* analyze child exit status, keep it same with system()‘s */
    if (WIFEXITED(status))
    {
        return WEXITSTATUS(status);
    }
    else if (WIFSIGNALED(status))
    {
        return 128 + WTERMSIG(status);
    }
    else
    {
        /* if child terminated (e.g. receive SIGSTOP), system() will not occur, but to be complete  */
        return -1;
    }

    return 0;
}

总结

  • system() 是一个便捷但受限的封装,适合执行简单命令,但存在安全风险和较低灵活性。
  • fork() + exec()底层、灵活、安全的组合,允许对进程创建和执行进行全面控制,但需要开发者编写更多代码并处理更多细节。

选择哪个取决于具体需求:如果需要快速执行一条 Shell 命令且不关心细节,用 system();如果需要精细控制、安全性或异步执行,用 fork() + exec()。掌握这些C语言函数的底层原理,能帮助你在实际开发中做出更优的选择。如果你对更多系统编程的实战经验感兴趣,欢迎到 云栈社区 交流探讨。




上一篇:Token详解:大模型如何理解与生成语言的核心计量单位
下一篇:港股迅策分析:AI数据平台为何难成“中国Palantir”
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-31 08:47 , Processed in 0.527640 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表