系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的C库函数遇上fork
1. test1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> /******全局变量位于数据区, 用于数据区测试*******/
int globvar = 6;
char buf[] = "a write to stdout!\n"; char gstring[] = "hello string"; int main(void)
{
/******局部变量位于栈, 用于栈测试*******/
int var; pid_t pid; var = 88;
if ( write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1 ){
printf("write error!! \n");
return -1;
}
printf("before fork!!, pid = %d\n", getpid() );
fflush(stdout); // 注意该行代码产生的效果 FILE* fp = fopen("s.txt", "wb+"); /******当带缓存的C库函数遇上fork: C库函数的缓存建立在堆上, 这就相当于用于堆测试*******/
fprintf(fp, "1st , string: %s, pid:%d ", gstring, getpid()); /* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */
if ((pid = fork()) < 0){
printf("fork error!! \n");
}
else if (pid == 0){ globvar++;
var++;
printf("son: pid = %d \n", getpid() );
}
else{
sleep(1);
printf("father: pid = %d \n", getpid() );
} /***********
父子进程内, globvar, var 打印出来的值是不一样的,
因为父进程中有这俩变量的一份物理内存,子进程中会分配这俩变量的另一份物理内存,
也就是说,经过本次测试得到:在物理内存上,父子进程有自己的数据区、栈。
而打印他们的地址值却是一样的,这说明子进程复制了父进程的虚拟地址空间。 事实上的结论是:子进程会复制父进程的虚拟地址空间。在物理内存上,父子进程共享代码段,但是有各自的数据段、栈、堆。 ***********/
printf("pid = %d, glob = %d, var = %d \n", getpid(), globvar, var);
printf("pid = %d, &glob = %ld, &var = %ld \n", getpid(), (long int)&globvar, (long int)&var); fprintf(fp, "2nd , string: %s, pid:%d ", gstring, getpid());
/*********
这里的代码,父子进程都会执行到。 对于父进程而言,之前fprintf一次,现在又一次,合计是两次,相信大家对这点都不会搞错。 对子进程而言,由于父进程fork子进程之前,已经向缓存区内写入了字符串,所以子进程复制了父进程的这份缓存区,
只是我们不方便修改这子进程内复制来的堆空间内的数据。
在子进程即将退出之际,这里再次使用fprintf,实际上相当于这是子进程第二次向该缓冲区内写东西了。 在程序退出后,缓存内的数据会被写入文件,那么合计,s.txt文件内最终记录的应该有4条打印语句。
***********/ exit(0);
}
运行:
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存
函数+fork# gcc fork3.c
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存
函数+fork# ./a.out
a write to stdout!
before fork!!, pid = 5102
son: pid = 5103
pid = 5103, glob = 7, var = 89
pid = 5103, &glob = 6295696, &var = 140732929243848
father: pid = 5102
pid = 5102, glob = 6, var = 88
pid = 5102, &glob = 6295696, &var = 140732929243848
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存
函数+fork#
2. test2
鉴于test1实验代码不便于修改子进程内由父进程复制得到的堆空间。
我们再来写个实验代码test2吧。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> pid_t pid; int main(void) {
int* p = (int*)malloc(sizeof(int)*100); if( (pid = fork()) < 0){
perror("fork "); }else if(pid > 0){ p[0] = 99; }else{
sleep(1); p[0] = 100; }
printf("pid=%d, p=0x%lx, *p=%d \n", getpid(), \
(unsigned long)&p[0], p[0]);
//printf("\n___ *p=%d \n", 0[p]); 等价于p[0] return 0;
}
运行:
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存函数+fork# ./a.out
pid=10424, p=0xc1a010, *p=99
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存函数+fork# pid=10425, p=0xc1a010, *p=100
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/IO缓存函数+fork#
经过test2的实验,可以看到,
fork前申请了堆空间,fork后,子进程内和父进程内对该获取到的堆指针进行打印,都是同一个值,表明子进程复制了父进程的虚拟内存的堆区,
然而父子进程打印p[0]却又有不同的值,表明父子进程内的堆实际上拥有各自独立的物理内存。
PS: 编写代码期间不小心犯错了,没加括号,C语言运算符优先级 小于符号的优先级 要高于 赋值符号!
下面是错误代码展示:

对于严谨思维,可以预测到test2的执行是:父进程先打印退出,之后延时1秒后,子进程才打印退出。
如果ubuntu内的现象不符合这个,例如两条语句同时打印出来,那么可以推测发生了预期异常!
结论:
子进程会复制父进程的虚拟地址空间。在物理内存上,父子进程共享代码段,但是有各自的数据区、栈、堆。

我的关联博文:
系统编程-进程-fork深度理解、vfork简介
.
系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的C库函数遇上fork的更多相关文章
- [并发编程 - socketserver模块实现并发、[进程查看父子进程pid、僵尸进程、孤儿进程、守护进程、互斥锁、队列、生产者消费者模型]
[并发编程 - socketserver模块实现并发.[进程查看父子进程pid.僵尸进程.孤儿进程.守护进程.互斥锁.队列.生产者消费者模型] socketserver模块实现并发 基于tcp的套接字 ...
- LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间
1什么是进程:进程是一个执行中的程序 执行的程序: 代码->资源->CPU 进程有很多数据维护:进程状态/进程属性 所有进程属性采用的一个树形结构体维护 ps -a//所有进程 ps - ...
- Linux系统编程(8)—— 进程之进程控制函数fork
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先 ...
- Linux系统编程(9)—— 进程之进程控制函数exec系列函数
在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int exe ...
- Linux系统编程(7)—— 进程之进程概述
我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体.现在我们全面了解一下其中都有哪些信息. 进程id.系统中每个进程有 ...
- PHP系统编程--02.PHP守护进程化
什么是守护进程? 一个守护进程通常补认为是一个不对终端进行控制的后台任务.它有三个很显著的特征:在后台运行,与启动他的进程脱离,无须控制终端.常用的实现方式是fork() -> setsid() ...
- Unix系统编程()检查进程的存在
检查进程的存在 kill系统调用还有另一重功用.若将参数sig指定为0(即所谓空信号),则无信号发送. 相反,kill仅会去执行错误检查,查看是否可以向目标进程发送信号. 从另一角度来看,这意味着,可 ...
- 探究JVM——运行时数据区
最近在读<深入理解Java虚拟机>,收获颇丰,记录一下,部分内容摘自原书. Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以 ...
- JVM运行时数据区--堆
一个进程对应一个jvm实例,一个运行时数据区,又包含多个线程,这些线程共享了方法区和堆,每个线程包含了程序计数器.本地方法栈和虚拟机栈. 核心概述 1.一个jvm实例只存在一个堆内存,堆也是java内 ...
- JVM详解(四)——运行时数据区-堆
一.堆 1.介绍 Java运行程序对应一个进程,一个进程就对应一个JVM实例.一个JVM实例就有一个运行时数据区(Runtime),Runtime里面,就只有一个堆,一个方法区.这里也阐述了,方法区和 ...
随机推荐
- oeasy 教您玩转 linux 之 010302 火狐浏览器 firefox
我们来回顾一下 上一部分我们都讲了什么? oneko xeyes 这次看看这个火狐 火狐 看看当前版本 看看是否可以更新 如果需要更新就更新 firefox -v apt search f ...
- [rCore学习笔记 04]安装SSH
因为每一个老嵌入式都喜欢使用他的老windows进行开发,因此我决定使用SSH来开发rust,这样也不用在虚拟机里边再装一个vscode. 参考博客 如何在windows下使用vscode连接linu ...
- JavaScript小面试~数组相关的方法和运用(学习笔记)
1,稀疏数组 稀疏数组是指数组中的某个下标未给出值或某个下标的值被删除.例如: let arrayOne=['xiaozi',,12,,true,23] let arrayTwo=[1,2,3,3,4 ...
- LeetCode102.二叉树的层序遍历
LeetCode题目链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/submissions/548489149/ 题 ...
- 【DataBase】MySQL 26 存储过程
一.概述 存储过程&函数,类似编程语言的方法 什么是方法? 完成特定功能的一组语句 方法的特点 1.可重用性 2.简化操作 二.存储过程[ Stored Procedures]: 一组预先编译 ...
- 瑞芯微|如何让拥有双网口的Linux设备实现数据包转发?【超实用】
本文主要讲解如何,解决基于3568实现双网口互通问题. 一.组网 如下图所示: rk3568自带2个千兆以太口,对应网卡名称为:eth0.eth1 pc1和pc2分别连接这2个网口 pc1与eth0连 ...
- Mac M1 安装Homebrew
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
- Django集成腾讯COS对象存储
前言 最近遇到一个场景需要把大量的资源文件存储到 OSS 里,这里选的是腾讯的 COS 对象存储 (话说我接下来想搞的 SnapMix 项目也是需要大量存储的,我打算搭个 MinIO 把 24T 的服 ...
- Excel 导入的开发经验
2020 年开始接触 Java Excel 导入的开发工作. 一家建筑机器人的公司离职后,来到广州找的是一家工厂,开始接触导入的开发工作.之前也没有什么使用开发经验, 是 教学视频 中看到过有些老 ...
- macOS 扫描局域网设备
arp-scan: arp-scan 是一个直接用于扫描本地网络中的设备的 UNIX 工具.这不是 macOS 自带的,但可以使用 Homebrew 安装.首先安装 Homebrew(如果尚未安装), ...