linux下fork的运行机制
fork 子进程不继承父进程打开的文件描述符
fork 子进程不继承父进程打开的文件描述符1.引言1.1 概述概述在操作系统中,进程是指正在执行的程序的实例。
当一个进程创建子进程时,子进程会继承父进程的一些属性和资源,以便能够继续执行相同的操作。
然而,在某些情况下,子进程并不会继承父进程的所有属性和资源,其中一个重要的例子就是父进程打开的文件描述符。
文件描述符是用来标识一个文件或者文件流的抽象概念。
当一个进程需要对文件进行操作时,它必须首先打开该文件,并获得一个文件描述符。
父进程通过打开文件来获取文件描述符,而子进程在创建时则不会继承这些父进程已打开的文件描述符。
这种设计有其合理性和必要性。
首先,父进程打开的文件描述符可能会包含一些敏感信息,比如数据库连接信息、加密密钥等。
如果这些信息被子进程继承,有可能会导致安全风险。
其次,大多数情况下,子进程并不需要继承父进程的文件描述符。
子进程是作为独立的实体运行的,通常具有自己独立的文件操作需求。
虽然子进程不继承父进程的文件描述符,但是子进程可以通过其他方式来获取和打开文件。
举例来说,子进程可以使用文件路径来打开需要的文件,或者通过网络传输文件描述符等方式来获取父进程已打开的文件描述符。
因此,对于子进程来说,并不存在无法获取文件描述符的问题。
总之,子进程不继承父进程打开的文件描述符是有意而为的设计。
这一设计使得父子进程之间的资源隔离得以实现,同时也增强了系统的安全性。
在实际应用中,开发人员需要考虑到这一点,并正确处理子进程的文件操作需求。
1.2文章结构文章结构部分的内容可以描述文章的整体框架和组织结构,包括各个章节的主题和内容概要。
可以按照以下方式编写文章结构部分的内容。
文章结构部分:本文主要围绕着fork子进程不继承父进程打开的文件描述符展开讨论,旨在探讨子进程不继承父进程文件描述符的原因以及对应的影响和应用。
在引言部分,我们将通过概述对fork子进程和文件描述符的概念进行简要介绍,并给出本文的目的。
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信息相同,用户态代码和数据也相同。
linux下的stdin,stdout,stderr
用户操作Linux 下stdin stdout stderr 的由来收藏现在就从linux kernel的源代码的角度来分析该。
二:fork()与execve()中stderr,stdio.stdout的继承关系其实用继承这个词好像不太准确,要准确一点,可能复制更适合.首先有二点:1:父进程fork出子进程后,是共享所有文件描述符的(实际上也包括socket)2:进程在execve后,除了用O_CLOEXEC标志打开的文件外,其它的文件描述符都是会复制到下个执行序列(注意这里不会产生一个新进程,只是将旧的进程替换了)下面我们从代码中找依据来论证以上的两个观点.对于第一点:我们在分析进程创建的时候,已经说过,如果父过程在创建子进程的时候带了CLONE_FILE S标志的时候,会和父进程共享task->files.如果没有定义,就会复制父进程的task->files.无论是哪种情况,父子进程的环境都是相同的.代码如下:static int copy_files(unsigned long clone_flags, struct task_struct * tsk) {struct files_struct *oldf, *newf;int error = 0;oldf = current->files;if (!oldf)goto out;if (clone_flags & CLONE_FILES) {atomic_inc(&oldf->count);goto out;}tsk->files = NULL;newf = dup_fd(oldf, &error);if (!newf)goto out;tsk->files = newf;error = 0;out:return error;}从上面的代码可以看出.如果带CLONE_FILES标志,只是会增加它的引用计数.否则,打开的文件描符述会全部复制.对于二:我们之前同样也分析过sys_execve().如果有不太熟悉的,到本站找到相关文章进行阅读.在这里不再详细说明整个流程.相关代码如下:static void flush_old_files(struct files_struct * files){long j = -1;struct fdtable *fdt;spin_lock(&files->file_lock);for (;;) {unsigned long set, i;j++;i = j * __NFDBITS;fdt = files_fdtable(files);if (i >= fdt->max_fds)break;set = fdt->close_on_exec->fds_bits[j];if (!set)continue;fdt->close_on_exec->fds_bits[j] = 0;spin_unlock(&files->file_lock);for ( ; set ; i++,set >>= 1) {if (set & 1) {sys_close(i);}}spin_lock(&files->file_lock);}spin_unlock(&files->file_lock);}该函数会将刷新旧环境的文件描述符信息.如果该文件描述符在fdt->close_on_exec被置位,就将其关闭.然后,我们来跟踪一下,在什么样的情况下,才会将fdt->close_on_exec的相关位置1. 在sys_open() àget_unused_fd_flags():int get_unused_fd_flags(int flags){………….if (flags & O_CLOEXEC)FD_SET(fd, fdt->close_on_exec);elseFD_CLR(fd, fdt->close_on_exec);……}只有在带O_CLOEXEC打开的文件描述符,才会在execve()中被关闭.三:用户空间的stderr,stdio.stdout初始化论证完上面的二个观点之后,后面的就很容易分析了.我们先来分析一下,在用户空间中,prin tf是可以使用的.哪它的stderr,stdio.stdout到底是从哪点来的呢?我们知道,用户空间的所有进程都是从init进程fork出来的.因此,它都是继承了init进程的相关文件描述符.因此,问题都落在,init进程的stderr,stdio.stdout是在何时被设置的?首先,我们来看一下内核中的第一个进程.它所代码的task_struct结构如下所示:#define INIT_TASK(tsk){.state = 0,.stack = &init_thread_info,.usage = ATOMIC_INIT(2),.flags = 0,.lock_depth = -1,.prio = MAX_PRIO-20,.static_prio = MAX_PRIO-20,.normal_prio = MAX_PRIO-20,.policy = SCHED_NORMAL,.cpus_allowed = CPU_MASK_ALL,……..files = &init_files,……}它所有的文件描述符信息都是在init_files中的,定义如下:static struct files_struct init_files = INIT_FILES;#define INIT_FILES{.count = ATOMIC_INIT(1),.fdt = &init_files.fdtab,.fdtab = INIT_FDTABLE,.file_lock = __SPIN_LOCK_UNLOCKED(init_task.file_lock),.next_fd = 0,.close_on_exec_init = { { 0, } },.open_fds_init = { { 0, } },.fd_array = { NULL, }}我们从这里可以看到,内核的第一进程是没有带打开文件信息的.我们来看一下用户空间的init进程的创建过程:Start_kernel() -àrest_init()中代码片段如下:static void noinline __init_refok rest_init(void)__releases(kernel_lock){int pid;kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);kthreadd_task = find_task_by_pid(pid);unlock_kernel();/** The boot idle thread must execute schedule()* at least once to get things moving:*/init_idle_bootup_task(current);preempt_enable_no_resched();schedule();preempt_disable();/* Call into cpu_idle with preempt disabled */cpu_idle();}该函数创建了两个进程,然后本进程将做为idle进程在轮转.在创建kernel_init进程的时候,带的参数是CLONE_FS | CLONE_SIGHAND.它没有携带CLONE_FILES标志.也就是说,kernel_init中的文件描述符信息是从内核第一进程中复制过去的.并不和它共享.以后,kernel_init进程中,任何关于files的打开,都不会影响到父进程.然后在kernel_init() àinit_post()中有:static int noinline init_post(void){ …………if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)printk(KERN_WARNING "Warning: unable to open an initial co nsole.\n");(void) sys_dup(0);(void) sys_dup(0);…………run_init_process(XXXX);}从上面的代码中可以看到,它先open了/dev/console.在open的时候,会去找进程没使用的最小文件序号.而,当前进程没有打开任何文件,所以sys_open()的时候肯定会找到0.然后,两次调用sys_dup(0)来复制文件描述符0.复制后的文件找述符肯定是1.2.这样,0.1.2就建立起来了.然后这个进程调用run_init_process() àkernel_execve()将当前进程替换成了用户空间的一个进程,这也就是用户空间init进程的由来.此后,用户空间的进程全是它的子孙进程.也就共享了这个0.1.2的文件描述符了.这也就是我们所说的stderr.stdio,stdout.从用户空间写个程序测试一下:#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>main(){int ret;char *ttyname0,*ttyname1,*ttyname2;ttyname0 = ttyname(0);ttyname1 = ttyname(1);ttyname2 = ttyname(2);printf(“file0 : %s\n”,ttyname0);printf(“file1 : %s\n”,ttyname1);printf(“file2 : %s\n”,ttyname2);return;}运行这个程序,我们会看到,0,1,2描述符的信息全为/dev/consle.四:内核创建用户空间进程的过程在内核中创建用户空间进程的相应接口为call_usermodehelper().实现上,它将要创建的进程信息链入一个工作队列中,然后由工作队列处理函数调用kernel _thread()创建一个子进程,然后在这个进程里调用kernel_execve()来创建用户空间进程.在这里要注意工作队列和下半部机制的差别.工作队列是利用一个内核进程来完成工作的,它和下半部无关.也就是说,它并不在一个中断环境中.那就是说,这样创建出来的进程,其实就是内核环境,它没有打开0,1.2的文件描述符.可能也有人会这么说:那我就不在内核环境下创建用户进程不就行了?例如,我在init_module的时候,创建一个内核线程,然后在这个内核线程里,kernel_execv e()一个用户空间进程不就可以了吗?的确,在这样的情况下,创建的进程不是一个内核环境,因为在调用init_module()的时候,已经通过系统调用进入kernel,这时的环境是对应用户进程环境.但是别忘了.在系统调用环境下,再进行系统调用是不会成功的(kernel_execve也对应一个系统调用.)举例印证如下:Mdoule代码:#include <linux/ioport.h>#include <linux/interrupt.h>#include <asm/io.h>#include <linux/serial_core.h>#include <linux/kmod.h>#include <linux/file.h>#include <linux/unistd.h>MODULE_LICENSE("GPL");MODULE_AUTHOR( "ericxiao:xgr178@" );static int exeuser_init(){int ret;char *argv[] ={"/mnt/hgfs/vm_share/user_test/main",NULL,};char *env[] ={"HOME=/","PATH=/sbin:/bin:/usr/sbin:/usr/bin",NULL,};printk("exeuser_init ...\n");ret = call_usermodehelper(argv[0], argv, env,UMH_WAIT_EXEC);return 0;}static int exeuser_exit(){printk("exeuser_exit ...\n");return 0;}module_init(exeuser_init);module_exit(exeuser_exit);用户空间程序代码:#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc,char *argv[],char *env[]){int i;int fd;int size;char *tty;FILE *confd;char printfmt[4012];system("echo i am coming > /var/console");for(i=0; env[i]!=NULL;i++){sprintf(printfmt,"echo env[%d]:%s. >>/var/console",i,env[i]);system(printfmt);}for(i=0; i<argc ;i++){sprintf(printfmt,"echo arg[%d]:%s. >>/var/console",i,argv[i]);system(printfmt);}tty = ttyname(0);if(tty == NULL)system("echo tty0 is NULL >> /var/console");else{sprintf(printfmt,"echo ttyname0 %s. >>/var/console",tty);system(printfmt);}tty = ttyname(1);if(tty == NULL)system("echo tty1 is NULL >> /var/console");else{sprintf(printfmt,"echo ttyname1 %s. >>/var/console",tty);system(printfmt);}tty = ttyname(2);if(tty == NULL)system("echo tty2 is NULL >> /var/console");else{sprintf(printfmt,"echo ttyname2 %s. >>/var/console",tty);system(printfmt);}tty = ttyname(fd);if(tty == NULL)system("echo fd is NULL >> /var/console");else{sprintf(printfmt,"echo fd %s. >>/var/console",tty);system(printfmt);}return 0;}插入模块过后,调用用户空间的程序,然后这个程序将进程环境输出到/var/console中,完了可以看到.这个进程输出的0,1,2描述符信息全部NULL.千万要注意,在测试的用户空间程序,不能打开文件.这样会破坏该进程的原始文件描述符环境(因为这个问题.狠调了一个晚上,汗颜…).这样.用户空间的printf当然就不能打印出东西了.ps:这位老兄的帖子解了我的一些疑惑。
Linux系统编程之进程控制(进程创建、终止、等待及替换)
Linux系统编程之进程控制(进程创建、终⽌、等待及替换)进程创建在上⼀节讲解进程概念时,我们提到fork函数是从已经存在的进程中创建⼀个新进程。
那么,系统是如何创建⼀个新进程的呢?这就需要我们更深⼊的剖析fork 函数。
1.1 fork函数的返回值调⽤fork创建进程时,原进程为⽗进程,新进程为⼦进程。
运⾏man fork后,我们可以看到如下信息:#include <unistd.h>pid_t fork(void);fork函数有两个返回值,⼦进程中返回0,⽗进程返回⼦进程pid,如果创建失败则返回-1。
实际上,当我们调⽤fork后,系统内核将会做:分配新的内存块和内核数据结构(如task_struct)给⼦进程将⽗进程的部分数据结构内容拷贝⾄⼦进程添加⼦进程到系统进程列表中fork返回,开始调度1.2 写时拷贝在创建进程的过程中,默认情况下,⽗⼦进程共享代码,但是数据是各⾃私有⼀份的。
如果⽗⼦只需要对数据进⾏读取,那么⼤多数的数据是不需要私有的。
这⾥有三点需要注意:第⼀,为什么⼦进程也会从fork之后开始执⾏?因为⽗⼦进程是共享代码的,在给⼦进程创建PCB时,⼦进程PCB中的⼤多数数据是⽗进程的拷贝,这⾥⾯就包括了程序计数器(PC)。
由于PC中的数据是即将执⾏的下⼀条指令的地址,所以当fork返回之后,⼦进程会和⽗进程⼀样,都执⾏fork之后的代码。
第⼆,创建进程时,⼦进程需要拷贝⽗进程所有的数据吗?⽗进程的数据有很多,但并不是所有的数据都要⽴马使⽤,因此并不是所有的数据都进⾏拷贝。
⼀般情况下,只有当⽗进程或者⼦进程对某些数据进⾏写操作时,操作系统才会从内存中申请内存块,将新的数据拷写⼊申请的内存块中,并且更改页表对应的页表项,这就是写时拷贝。
原理如下图所⽰:第三,为什么数据要各⾃私有?这是因为进程具有独⽴性,每个进程的运⾏不能⼲扰彼此。
1.3 fork函数的⽤法及其调⽤失败的原因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函数的理解
对fork函数的理解前言:对于刚刚接触Unix/Linux操作系统,在Linux下编写多进程的人来说,fork 是最难理解的概念之一:它执行一次却返回两个值。
因此,本文着重从以下几个方面来使初学者加深对fork函数的理解和应用:fork函数的机制与特性、fork 函数的两次返回和父子进程的执行顺序介绍、关键字:fork函数、返回值、父进程、子进程正文:一、fork函数的机制与特性1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>45 int main(void)6 {7 pid_t pid;8 if ((pid = fork()) == 00) {9 getchar();10 exit(0);11 }12 getchar ();13}14父进程成功的调用fork(8行)后将会产生一个子进程。
此时会有两个问题:1、子进程的代码从哪里来?2、子进程首次被OS调用时,执行的第一条代码是哪条代码?子进程的代码是父进程代码的一个完全相同拷贝。
事实上不仅仅是text 段,子进程中的全部进程空间都是(包括:text/data/bss/heap/commandline/envir onment)父进程空间的一个完全拷贝。
下一个问题是谁为子进程分配了内存空间?谁复制了父进程空间的内容到子空间?fork当仁不让。
事实上,fork 实现的源代码,由四部分组成:首先,为子进程分配内存空间;然后,将父进程空间的全部内容复制到分配给子进程的内存空间;接着在内核数据结构中创建并正确初始化子进程的PCB (包括两个重要信息:子进程pid,PC 的值=善后代码的第一条指令地址);最后是一段善后代码。
由于子进程的PCB已经产生,因此子进程可以被OS调度子进程首次被OS调度时,执行的第一条代码在fork 内部,不过从引用程序的角度来看,子进程首次被OS调度时,执行的第一条代码是从fork返回。
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;}运行结果如下:现在来解释上面提出的问题。
fork函数返回值浅谈
fork函数返回值浅谈fork函数返回值浅谈 fork简介 fork英文原意是分岔分支的意思而在操作系统中乃是著名的Unix或类Unix如LinuxMinix中用于创建子进程的系统调用。
【NOTE1】 fork的作用是什么换句话说你用fork的目的是什么――是为了产生一个新的进程地球人都知道产生一个什么样的进程――和你本来调用fork的那个进程基本一样的进程其实就是你原来进程的副本真的完全一样吗――当然不能完全一样你要两个除了pid之外其它一模一样的进程干什么就算memory 再多也不用这么摆谱吧哪里不一样――当然最重要的是fork之后执行的代码不一样you knowi know 怎么实现呢――如果是Windows它会让你在fork里面提供一大堆东西指明这个那个什么的… 我用的是unix啊――所以很简单unix会让两个进程不错原来是一个unix替你复制了一个现在有两个在fork之后产生不同返回值不同。
其中一个进程使用新的pid里面的fork返回零这个进程就是子进程而另一个进程使用原来的pid中的fork返回前面那个子进程的 pid他自己被称为父进程然后呢――写代码的人又不笨当然就根据返回值是否非零来判断了现在我是在子进程里面呢还是在父进程里面在子进程里面就执行子进程该执行的代码在父进程里面就执行父进程的代码… 有铁杆windows fans借此说明windows好啊子进程用子进程的代码父进程用父进程的你unix笨了吧子进程包含父进程、子进程的代码父进程包含父进程子进程的代码岂不是多占用内存了吗――据我所知unix代码段都是可重入代码也就是说进程复制并不复制代码段若干个进程共享同一代码段增加的只是全局共享数据和对文件描述符的引用等另外就是堆栈。
你一个代码长达10M的进程fork出三四个子进程只是增加一点内存占用如果你没有使用很多全局变量的话而不是占用40M以上的内存。
【NOTE2】程序从fork开始分支称分支不准确一路是主进程pid 0pid是子进程ID一路是子进程pid0自此分成两个任务其实fork的时候已经两个分支了数据段被复制了一份因此pid有两份执行pidfork时返回值赋给pid在两个进程中运行 fork会返回给父进程的那个0的值告诉调用者新建进程的pid 子进程的fork返回值是0 更不用说if.else的比较也是在两个进程中都做的了【NOTE3】 fork的精辟剖析程序如下 include unistd.h include sys/types.h main pid_t pid pidfork ifpid 0printferror in fork else ifpid0printfi am the child processmy process id isdngetpid else printfi am the parent processmyprocess id isdngetpid 结果是 rootlocalhost c./a.out iam the child processmy process idis 4286 iam the parent processmy process id is 4285 一要搞清楚fork 的执行过程就必须先讲清楚操作系统中的进程process概念。
fork clone 和 fork 的区别
fork/vfork/clone分类:Linux系统C++2010-08-27 09:51 34人阅读评论(0) 收藏举报首先说明Linux下的进程与线程比较相近。
这么说的一个原因是它们都需要相同的数据结构来表示,即task_struct。
区别在于一个有独立的用户空间,一个是共享的用户空间(如果完全没有用户空间则是内核线程,不需要)。
Linux的用户进程不能直接被创建出来,因为不存在这样的API。
它只能从某个进程中复制出来,再通过exec这样的API来切换到实际想要运行的程序文件。
复制的API包括三种:fork、clone、vfork。
这三个API的内部实际都是调用一个内核内部函数do_fork,只是填写的参数不同而已。
vfork,其实就是fork的部分过程,用以简化并提高效率。
而fork与clone是区别的。
fork是进程资源的完全复制,包括进程的PCB、线程的系统堆栈、进程的用户空间、进程打开的设备等。
而在clone中其实只有前两项是被复制了的,后两项都与父进程共享。
在四项资源的复制中,用户空间是相对庞大的,如果完全复制则效率会很低。
在Linux中采用的“写时复制”技术,也就是说,fork执行时并不真正复制用户空间的所有页面,而只是复制页面表。
这样,无论父进程还是子进程,当发生用户空间的写操作时,都会引发“写复制”操作,而另行分配一块可用的用户空间,使其完全独立。
这是一种提高效率的非常有效的方法。
而对于clone来说,它们连这些页面表都是与父进程共享,故而是真正意义上的共享,因此对共享数据的保护必须有上层应用来保证。
1. fork, vfork and clone三者最终都会调用do_fork函数,三者的差别就是参数上的不同而已。
fork的实现:do_fork(CLONE_SIGCHLD,...)clone的实现:do_fork(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGCHLD,...)vfork的实现:do_fork(CLONE_VFORK|CLONE_VM|CLONE_SIGCHLD,...)2. Linux使用copy on wirte的技术,Linux中的fork代价仅仅是创建子进程的页表结构和创建一个task_struct结构。
1简答题答案
1简答题答案简答题红⾊标记为不确定答案,请⼤家补充!1、在使⽤GDB调试程序时,如何设置断点:答:在由“(gdb)”开头的命令⾏界⾯输⼊“b”+对应⾏号例如: b 6回车2、在使⽤GDB调试程序时,如何运⾏程序:答:在由“(gdb)”开头的命令⾏界⾯输⼊:r回车3、在使⽤GDB调试程序时,如何实现从指定代码位置开始单步运⾏:答:先设置断点,再运⾏程序⾄该断点,再单步运⾏。
具体操作:在由“(gdb)”开头的命令⾏界⾯输⼊“b”+对应⾏号例如: b 6回车输⼊ r 回车输⼊ step 回车4、在使⽤GDB调试程序时,如何查看断点处的相关变量值:答:在由“(gdb)”开头的命令⾏界⾯输⼊“p”+ 变量值例如: p n回车5、什么是系统调⽤答:系统调⽤是指操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝,⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务。
例如⽤户可以通过进程控制相关的系统调⽤来创建进程、实现进程调度、进程管理等。
6、标准I/O和低级I/O(⽂件I/O)的区别答:标准I/O默认采⽤了缓冲机制,低级I/O⼀般没有采⽤缓冲,需要⾃⼰创建缓冲区。
⽂件I/O主要针对⽂件操作,读写硬盘等,标准I/O,主要是打印输出到屏幕等。
因为他们设备不⼀样,⽂件I/O针对的是⽂件,标准I/O是对控制台,操作的是字符流。
使⽤的打开、关闭、读、写函数也不同。
7、什么是进程答:进程是Linux系统的基本调度和管理资源的单位,它是通过进程控制块来描述的。
进程控制块包含了进程的描述信息、控制信息以及资源信息,它是进程的⼀个静态描述。
在Linux 中,进程控制块中的每⼀项都是⼀个task_struct结构。
进程是程序的执⾏过程,根据它的⽣命周期可以划分成3种状态。
执⾏态:该进程正在运⾏,即进程正在占⽤CPU。
就绪态:进程已经具备执⾏的⼀切条件,正在等待分配CPU的处理时间⽚。
等待态:进程不能使⽤CPU,若等待事件发⽣(等待的资源分配到)则可将其唤醒。
fork函数的2个返回值说明
fork简介:fork英文原意是“分岔,分支”的意思,而在操作系统中,乃是著名的Unix(或类Unix,如Linux,Minix)中用于创建子进程的系统调用。
【NOTE1】fork () 的作用是什么?换句话说,你用fork () 的目的是什么?――是为了产生一个新的进程,地球人都知道:)产生一个什么样的进程?――和你本来调用fork () 的那个进程基本一样的进程,其实就是你原来进程的副本;真的完全一样吗?――当然不能完全一样,你要两个除了pid 之外其它一模一样的进程干什么,就算memory 再多也不用这么摆谱吧?哪里不一样?――当然最重要的是fork () 之后执行的代码不一样,you know, i know :)怎么实现呢?――如果是Windows,它会让你在fork () 里面提供一大堆东西,指明这个那个什么的…… 我用的是unix 啊――所以很简单,unix 会让两个进程(不错,原来是一个,unix 替你复制了一个,现在有两个)在fork () 之后产生不同:返回值不同。
其中一个进程(使用新的pid)里面的fork () 返回零,这个进程就是“子进程”;而另一个进程(使用原来的pid)中的fork () 返回前面那个子进程的pid,他自己被称为“父进程”然后呢?――写代码的人又不笨,当然就根据返回值是否非零来判断了,现在我是在子进程里面呢,还是在父进程里面?在子进程里面就执行子进程该执行的代码,在父进程里面就执行父进程的代码……有铁杆windows fans 借此说明,windows 好啊,子进程用子进程的代码,父进程用父进程的,你unix 笨了吧,子进程包含父进程、子进程的代码,父进程包含父进程子进程的代码,岂不是多占用内存了吗?――据我所知,unix 代码段都是可重入代码,也就是说,进程复制,并不复制代码段,若干个进程共享同一代码段,增加的只是全局共享数据和对文件描述符的引用等,另外就是堆栈。
linux上应用程序的执行机制
linux上应用程序的执行机制1.父进程的行为: 复制,等待执行应用程序的方式有很多,从shell中执行是一种常见的情况。
交互式shell是一个进程(所有的进程都由pid号为1的init进程fork得到,关于这个话题涉及到Linux启动和初始化,以及idle进程等,有空再说),当在用户在shell中敲入./test执行程序时,shell先fork()出一个子进程(这也是很多文章中说的子shell),并且wait()这个子进程结束,所以当test执行结束后,又回到了shell等待用户输入(如果创建的是所谓的后台进程,shell则不会等待子进程结束,而直接继续往下执行)。
所以shell进程的主要工作是复制一个新的进程,并等待它的结束。
2.子进程的行为: "执行"应用程序2.1 execve()另一方面,在子进程中会调用execve()加载test并开始执行。
这是test被执行的关键,下面我们详细分析一下。
execve()是操作系统提供的非常重要的一个系统调用,在很多文章中被称为exec()系统调用(注意和shell内部exec命令不一样),其实在Linux中并没有exec()这个系统调用,exec只是用来描述一组函数,它们都以exec开头,分别是:#includeintexecl(const char *path, const char *arg, ...);intexeclp(const char *file, const char *arg, ...);intexecle(const char *path, const char *arg, ..., char *constenvp[]);intexecv(const char *path, char *constargv[]);intexecvp(const char *file, char *constargv[]);intexecve(const char *path, char *constargv[], char *constenvp[]);这几个都是都是libc中经过包装的的库函数,最后通过系统调用execve()实现(#define__NR_evecve 11,编号11的系统调用)。
linux cpu运行2种状态
1.操作系统需要两种CPU状态内核态(Kernel Mode):运行操作系统程序,操作硬件用户态(User Mode):运行用户程序2.指令划分特权指令:只能由操作系统使用、用户程序不能使用的指令。
举例:启动I/O 内存清零修改程序状态字设置时钟允许/禁止终端停机非特权指令:用户程序可以使用的指令。
举例:控制转移算数运算取数指令访管指令(使用户程序从用户态陷入内核态)3.特权级别特权环:R0、R1、R2和R3R0相当于内核态,R3相当于用户态;不同级别能够运行不同的指令集合;4.CPU状态之间的转换用户态--->内核态:唯一途径是通过中断、异常、陷入机制(访管指令)内核态--->用户态:设置程序状态字PSW5.内核态与用户态的区别内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。
因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;当程序运行在0级特权级上时,就可以称之为运行在内核态。
运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。
这两种状态的主要差别是◆处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的◆处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。
6. 通常来说,以下三种情况会导致用户态到内核态的切换◆系统调用这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。
比如前例中fork()实际上就是执行了一个创建新进程的系统调用。
而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
C语言fork函数解析
首先看下fork的基本知识:函数原型:pid_t fork( void);返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1一个现有进程可以调用fork函数创建一个新进程。
由fork创建的新进程被称为子进程(child process)。
fork函数被调用一次但返回两次。
两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
注意要点:1、子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
此处先简要介绍下COW(Copy-on-write)机制,大致原理如下:在复制一个对象的时候并不是真正的把原先的对象复制到内存的另外一个位置上,而是在新对象的内存映射表中设置一个指针,指向源对象的位置,并把那块内存的Copy-On-Write位设置为1.这样,在对新的对象执行读操作的时候,内存数据不发生任何变动,直接执行读操作;而在对新的对象执行写操作时,将真正的对象复制到新的内存地址中,并修改新对象的内存映射表指向这个新的位置,并在新的内存位置上执行写操作。
linux内核下fork使用COW机制工作原理:进程0(父进程)创建进程1(子进程)后,进程0和进程1同时使用着共享代码区内相同的代码和数据内存页面, 只是执行代码不在一处,因此他们也同时使用着相同的用户堆栈区。
在为进程1(子进程)复制其父进程(进程0)的页目录和页表项时,进程0的640KB页表项的属性没有改动过(仍然可读写),但是进程1的640KB对应的页表项却被设置成只读。
因此当进程1(子进程)开始执行时,对用户堆栈的入栈操作将导致页面写保护异常,从而使得内核的内存管理程序为进程1在主内存区中分配一内存页面,并把进程0中的页面内容复制到新的页面上。
从此时开始,进程1开始有自己独立的内存页面,由于此时的内存页面在主内存区,因此进程1中继续创建新的子进程时也可以采用COW技术。
内核调度进程运行时次序是随机的,进程0创建进程1后,可能先于进程1修改共享区,进程0是可读写的,在未分开前,进程1是只读的,由于两个进程共享内存空间,为了不出现冲突问题,就必须要求进程0在进程1执行堆栈操作(进程1的堆栈操作会导致页面保护异常,从而使得进程1在主内存区得到新的用户页面区,此时进程1和进程0才算是真正独立,如前面所述)之前禁止使用用户堆栈区。
fork 用法
fork 用法fork 是一个系统调用,用于创建一个新的进程。
新的进程是原始进程(父进程)的一个副本,称为子进程。
这两个进程在几乎所有方面都是相同的,包括代码、数据和上下文。
在编程中,fork 通常用于创建一个新的进程,以便在子进程中执行不同的任务。
基本用法:#include <unistd.h>#include <stdio.h>int main() {pid_t pid = fork();if (pid == -1) {// 处理 fork 失败的情况perror("fork");return 1;}if (pid == 0) {// 子进程执行的代码printf("This is the child process (PID=%d)\n", getpid());} else {// 父进程执行的代码printf("This is the parent process (PID=%d), child PID=%d\n", getpid(), pid);}return 0;}注意事项:fork 返回两次,一次在父进程中返回子进程的PID,另一次在子进程中返回0。
在父子进程中的变量和状态是相互独立的,它们不会相互影响。
在fork 之后,通常会使用exec 函数族在子进程中加载新的程序。
父子进程的执行顺序和执行时间是不确定的,取决于操作系统的调度。
示例:在子进程中执行其他程序#include <unistd.h>#include <stdio.h>#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid == 0) {// 子进程中执行其他程序execl("/bin/ls", "ls", "-l", NULL);} else {// 等待子进程结束wait(NULL);printf("Parent process done.\n");}return 0;}这个例子中,父进程创建了一个子进程,子进程通过 execl 加载了 /bin/ls 程序。
Linux高级编程笔试题答案
Linux⾼级编程笔试题答案⾼级编程笔试题答案⼀、选择题1.在⽹络字节序中,所谓”⼩端”(little endian)说法正确的是( B )A. ⾼字节数据存放在低地址处,低字节数据存放在⾼地址处B. 低字节位数据存放在内存低地址处, ⾼字节位数据存放在内存⾼地址处C. 和编译器相关D. 上述答案都不正确2.C语⾔中,系统⾃动打开的⽂件是( D )A. ⼆进制⽂件B.随机⽂件C.⾮缓冲⽂件D.设备⽂件3.TCP使⽤( B )进⾏流量控制。
A. 3次握⼿法B. 窗⼝控制机制C. ⾃动重发机制D. 端⼝机制4.TCP/IP层IP协议的服务是( C )A. 可靠服务B. 有确认的服务C. ⽆连接数据报D. 以上都不对5.对于⼀个没有设置任何套接⼝选项的阻塞套接⼝,调⽤recv接收对⽅的数据,对⽅发送数据前突然断电,下列哪种情况将会发⽣( A)A. recv永远不会返回B. recv⽴刻返回-1C. recv⽴刻返回0D. recv在等待很长⼀段时间后返回-16.下列哪些关于套接⼝选项函数的说法是正确的( C )A. SO_DONTLINGER选项是让TCP套接⼝不对数据进⾏缓存,调⽤send函数后⽴刻将数据发送出去;B.调⽤SO_RCVBUF和SO_SNDBUF调整TCP窗⼝的⼤⼩;C. SO_REUSEADDR选项允许套接⼝绑定在⼀个已经在使⽤的地址上;D. SO_MAX_MSG_SIZE选项获取每次调⽤TCP套接⼝send时,所能发送的最⼤字节数;⼆、问答题1.fork和vfork的区别?vfork⽤于创建⼀个新进程,⽽该进程的⽬的是exec⼀个新程序。
vfork与fork⼀样都创建⼀个⼦进程,但他并不把⽗进程地址空间完全复制到⼦进程中,因为⼦进程通常都会调⽤exec或(_exit),于是也就不会访问该地址空间。
vfork与fork的另⼀个区别是vfork保证⼦进程先运⾏,在它调⽤exec或_exit之后⽗进程才可能被调度运⾏。
fork和vfork的区别(待细看)
NO2、fork和vfork的区别(待细看)fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性,但是二者之间的通讯需要通过专门的通讯机制,如:pipe,popen&pclose、协同进程、fifo,System V IPC(消息队列、信号量和共享内存)机制等,另外通过fork创建子进程系统开销很大,需要将上面描述的每种资源都复制一个副本。
这样看来,fork是一个开销十分大的系统调用,这些开销并不是所有的情况下都是必须的,比如某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另一个执行文件,那么在fork过程中对于虚存空间的复制将是一个多余的过程(由于Linux中是采取了copy-on-write技术,所以这一步骤的所做的工作只是虚存管理部分的复制以及页表的创建,而并没有包括物理也面的拷贝);vfork系统调用不同于fork,用vfork创建的子进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改同样为父进程所见。
但是用vfork创建子进程后,父进程会被阻塞直到子进程调用exec或exit。
这样的好处是在子进程被创建后仅仅是为了调用exec执行另一个程序时,因为它就不会对父进程的地址空间有任何引用,所以对地址空间的复制是多余的,通过vfork可以减少不必要的开销。
按指定条件创建子进程。
Linux内核在2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。
在内核中,clone()调用经过参数传递和解释后会调用do_fork(),这个核内函数同时也是fork()、vfork()系统调用的最终实现在fork之后,子进程和父进程都会继续执行fork调用之后的指令。
子进程是父进程的副本。
它将获得父进程的数据空间,堆和栈的副本,这些都是副本,父子进程并不共享这部分的内存。
操作系统 fork()
void main (void)
{
int x=5;
if( fork( ) )
{
x+=30;
printf ("%d\n",x);
}
else
printf("%d\n",x);
printf("%d\n",x);
}
1.2预测结果:
administrator@ubuntu:~/yanhong$ ./a.out
5
35
35
5
5
将第一个printf("%d\n",x);----printf("%d ",x);后得到B程序
B:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int y=9;
if( fork( ) )
{
x+=30;
printf ("%d\n",x);
}
else
{
printf("%d\n",y);
printf("%d\n",x);
}
printf("%d\n",x);
}
结果::
administrator@ubuntu:~/yanhong$ vi 4.c
administrator@ubuntu:~/yanhong$ cc 4.c
实验心得:
(一)对进程的理解:
一个进程包含三个元素:
linux下内核与用户态间的交互机制
linux下内核与用户态间的交互机制(原创版)目录1.引言:介绍 Linux 内核与用户态的概念及其关系2.Linux 内核与用户态交互的方式2.1 系统调用2.2 信号2.3 消息队列2.4 套接字3.实例:fork() 系统调用4.特权级5.结论:总结 Linux 下内核与用户态间的交互机制的重要性正文1.引言Linux 是一个开源的操作系统,其最大的特点就是内核与用户态的交互机制。
在 Linux 系统中,内核负责管理系统的资源和运行状态,而用户态则负责运行应用程序。
内核与用户态之间的交互机制是 Linux 系统运行的核心,也是操作系统管理的关键。
2.Linux 内核与用户态交互的方式在 Linux 系统中,内核与用户态之间的交互主要通过以下几种方式实现:2.1 系统调用系统调用是操作系统提供给用户程序的一组应用编程接口 (API),用户程序可以通过系统调用请求操作系统内核提供的服务,如文件操作、进程管理等。
系统调用是内核与用户态之间最重要的交互方式之一。
2.2 信号信号是操作系统内核与用户程序之间进行异步通信的一种机制。
当发生某个特定事件时,如程序异常、硬件故障等,操作系统内核可以向用户程序发送信号,告知用户程序需要采取相应的措施。
2.3 消息队列消息队列是 Linux 系统中一种先进的通信机制,可以实现内核与用户态之间的双向通信。
消息队列是一种特殊的数据结构,用于存储消息,消息可以是内核与用户程序之间的通信信息,也可以是用户程序之间的通信信息。
2.4 套接字套接字是 Linux 系统中一种通用的通信接口,可以实现不同进程之间的通信,也可以实现内核与用户态之间的通信。
套接字提供了一种灵活的通信机制,可以满足不同场景下的通信需求。
3.实例:fork() 系统调用fork() 是 Linux 系统中一个典型的系统调用,用于创建一个新的进程。
当用户程序调用 fork() 时,操作系统内核会创建一个新的进程,并将新的进程的资源映射到原始进程的资源上。
fork函数的工作原理
fork函数的工作原理
一、fork()函数的工作原理
1、fork()函数的基本概念
fork()函数是Unix/Linux系统中一种操作系统调用,用于复制当前进程的所有资源,从而创建一个新的子进程,从而实现多进程的程序设计。
2、fork()函数的工作原理
fork()函数的工作原理主要有以下几个步骤:
(1)首先,fork()函数会分配一块新的内存空间,将当前进程的所有资源,如文件句柄,函数指针,虚拟地址空间等,全部复制到新的内存空间中;
(2)接着,fork()函数会根据当前进程的运行状态,在新的内存空间中创建一个新的子进程;
(3)最后,fork()函数会返回两个值,一个给父进程,一个给子进程,父进程拿到的是子进程的进程号,而子进程拿到的是0;
(4)复制完成以后,父进程与子进程将分别独立地执行后续程序,从而实现多进程的工作。
- 1 -。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
linux下fork的运行机制
如下C程序,在linux下使用gcc编译:
1 #include "stdio.h"
2 #include "sys/types.h"
3 #include "unistd.h"
int main()
4 {
5 pid_t pid1;
6 pid_t pid2;
7 pid1 = fork();
8 pid2 = fork();
10 printf("pid1:%d, pid2:%d\n", pid1, pid2);
11 }
要求如下:
已知从这个程序执行到这个程序的所有进程结束这个时间段内,没有其它新进程执行。
1、请说出执行这个程序后,将一共运行几个进程。
2、如果其中一个进程的输出结果是“pid1:1001, pid2:1002”,写出其他进程的输出结果(不考虑进程执行顺序)。
学习linux下fork的运行机制
预备知识
1、进程可以看做程序的一次执行过程。
在linux下,每个进程有唯一的PID标识进程。
PID是一个从1到32768的正整数,其中1一般是特殊进程init,其它进程从2开始依次编号。
当用完32768后,从2重新开始。
2、linux中有一个叫进程表的结构用来存储当前正在运行的进程。
可以使用“ps aux”命令查看所有正在运行的进程。
3、进程在linux中呈树状结构,init为根节点,其它进程均有父进程,某进程的父进程就是启动这个进程的进程,这个进程叫做父进程的子进程。
4、fork的作用是复制一个与当前进程一样的进程。
新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
解题的关键
有了上面的预备知识,我们再来看看解题的关键。
我认为,解题的关键就是要认识到fork将程序切成两段。
看下图:
上图表示一个含有fork的程序,而fork语句可以看成将程序切为A、B两个部分。
然后整个程序会如下运行:
step1、设由shell直接执行程序,生成了进程P。
P执行完Part. A的所有代码。
step2、当执行到pid = fork();时,P启动一个进程Q,Q是P的子进程,和P是同一个程序的进程。
Q继承P的所有变量、环境变量、程序计数器的当前值。
step3、在P进程中,fork()将Q的PID返回给变量pid,并继续执行Part. B的代码。
step4、在进程Q中,将0赋给pid,并继续执行Part. B的代码。
这里有三个点非常关键:
1、P执行了所有程序,而Q只执行了Part. B,即fork()后面的程序。
(这是因为Q继承了P的PC-程序计数器)
2、Q继承了fork()语句执行时当前的环境,而不是程序的初始环境。
3、P中fork()语句启动子进程Q,并将Q的PID返回,而Q中的fork()语句不启动新进程,仅将0返回。
解题
下面利用上文阐述的知识进行解题。
这里我把两个问题放在一起进行分析。
1、从shell中执行此程序,启动了一个进程,我们设这个进程为P0,设其PID为XXX(解题过程不需知道其PID)。
2、当执行到pid1 = fork();时,P0启动一个子进程P1,由题目知P1的PID为1001。
我们暂且不管P1。
3、P0中的fork返回1001给pid1,继续执行到pid2 = fork();,此时启动另一个新进程,设为P2,由题目知P2的PID为1002。
同样暂且不管P2。
4、P0中的第二个fork返回1002给pid2,继续执行完后续程序,结束。
所以,P0的结果为“pid1:1001, pid2:1002”。
5、再看P2,P2生成时,P0中pid1=1001,所以P2中pid1继承P0的1001,而作为子进程pid2=0。
P2从第二个fork后开始执行,结束后输出“pid1:1001, pid2:0”。
6、接着看P1,P1中第一条fork返回0给pid1,然后接着执行后面的语句。
而后面接着的语句是pid2 = fork();执行到这里,P1又产生了一个新进程,设为P3。
先不管P3。
7、P1中第二条fork将P3的PID返回给pid2,由预备知识知P3的PID为1003,所以P1的pid2=1003。
P1继续执行后续程序,结束,输出“pid1:0, pid2:1003”。
8、P3作为P1的子进程,继承P1中pid1=0,并且第二条fork将0返回给pid2,所以P3最后输出“pid1:0, pid2:0”。
9、至此,整个执行过程完毕。
所得答案:
1、一共执行了四个进程。
(P0, P1, P2, P3)
2、另外几个进程的输出分别为:
pid1:1001, pid2:0
pid1:0, pid2:1003
pid1:0, pid2:0
进一步可以给出一个以P0为根的进程树:
验证
下面我们去linux下实际执行这个程序,来验证我们的答案。
程序如下图:
用gcc编译、执行后结果如下:
由于我们不太可能刚巧碰上PID分配到1001的情况,所以具体数值可能和答案有所差别。
不过将这里的2710看做基数的话,结果和我们上面的解答是一致的。
总结
这不是一道特别难或特别刁钻的题目,但是由于fork函数运行机制的复杂性,造就了当两个fork并排时,问题就变得很复杂。
解这个题的关键,一是要对linux下进程的机制有一定认识,二是抓住上文提到的几个关于fork的关键点。
朋友说,这个题给的时间是5分钟,应该说时间还算充裕,但是在面试的场合下,还是很考验一个人对进程、fork的掌握程度和现场推理能力。