windows和linux使用system启动进程是一样的吗?

目录

1.背景

2.windows和linux使用system启动进程的区别

2.1.核心差异总览

2.2.底层实现深度解析

[2.3.TCP 监听端口继承问题详解](#2.3.TCP 监听端口继承问题详解)

2.4.解决端口继承问题

[3.Linux 进程创建与端口继承问题的终极解决方案: posix_spawn](#3.Linux 进程创建与端口继承问题的终极解决方案: posix_spawn)

3.1.核心函数与原理

3.2.完整的代码


1.背景

在ubuntu系统中,在主进程中去启动另外一个进程,当这个进程退出后,主进程监听的端口被启动的进程引用了,造成主进程和另外一个进程共同监听同一端口,关闭主进程,端口仍在监听,再次启动主进程,就提示端口占用。用ss -tulnp命令显示如下图所示:

关键信息:

协议 状态 接收队列 发送队列 本地地址:端口 外部地址 关联进程信息
tcp LISTEN 0 4096 0.0.0.0:8088 0.0.0.0:* dmserver(pid=4227)、DataContainerSe(pid=3976)
tcp LISTEN 0 4096 0.0.0.0:2121 0.0.0.0:* dmserver(pid=4227)、DataContainerSe(pid=3976)
  • tcp:表示 TCP 协议(面向连接、可靠的传输层协议)
  • LISTEN:端口处于监听状态,等待外部连接请求
  • 0 4096:接收 / 发送队列长度,0 表示无积压待处理数据,4096 为队列最大容量
  • 0.0.0.0:端口 :表示监听本机所有网卡的对应端口,可接受任意 IP 的连接
    • 8088:常见的 HTTP/HTTPS 替代端口,常用于 Web 服务、代理或自定义应用
    • 2121:FTP 服务的常用替代端口(标准 FTP 为 21),用于文件传输
  • 0.0.0.0:*:外部地址未指定,代表等待任意客户端连接
  • 进程信息 :两个端口均由 dmserver(主服务进程,PID 4227)和 DataContainerSe(数据容器服务,PID 3976)共同占用,说明这两个服务在这两个端口上提供网络服务

同样的代码,在windows上用system启动这个进程没有什么问题,怎么在ubuntu下就出现问题了呢?

2.windows和linux使用system启动进程的区别

2.1.核心差异总览

特性 Windows system() Linux system()
底层实现 CreateProcess(..., "cmd.exe /c command") fork() + execve("/bin/sh -c command") + waitpid()
进程创建模型 一次性创建新进程,无进程复制 先复制父进程(COW),再替换为新程序
资源继承 仅继承标记为「可继承」且bInheritHandles=TRUE的句柄 默认继承所有未设置FD_CLOEXEC的文件描述符
TCP 端口继承 默认不继承(需显式设置bInheritHandle=TRUE 默认继承监听 socket fd,导致端口占用
阻塞行为 父进程阻塞,等待子进程退出 父进程阻塞,等待子进程退出
信号处理 子进程不继承父进程信号掩码,SIGCHLD无意义 父进程阻塞SIGCHLD,忽略SIGINT/SIGQUIT
性能 无进程复制开销,性能较好 大内存进程fork()有 COW 开销,性能较差
错误返回 成功返回 0,失败返回非 0,GetLastError()获取详情 成功返回子进程退出码,失败返回 - 1,errno设置错误码

2.2.底层实现深度解析

1.Linux system()执行流程

cpp 复制代码
// 伪代码,POSIX标准实现
int system(const char *command) {
    pid_t pid = fork();  // 1. 复制父进程,子进程继承所有fd
    if (pid == -1) return -1;
    
    if (pid == 0) {  // 子进程
        // 2. 执行shell命令
        execl("/bin/sh", "sh", "-c", command, (char *)NULL);
        _exit(127);  // exec失败,返回127
    }
    
    // 父进程
    int status;
    // 3. 等待子进程退出,阻塞父进程
    if (waitpid(pid, &status, 0) == -1) return -1;
    return WEXITSTATUS(status);
}

关键问题fork()会完整复制父进程文件描述符表,包括 TCP 监听 socket 的 fd,导致子进程继承监听端口引用,即使父进程退出,端口仍被子进程占用

2.Windows system()执行流程

cpp 复制代码
// 伪代码,Windows实现
int system(const char *command) {
    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi;
    si.cb = sizeof(si);
    
    // 1. 构造cmd.exe命令行
    char cmd[MAX_PATH];
    sprintf(cmd, "cmd.exe /c %s", command);
    
    // 2. 创建新进程,默认bInheritHandles=FALSE
    BOOL success = CreateProcess(
        NULL, cmd, NULL, NULL,
        FALSE,  // 关键:默认不继承父进程句柄
        0, NULL, NULL, &si, &pi
    );
    
    if (!success) return -1;
    
    // 3. 等待子进程退出,阻塞父进程
    WaitForSingleObject(pi.hProcess, INFINITE);
    
    // 4. 获取退出码
    DWORD exit_code;
    GetExitCodeProcess(pi.hProcess, &exit_code);
    
    // 5. 关闭进程/线程句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    
    return exit_code;
}

核心区别CreateProcess默认不继承父进程句柄(bInheritHandles=FALSE),子进程不会自动获得监听 socket 句柄

2.3.TCP 监听端口继承问题详解

1.Linux:默认继承,端口占用风险高

  • 问题本质fork()复制父进程所有文件描述符,包括监听 socket 的 fd,这些 fd 指向内核中同一个 socket 对象,引用计数 + 1
  • 现象 :父进程退出后,子进程仍持有监听 socket fd,端口无法释放,导致服务重启时bind()失败(Address already in use
  • 风险 :子进程若调用accept(),会与父进程争抢新连接,导致业务逻辑混乱

2.Windows:默认不继承,安全性更高

  • 双重限制 :子进程继承句柄需满足两个条件:
    1. 句柄创建时设置SECURITY_ATTRIBUTES.bInheritHandle=TRUE
    2. CreateProcess调用时设置bInheritHandles=TRUE
  • TCP 套接字特殊情况
    • Winsock 套接字本身就是句柄,但默认创建时不可继承
    • 即使手动设置bInheritHandle=TRUE,某些 LSP(分层服务提供程序)可能导致继承失效
  • 默认行为system()调用CreateProcessbInheritHandles=FALSE,子进程不会继承监听端口句柄,无需担心端口占用

2.4.解决端口继承问题

1.Linux 解决方案(双重保障)

方案 代码示例 适用场景
创建时设置SOCK_CLOEXEC `int sockfd = socket(AF_INET, SOCK_STREAM SOCK_CLOEXEC, 0);` Linux 2.6.27+,推荐优先使用
fcntl设置FD_CLOEXEC fcntl(sockfd, F_SETFD, FD_CLOEXEC); 兼容旧系统 / 第三方库创建的 socket
posix_spawn显式关闭 posix_spawn_file_actions_addclose(&fa, sockfd); 替代system(),精细控制 fd,无fork()开销

2.Windows 解决方案(默认安全,无需额外操作)

  • system()默认不继承监听端口句柄,无需特殊处理
  • 若需主动控制句柄继承:
cpp 复制代码
// 创建可继承的监听socket
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;  // 允许子进程继承
SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);

3.Linux 进程创建与端口继承问题的终极解决方案: posix_spawn

posix_spawnPOSIX 标准定义的轻量级进程创建接口 ,用于替代传统的 fork() + exec() 组合,核心优势是可以精细控制子进程的文件描述符、信号、进程属性 ,完美解决你之前遇到的「system() 子进程继承 TCP 监听端口」的问题,同时性能更优,适合 Linux 下的 C/C++ 服务端开发。

3.1.核心函数与原理

1.标准函数原型

cpp 复制代码
#include <spawn.h>
#include <sys/wait.h>

// 主函数:创建子进程
int posix_spawn(
    pid_t *restrict pid,                // 输出:子进程PID
    const char *restrict path,          // 可执行文件的绝对路径
    const posix_spawn_file_actions_t *restrict file_actions, // 核心:控制文件描述符
    const posix_spawnattr_t *restrict attrp,                 // 控制进程属性(信号、调度等)
    char *const restrict argv[],        // 命令行参数(argv[0]为程序名,以NULL结尾)
    char *const restrict envp[]         // 环境变量(以NULL结尾,传environ则继承父进程)
);

// 变体:从PATH环境变量查找可执行文件(类似execvp)
int posix_spawnp(
    pid_t *pid, const char *file,
    const posix_spawn_file_actions_t *file_actions,
    const posix_spawnattr_t *attrp,
    char *const argv[], char *const envp[]
);

2.关键辅助函数

函数 核心作用
posix_spawn_file_actions_init() / destroy() 初始化 / 销毁文件动作对象(必须配对,避免内存泄漏)
posix_spawn_file_actions_addclose() 向子进程添加「关闭指定文件描述符」的动作(解决端口继承的核心)
posix_spawn_file_actions_adddup2() 向子进程添加「重定向文件描述符」的动作(如重定向 stdout 到日志)
posix_spawnattr_init() / destroy() 初始化 / 销毁进程属性对象
posix_spawnattr_setflags() 设置进程属性标志(如POSIX_SPAWN_SETSIGMASK控制信号掩码)

3.执行流程对比(与system()

特性 system(command) posix_spawn()
底层实现 fork() + execve(/bin/sh -c command) + waitpid() Linux 2.6+ 为内核系统调用,无fork()的 COW 开销
阻塞性 父进程阻塞,等待子进程退出 父进程非阻塞,立即返回子进程 PID
文件描述符 子进程默认继承所有未设置FD_CLOEXEC的 fd 可通过file_actions显式控制 fd(关闭 / 重定向)
信号处理 子进程继承父进程信号掩码,SIGCHLD被阻塞 可通过attrp精细控制信号掩码、默认处理
性能 大内存进程fork()有 COW 开销,性能差 fork()开销,适合高频创建子进程
僵尸进程 自动waitpid,无僵尸进程 需手动waitpid,否则产生僵尸进程

3.2.完整的代码

cpp 复制代码
#define _GNU_SOURCE
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <cstring>

char** environ;

int main() {
    std::string strPath = "/opt/dmdbms/bin"; // 替换为实际达梦二进制目录
    const char* target_dir = strPath.c_str();
    const char* cmd = "./DmServiceDMSERVER";
    char* argv[] = {const_cast<char*>(cmd), const_cast<char*>("start"), NULL};
    pid_t pid;

    posix_spawnattr_t attr;
    posix_spawn_file_actions_t fa;

    if (posix_spawn_file_actions_init(&fa) != 0) {
        perror("posix_spawn_file_actions_init");
        return 1;
    }
    if (posix_spawnattr_init(&attr) != 0) {
        perror("posix_spawnattr_init");
        posix_spawn_file_actions_destroy(&fa);
        return 1;
    }

    // 切换子进程工作目录
    if (posix_spawn_file_actions_addchdir_np(&fa, target_dir) != 0) {
        perror("posix_spawn_file_actions_addchdir_np");
        posix_spawn_file_actions_destroy(&fa);
        posix_spawnattr_destroy(&attr);
        return 1;
    }

    // 关闭所有≥3的文件描述符,彻底杜绝端口继承
    int ret = posix_spawn_file_actions_addclosefrom_np(&fa, 3);
    if (ret != 0) {
        perror("posix_spawn_file_actions_addclosefrom_np");
        posix_spawn_file_actions_destroy(&fa);
        posix_spawnattr_destroy(&attr);
        return 1;
    }

    // 启动子进程
    ret = posix_spawn(&pid, cmd, &fa, &attr, argv, environ);

    // 清理资源
    posix_spawn_file_actions_destroy(&fa);
    posix_spawnattr_destroy(&attr);

    if (ret == 0) {
        waitpid(pid, NULL, 0);
        std::cout << "SUCCESS START DMSERVER\n";
    } else {
        perror("posix_spawn failed to start DMSERVER");
        return 1;
    }

    return 0;
}

glibc 版本要求addchdir_np/addclosefrom_np是 glibc 2.29 + 引入的 GNU 扩展,旧版本(如 CentOS 7、银河麒麟 V10 早期版本)不支持。

兼容性方案(旧 glibc 系统):

若系统 glibc < 2.29,无法使用addclosefrom_np,需替换为遍历关闭 fd的兼容写法:

cpp 复制代码
// 替换 addclosefrom_np
#include <sys/resource.h>

static void close_all_fds_except_stdio(posix_spawn_file_actions_t *fa) {
    struct rlimit rl;
    if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
        perror("getrlimit failed");
        return;
    }
    for (rlim_t fd = 3; fd < rl.rlim_cur; fd++) {
        posix_spawn_file_actions_addclose(fa, fd);
    }
}

// 调用方式
close_all_fds_except_stdio(&fa);
相关推荐
Dshuishui2 小时前
VSCode 环境下编译运行 C++ 项目
c++·ide·vscode
此刻觐神2 小时前
IMX6ULL开发板学习-04(Linux磁盘管理相关命令)
linux·运维·学习
liu****2 小时前
第15届省赛蓝桥杯大赛C/C++大学B组
开发语言·数据结构·c++·算法·蓝桥杯·acm
qq_8573058192 小时前
ubuntu 22 源码安装bochs
linux·运维·ubuntu
Zhu7582 小时前
【软件更新】在Ubuntu24 LTS中更新openssl到指定版本,例如openssl3.5.6 LTS
linux·ssh·ssl
ALINX技术博客2 小时前
【黑金云课堂】VMware Ubuntu 开发环境安装教程
linux·fpga开发·fpga
charlie1145141912 小时前
嵌入式Linux模块开发——struct module 深度解析:内核模块的核心数据结构
linux·开发语言·数据结构·c
无缘之缘2 小时前
蓝桥杯手把手教你备战(C/C++ B组)(最全面!最贴心!适合小白!)
c语言·c++·算法·蓝桥杯
刘某的Cloud2 小时前
svc中外部流量访问限制
linux·运维·docker·kubernetes·service