当前位置: 首页 > news >正文

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-ahello)。
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

注意事项:

  1. 执行流printf("【父进程】准备调用fork()...") 只在 fork() 调用前执行了一次,所以只打印一次。

  2. 分裂点pid = fork(); 是分裂点。之后,父进程和子进程都从下一行代码开始执行。

  3. 返回值:父进程进入 else 分支,因为它得到的 pid 是子进程的 ID(一个正数)。子进程进入 else if (pid == 0) 分支。

  4. 并发执行:父进程和子进程是独立的调度单位,它们的 printf 语句的输出可能会交织在一起,顺序是不确定的。你可能会先看到父进程的全部输出,也可能先看到子进程的,或者混合。

  5. 进程ID

    • getpid() 获取当前进程自己的ID。

    • getppid() 获取当前进程的父进程ID。

3.fork 的核心机制:写时复制

在深入讲解 fork() 的细节之前,必须先理解其底层的关键优化技术:写时复制

  • 早期的朴素想法:当调用 fork() 时,立即将父进程的全部数据、堆栈、代码等内存空间复制一份给子进程。这种方式简单,但效率极低,因为:

    1. 复制大量内存非常耗时。

    2. 很多情况下,子进程会立即调用 exec() 来执行另一个程序,这会导致刚才复制的内存被全部丢弃,造成巨大的浪费。

  • 现代的写时复制

    1. 不立即复制:当 fork() 被调用时,内核并不会立即复制父进程的物理内存页。

    2. 共享内存:内核会为子进程创建父进程地址空间的一个副本,但这个副本目前指向与父进程相同的物理内存页。这些内存页被标记为只读

    3. 延迟复制:当父进程或子进程中的任何一个尝试修改这些共享的内存页时,CPU 会触发一个页错误。

    4. 真正复制:内核此时才会拦截这个错误,为修改进程复制一份该内存页的物理副本,然后恢复该页的读写权限。这样,修改操作就在自己的私有副本上进行,不会影响另一个进程。

写时复制的优势

  • 高效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);}

http://www.dtcms.com/a/614975.html

相关文章:

  • QPushButton菜单样式表深度解析
  • Pybullet环境中搭建一个UR10机械臂带Robotiq夹爪并实现一个简单的抓取任务
  • 免费建立个人网站的哪些平台好网站建设课程的感受
  • Qt QML 模块及其函数详解
  • 2019年JCP SCI1区TOP,改进蚁群算法+多车场多目标绿色车辆路径规划,深度解析+性能实测
  • 宜兴建设局 审图中心 网站怎么建立自己的网站视频网站
  • 网站开发与设计中学生有哪些企业公司
  • 专门做尾单的那个网站叫啥一键wordpress 伪静态
  • 成都网站维护公司网站开发大数据库
  • 招聘网站排行榜烟台市建设工程检测站网站
  • 通用会话控制方案
  • 深度学习:Adam 优化器实战(Adam Optimizer)
  • 建个网站需要多少钱网站备份查询
  • NetApp FAS8200 控制器启动报错-PCIe device missing
  • 找个人做网站的做的好的音乐网站的特点
  • 网站设计的简称河南中国建设厅官方网站
  • 唐山自助建站福州如何做百度的网站推广
  • 政务网站建设建议湖南广告优化
  • 网站建设排名北京郑州市主城区
  • 做网站为什么要建站点海口网站制作设计
  • 淄博市住房和城乡建设局网站怎样在潇湘书院网站做兼职
  • STL容器
  • 北京网站建设企业网站制作益阳seo网站建设
  • 天猫网站建设目的小学最好的网站建设
  • JavaWeb(前端实战)
  • 新网站多久收录内页4399任天堂
  • 买网站做网站设计怎么学
  • Qt Core 模块中的函数详解
  • 网站备案检验单网站搭建免费
  • 设置网站模板免费的app软件下载安装