Linux进程复制与替换(1)
1.主函数参数介绍
通常我们在下程序的时候,主函数int main()是没有参数的。但是在在c/c++程序带环境参数的main函数标准写法是有的,其中主函数参数为int main( int argc, char* argv[], char* envp[]);
1)argc 函数参数 类型是int
2)argv 函数内容 类型是char*[]/char**
3)envp 环境变量 类型是char*[]/char**
参数详细讲解:
1. argc:参数个数计数器
- 最小值为
1(即使没有手动传参,也会默认包含程序自身的路径)。 - 例如:在终端执行
./program -a hello,则argc = 3(参数为:./program、-a、hello)。
2. argv:命令行参数数组
- 每个元素是一个以
\0结尾的字符串(C 风格字符串)。 argv[0]:程序自身的路径(绝对路径或相对路径,取决于启动方式)。argv[1]~argv[argc-1]:手动传入的命令行参数(按输入顺序排列)。argv[argc]:固定为NULL(作为数组结束的标志)。
3. envp:环境变量数组
- 每个元素是
KEY=VALUE格式的字符串,存储系统环境变量(如路径、用户名、语言等)。 - 常见环境变量:
PATH(可执行程序路径)、HOME(用户主目录)、USER(当前用户名)、LANG(系统语言)。 - 数组以
NULL结尾(遍历到NULL即结束)。 - 注意:
envp是 POSIX 标准扩展(非 C 标准强制要求),但主流编译器(GCC、Clang、MSVC)均支持。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>//参数个数 参数内容 环境变量
int main(int argc, char* argv[],char* envp[])
{int i = 0;printf("argc=%d\n",argc);for( ;i < argc; i++ ){printf("argv[%d]=%s\n",i,argv[i]);}for( i = 0; envp[i] != NULL; i++ ){printf("envp[%d]=%s\n",i,envp[i]);}exit(0);
}2.复制进程fork
1.什么是fork?
fork()是类 Unix 操作系统(如 Linux, macOS)中用于创建新进程的一个系统调用。它的核心特点是:通过复制调用它的进程(称为父进程)来创建一个新的进程(称为子进程)。简单来说,就像一个细胞分裂。一个进程调用
fork(),然后“砰”的一声,系统中就出现了两个几乎完全相同的进程。
2.fork()核心定义与基本用法
pid_t fork();//pid_t是进程的id类型,其本质是int类型
fork()是唯一调用一次,返回两次的函数,调用fork()函数后就会创建子进程,父进程和子进程会从fork()的返回处进行执行,但是返回值不同。
父进程:fork()返回的是新穿件的子进程的id
子进程:fork()的返回值是0
出错的时候:返回-1,比如系统资源不足时就会回创建失败。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid; // pid_t 是专门用于表示进程ID的数据类型printf("【父进程】准备调用fork(),当前进程ID: %d\n", getpid());// 调用 fork(),程序在这里“分裂”pid = fork();// ========== 从这里开始,有两个进程在同时运行 ==========if (pid < 0) {// fork 失败fprintf(stderr, "fork failed!\n");return 1;} else if (pid == 0) {// 这是子进程的代码分支printf("【子进程】我被创建了!我的进程ID: %d,我收到的fork返回值是: %d\n", getpid(), pid);printf("【子进程】我的父进程ID是: %d\n", getppid());// 子进程可以做自己的工作,例如执行另一个程序// execlp("/bin/ls", "ls", "-l", NULL);} else {// 这是父进程的代码分支 (pid > 0, pid就是子进程的ID)printf("【父进程】我创建了一个子进程!子进程ID: %d,我收到的fork返回值是: %d\n", getpid(), pid);printf("【父进程】我可能会等待子进程结束...\n");// 父进程可以在这里等待子进程结束// wait(NULL);}// 这个printf语句,父进程和子进程都会执行printf("【进程 %d】这条信息来自进程: %d\n", getpid(), getpid());return 0;
}输出的是:
【父进程】准备调用fork(),当前进程ID: 1234
【父进程】我创建了一个子进程!子进程ID: 1235,我收到的fork返回值是: 1235
【父进程】我可能会等待子进程结束...
【进程 1234】这条信息来自进程: 1234
【子进程】我被创建了!我的进程ID: 1235,我收到的fork返回值是: 0
【子进程】我的父进程ID是: 1234
【进程 1235】这条信息来自进程: 1235
注意事项:
执行流:
printf("【父进程】准备调用fork()...")只在fork()调用前执行了一次,所以只打印一次。分裂点:
pid = fork();是分裂点。之后,父进程和子进程都从下一行代码开始执行。返回值:父进程进入
else分支,因为它得到的pid是子进程的 ID(一个正数)。子进程进入else if (pid == 0)分支。并发执行:父进程和子进程是独立的调度单位,它们的
printf语句的输出可能会交织在一起,顺序是不确定的。你可能会先看到父进程的全部输出,也可能先看到子进程的,或者混合。进程ID:
getpid()获取当前进程自己的ID。
getppid()获取当前进程的父进程ID。
3.fork 的核心机制:写时复制
在深入讲解 fork() 的细节之前,必须先理解其底层的关键优化技术:写时复制。
早期的朴素想法:当调用
fork()时,立即将父进程的全部数据、堆栈、代码等内存空间复制一份给子进程。这种方式简单,但效率极低,因为:复制大量内存非常耗时。
很多情况下,子进程会立即调用
exec()来执行另一个程序,这会导致刚才复制的内存被全部丢弃,造成巨大的浪费。
现代的写时复制:
不立即复制:当
fork()被调用时,内核并不会立即复制父进程的物理内存页。共享内存:内核会为子进程创建父进程地址空间的一个副本,但这个副本目前指向与父进程相同的物理内存页。这些内存页被标记为只读。
延迟复制:当父进程或子进程中的任何一个尝试修改这些共享的内存页时,CPU 会触发一个页错误。
真正复制:内核此时才会拦截这个错误,为修改进程复制一份该内存页的物理副本,然后恢复该页的读写权限。这样,修改操作就在自己的私有副本上进行,不会影响另一个进程。
写时复制的优势:
高效:
fork()本身变得非常快,因为它只需要复制进程的内核数据结构(如页表、进程描述符等),而不需要复制沉重的物理内存。节省资源:如果进程在修改内存前就退出了,或者执行了
exec(),就完全避免了不必要的复制,节省了大量内存和 CPU 时间。
通俗的来讲写时拷贝是推迟或者免除拷贝数据的技术,当fork时,内核让子进程共享地址空间的所有页面,当父或子进程要修改的时候,才复制某个页面。对程序员是透明的,写时拷贝以页为单位。
面试题1:
int main()
{int i=0;for(;i<2:i++){fork();printf("A\n");}
}
exit(0);
}面试题2:
int main()
{int i=0;for(;i<2:i++){fork();printf("A");}
}
exit(0);
}面试题3:
int main()
{fork()||fork();printf("A\n");exit(0);
}3.僵死进程的解决方法和孤儿进程
僵死进程:
- 成因:子进程先于父进程结束,但其退出状态(如退出码)未被父进程读取,子进程的 PCB(进程控制块) 仍保留在系统中(占用少量资源);
- 危害:大量僵尸进程会耗尽系统的 PID 资源,导致无法创建新进程;
- 解决:父进程需调用
wait()或waitpid()读取子进程的退出状态,释放其 PCB;若父进程长期运行,可注册SIGCHLD信号处理函数,
孤儿进程:
- 成因:父进程先于子进程结束,子进程的 PPID 会被系统设置为
1(init 进程,或 systemd 等现代 init 替代进程);- 危害:孤儿进程本身无害,因为 init 进程会自动回收其退出状态,不会成为僵尸进程;
- 注意:若孤儿进程长期运行,需确保其逻辑合理(如后台服务)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
int main(int argc, char* argv[],char* envp[])
{
char * s = NULL;
int n = 0;
pid_t pid = fork();
assert( pid != -1 );if ( pid == 0 ){s = "child";n = 4;}else{s = "parent";n = 10;}int i = 0;for(; i < n; i++ ){printf("pid=%d,s=%s\n",getpid(),s);sleep(1);}exit(0);}

