Linux下的fork和exec函数

发布时间:2026/7/9 17:03:48
Linux下的fork和exec函数 引言多进程编程的概念早在六十年代就被提出但直到八十年代中期Unix系统中才引入多进程机制。如今由于自身的许多优点多进程编程已经得到了广泛的应用。fork函数是 Unix 系统最杰出的成就之一它是七十年代 UNIX 早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果。一方面它使操作系统在进程管理上付出了最小的代价另一方面又为程序员提供了一个简洁明了的多进程方法。Linux下进程的结构Linux 下一个进程在内存里有三部分的数据就是代码段、堆栈段和数据段。代码段存放程序代码的数据。如果机器中有数个进程运行相同的一个程序它们可以共享相同的代码段。堆栈段存放子程序的返回地址、子程序的参数以及程序的局部变量。数据段存放程序的全局变量、常数以及动态数据分配的空间如用malloc函数取得的空间。系统如果同时运行数个相同的程序它们之间不能使用同一个堆栈段和数据段但可以共享代码段。Linux下的进程控制fork 函数fork()用来创建一个新的进程该进程几乎是当前进程的一个完全拷贝。#includesys/types.h#includeunistd.hpid_tfork(void);exec 函数族exec()函数族用来启动另外的进程以取代当前运行的进程。fork 的返回值理解fork最困难之处在于调用它一次它却返回两次。在父进程中返回新派生进程子进程的进程 ID 号在子进程中返回值为 0因此返回值本身告知当前进程是子进程还是父进程。pid_tpidfork();if(pid-1){// 出错}elseif(pid0){// 子进程}else{// 父进程pid 是子进程的 ID}fork在子进程返回 0 而不是父进程 ID 的原因任何子进程只有一个父进程子进程可以通过getppid()取得父进程 ID相反父进程可以有许多子进程无法直接获取各个子进程 ID。如果父进程想要跟踪所有子进程必须记录每次调用fork的返回值。fork 的两个典型用法一个进程创建一个自身的副本这样每个副本都可以在另一个副本执行其他任务的同时处理各自的某个操作。这是网络服务器的典型用法。一个进程想要执行另一个程序。既然创建新进程的唯一办法是调用fork该进程于是首先调用fork创建一个自身的副本然后其中一个副本通常为子进程调用exec把自身替换成新的程序。这是 Shell 之类程序的典型用法。fork 的注意事项fork()通过复制调用进程来创建一个新进程新进程称为子进程是调用进程父进程的完全副本但以下情况除外子进程有自己唯一的进程 ID并且这个 PID 不匹配任何现有进程组的 ID。子进程的父进程 ID 与父进程 ID 相同。子进程不会继承其父进程的内存锁和信号量调整。子代不会从其父代继承未完成的异步 I/O 操作也不会从其父代继承任何异步 I/O 上下文。父进程中调用fork之前打开的所有描述符在fork返回之后由子进程共享。网络服务器利用了这个特性父进程调用accept之后调用fork所接受的已连接套接字随后在父进程与子进程之间共享。通常情况下子进程接着读写这个已连接套接字父进程则关闭这个已连接套接字。exec 函数族exec 函数族的作用exec函数族的作用是替换当前进程的代码段和数据段将一个新程序加载到当前进程的内存空间中执行。与fork不同exec不会创建新进程而是在原有进程的基础上替换执行内容。6 个 exec 函数系统调用execve()对当前进程进行替换替换者为一个指定的程序。在 Linux 中exec 函数族包括 6 个函数函数名路径指定方式参数传递方式环境变量execl路径名列表形式使用当前环境execlp文件名搜索 PATH列表形式使用当前环境execle路径名列表形式自定义环境execv路径名数组形式使用当前环境execvp文件名搜索 PATH数组形式使用当前环境execve路径名数组形式自定义环境函数命名规律这些函数的命名遵循一定的规律llist参数以列表形式逐个传递vvector参数以指针数组形式传递ppath使用 PATH 环境变量搜索可执行文件eenvironment自定义环境变量函数原型#includeunistd.hintexecl(constchar*path,constchar*arg,...);intexeclp(constchar*file,constchar*arg,...);intexecle(constchar*path,constchar*arg,...,char*constenvp[]);intexecv(constchar*path,char*constargv[]);intexecvp(constchar*file,char*constargv[]);intexecve(constchar*path,char*constargv[],char*constenvp[]);这些函数只在出错时才返回到调用者否则控制将被传递给新程序的起始点通常是main函数。调用示例// execl 示例列表形式路径名execl(/bin/ls,ls,-l,NULL);// execlp 示例列表形式搜索 PATHexeclp(ls,ls,-l,NULL);// execv 示例数组形式路径名char*argv[]{ls,-l,NULL};execv(/bin/ls,argv);// execvp 示例数组形式搜索 PATHexecvp(ls,argv);// execve 示例自定义环境变量char*envp[]{PATH/usr/bin,HOME/root,NULL};execve(/bin/ls,argv,envp);fork 和 exec 代码示例示例 1基本 fork 用法#includestdio.h#includestdlib.h#includeunistd.h#includesys/types.hintmain(){pid_tpidfork();if(pid-1){perror(fork failed);exit(EXIT_FAILURE);}elseif(pid0){// 子进程printf(子进程: PID%d, PPID%d\n,getpid(),getppid());exit(EXIT_SUCCESS);}else{// 父进程printf(父进程: PID%d, 子进程PID%d\n,getpid(),pid);wait(NULL);// 等待子进程结束exit(EXIT_SUCCESS);}return0;}示例 2fork exec 组合使用#includestdio.h#includestdlib.h#includeunistd.h#includesys/types.h#includesys/wait.hintmain(){pid_tpidfork();if(pid-1){perror(fork failed);exit(EXIT_FAILURE);}elseif(pid0){// 子进程执行 ls 命令printf(子进程: 执行 ls -l\n);char*argv[]{ls,-l,NULL};execvp(ls,argv);// exec 失败才会执行到这里perror(execvp failed);exit(EXIT_FAILURE);}else{// 父进程等待子进程结束printf(父进程: 等待子进程完成\n);intstatus;waitpid(pid,status,0);if(WIFEXITED(status)){printf(子进程正常结束退出码: %d\n,WEXITSTATUS(status));}elseif(WIFSIGNALED(status)){printf(子进程被信号终止信号: %d\n,WTERMSIG(status));}exit(EXIT_SUCCESS);}return0;}示例 3模拟 Shell 执行命令#includestdio.h#includestdlib.h#includeunistd.h#includesys/types.h#includesys/wait.h#includestring.h#defineMAX_ARGS10intmain(){charcommand[100];while(1){printf(mini-shell );fgets(command,sizeof(command),stdin);// 去除换行符command[strcspn(command,\n)]0;if(strcmp(command,exit)0){break;}pid_tpidfork();if(pid-1){perror(fork failed);continue;}elseif(pid0){// 解析命令参数char*argv[MAX_ARGS];intargc0;char*tokenstrtok(command, );while(token!NULLargcMAX_ARGS-1){argv[argc]token;tokenstrtok(NULL, );}argv[argc]NULL;execvp(argv[0],argv);perror(execvp failed);exit(EXIT_FAILURE);}else{wait(NULL);}}return0;}总结fork和exec是 Unix/Linux 系统中进程控制的核心函数fork创建一个与当前进程几乎完全相同的子进程返回两次exec用新程序替换当前进程的执行内容不创建新进程两者结合使用是创建新进程并执行新程序的标准模式fork创建子进程exec替换子进程的执行内容。这种模式被广泛应用于 Shell、网络服务器等程序中。本文部分内容参考自《UNIX网络编程卷1套接字联网API》。