news 2026/5/1 11:14:26

system函数与exec函数族

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
system函数与exec函数族

文章目录

  • system函数
    • 函数原型
      • 功能
      • 参数
      • 返回值
        • 返回值获取shell退出状态
    • 实现原理
    • 安全风险
      • system运行任意的shell命令
      • 命令注入漏洞
  • exec函数族
    • 核心功能
    • 函数原型
      • 命名规律
      • 参数对比
      • 返回值
      • 常见错误码
    • 应用例程
      • execl() - 参数列表形式
      • execv() - 参数数组形式
      • execle() - 自定义环境变量
      • execve() - 系统调用(最底层)
      • execlp() - 自动搜索PATH
      • execvp() - PATH搜索 + 参数数组
      • 标准模式:fork + exec

system函数

函数原型

#include<stdlib.h>intsystem(constchar*command);

功能

  • 执行shell命令,就像在终端中输入命令一样
system("ls -la");// 执行ls命令system("echo Hello");// 执行echo命令system("gcc test.c");// 执行编译命令

参数

  • 有效命令:执行命令
  • NULL:检查shell是否可用
  • 空字符串:""直接返回成功
#include<stdlib.h>#include<stdio.h>intmain(){// 1. 执行简单命令system("pwd");// 打印当前目录// 2. 执行带参数的命令system("ls -l /tmp");// 3. 执行复杂命令(管道、重定向等)system("ps aux | grep bash > output.txt");// 4. 使用变量构建命令charfilename[100]="test.txt";charcommand[200];snprintf(command,sizeof(command),"cat %s",filename);system(command);return0;}

返回值

  • == 127:启动shell失败,/bin/sh 不存在或不可执行
  • == -1:其他错误,fork失败、内存不足等
  • 其他值:命令执行状态,shell的退出状态编码
intret=system(command);if(ret==-1){// system()调用本身失败perror("system() failed");}elseif(WIFEXITED(ret)){// 命令正常结束printf("命令退出码: %d\n",WEXITSTATUS(ret));if(WEXITSTATUS(ret)==127){printf("错误: shell无法启动或命令不存在\n");}elseif(WEXITSTATUS(ret)==126){printf("错误: 命令不可执行\n");}}elseif(WIFSIGNALED(ret)){// 命令被信号终止printf("命令被信号终止: %d\n",WTERMSIG(ret));}
返回值获取shell退出状态
// 要获取真正的退出状态,需要处理返回值intstatus=system("ls /nonexistent");if(status==-1){printf("system()调用失败\n");}else{// 使用wait相关的宏if(WIFEXITED(status)){intexit_code=WEXITSTATUS(status);printf("命令退出码: %d\n",exit_code);// ls /nonexistent 会返回 2}}

实现原理

system("ls -l") ↓ fork() 创建子进程 ↓ 子进程:execl("/bin/sh", "sh", "-c", "ls -l", NULL) ↓ shell解析命令:ls -l ↓ shell执行ls命令 ↓ shell退出,返回ls的退出状态 ↓ 父进程接收状态,返回给调用者
// system() 大致相当于以下代码:intsystem(constchar*command){if(command==NULL){// 检查shell是否存在returnshell_exists()?1:0;}pid_tpid=fork();if(pid==0){// 子进程:执行shellexecl("/bin/sh","sh","-c",command,(char*)NULL);_exit(127);// 如果execl失败}elseif(pid>0){// 父进程:等待子进程结束intstatus;waitpid(pid,&status,0);returnstatus;// 返回shell的退出状态}else{// fork失败return-1;}}

安全风险

system运行任意的shell命令

#include<stdio.h>#include<stdlib.h>intmain(intargc,constchar*argv[]){charbuf[1024]={};while(1){printf("命令->");fgets(buf,1024,stdin);system(buf);}return0;}

命令注入漏洞

  • 如果键入rm -rf *;会造成不可挽回的后果
// ⚠️ 危险!可能被攻击!charuser_input[100];printf("输入文件名: ");fgets(user_input,sizeof(user_input),stdin);charcommand[200];sprintf(command,"rm %s",user_input);// ⚠️ 危险!system(command);// 如果用户输入: "test.txt; rm -rf /"// 实际执行: rm test.txt; rm -rf /

exec函数族

核心功能

  • 进程替换
    • 不创建新进程:替换当前进程的代码和数据
    • 继承PID:保持原进程ID不变
    • 全新开始:从新程序的main()函数开始执行

函数原型

// 所有exec函数都在 <unistd.h> 中#include<unistd.h>intexecl(constchar*path,constchar*arg0,...,(char*)NULL);intexecv(constchar*path,char*constargv[]);intexecle(constchar*path,constchar*arg0,...,(char*)NULL,char*constenvp[]);intexecve(constchar*path,char*constargv[],char*constenvp[]);intexeclp(constchar*file,constchar*arg0,...,(char*)NULL);intexecvp(constchar*file,char*constargv[]);

命名规律

execl - l: list (参数列表) execv - v: vector (参数数组) execle - e: environment (传递环境变量) execlp - p: PATH (在PATH中查找程序) execvp - p + v: PATH + 参数数组 execve - 系统调用 (v + e)

参数对比

函数程序路径参数传递方式环境变量PATH搜索
execl()完整路径参数列表继承当前
execv()完整路径参数数组继承当前
execle()完整路径参数列表自定义
execve()完整路径参数数组自定义
execlp()文件名参数列表继承当前
execvp()文件名参数数组继承当前

返回值

  • 执行成功:不返回(进程被替换)
  • 执行失败:返回 -1,设置 errno
// 重要:exec成功时,后面的代码不会执行!if(execl("/bin/ls","ls",NULL)==-1){// 只有exec失败时才会执行到这里perror("exec failed");exit(1);}// exec成功后,这里的代码永远不会执行printf("这行不会打印!\n");

常见错误码

#include<errno.h>intret=execvp("nonexistent",args);if(ret==-1){switch(errno){caseENOENT:printf("程序不存在\n");break;caseEACCES:printf("没有执行权限\n");break;caseENOMEM:printf("内存不足\n");break;caseE2BIG:printf("参数列表太长\n");break;caseENOEXEC:printf("不是可执行文件\n");break;default:perror("exec失败");}exit(1);}

应用例程

execl() - 参数列表形式

intexecl(constchar*path,constchar*arg0,...,(char*)NULL);// 参数以列表形式传递,以NULL结束// 示例:执行 ls -l /tmpexecl("/bin/ls","ls","-l","/tmp",NULL);// 相当于终端命令:$ ls -l /tmp

execv() - 参数数组形式

intexecv(constchar*path,char*constargv[]);// 参数以数组形式传递// 示例:执行 ls -l /tmpchar*args[]={"ls","-l","/tmp",NULL};execv("/bin/ls",args);// argv数组必须以NULL结束!

execle() - 自定义环境变量

intexecle(constchar*path,constchar*arg0,...,(char*)NULL,char*constenvp[]);// 示例:执行程序并传递新环境变量char*env[]={"MYVAR=hello","PATH=/usr/bin",NULL};execle("/bin/sh","sh","-c","echo $MYVAR",NULL,env);

execve() - 系统调用(最底层)

intexecve(constchar*path,char*constargv[],char*constenvp[]);// 示例:完全控制参数和环境变量char*args[]={"myprog","-v",NULL};char*env[]={"DEBUG=1","LOG_LEVEL=debug",NULL};execve("/usr/local/bin/myprog",args,env);

execlp() - 自动搜索PATH

intexeclp(constchar*file,constchar*arg0,...,(char*)NULL);// 示例:不需要完整路径execlp("ls","ls","-l","/tmp",NULL);// 系统会在PATH中查找ls// 相当于:execl("/bin/ls", "ls", "-l", "/tmp", NULL);

execvp() - PATH搜索 + 参数数组

intexecvp(constchar*file,char*constargv[]);// 示例:最常用的组合char*args[]={"gcc","test.c","-o","test",NULL};execvp("gcc",args);// 自动在PATH中找gcc

标准模式:fork + exec

#include<unistd.h>#include<sys/wait.h>#include<stdio.h>#include<stdlib.h>intmain(){pid_tpid=fork();if(pid==-1){perror("fork失败");exit(1);}if(pid==0){// 子进程:执行新程序printf("子进程 PID=%d 准备执行新程序\n",getpid());// 方法1:使用execlexecl("/bin/ls","ls","-l","/tmp",NULL);// 方法2:使用execvp// char *args[] = {"ls", "-l", "/tmp", NULL};// execvp("ls", args);// 如果exec失败perror("exec失败");_exit(1);// 子进程结束}else{// 父进程:等待子进程printf("父进程 PID=%d 等待子进程\n",getpid());intstatus;waitpid(pid,&status,0);if(WIFEXITED(status)){printf("子进程退出码: %d\n",WEXITSTATUS(status));}}return0;}
  • 资源在exec后保持不变:
      1. 进程ID和父进程ID
      1. 实际用户ID和实际组ID
      1. 进程组ID和会话ID
      1. 控制终端
      1. 当前工作目录
      1. 文件创建掩码
      1. 文件锁
      1. 未处理的闹钟
      1. 信号处理方式(但被忽略的信号保持忽略)
  • 资源在exec后变化:
      1. 代码段、数据段、堆栈(全新)
      1. 文件描述符的close-on-exec标志
      1. 共享内存、内存映射文件
      1. 线程(所有线程终止)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 7:19:11

如何在PyTorch-CUDA-v2.8中使用FSDP进行大规模训练?

如何在 PyTorch-CUDA-v2.8 中使用 FSDP 进行大规模训练 当一个拥有千亿参数的大语言模型摆在面前&#xff0c;而你手头只有几块 A100 显卡时&#xff0c;该怎么办&#xff1f;单卡显存爆满、多卡并行效率低下、环境配置千头万绪——这些是每个大模型开发者都可能遇到的现实困境…

作者头像 李华
网站建设 2026/5/1 6:19:34

一文看透:提示工程架构师剖析 AI 与提示工程应用场景

一文看透&#xff1a;提示工程架构师剖析 AI 与提示工程应用场景 一、引言&#xff1a;为什么你需要懂提示工程&#xff1f; 1. 一个扎心的问题&#xff1a;为什么你的AI不好用&#xff1f; 你一定有过这样的经历&#xff1a; 用ChatGPT写文案&#xff0c;得到的内容要么偏离主…

作者头像 李华
网站建设 2026/4/27 13:13:15

基于SSM的电竞陪玩管理系统【源码+文档+调试】

&#x1f525;&#x1f525;作者&#xff1a; 米罗老师 &#x1f525;&#x1f525;个人简介&#xff1a;混迹java圈十余年&#xff0c;精通Java、小程序、数据库等。 &#x1f525;&#x1f525;各类成品Java毕设 。javaweb&#xff0c;ssm&#xff0c;springboot等项目&#…

作者头像 李华
网站建设 2026/4/25 19:27:55

Docker Compose配置共享数据卷实现PyTorch训练资源共享

Docker Compose配置共享数据卷实现PyTorch训练资源共享 在现代AI研发团队中&#xff0c;一个常见的场景是&#xff1a;多个开发者并行开展模型实验&#xff0c;有人训练ResNet&#xff0c;有人微调BERT&#xff0c;还有人做可视化分析。但很快就会遇到几个令人头疼的问题——数…

作者头像 李华
网站建设 2026/5/1 10:44:00

清华镜像源加速PyTorch相关依赖安装,配合CUDA镜像更流畅

清华镜像源加速PyTorch安装&#xff0c;结合CUDA容器实现高效AI开发 在深度学习项目中&#xff0c;最让人头疼的往往不是模型设计本身&#xff0c;而是环境搭建——尤其是当你面对“pip install torch 卡在 0%”、CUDA 版本不匹配报错、或者多台机器环境无法对齐的问题时。这种…

作者头像 李华