一个fork()系统调用的问题

转载:http://coolshell.cn/articles/7965.html

题目:请问下面的程序一共输出多少个“-”?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main(void)

{

   int i;

   for(i=0; i<2; i++){

      fork();

      printf("-");

   }

   wait(NULL);

   wait(NULL);

   return 0;

}

如果你对fork()的机制比较熟悉的话,这个题并不难,输出应该是6个“-”,但是,实际上这个程序会很tricky地输出8个“-”。

要讲清这个题,我们首先需要知道fork()系统调用的特性,

  • fork()系统调用是Unix下以自身进程创建子进程的系统调用,一次调用,两次返回,如果返回是0,则是子进程,如果返回值>0,则是父进程(返回值是子进程的pid),这是众为周知的。
  • 还有一个很重要的东西是,在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,变量值,程序调用栈,环境变量,缓冲区,等等。

所以,上面的那个程序为什么会输入8个“-”,这是因为printf(“-“);语句有buffer,所以,对于上述程序,printf(“-“);把“-”放到了缓存中,并没有真正的输出(参看《C语言的迷题》中的第一题),在fork的时候,缓存被复制到了子进程空间,所以,就多了两个,就成了8个,而不是6个。

另外,多说一下,我们知道,Unix下的设备有“块设备”和“字符设备”的概念,所谓块设备,就是以一块一块的数据存取的设备,字符设备是一次存取一个字符的设备。磁盘、内存都是块设备,字符设备如键盘和串口。块设备一般都有缓存,而字符设备一般都没有缓存

对于上面的问题,我们如果修改一下上面的printf的那条语句为:


1

printf("-\n");

或是


1

2

printf("-");

fflush(stdout);

就没有问题了(就是6个“-”了),因为程序遇到“\n”,或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。需要注意的是,标准输出是行缓冲,所以遇到“\n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“\n”并不会引起缓冲区刷出的动作,那是全缓冲,你可以使用setvbuf来设置缓冲区大小,或是用fflush刷缓存。

我估计有些朋友可能对于fork()还不是很了解,那么我们把上面的程序改成下面这样:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

int main(void)

{

   int i;

   for(i=0; i<2; i++){

      fork();

      //注意:下面的printf有“\n”

      printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i);

   }

   sleep(10); //让进程停留十秒,这样我们可以用pstree查看一下进程树

   return 0;

}

于是,上面这段程序会输出下面的结果,(注:编译出的可执行的程序名为fork)


1

2

3

4

5

6

7

8

9

10

ppid=8858, pid=8518, i=0

ppid=8858, pid=8518, i=1

ppid=8518, pid=8519, i=0

ppid=8518, pid=8519, i=1

ppid=8518, pid=8520, i=1

ppid=8519, pid=8521, i=1

$ pstree -p | grep fork

|-bash(8858)-+-fork(8518)-+-fork(8519)---fork(8521)

|            |            `-fork(8520)

面对这样的图你可能还是看不懂,没事,我好事做到底,画个图给你看看:

注意:上图中的我用了几个色彩,相同颜色的是同一个进程。于是,我们的pstree的图示就可以成为下面这个样子:(下图中的颜色与上图对应)

这样,对于printf(“-“);这个语句,我们就可以很清楚的知道,哪个子进程复制了父进程标准输出缓中区里的的内容,而导致了多次输出了。(如下图所示,就是我阴影并双边框了那两个子进程)

现在你明白了吧。

时间: 2024-10-10 10:29:51

一个fork()系统调用的问题的相关文章

fork 系统调用的执行过程与调试

我们可以通过fork系统调用来处理进程创建的任务.对于进程的创建, 可以sys_clone, sys_vfork,以及sys_fork. 这些系统调用的内部都使用了do_fork.函数. 对于do_fork函数, 会copy tast_struct, 设置内核堆栈, 并且对一些特定的数据结构进行修改.其中里面还有copy_thread 函数, 会设置这个进程的cs和ip.这个是在进程的thread_info中保持的.这里的ip设置成了ret_from_fork函数(在ret_from_frok里

linux中fork()系统调用总结

由fork创建的新进程被称为子进程(child process).该函数被调用一次,但返回两次.两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id.将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id.对子进程来说,之所以fork返回0给它,是因为它随时可以调用getpid()来获取自己的pid:也可以调用getppid()来获取父进程的id.(进程id 0总是由交换进程使用,所以一个子进程的进

《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程

一.进程控制块PCB-stack_struct 进程在操作系统中都有一个结构,用于表示这个进程.这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息: 状态信息,例如可执行状态.就绪状态.阻塞状态等. 性质,由于unix有很多变种,进行有自己独特的性质. 资源,资源的链接比如内存,还有资源的限制和权限等. 组织,例如按照家族关系建立起来的树(父进程.子进程等). task_struct结构体内容非常庞大,暂时没有去分析源代码,以后有时间再去研究

fork系统调用(转载)

(1) fork系统调用说明 fork系统调用用于从已存在进程中创建一个新进程,新进程称为子进程,而原进程称为父进程.fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返回值是子进程的进程号,而子进程中的返回值则返回 0.因此,可以通过返回值来判定该进程是父进程还是子进程. 使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程上下文.进程堆栈.内存信息.打开的文件描述符.信号控制设定.进程优先级.进程组号.当前工作目录.根

linux: fork系统调用实现剖析

首先了解一下程序和进程的概念: 程序:程序是完成特定任务的一系列指令集合. 进程:从用户角度来看,进程是程序的一次执行过程.从系统的角度看,进程是操作系统分配内存和cpu等资源的基本单位,进程是资源分配的最小单位.每一个进程都有自己独立的地址空间与执行状态,像unix这样的多任务操作系统能够让许多程序同时运行,每一个运行着的程序就构成了一个进程. 进程数据结构: 进程的静态描述:由pcb.有关程序段和该程序段所操作的数据结构集. 进程控制块:描述进程情况及控制运行所需要的全部信息 代码段:是进程

[转帖]一个FORK的面试题

一个FORK的面试题 https://coolshell.cn 搞不懂 fork 的含义. Linux 里面的线程不是教科书上面的标准的线程 好像用 父子进程来进行 模拟线程的处理 父子线程应该共享 数据段 只不过有自己独立的堆栈. 上面两句我瞎掰的. 前两天有人问了个关于Unix的fork()系统调用的面试题,这个题正好是我大约十年前找工作时某公司问我的一个题,我觉得比较有趣,写篇文章与大家分享一下.这个题是这样的: 题目:请问下面的程序一共输出多少个“-”? 1 2 3 4 5 6 7 8

Linux shell 的一个fork炸弹

.(){ .|.& };.  或者  :(){ :|:& };: 这13个字符由Jaromil ,在 2002 年设计了最为精简的一个 fork炸弹的实现. .()  说明下面要定义一个函数,函数名为小数点,没有可选参数:{    表示函数体的开始:.|.& 表示函数体真正要做的事情,首先它递归使用本函数,然后李永贵管道调用一个新进程(它要做的事情也是递归调用本函数),并将其放到后台执行.}    表示函数体的结束:;    并不会执行什么操作,在命令行中用来分隔两个命令用.从总体

以python代码解释fork系统调用

import os print('Process (%s) start...' % os.getpid()) # Only works on Unix/Linux/Mac: pid = os.fork() print ("haha") if pid == 0: print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid())) else: print('I (%s) just creat

fork系统调用

fork()学习, 理解 例1: (独立的上下文) 点击(此处)折叠或打开 #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> #define ERROR(flag)                 \ if(flag)