linux 下的fork,exec 等函数的一个例子

本文通过一个具体的C语言程序示例介绍了如何使用fork创建子进程,并在子进程中调用execle函数来执行指定程序。示例展示了父进程与子进程间的交互过程,包括参数传递及环境变量设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本例中使用fork创建一个子进程然后执行exec族函数:

首先来看看运行的结果:

 

 

先来看看fork_exec.c

这个程序的工作是创建一个子进程,然后调用execle执行打印参数和环境变量的函数

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
char *envp[]={"USER=unknown","PATH=/tmp",NULL};
int main(int argc,char **argv){
 pid_t pid;
 if((pid=fork())<0)
 {
  return -1;
 }else if(pid==0){
 //子进程
  if(execle("/root/apue/fork_exec/echo_argv","echo_argv","argv1","argv2",(char *)0,envp)<0)
  {
   return -1;
  }
 }
 //等待子进程结束
 if(waitpid(pid,NULL,0)<0)
 {
  return -1;
 }
 printf("fork_exec.c:pid:%d , child_pid:%d\n",getpid(),pid);
return 0;
}

echo_argv.c代码如下:

这个函数就是打印execle传进来的参数和环境;

 

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char **argv){
 int i;
 char **env;
 extern char **environ;
 printf("echo_argv.c:pid:%d , ppid:%d\n",getpid(),getppid());
 for(i=0;i<argc;i++){
 printf("argv[%d]:%s\n",i,argv[i]);
 }
 //查看这个进程的环境
 for(env=environ;*env !=0;env++)
 {
  printf("%s\n",*env);
 }
exit(0);
}

从运行的结果可以看看execle之后用的是execle函数传进来的环境变量;

代码参考《apue》

<think>我们正在讨论Linux中的fork()函数。根据引用,fork()用于创建新进程,新进程是调用进程(父进程)的副本。关键点:1.fork()调用一次,返回两次:在父进程中返回子进程的PID,在子进程中返回0。2.如果创建失败,返回-1。3.子进程复制父进程的代码、数据、堆栈等,但拥有独立的地址空间。我们将通过几个示例详细说明。示例1(基础):引用[1]中的代码:```c#include<stdio.h>#include<unistd.h>intmain(void){pid_tpid;printf("Before:pidis%d\n",getpid());pid=fork();//创建子进程if(pid==-1){perror("forkfailed");return1;}printf("After:pidis%d,forkreturn%d\n",getpid(),pid);sleep(1);//延迟1秒return0;}```运行结果可能:Before:pidis1234(父进程先打印)然后,fork之后,父进程打印:After:pidis1234,forkreturn1235(假设子进程pid为1235)子进程打印:After:pidis1235,forkreturn0注意:由于调度顺序,父进程和子进程的打印顺序不确定。示例2(区分父子进程):引用[2]中的代码:```c#include<iostream>#include<stdlib.h>#include<unistd.h>usingnamespacestd;intmain(){pid_tpid;pid=fork();if(pid==-1){cout<<"forkerror."<<endl;exit(1);}elseif(pid==0){//子进程cout<<"I'machild,pid="<<getpid()<<",ppid="<<getppid()<<endl;//调用execlp执行ls命令execlp("ls","ls","-l",NULL);//如果execlp成功,不会执行到这里;失败则退出exit(0);}else{//父进程cout<<"I'maparent,pid="<<getpid()<<",ppid="<<getppid()<<endl;sleep(1);//等待子进程执行}return0;}```输出:父进程:I'maparent,pid=1234,ppid=5678(父进程的父进程是shell)子进程:I'machild,pid=1235,ppid=1234然后子进程执行`ls-l`,列出当前目录的详细信息。注意:子进程调用execlp后,其进程映像被替换为ls程序,所以后面的exit(0)不会执行,除非execlp失败。示例3(多个fork):考虑连续两次fork:```c#include<stdio.h>#include<unistd.h>intmain(){printf("A\n");fork();printf("B\n");fork();printf("C\n");return0;}```输出:第一次fork后,进程数变为2,第二次fork后,进程数变为4。因此,A打印一次,B打印两次,C打印四次。但是注意:由于缓冲,如果使用printf(带换行符,通常是行缓冲),在fork之前刷新缓冲区很重要。否则,如果缓冲区中有数据,子进程会复制缓冲区,导致重复输出。这里每个printf都有换行,所以会立即刷新,不会重复。关于写时复制(Copy-On-Write):fork()之后,子进程并不立即复制父进程的数据段、堆和栈,而是共享这些内存空间,直到其中一方试图修改这些区域,才会复制该页面。这提高了效率。注意事项:1.文件描述符:子进程会复制父进程的文件描述符表,所以同一个文件描述符在父子进程中都指向同一个文件表项。如果父子进程同时写一个文件,可能会交错,需要同步。2.避免僵尸进程:父进程应该等待子进程结束(使用wait或waitpid),否则子进程退出后会成为僵尸进程。总结:fork()是创建进程的基本方法,通过返回值区分父子进程,子进程从fork()返回处开始执行。相关问题:1.fork()和vfork()有什么区别?2.如何避免fork()后的子进程成为僵尸进程?3.写时复制(Copy-On-Write)是如何工作的?4.fork()之后,父子进程之间如何通信?</think>### Linux fork() 函数详解 `fork()` 是 Linux 系统中创建新进程的核心系统调用,通过复制调用进程(父进程)生成子进程。其核心特性是**调用一次,返回两次**(父进程和子进程各返回一次),具体行为如下: #### 一、函数原型与返回值 ```c #include <unistd.h> pid_t fork(void); ``` - **父进程**:返回子进程的 PID(正整数) - **子进程**:返回 0 - **失败**:返回 -1(通过 `errno` 获取错误码) #### 二、执行流程 1. **内核分配资源**:为新进程分配独立的 PCB、进程号、内存空间等 2. **写时复制(Copy-On-Write)**:子进程共享父进程的代码段和数据段,仅在修改时复制物理页 3. **复制上下文**:复制文件描述符表、信号处理方式等 4. **调度执行**:父/子进程进入就绪队列,由调度器决定执行顺序 #### 三、代码示例分析 ##### 示例 1:基础用法(引用[1]) ```c #include <stdio.h> #include <unistd.h> int main() { pid_t pid; printf("Before fork: PID=%d\n", getpid()); // 仅父进程执行 pid = fork(); // 分叉点 if (pid == -1) { perror("fork failed"); return 1; } // 父子进程都会执行以下代码 printf("After fork: PID=%d, fork() returned %d\n", getpid(), pid); sleep(1); return 0; } ``` **运行结果**: ``` Before fork: PID=1234 After fork: PID=1234, fork() returned 1235 // 父进程 After fork: PID=1235, fork() returned 0 // 子进程 ``` ##### 示例 2:进程替换(引用[2]) ```c #include <iostream> #include <unistd.h> #include <stdlib.h> using namespace std; int main() { pid_t pid = fork(); if (pid == 0) { // 子进程 cout << "Child PID=" << getpid() << ", PPID=" << getppid() << endl; execlp("ls", "ls", "-l", NULL); // 替换为ls命令 exit(0); // 仅execlp失败时执行 } else { // 父进程 cout << "Parent PID=" << getpid() << ", PPID=" << getppid() << endl; sleep(1); // 等待子进程结束 } return 0; } ``` **关键点**: - `execlp()` 替换子进程内存空间 - 父进程通过 `sleep()` 防止子进程变僵尸进程 #### 四、注意事项 1. **共享资源**: - 文件描述符共享(相同文件偏移量) - 内存采用写时复制(COW)优化 - 信号处理方式继承但独立修改 2. **常见问题**: - **僵尸进程**:父进程需调用 `wait()` 回收子进程 - **竞争条件**:父子进程执行顺序不确定 - **死锁风险**:共享文件描述符时需同步 3. **与 `vfork()` 区别**: - `vfork()` 创建的子进程**共享父进程地址空间** - 子进程必须立即调用 `exec()` 或 `exit()` - 执行期间父进程被挂起 > 引用[4]强调:fork 的核心是**通过复制进程控制块(PCB)创建新进程**,子进程获得父进程资源的“快照”,但实际物理内存通过写时复制延迟分配[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值