系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的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]python0027_整合程序_延迟输出时间_整合两个py程序
整合程序 回忆上次内容 通过搜索发现 time中有函数可以延迟 time.sleep(1) 还可以让程序无限循环 while True: 现在需要两个程序的整合 循环延迟输出 时间输出 编辑 ...
- Spectre.Console.Cli注入服务的几种姿势
Spectre.Console大家可能都不陌生,写控制台程序美化还是不错的,支持着色,表格,图标等相当nice,如果对这个库不熟悉我强烈推荐你了解一下,对于写一些CLI小工具还是相当方便的, 本文主要 ...
- 涨见识了!脱离vue项目竟然也可以使用响应式API
前言 vue3的响应式API大家应该都特别熟悉,比如ref.watch.watchEffect等.平时大家都是在vue-cli或者vite创建的vue项目里面使用的这些响应式API,今天欧阳给大家带来 ...
- 03 OLED显示屏实现
目录 前言 一.软件模拟IIC协议 1.开启IIC协议 2.结束IIC协议 3.传输数据 二.OLED的操作 1.传输数据的准备 2.写入命令 3.写入数据 4.初始化函数 5.设置光标 6.显示字符 ...
- 【Vue】未读消息标记功能
页面展示的效果如图,需要定时更新未读消息 首先是后台的接口,查询未处理的消息数量 因为是七张消息表,数据我需要合在一起返回给前台: 这里使用UNION连接各个表 SELECT COUNT(*) AS ...
- C# Cefsharp 如何利用[Attribute]的把C#中的方法给到浏览器中调用
背景 "有没有遇见这样一个场景,需要注入到浏览器的类太多,又想统一管理且不遗漏,有没有什么好办法?""有有有,把头伸过来~" 解决办法 第一步:提供一个[Att ...
- Docker部署rabbitmq遇到的问题
1.背景 Docker部署rabbitmq遇到的如下两个问题 问题一:访问交换机时报错 Management API returned status code 500 问题二:访问channel时报错 ...
- SNMP基础概念
一.什么是SNMP? SNMP=Simple Network Management Protocol (简单网络管理协议) SNMP是被广泛接受并投入使用的工业标准,提供了一个框架来定义管理信 ...
- 甲方扔给两个存在包名与类名均相同的Jar包,要在工程中同时使用怎么办?
你的项目是否曾遇到过有jar包冲突,而这些冲突的jar包又必须同时存在的情况?一般来说,jar 冲突都是因不同的上层依赖项,自身又依赖了相同 jar 包的不同版本所致,解决办法也都是去除其中一个即可. ...
- “从零到一:如何在鸿蒙OS上启动你的第一个项目”
背景与引言 全球操作系统市场现状如何? 长期以来,Android.iOS.Windows等巨头几乎垄断了整个市场,成为人们日常生活中不可或缺的工具.然而,尽管它们在各自领域有着不可否认的成功,却也逐渐 ...