【Linux进程】四、printf函数的缓冲区刷新机制与父子进程间的“读共享写拷贝”问题

简介: 【Linux进程】四、printf函数的缓冲区刷新机制与父子进程间的“读共享写拷贝”问题

1. printf函数缓冲区刷新与C语言的 ‘\n’ 字符

我们先看一个简单的程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char* argv[])
{
  printf("begin...");
  fork();
  printf("end...\n");
  return 0;
}

运行后发现打印了两次begin,而根据前面的学习,实际上应该打印一次才对

实际上这是printf()函数缓冲区的机制造成的,缓冲区我们在Linux系统调用专题中已经讲过了。在系统调用时,遇到 ‘/n’ 输出行缓冲,我们这里第一个printf()函数中没有 ‘\n’ 字符,所以第一个printf()函数执行的时候没有打印缓冲区的内容,当我们fork一个子进程的时候,我们既没有输出这个缓冲区的内容,也没有刷新缓冲区,所以这段内容恢复至到子进程中。等到父子进程都执行到第二个printf()函数的时候,遇到 ‘\n’ 打印缓冲区内容,就把上一次和这一次的内容一块打印出来了。这也是为什么fork在第一个printf()语句之后,子进程却能打印出一个printf()语句中内容的原因,因为缓冲区没有刷新,所以被赋值给了子进程。这也告诉我们Linux和Windows是有区别的,在Linux下用pintf()函数一定要加 ‘\n’ 。

所以我们只要在第一个printf()语句中加上 ‘\n’ 字符就可以了。

2. 父子进程空间共享问题

执行fork()函数后,子进程与父进程有相同的全局变量、.data段、.text段、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式等;不同之处在于,进程自己的ID、父进程ID、fork()函数返回值、进程运行时间(父进程在fork之前就已经运行了,而子进程在fork之后才开始运行)、定时器、未决信号集等不同。但是,子进程并不是直接把父进程0到3G的用户空间全部复制,而是遵循一种读时共享、写时复制这样的原则,这样无论是子进程执行父进程的逻辑,还是执行自己的逻辑都能节省内存开销。也就是说,父子进程的虚拟地址空间中,比如说数据段,它们都是指向同一块物理地址空间的,如果子进程只是读取该空间,那么就没必要复制这块物理内存,即读时共享,如果子进程要修改这块物理空间,那么将会复制一块物理空间然后修改复制的空间,即写时复制。

这里要注意,即便是全局数据,也遵循读时共享写时复制的原则,也就是说全局变量在父子进程之间也不是共享的。下面我们通过一个例子演示这种读时共享写时复制的原则。

/************************************************************
  >File Name  : shared_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月19日 星期四 16时25分27秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int g_data = 10;
int main(int argc, char* argv[])
{
  pid_t pid = fork();
  if(pid == 0)
  {
    printf("child: g_data = %d\n", g_data);
    g_data = 11;
    printf("child: g_data = %d\n", g_data);
    sleep(2);
    g_data = 13;
    printf("child: g_data = %d\n", g_data);
  }
  if(pid > 0)
  {
    sleep(1); /*1.保证printf时子进程已经修改全局变量 2.防止父进程提前结束*/
    printf("call: g_data = %d\n", g_data);  
    g_data = 12;
    printf("call: g_data = %d\n", g_data);
    sleep(2);
  }
  return 0;
}

编译运行,我们可以在打印结果中看到,当子进程修改全局变量的时候,父进程和子进程的全局变量值就可以使不再一样了,这就是写时复制,这时候,父子进程都有自己的g_data,修改的时候也是修改的自己的g_data的值。

相关文章
|
19天前
|
存储 缓存 固态存储
|
2月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
64 6
|
2月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
108 3
|
2月前
|
存储 缓存 Linux
Linux下的Cache和TLB刷新 【ChatGPT】
Linux下的Cache和TLB刷新 【ChatGPT】
63 11
|
2月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
2月前
|
编译器
【收藏】内核级利用通用Hook函数方法检测进程
【收藏】内核级利用通用Hook函数方法检测进程
|
2月前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
3月前
|
Linux PHP
Linux CentOS 宝塔 Suhosin禁用php5.6版本eval函数详细图文教程
【8月更文挑战第27天】本文介绍两种禁用PHP执行的方法:使用`PHP_diseval_extension`禁用和通过`suhosin`禁用。由于`suhosin`不支持PHP8,仅适用于PHP7及以下版本,若服务器安装了PHP5.6,则需对应安装`suhosin-0.9.38`版本。文章提供了详细的安装步骤,并强调了宝塔环境下与普通环境下的PHP路径差异。安装完成后,在`php.ini`中添加`suhosin.so`扩展并设置`executor.disable_eval = on`以禁用执行功能。最后通过测试代码验证是否成功禁用,并重启`php-fpm`服务生效。
35 2
|
3月前
|
Shell Linux C语言
Linux0.11 execve函数(六)
Linux0.11 execve函数(六)
43 1
|
4月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
137 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)