系统编程-进程-当fork遇到管道,可能碰撞出什么?
第一部分
1. 直接上代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> int globvar = 6;
char buf[] = "a write to stdout!\n"; void son_process_end_func(void)
{
printf("son process end!\n");
}
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() );
/* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */
if ((pid = fork()) < 0){
printf("fork error!! \n");
}
else if (pid == 0){
atexit(son_process_end_func);
globvar++;
var++;
printf("son: pid = %d \n", getpid() );
}
else{
sleep(2);
printf("father: pid = %d \n", getpid() );
} printf("pid = %d, glob = %d, var = %d \n", getpid(), globvar, var);
exit(0);
}
2. 编译运行记录
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# gcc fork.c -o ab
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab > out.txt 注:这里使用了管道进行重定向
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# cat out.txt
a write to stdout!
before fork!!, pid = 10425
son: pid = 10426
pid = 10426, glob = 7, var = 89
son process end! // 调用了进程终止函数,表明子进程正在终止
before fork!!, pid = 10425 // 难点解释:看第22行的printf使用,由于标准I/O库带缓冲,
father: pid = 10425 // 重定向则会将缓存中的内容也拷贝到子进程中一份,当子进程结束,该缓存就会被再次输出。
pid = 10425, glob = 6, var = 88
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab
a write to stdout!
before fork!!, pid = 10430
son: pid = 10431
pid = 10431, glob = 7, var = 89
son process end! // 调用了进程终止函数,表明子进程正在终止
father: pid = 10430
pid = 10430, glob = 6, var = 88
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
小结:
fork遇到管道并不是问题的重点,它俩没碰撞出啥东西。
不要被本博文的标题给误导咯(因为我也是在实验中一步步加深自己的理解),哈哈,结论:
若fork前标准I/O库函数也在场,那么由于使用IO库函数导致的缓存中的内容会被拷贝给子进程一份。
准确地说,fork前缓存中的内容在父进程的堆空间中,fork后子进程会复制父进程的堆空间。
如果体会不深,自己跑一遍上面的实验代码,感受感受就是了。
知识点补充:
atexit注册多个进程终止处理函数,先注册的后执行(先进后出,和栈一样)
atexit()用于注册进程结束后所执行的函数
return、exit和_exit的区别:
return和exit效果一样,都是会执行进程终止处理函数,
但是用_exit终止进程时并不执行atexit注册的进程终止处理函数。
贴个实验代码的图(看图更方便)

第二部分
在第一部分的代码基础上只增加一句代码:fflush(stdout),
完整的代码如下
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> int globvar = 6;
char buf[] = "a write to stdout!\n"; void son_process_end_func(void)
{
printf("son process end!\n");
}
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); // 注意该行代码产生的效果
/* 父进程中,fork返回新创建子进程的进程ID. 子进程中,fork返回0. 出现错误,fork返回负值. */
if ((pid = fork()) < 0){
printf("fork error!! \n");
}
else if (pid == 0){
atexit(son_process_end_func);
globvar++;
var++;
printf("son: pid = %d \n", getpid() );
}
else{
sleep(2);
printf("father: pid = %d \n", getpid() );
} printf("pid = %d, glob = %d, var = %d \n", getpid(), globvar, var);
exit(0);
}
我们再次编译运行:
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# gcc fork.c -o ab
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# ./ab > out.txt
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1# cat out.txt
a write to stdout!
before fork!!, pid = 14030
son: pid = 14031
pid = 14031, glob = 7, var = 89
son process end!
father: pid = 14030
pid = 14030, glob = 6, var = 88
root@lmw-virtual-machine:/home/lmw/MINE/Linux_C_exercise/fork/test1#
现在我们通过管道得到的打印结果,和本博客第一部分中直接./ab 运行的结果,是一样的。
分析:
增加了一行代码,使用fflush(stdout)刷新了缓冲区,所以fork前缓冲区内是空的,我们使用管道重定位的时候,子进程就不会从缓冲区复制数据了。
此时的out.txt内的内容将和直接运行 ./ab 一样。
小结: fflush是从内存缓冲区将数据写到内核缓冲,针对用户空间。
fsync再将内核缓冲写到磁盘,针对内核空间。
由此可见,当fork遇到管道的时候,子进程内会复制fork前的用户空间的缓冲区的数据,例如使用了C标准库的IO函数scanf、printf时,因为标准输入和标准输出通常是带缓冲的。
PS: 而标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。
当fork遇到管道的时候,在fork前,使用标准输入和标准错误的情形,本博客未做实验,读者可以自行尝试一下。
根据本实验的运行结果来看,推测:虽然printf是行缓冲,但是执行代码 printf("before fork!!, pid = %d\n", getpid() ) 时,并没有立即刷新用户空间的缓冲区到内核,而当我们使用fflush时,才刷新到了内核。
.
系统编程-进程-当fork遇到管道,可能碰撞出什么?的更多相关文章
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
- linux系统编程-进程
进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com ...
- linux服务器开发二(系统编程)--进程相关
进程相关的概念 程序与进程 程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(CPU.内存.打开的文件.设备.锁等等). 进程,是一个抽象的概念,与操作系统原理联系紧密.进程是活跃的程序,占用系 ...
- Linux系统编程@进程管理(一)
课程目标: 构建一个基于主机系统的多客户即时通信/聊天室项目 涉及的理论知识 进程控制:僵尸进程/孤儿进程.进程控制.守护进程... 进程间通信:管道.命名管道.信号... 多线程编程: 锁.信号量. ...
- [linux] C语言Linux系统编程进程基本概念
1.如果说文件是unix系统最重要的抽象概念,那么进程仅次于文件.进程是执行中的目标代码:活动的.生存的.运行的程序. 除了目标代码进程还包含数据.资源.状态以及虚拟化的计算机. 2.进程体系: 每一 ...
- Linux系统编程-----进程fork()
在开始之前,我们先来了解一些基本的概念: 1. 程序, 没有在运行的可执行文件 进程, 运行中的程序 2. 进程调度的方法: 按时间片轮转 先来先服务 短时间优先 按优先级别 3. 进程的状态: 就绪 ...
- Linux系统编程@进程管理(二)
1.创建守护进程(Deamon) 守护进程的概念与作用 后台服务程序 – 系统服务,进程名字往往以’d’结尾,生存周期比较长(系统装入时启动,关闭时候终止.系统装入两种启动方式:1从启动脚本.etc/ ...
- python之系统编程 --进程
1.调试(PDB) 代码: [root@master gaoji]# vim test2.py 1 #!/usr/local/bin/python3 2 # -*- coding:utf-8 -*- ...
- Linux系统编程——进程替换:exec 函数族
在 Windows 平台下,我们能够通过双击运行可运行程序,让这个可运行程序成为一个进程.而在 Linux 平台.我们能够通过 ./ 运行,让一个可运行程序成为一个进程. 可是.假设我们本来就执行着一 ...
- Linux系统编程—进程间同步
我们知道,线程间同步有多种方式,比如:信号量.互斥量.读写锁,等等.那进程间如何实现同步呢?本文介绍两种方式:互斥量和文件锁. 互斥量mutex 我们已经知道了互斥量可以用于在线程间同步,但实际上,互 ...
随机推荐
- 题解:P10417 [蓝桥杯 2023 国 A] 第 K 小的和
分析 这道题不是板子么. 先对序列排序,然后二分答案,设当前答案为 \(x\),枚举 \(a\) 中的数,然后二分查找 \(b\) 中不大于 \(x-a\) 的元素个数,累加判断是否不大于 \(k\) ...
- Python 实时获取任务请求对应的Nginx日志
需求描述 项目需求测试过程中,需要向Nginx服务器发送一些用例请求,然后查看对应的Nginx日志,判断是否存在特征内容,来判断任务是否执行成功.为了提升效率,需要将这一过程实现自动化. 实践环境 P ...
- Django 处理http请求之使用session
Django 处理http请求之使用session by:授客 QQ:1033553122 欢迎加入全国软件测试交流群:7156436 测试环境 Win7 Django 1.11 Django提供 ...
- jwt redis,微信登陆知识复习 uniapp 请求封装,统一异常处理 相关, HutoolDemo工具介绍)
第三节 后台布局搭建,代码可以人工智能来写,但是环境初步搭建需要我们先建起来,所以以下记录快带搭建的过程, 思路: 后台首页的搭建 第一 用到了element--UI 自带的页面布局组件,它就 ...
- Jmeter函数助手37-setProperty
setProperty函数用于修改jmeter属性值. 属性名称:填入需要修改的属性名 Value of property:填入需要修改的属性值 Return Original Value of pr ...
- 【ELK】Kibana-7.13.1版本 启动报错 Centos6
报错信息: [root@centos6-1 gcc-4.8.2]# /opt/kibana-7.13.1-linux-x86_64/bin/kibana /opt/kibana-7.13.1-linu ...
- 【SpringBoot】09 日志集成
原来日志还分抽象层和实现层... 抽象层被称为是日志门面,实现层被称为是日志实现 门面的有: - JCL[Jakarta Commons Logging] 远古门面 - SLF4J[Simple ...
- batch normalization的multi-GPU版本该怎么实现? 【Tensorflow 分布式PS/Worker模式下异步更新的情况】
最近由于实验室有了个AI计算平台,于是研究了些分布式和单机多GPU的深度学习代码,于是遇到了下面的讨论: https://www.zhihu.com/question/59321480/answer/ ...
- 查看numpy中不同数据类型的表示范围
在numpy中数据类型主要可以分为int和float两个类型,查看int类型的表示范围可以使用numpy.iinfo,查看float类型的表示范围可以使用numpy.finfo . 例子: impo ...
- 使用 extract_sqlaudit_proc 存过分析ob性能问题
最近在某个金融单位核心系统项目做ob的性能压测,期间遇到不少问题,现场两周了每天都加班到凌晨一两点左右,真的是累死. 我其实进ob之前有心理预期,卷就卷吧,八九点下班也能接受,没想到真到了干项目的情况 ...