解析进程复制:父子进程内存地址的神秘之处

简介: 解析进程复制:父子进程内存地址的神秘之处

当涉及到进程复制时,有时会出现一个令人困惑的现象:在父进程和子进程中,某些变量的内存地址似乎是相同的,尽管它们实际上是独立的进程。下面我将简单解释这个现象以及背后的原因。

进程复制:父子进程的神秘关系

在多进程编程中,使用 `fork()` 函数可以创建一个新的子进程。这个子进程是父进程的几乎完美副本,包括代码、数据和资源。这就是为什么在子进程中有时会看到父进程的某些变量具有相同的内存地址的原因。

为什么内存地址相同?

这种现象的原因在于虚拟内存系统。虚拟内存允许操作系统为每个进程创建一个独立的虚拟地址空间,使得每个进程都认为它在使用整个计算机的内存。但实际上,物理内存被分割和共享。

当调用 `fork()` 创建子进程时,操作系统并不会复制整个物理内存。相反,它会为子进程创建一个新的页表,该页表将虚拟地址映射到相同的物理内存页面。这就是为什么子进程看起来拥有与父进程相同的内存地址。

数据独立性

虽然父子进程共享相同的物理内存页面,但它们的数据是独立的。这意味着当一个进程修改共享页面上的数据时,不会影响到另一个进程。因为每个进程都有自己的页表,操作系统会根据页表来决定虚拟地址如何映射到物理内存。

进程间通信

尽管父子进程拥有相同的内存页面,但它们通常需要使用进程间通信(IPC)机制来实现数据共享。这可以通过管道、共享内存、消息队列等方式来实现。IPC 机制允许这两个进程在独立的虚拟地址空间中交换数据,确保数据的一致性和安全性。

结论

进程复制是多进程编程的常见技术,它允许创建独立运行的子进程,但在虚拟内存背后,这两个进程共享相同的物理内存页面。这种现象可以解释为什么父进程和子进程有时会看到某些变量的内存地址是相同的,尽管它们实际上是独立的进程。要确保数据的正确性和安全性,需要使用适当的 IPC 机制来实现进程间通信。对于开发者来说,了解虚拟内存系统和进程复制机制是编写稳健多进程应用程序的关键。

demo案例

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) 
{
  int fd[2];
  int ret;
    int status;
  //创建了两个字符数组 buff1 和 buff2,用于存储从父进程到子进程和从子进程到父进程的信息。
  char buff1[1024];
  char buff2[1024];
  pid_t pd;
  //调用 pipe(fd) 创建了一个管道,其中 fd 是一个包含两个文件描述符的数组,fd[0] 用于读取数据,fd[1] 用于写入数据。
  ret = pipe(fd);
  if (ret !=0) {
    printf("create pipe failed!\n");
    exit(1);
  }
  pd = fork();//调用 fork() 创建了一个子进程。如果 fork() 失败,程序将打印错误消息并退出。
  if (pd == -1) {
    printf("fork error!\n");
    exit(1);
  } else if (pd == 0) {//在子进程中(当 pd == 0 时),首先清空了 buff2,然后从管道的读取端 fd[0] 读取数据到 buff2 中。接着,子进程将一条信息写入管道的写入端 fd[1] 中,然后打印发送的信息和缓冲区地址。
    bzero(buff2, sizeof(buff2));
    read(fd[0], buff2, sizeof(buff2));
    printf("process(%d) received information[child-buff2]:%s  [%p]\n", getpid(), buff2,buff2);
    strcpy(buff1,"Hello!parent!");
        write(fd[1],buff1,strlen(buff1));
    printf("process(%d) send information[child-buff1]:%s  [%p]\n", getpid(), buff1,buff1);
  } else {//在父进程中(当 pd > 0 时),首先将一条信息写入管道的写入端 fd[1] 中,然后睡眠了5秒钟。接着,从管道的读取端 fd[0] 读取数据到 buff2 中,然后打印接收的信息和缓冲区地址。
    strcpy(buff1, "Hello!child");
    write(fd[1], buff1, strlen(buff1)); 
    printf("process(%d) send information[parent-buff1]:%s [%p]\n", getpid(), buff1,buff1);
    sleep(5);
        read(fd[0],buff2,sizeof(buff2));
    printf("process(%d) received information[parent-buff2]:%s [%p]\n", getpid(), buff2,buff2);
  }
  if (pd > 0) {
    wait(&status);//父进程等待子进程的结束(通过 wait(&status)),以确保子进程在父进程之前结束。
  }
  return 0; 
}

输出结果:结果表明,子进程和父进程中相同变量名的内存地址相同,但所存储的内容却并不一样。

目录
相关文章
|
8天前
|
Java
JVM之本地内存以及元空间,直接内存的详细解析
JVM之本地内存以及元空间,直接内存的详细解析
25 0
|
19天前
|
存储 Java C++
C++ 引用和指针:内存地址、创建方法及应用解析
C++中的引用是现有变量的别名,创建时需用`&`运算符,如`string &meal = food;`。指针存储变量的内存地址,使用`*`创建,如`string* ptr = &food;`。引用必须初始化且不可为空,而指针可初始化为空。引用在函数参数传递和提高效率时有用,指针适用于动态内存分配和复杂数据结构操作。选择使用取决于具体需求。
38 9
|
13天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
30 0
|
14天前
|
域名解析 网络协议 Linux
TCP/IP协议及配置、IP地址、子网掩码、网关地址、DNS与DHCP介绍
TCP/IP协议及配置、IP地址、子网掩码、网关地址、DNS与DHCP介绍
|
19天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
|
23天前
|
存储 算法 安全
深度解析JVM世界:JVM内存分配
深度解析JVM世界:JVM内存分配
|
23天前
|
域名解析 缓存 网络协议
找不到DNS地址的解决方案
找不到DNS地址的解决方案
34 1
|
23天前
|
Linux 编译器 Windows
【Linux】10. 进程地址空间
【Linux】10. 进程地址空间
19 4
|
27天前
|
存储 缓存 监控
深入解析linux内存指标:快速定位系统内存问题的有效技巧与实用方法(free、top、ps、vmstat、cachestat、cachetop、sar、swap、动态内存、cgroops、oom)
深入解析linux内存指标:快速定位系统内存问题的有效技巧与实用方法(free、top、ps、vmstat、cachestat、cachetop、sar、swap、动态内存、cgroops、oom)
152 0
|
2月前
|
域名解析 网络协议 搜索推荐
阿里云DNS常见问题之获取不到用户真实IP地址如何解决
阿里云DNS(Domain Name System)服务是一个高可用和可扩展的云端DNS服务,用于将域名转换为IP地址,从而让用户能够通过域名访问云端资源。以下是一些关于阿里云DNS服务的常见问题合集:

推荐镜像

更多