系统编程-进程-探究父子进程的数据区、堆、栈空间/ 当带缓存的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里面,就只有一个堆,一个方法区.这里也阐述了,方法区和 ...
随机推荐
- Linux中&&、&、|、||等特殊符号
&& 和 & & 表示任务后台执行,与nohup命令功能差不多. # 运行jar包,并且置于后台执行,执行的日志重定向到当前默认的log.txt文件中 [root@lo ...
- .NET 控件转图片
Windows应用开发有很多场景需要动态获取控件显示的图像,即控件转图片,用于其它界面的显示.传输图片数据流.保存为本地图片等用途. 下面分别介绍下一些实现方式以及主要使用场景 RenderTarge ...
- Jmeter函数助手27-urlencode
urlencode函数用于将字符串进行application/x-www-form-urlencoded编码格式化. String to encode in URL encoded chars:填入字 ...
- 【Java】将枚举类转换为Redis字典缓存
字典翻译框架实现看这篇: https://www.cnblogs.com/mindzone/p/16890632.html 枚举的特性 首先是枚举的一些特性: 1.枚举实例直接在枚举类中声明 2.重载 ...
- 【Project】原生JavaWeb工程 01 概述,搭建
一.环境准备: 操作系统:Windows7 或者 Windows10 IDE集成环境:IDEA 2018版本或者更高 数据库:MySQL 5版本或者更高 服务器:Tomcat 8版本或者更高 二.数据 ...
- 【Docker】05 容器操作
[查看容器 Check Container] 查看所有容器: docker ps 查看所有正在运行的,或者运行过的容器: docker ps -a 查看单个指定的容器完整信息: docker insp ...
- 从.net开发做到云原生运维(六)——分布式应用运行时Dapr
1. 前言 上一篇文章我们讲了K8s的一些概念,K8s真的是带来了很多新玩法,就像我们今天这篇文章的主角Dapr一样,Dapr也能在K8s里以云原生的方式运行.当然它也可以和容器一起运行,或者是CLI ...
- python3.6—opencv-python报错:Exception: Not found: 'python/cv2/py.typed'
报错: self).run_setup(setup_script=setup_script) File "/tmp/pip-build-env-zsqslesq/overlay/lib/py ...
- 9组-Alpha冲刺-6/6
一.基本情况 队名:不行就摆了吧 组长博客: https://www.cnblogs.com/Microsoft-hc/p/15546711.html 小组人数: 8 二.冲刺概况汇报 张伟鹏 过去两 ...
- 03-canvas线条属性
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...