当前位置:首页 > 嵌入式 > 嵌入式教程
[导读]FORK()函数的理解

对于刚刚接触Unix/Linux操作系统,在Linux下编写多进程的人来说,fork是最难理解的概念之一:它执行一次却返回两个值。

首先我们来看下fork函数的原型:

#i nclude

#i nclude

pid_t fork(void);

返回值:

负数:如果出错,则fork()返回-1,此时没有创建新的进程。最初的进程仍然运行。

零:在子进程中,fork()返回0

正数:在负进程中,fork()返回正的子进程的PID

其次我们来看下如何利用fork创建子进程。

创建子进程的样板代码如下所示:

pid_t child;

if((child = fork())<0)

/*错误处理*/

else if(child == 0)

/*这是新进程*/

else

/*这是最初的父进程*/

fock函数调用一次却返回两次;向父进程返回子进程的ID,向子进程中返回0,

这是因为父进程可能存在很多过子进程,所以必须通过这个返回的子进程ID来跟踪子进程,

而子进程只有一个父进程,他的ID可以通过getppid取得。

下面我们来对比一下两个例子:

第一个:

#include

#include

int main()

{

pid_t pid;

int count=0;

pid = fork();

printf( "This is first time, pid = %dn", pid );

printf( "This is secONd time, pid = %dn", pid );

count++;

printf( "count = %dn", count );

if ( pid>0 )

{

printf( "This is the parent process,the child has the pid:%dn", pid );

}

else if ( !pid )

{

printf( "This is the child Process.n")

}

else

{

printf( "fork failed.n" );

}

printf( "This is third time, pid = %dn", pid );

printf( "This is fouth time, pid = %dn", pid );

return 0;

}

运行结果如下:

 

 

问题:

这个结果很奇怪了,为什么printf的语句执行两次,而那句“count++;”的语句却只执行了一次

接着看:

#include

#include

int main(void)

{

pid_t pid;

int count=0;

pid = fork();

printf( "Now, the pid returned by calling fork() is %dn", pid );

if ( pid>0 )

{

printf( "This is the parent procESS,the child has the pid:%dn", pid );

printf( "In the parent process,count = %dn", count );

}

else if ( !pid )

{

printf( "This is the child process.n");

printf( "Do your own things here.n" );

count ++;

printf( "In the child process, count = %dn", count );

}

else

{

printf( "fork failed.n" );

}

return 0;

}

运行结果如下:

现在来解释上面提出的问题。

看这个程序的时候,头脑中必须首先了解一个概念:在语句pid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的代码部分完全相同,将要执行的下一条语句都是if ( pid>0 )……。

两个进程中,原先就存在的那个被称作“父进程”,新出现的那个被称作“子进程”。父子进程的区别除了进程标志符(process ID)不同外,变量pid的值也不相同,pid存放的是fork的返回值。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

1. 在父进程中,fork返回新创建子进程的进程ID;

2.在子进程中,fork返回0;

3.如果出现错误,fork返回一个负值;

fork出错可能有两种原因:(1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。(2)系统内存不足,这时errno的值被设置为ENOMEM。

接下来我们来看看APUE2中对fork的说明:

The new process created by fork is called the child process. This function is called once but returns twice. The only difference in the returns is that the return value in the child is 0, whereas the return value in the parent is the process ID of the new child. The reason the child‘s process ID is returned to the parent is that a process can have more than one child, and there is no function that allows a process to o^ain the process IDs of its children. The reason fork returns 0 to the child is that a process can have only a single parent, and the child can always call getppid to o^ain the process ID of its parent. (Process ID 0 is reserved for use by the kernel, so it‘s not possible for 0 to be the process ID of a child.)

被fork创建的新进程叫做自进程。fork函数被调用一次,却两次返回。返回值唯一的区别是在子进程中返回0,而在父进程中返回子进程的pid。在父进程中要返回子进程的pid的原因是父进程可能有不止一个子进程,而一个进程又没有任何函数可以得到他的子进程的pid。

Both the child and the parent continue executing with the instruction that follows the call to fork. The child is a copy of the parent. For example, the child gets a copy of the parent‘s data space, heap, and stack. Note that this is a copy for the child; the parent and the child do not share these portions of memory. The parent and the child share the text segment (Section 7.6).[!--empirenews.page--]

子进程和父进程都执行在fork函数调用之后的代码,子进程是父进程的一个拷贝。例如,父进程的数据空间、堆栈空间都会给子进程一个拷贝,而不是共享这些内存。

Current implementations don‘t perform. a complete copy of the parent‘s data, stack, and heap, since a fork is often followed by an exec. Instead, a technique called copy-on-write (COW) is used. These regions are shared by the parent and the child and have their protection changed by the kernel to read-only. If either process tries to modify these regions, the kernel then makes a copy of that piece of memory only, typically a "page" in a virtual memory system. Section 9.2 of Bach [1986] and Sections 5.6 and 5.7 of McKusick et al. [1996] provide more detail on this feature.

我们来给出详细的注释

#include

#include

int main(void)

{

pid_t pid;

int count=0;

/*此处,执行fork调用,创建了一个新的进程, 这个进程共享父进程的数据和堆栈空间等,这之后的代码指令为子进程创建了一个拷贝。 fock 调用是一个复制进程,fock 不象线程需提供一个函数做为入口, fock调用后,新进程的入口就在 fock的下一条语句。*/

pid = fork();

/*此处的pid的值,可以说明fork调用后,目前执行的是父进程还是子进程*/

printf( "Now, the pid returned by calling fork() is %dn", pid );

if ( pid>0 )

{

/*当fork在子进程中返回后,fork调用又向父进程中返回子进程的pid, 如是该段代码被执行,但是注意的事,count仍然为0, 因为父进程中的count始终没有被重新赋值, 这里就可以看出子进程的数据和堆栈空间和父进程是独立的,而不是共享数据*/

printf( "This is the parent process,the child has the pid:%dn", pid );

printf( "In the parent process,count = %dn", count );

}

else if ( !pid )

{ /*在子进程中对count进行自加1的操作,但是并没有影响到父进程中的count值,父进程中的count值仍然为0*/

printf( "This is the child process.n");

printf( "Do your own things here.n" );

count++;

printf( "In the child process, count = %dn", count );

}

else

{

printf( "fork failed.n" );

}

return 0;

}

也就是说,在Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

仔细分析后,我们就可以知道:

一个程序一旦调用fork函数,系统就为一个新的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。

fork()不仅创建出与父进程代码相同的子进程,而且父进程在fork执行点的所有上下文场景也被自动复制到子进程中,包括:

——全局和局部变量

——打开的文件句柄

——共享内存、消息等同步对象

而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭