fork系统调用
fork系统调用
(1) fork系统调用说明
fork系统调用用于从已存在进程中创建一个新进程,新进程称为子进程,而原进程称为父进程。
fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返回值是子进程的进程号,而子进程中的返回值则返回 0。
因此,可以通过返回值来判定该进程是父进程还是子进程。
使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等,而子进程所独有的只有它的进程号、计时器等。
因此可以看出,使用fork系统调用的代价是很大的,它复制了父进程中的数据段和堆栈段里的绝大部分内容,使得fork系统调用的执行速度并不很快。
fork的返回值这样设计是有原因的,fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程ID,也可以调用getppid函数得到父进程的进程ID。
在父进程中使用getpid函数可以得到自己的进程ID,然而要想得到子进程的进程ID,只有将fork的返回值记录下来,别无它法。
fork的另一个特性是所有由父进程打开的文件描述符都被复制到子进程中。
父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。
由于代码段(加载到内存的执行码)在内存中是只读的,所以父子进程可共用代码段,而数据段和堆栈段子进程则完全从父进程复制拷贝了一份。
(2)父进程进行fork系统调用时完成的操作
假设id=fork(),父进程进行fork系统调用时,fork所做工作如下:
①为新进程分配task_struct任务结构体内存空间。
②把父进程task_struct任务结构体复制到子进程task_struct任务
结构体。
③为新进程在其内存上建立内核堆栈。
④对子进程task_struct任务结构体中部分变量进行初始化设置。
⑤把父进程的有关信息复制给子进程,建立共享关系。
⑥把子进程加入到可运行队列中。
⑦结束fork()函数,返回子进程ID值给父进程中栈段变量id。
⑧当子进程开始运行时,操作系统返回0给子进程中栈段变量id。
(3)fork调用时所发生的事情
下面代码是fork函数调用模板,fork函数调用后常与if-else语句结合使用使父子进程执行不同的流程。
假设下面代码执行时产生的是X进程,fork后产生子进程的是XX进程,XX进程的进程ID号为1000。
int pid ;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
}
if (pid == 0) {
message = "This is the child/n";
} else {
message = "This is the parent/n";
}
调用fork前,内存中只有X进程,如图12-9所示,此时XX进程还没“出生”。
fork函数超详解及其用法
3. 进程控制上一页第30 章进程下一页3. 进程控制3.1. fork函数#include <sys/types.h>#include <unistd.h>pid_t fork(void);fork调用失败则返回-1,调用成功的返回值见下面的解释。
我们通过一个例子来理解fork是怎样创建新进程的。
例30.3. fork#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>int main(void){pid_t pid;char *message;int n;pid = fork();if (pid < 0) {perror("fork failed");exit(1);}if (pid == 0) {message = "This is the child\n"; n = 6;} else {message = "This is the parent\n"; n = 3;}for(; n > 0; n--) {printf(message);sleep(1);}return 0;}$ ./a.outThis is the childThis is the parentThis is the childThis is the parentThis is the childThis is the parentThis is the child$ This is the childThis is the child这个程序的运行过程如下图所示。
图30.4. fork父进程初始化。
父进程调用fork,这是一个系统调用,因此进入内核。
内核根据父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同。
fork函数的基本介绍和应用
fork函数的基本介绍和应⽤从fork()函数的⾓度来看,⼀个进程⼤致包括以下三点:代码数据分配给进程的资源fork()函数通过系统调⽤,创建⼀个与原来进程⼏乎完全相同的进程,接⼊点从调⽤fork()函数处开始。
也就是两个进程在之后的步骤⾥可以做完全相同的事,但如果初始参数或者传⼊的变量不同,或者是判断条件不同,两个进程也可以做不太⼀样的事。
⼀个进程调⽤fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。
然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同,相当于孪⽣兄弟。
⼀个同学给我的例⼦:what happens when the follwing code executes?#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(void){pid_t childpid;pid_t mypid;mypid = getpid();childpid = fork();//此处开始进⾏fork接⼊if(childpid == -1){perror("Failed to fork");return1;}if(childpid == 0) //child codeprintf("I am child %ld,ID = %ld\n",(long)getpid(),(long)mypid);else//parent codeprintf("I am parent %ld,ID = %ld\n",(long)getpid(),(long)mypid);return0 ;}结果为:I am parent 14789,ID = 14789I am child 14790,ID = 14789ID即mypid,由于fork执⾏前就得到了值,所以在两个进程中都没有变化。
linux中fork的作用
linux中fork的作用在Linux中,fork(是一个非常重要的系统调用。
它的作用是创建一个新的进程,这个新的进程被称为子进程,而原始进程被称为父进程。
fork(系统调用会在父进程和子进程之间复制一份相同的当前执行状态,包括程序的代码、数据、堆栈以及其他相关资源。
当一个进程调用fork(时,操作系统会将当前的进程映像复制一份,包括进程的地址空间、文件描述符、信号处理器等。
然后操作系统会分配一个唯一的进程ID(PID)给子进程,父进程和子进程会分别返回子进程的PID和0。
子进程会从fork(调用的位置开始执行,而父进程则继续执行接下来的指令。
fork(的作用有以下几个方面:1. 多任务处理:通过fork(,一个进程可以生成多个子进程,每个子进程可以执行不同的任务。
这种多任务处理的能力是Linux操作系统的基石之一,它允许同时运行多个进程,从而提高系统的并发性和响应性能。
2. 进程间通信:fork(可以为不同的进程提供通信机制。
子进程可以通过进程间通信(IPC)机制与父进程进行数据交换,包括管道、消息队列、共享内存等。
这样实现了进程间的数据共享和协同工作。
3. 服务器模型:fork(在服务器模型中起到关键作用。
通过fork(,一个服务器进程可以创建多个子进程来处理客户端请求。
子进程在接收到请求后,可以独立地为客户端提供服务,这样能够极大地提高服务器的吞吐量和并发处理能力。
4. 资源管理:通过fork(,Linux可以对资源进行有效的管理。
当一个进程需要一个完全相同的副本来执行其他任务时,可以使用fork(来复制当前进程的状态。
这种状态的复制可以节省时间和资源,避免了重新加载和初始化的开销。
5. 守护进程创建:守护进程是在后台执行的长时间运行的进程,不依赖于任何终端。
通过调用fork(,父进程可以使自己成为一个后台进程,并终止自己,而子进程则变为一个孤儿进程并被init进程接管。
这样,守护进程就能够在系统启动后一直运行,提供服务。
fork的底层实现方式
fork的底层实现方式Fork是Unix系统中的一个重要概念,它可以创建一个新的进程,这个新的进程是原进程的一个副本。
在Unix系统中,每个进程都有一个唯一的进程ID,fork会为新的进程分配一个新的进程ID,并且这个新的进程会继承原进程的所有资源,包括代码段、数据段、堆栈、文件描述符等等。
那么,fork的底层实现方式是什么呢?在Unix系统中,fork是通过系统调用来实现的。
当一个进程调用fork时,操作系统会为新的进程创建一个新的进程控制块(PCB),并将原进程的PCB复制一份到新的进程的PCB中。
这样,新的进程就拥有了原进程的所有资源。
在fork的实现过程中,操作系统会为新的进程分配一个新的虚拟地址空间,并将原进程的虚拟地址空间复制一份到新的进程的虚拟地址空间中。
这个过程被称为“写时复制”(Copy-On-Write)。
也就是说,当新的进程需要修改某个资源时,操作系统会将这个资源从原进程的虚拟地址空间中复制一份到新的进程的虚拟地址空间中,然后再进行修改。
这样,就避免了资源的重复复制,提高了fork的效率。
除了资源的复制外,fork还需要进行一些其他的操作。
例如,新的进程需要拥有自己的进程ID、父进程ID、用户ID、组ID等等。
操作系统会为新的进程分配一个新的进程ID,并将原进程的进程ID作为新进程的父进程ID。
此外,新的进程还需要重新打开文件描述符、清空信号处理器等等。
总的来说,fork的底层实现方式是通过系统调用来实现的。
在实现过程中,操作系统会为新的进程创建一个新的进程控制块,并将原进程的所有资源复制一份到新的进程中。
为了提高效率,操作系统采用了“写时复制”的技术,避免了资源的重复复制。
此外,新的进程还需要进行一些其他的操作,例如重新打开文件描述符、清空信号处理器等等。
linux中的fork()函数
xx理工大学课程实验报告课程名称操作系统班级实验日期2012.4.27姓名学号实验成绩实验名称实验3 进程并发与同步实验目的及要求1、加深对进程概念的理解,区分进程并发执行与串行执行;2、掌握进程并发执行的原理,理解进程并发执行的特点;3、了解fork( )系统调用的返回值,掌握用fork()创建进程的方法;熟悉wait、exit等系统调用;4、能利用相应的系统调用实现进程树与进程间的同步。
实验环境Liunx系统PC机实验内容1、编写一C语言程序,实现在程序运行时通过系统调用fork( )创建两个子进程,使父、子三进程并发执行,父亲进程执行时屏幕显示“I am father”,儿子进程执行时屏幕显示“I am son”,女儿进程执行时屏幕显示“I am daughter”。
要求多次连续反复运行这个程序,观察屏幕显示结果的顺序,直至出现不一样的情况为止。
要求有运行结果截图与结果分析2、连续4个fork()的进程家族树,family1-1.c程序清单如下:#include <stdio.h>main(){fork();fork();fork();fork();printf(“A\n”);}请根据程序运行结果,画出进程家族树,并分析原因。
3、修改程序1,在父、子进程中分别使用wait、exit等系统调用“实现”其同步推进,父进程必须等待儿子进程与女儿进程结束,才可以输出消息。
写出相应的同步控制,并分析运行结果。
4、创建一个子进程,并给它加载程序,其功能是调用键盘命令“ls -l”,已知该键盘命令的路径与文件名为:/bin/ls。
父进程创建子进程,并加载./child2程序。
写出相应的程序代码并分析程序运行结果。
算法描述及实验步骤1.(1)父进程创建儿子进程,成功输出“I am son”,失败进入第二步。
(2)父进程失败,继续创建女儿进程,成功输出“I am daughter”,失败则输出“I am father”。
fork函数
父进程
父进程
#include <sys/types.h> main() 执 { 行 pid_t val; printf(“PID…); 分裂 val=fork(); if(val!=0) printf(“parent…”); else printf(“child…); }
main( ) { pid_t val; printf(“PID…); val=fork(); if(val!=0) printf(“parent…”); else printf(“child…); }
运行结果1 global=5,vari=4;
global=4,vari=5;
运行结果2
global=4,vari=5; global=5,vari=4
分析:子进程和父进程的数据段、堆栈段不同。子进程执 行语句“global++,vari—”时,只是对自己的数据段 进行了修改,并未影响到父进程的数据段,
exec()—执行一个文件的调用
子进程如何执行一个新的程序文本?
通过exec()调用族,加载新的程序文本
通过一个系统调用exec,子进程可以拥有自己的可 执行代码。即exec用一个新进程覆盖调用进程。 它的参数包括新进程对应的文件和命令行参数。成 功调用时,不再返回;否则,返回出错原因。 六种exec调用格式:各种调用的区别在于参数的处 理方法不同,常用的格式有:
void main(void) { printf(“Hello \n”); fork(); printf(“Bye \n”); }
运行结果 Hello Bye Bye
fork()调用例子(2)
#include<stdio.h> #Include<sys/types.h> #include<unistd.h> void main(void){ if (fork()==0) printf(“In the CHILD process\n”); else printf(“In the PARENT process\n”); }
fork()系统调用
fork系统调用分类:LINUX1.预备知识不妨简单理解为,一个进程表示的,就是一个可执行程序的一次执行过程中的一个状态。
操作系统对进程的管理,典型的情况,是通过进程表完成的。
进程表中的每一个表项,记录的是当前操作系统中一个进程的情况。
对于单 CPU的情况而言,每一特定时刻只有一个进程占用 CPU,但是系统中可能同时存在多个活动的(等待执行或继续执行的)进程。
一个称为“程序计数器(program counter, pc)”的寄存器,指出当前占用CPU的进程要执行的下一条指令的位置。
当分给某个进程的CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;把将要接替这个进程占用 CPU的那个进程的上下文,从进程表中读出,并更新相应的寄存器(这个过程称为“上下文交换(process context switch)”,实际的上下文交换需要涉及到更多的数据,那和fork无关,不再多说,主要要记住程序寄存器pc指出程序当前已经执行到哪里,是进程上下文的重要内容,换出 CPU的进程要保存这个寄存器的值,换入CPU的进程,也要根据进程表中保存的本进程执行上下文信息,更新这个寄存器)。
2.fork系统调用当你的程序执行到下面的语句:pid=fork();操作系统创建一个新的进程(子进程),并且在进程表中相应为它建立一个新的表项。
新进程和原有进程的可执行程序是同一个程序;上下文和数据,绝大部分就是原进程(父进程)的拷贝,但它们是两个相互独立的进程!此时程序寄存器pc,在父、子进程的上下文中都声称,这个进程目前执行到fork调用即将返回(此时子进程不占有CPU,子进程的pc不是真正保存在寄存器中,而是作为进程上下文保存在进程表中的对应表项内)。
问题是怎么返回,在父子进程中就分道扬镳。
父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(一个正整数),所以下面的if语句中pid<0, pid==0的两个分支都不会执行。
父子进程的说法
父子进程的说法
"父子进程"是指在操作系统中,由一个进程创建(通常是通过fork系统调用)的进程关系。
这种关系通常用于实现并发执行或并行计算,允许父进程和子进程在独立的执行空间中运行。
以下是关于父子进程的一些说明:
1.父进程:执行fork系统调用的进程称为父进程。
父进程在创建子进程后,通常会继续执行一些任务,或者等待子进程完成执行。
2.子进程:通过fork系统调用创建的新进程称为子进程。
子进程是父进程的副本,拥有独立的内存空间,但通常会继承父进程的代码段、数据段、文件描述符等信息。
3.进程独立性:父进程和子进程之间是相对独立的,它们可以并发执行,互不影响。
子进程的修改通常不会影响父进程,反之亦然。
4.通信机制:父子进程之间可以通过进程间通信(Inter-Process Communication,IPC)来进行数据交换。
常见的IPC方法包括管道、共享内存、消息队列等。
5.等待子进程:父进程通常会使用wait系统调用等待子进程的结束,并获取子进程的退出状态。
这样可以确保父进程在子进程执行完毕后进行进一步的处理。
在多进程的环境中,父子进程的概念非常重要,它们共同构成了并发执行的基础。
在Unix/Linux等操作系统中,fork系统调用是实现多进程的一种常见方式。
linux中fork的语法及用法
在Linux中,fork()函数是用于创建一个新进程的系统调用。
以下是fork()函数的语法和用法:
语法:
fork()`函数返回两次:一次是在父进程中,另一次是在新创建的子进程中。
在父进程中,fork()函数返回新创建子进程的进程ID(PID)。
在子进程中,fork()函数返回0。
如果fork()函数出现错误,它会返回一个负值,通常为-1。
用法:
1.在父进程中调用fork()函数,创建一个子进程。
2.父进程和子进程从fork()函数返回处开始执行。
3.父进程和子进程可以继续执行不同的代码路径,实现并行执行。
4.可以通过wait()或waitpid()函数等待子进程结束,以回收子进程的资
源。
示例:
在上述示例中,父进程和子进程分别打印不同的消息,并使用wait()函数等待子进程结束。
注意,在子进程中,我们使用return 0;语句来结束子进程的执行。
fork()函数的理解
对于刚刚接触Unix/Linux操作系统,在Linux下编写多进程的人来说,fork是最难理解的概念之一:它执行一次却返回两个值。
首先我们来看下fork函数的原型:#i nclude <sys/types.h>#i nclude <uni ST d.h>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 <unistd.h>#include <stdio.h>int main(){pid_t pid;int count=0;pid = fork();printf( "This is first time, pid = %d\n", pid );printf( "This is sec ON d time, pid = %d\n", pid );count++;printf( "count = %d\n", count );if ( pid>0 ){printf( "This is the parent process,the child has the pid:%d\n", pid );}else if ( !pid ){printf( "This is the child Process.\n")}else{printf( "fork failed.\n" );}printf( "This is third time, pid = %d\n", pid );printf( "This is fouth time, pid = %d\n", pid );return 0;}运行结果如下:问题:这个结果很奇怪了,为什么printf的语句执行两次,而那句“count++;”的语句却只执行了一次接着看:#include <unistd.h>#include <stdio.h>int main(void){pid_t pid;int count=0;pid = fork();printf( "Now, the pid returned by calling fork() is %d\n", pid );if ( pid>0 ){printf( "This is the parent proc ESS,the child has the pid:%d\n", pid );printf( "In the parent process,count = %d\n", 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 = %d\n", count );}else{printf( "fork failed.\n" );}return 0;}运行结果如下:现在来解释上面提出的问题。
