Linux:fork是什么、使用方法、缓冲区问题、frok使用实例

一、fork是什么及使用方法

       fork是什么:fork为创建进程的系统调用函数,用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc(程序计数器),相同的CPU寄存器,在父进程中使用的相同打开文件。
 
       fork作用: fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己
 
       fork函数原型:pid_t fork( void);(pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>中)
在这里插入图片描述
调用fork()函数需要包含的头文件:
    #include <sys/types.h>
    #include <unistd.h>
 

二、fork使用实例及过程解析

fork使用实例:
    如图所示:为一个使用fork来创建一个子进程的简单代码(本博客上所示代码均在红帽企业版Linux 6.3版本上操作)
在这里插入图片描述
那么在这段简单代码使用过程中发生了什么?
①在语句pid_t pid = fork();之前,只有一父进程在执行这段代码。printf输出:hahaha该字符串。
②但在执行到pid_t pid = fork();时,我们之前提到fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,相当于克隆了一个自己,两个进程同时在执行。一个为子进程,另一个为父进程。
③父进程fork返回新创建子进程的进程ID,一般为非0值,因此在if判断语句中,输出world。
④子进程fork返回0,在if判断语句中,输出hello。
其过程如图所示:
在这里插入图片描述
因此,最后结果输出如图:
在这里插入图片描述

三、缓冲区问题

学习这几个实例之前,我们还需要清楚一个东西,那就是缓冲区的问题。
缓冲区这个缓冲区既不是内核中的缓冲区,也不是用户分配的缓冲区,而是有编译器维护的用户进程空间中的缓冲区。分为全缓冲(大部分缓冲都是这类型)、行缓冲、无缓冲。
全缓冲:全缓冲指的是系统在填满标准IO缓冲区之后才进行实际的IO操作;注意,对于驻留在磁盘上的文件来说通常是由标准IO库实施全缓冲。
行缓冲:在这种情况下,标准IO在输入和输出中遇到换行符时执行IO操作;注意,当流涉及终端的时候,通常使用的是行缓冲。
无缓冲:无缓冲指的是标准IO库不对字符进行缓冲存储;注意,标准出错流stderr通常是无缓冲的
        printf默认是行缓冲,没有遇到换行符所以内容没有被冲出来。所以我们会是先延迟才打印。flush(stdout) 和\n 效果相同。

为什么要了解它,它有什么用?
       因为在Linux中存在着缓冲区的问题。当我们使用printf函数时,如果没有换行的话,它是不会输出的,而是先将要输出的内容存放再缓冲区中,当碰到换行时,将缓冲区中的内容再一起输出所以fork后子进程会复制父进程的缓冲区,因此也将待输出内容复制到自己“与父进程独立的缓冲区中”,因此当子进程执行遇到换行,就将缓冲区中的内容全都输出来。
下面是一个使用过程中的对比:
1.使用printf时没有加\n,如图:
在这里插入图片描述
我们来看看结果:
在这里插入图片描述
打印结果为两行:helloworld。因为printf不加\n是有输出缓冲区的,不使用\n时,数据不刷新。fork()创建子进程时,复制了父进程的数据段和堆栈段,包括上面的缓冲区,再执行后面的printf ,直到子进程结束输出打印,就打印了两遍hello world。

2.使用printf时添加\n,这次我们在hello的后面加上了world,如图:
在这里插入图片描述
我们来看看结果:
在这里插入图片描述
根据我们前面了解到:使用换行符\n后,会刷新我们的缓冲区,在执行到第一个printf时,会将hello输出,缓冲区数据被清洗。执行到fork时,fork从该处复制一个与父进程完成相同的子进程且前面的代码不会执行,父子进程都再执行最后一个printf,输出2个world。所以输出了1个hello,2个world。
 

四、fork使用实例

实例1:或的情况
int main()
{
	fork() || fork();
	
	printf("A\n");
	
	exit(0);
} 

这段代码的输出结果为什么呢?
①首先我们来看,这段代码里有一个逻辑判断语句:||
对于A||B这种情况:
      A为1时,就不需要再判断B,则表达式值为1;
      A为0时,需要再判断B,如果B为1,则表达式值为0;
②明白了逻辑判断,我们来看:前面我们提到fork再子进程中会返回0,父进程中返回非0的子进程pid,因此程序在执行正确的情况下,当执行第一个fork时,产生进程1:父进程,它返回非0的子进程的pid,非0为真,不再继续判断;此时,由第一个fork产生了另外进程2:子进程,该进程返回0,需要再次执行第二个fork,于是产生进程3,所以输出3了次A。
打印结果为:AAA
测试结果如图所示:
在这里插入图片描述
为了更清楚看到执行过程,我们也可以可以采用一颗树来表示:
在这里插入图片描述
 

实例2:与的情况
int main()
{
	if(fork() && fork())
	{
	   printf("A\n");
	}
    else
    {
       printf("B\n");
    }
	exit(0);
}

同理:这段代码里有一个逻辑判断语句:&&;对于A&&B这种情况:
      A为1时,继续判断B,如果都为1,则表达式为1,否则为0;
      A为0时,不在判断B,表达式为0;
其过程如图所示:
①父进程执行到fork时,产生一个子进程1,返回子进程1的pid(非0)为真;
②父进程继续执行第二个fork产生子进程2,返回子进程2的pid(非0)为真;因此,父进程中2个fork都为真,输出一个A。
③子进程1第一次调用fork后开始,它的fork()返回值为0,因此为假,子进程1输出一个B。
④子进程2第一次调用fork返回值也为0,因此为假,子进程输出一个B。
打印结果为:ABB
在这里插入图片描述
测试结果如图所示:
在这里插入图片描述

实例3:循环嵌套中加了\n(没有if情况)
int main()
{
	int i = 0;
	for (; i < 2; i++)
	{
		fork();
		printf("A\n");
	}

	exit(0);
}

如图所示:
父进程会输出2个中划线(总计:2个)
①进程1会输出2个自己的A,无主进程缓冲区拷贝输出(总计:2个)
②进程2会输出1个自己的A,无主进程缓冲区拷贝输出(总计:1个)
③进程3会输出1个自己的A,无first进程缓冲区拷贝输出(总计:1个)
打印结果为:6个A。
在这里插入图片描述
测试结果如图所示:
在这里插入图片描述

实例4:循环嵌套中没有加\n(没有if情况)
int main()
{
	int i = 0;
	for (; i < 2; i++)
	{
		fork();
		printf("A");
	}

	exit(0);
}

①父进程会输出2个中划线(总计:2个)
②进程1会输出2个自己的A,无主进程缓冲区拷贝输出(总计:2个)
③进程2会输出1个自己的A,考虑到注意要点2,会输出从父进程缓冲区拷贝过来的1个A(总计:2个)
④进程3会输出1个自己的A,考虑到注意要点2,会输出从进程1缓冲区拷贝过来的1个A (总计:2个)
打印结果为:8个A。
在这里插入图片描述
测试结果如图所示:
在这里插入图片描述

实例5:循环嵌套中加了\n(有if情况) 3A3B
int main()
{
	int i = 0;
	for (; i < 2; i++)
	{
		if (fork())
		{
			printf("A\n");
		}
		else
		{
			printf("B\n");
		}
	}

	exit(0);
}

①i=0时,第一次调用fork生成子进程1,打印"B";此时生成的子进程和第一个父进程各自运行;i=1时,第二次调用fork,生成子进程2,打印"B",i=2程序退出。
②子进程1状态和父进程创建时一样为i=0,它的返回值是0为假,打印"A";子进程1继续运行,创建子进程3。相对于子进程3时子进程1为父进程,返回值为非0,为真打印"B",i=2时循环结束。
③子进程3为子进程1创建的子程序,与子进程1状态一致为i=1时,fork返回值为0,打印"A",i=2时循环结束。
④子进程2为父进程第二次调用fork时,生成的第二个子进程,状态和父进程一致为i=1时。返回值为0,打印"A"。i=2时循环结束。
打印结果为:3A3B(由于不同系统环境和进程调度算法顺序可能不一样)
其过程如图所示:
在这里插入图片描述
测试结果如图所示:
在这里插入图片描述

实例6:循环嵌套中没有加\n(有if情况) 4A4B
int main()
{
	int i = 0;
	for (; i < 2; i++)
	{
		if (fork())
		{
			printf("A");
		}
		else
		{
			printf("B");
		}
	}

	exit(0);
}

结果存储在缓冲区,只有当缓冲区遇到\n才会将结果显示在界面上,否则就等程序运行结束或缓冲区满再显示到界面上。与实例4同理,会存在缓冲区,因此会有拷贝情况。
最后打印出来4A4B(顺序可能不一定)。
在这里插入图片描述

实例7:缓冲区问题
int main()
{
	printf("A");
	write(1, "B", 1);
	fork();
	exit(0);
}

①执行到printf时,printf()为库函数,没有\n时printf执行以后的A被放入缓冲区中,不能直接打到屏幕上。
②write为系统调用,代码中write往1上面写,而1是一个文件描述符:标准输出屏幕,因此B直接被打到屏幕上。
③再执行到fork,由于前面printf中无\n被放入缓冲区中,因此执行fork时,父进程的A会在子进程中再被打印一次。程序结束时,打印两个A,一个为父进程被放入缓冲区的A,一个为子进程从缓冲区fork来的A。所以,打印出来BAA。
测试结果如图所示:
在这里插入图片描述
参考链接:
关于fork()||fork();
操作系统—fork函数解析与例题详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值