fork()的原理fork是什么么样的

博客访问: 791681
博文数量: 203
博客积分: 2743
博客等级: 少校
技术积分: 4004
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
前面已经谈了内核加载与系统引导过程,下面我们来看看内核的 do_fork() 函数是如何创建一个新的进程的。
在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是 sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数 do_fork() 来实现的。根据
调用时所使用的 clone_flags 参数不同,do_fork() 函数完成的工作也各异。
这部分内容简单,我不打算就此而展开分析。下面我们重点来讲解以下 do_fork() 函数的工作原理。
我们知道 do_fork() 函数生成一个新的进程,大致分为三个步骤。
1、建立进程控制结构并赋初值,使其成为进程映像。这个过程完成以下内容。
在内存中分配一个 task_struct 数据结构,以代表即将产生的新进程。
把父进程 PCB 的内容复制到新进程的 PCB 中。
为新进程分配一个唯一的进程标识号 PID 和 user_struct 结构。然后检查用户具有执行一个新进程所必须具有的资源。
重新设置 task_struct 结构中那些与父进程值不同的数据成员。
设置进程管理信息,根据所提供的 clone_flags 参数值,决定是否对父进程 task_struct 中的指针 fs 、files 指针等所选择的部分进行拷贝,如果 clone_flags 参数指明的是
共享而不是拷贝,则将其计数器 count 的值加 1 ,否则就拷贝新进程所需要的相关信息内容 PCB 。这个地方是区分 sys_fork() 还是 sys_clone() 。
2、必须为新进程的执行设置跟踪进程执行情况的相关内核数据结构。包括 任务数组、自由时间列表 tarray_freelist 以及 pidhash[] 数组。
这部分完成如下内容:
把新进程加入到进程链表中。
把新进程加入到 pidhash 散列表中,并增加任务计数值。
通过拷贝父进程的上、下文来初始化硬件的上下文(TSS段、LDT以及 GDT)。
3、启动调度程序,使子进程获得运行的机会。
这部分完成以下动作:
设置新的就绪队列状态 TASK_RUNING , 并将新进程挂到就绪队列中,并重新启动调度程序使其运行。
向父进程返回子进程的 PID,设置子进程从 do_fork() 返回 0 值。
下面就具体的 do_fork() 函数程序代码进行分析(该代码位于 kernel/fork.c 文件中)
int do_fork(unsigned long clone_flags,unsigned long stack_start, struct pt_regs *regs,
& && && && && & unsigned long stack_size)
& && &&&int& && && && && && &
& && &&&struct&&task_struct& &*p;
& && &&&struct&&completion& &
& && &&&retval = -EPERM ;
& && &&&if ( clone_flags & CLONE_PID )
& && && && &&&if ( current->pid )
& && && && && && && & goto fork_
& && &&&reval = -ENOMEM ;
& && &&&p = alloc_task_struct();& &&// 分配内存建立新进程的 task_struct 结构
& && &&&if ( !p )
& && && && && &goto fork_
& && &&&*p = *&&//将当前进程的 task_struct 结构的内容复制给新进程的 PCB结构
& && &&&retval = -EAGAIN;
& && &&&//下面代码对父、子进程 task_struct 结构中不同值的数据成员进行赋值
& && &&&if ( atomic_read ( &p->user->processes ) >= p->rlim[RLIMIT_NPROC].rlim_cur
& && && && && & && !capable( CAP_SYS_ADMIN ) && !capable( CAP_SYS_RESOURCE ))
& && && && && & goto bad_fork_
& && &&&atomic_inc ( &p->user->__count);& &//count 计数器加 1
& && &&&atomic_inc ( &p->user->processes);&//进程数加 1
& && &&&if ( nr_threads >= max_threads )
& && && && && &goto bad_fork_cleanup_
& && &&&get_exec_domain( p->exec_domain );
& && &&&if ( p->binfmt && p->binfmt->module )
& && && && && && &__MOD_INC_USE_COUNT( p->binfmt->module );&//可执行文件 binfmt 结构共享计数 + 1&
& && &&&p->did_exec = 0 ;& && && && && && && && && && && &&&//进程未执行
& && &&&p->swappable = 0 ;& && && && && && && && && && && &&//进程不可换出
& && &&&p->state = TASK_UNINTERRUPTIBLE ;& && && && && && &&//置进程状态
& && &&&copy_flags( clone_flags,p );& && && && && && && && &//拷贝进程标志位
& && &&&p->pid = get_pid( clone_flags );& && && && && && &&&//为新进程分配进程标志号
& && &&&p->run_list.next = NULL ;
& && &&&p->run_list.prev = NULL ;
& && &&&p->run_list.cptr = NULL ;
& && &&&init_waitqueue_head( &p->wait_childexit );& && && &&//初始化 wait_childexit 队列
& && &&&p->vfork_done&&= NULL ;
& && &&&if ( clone_flags & CLONE_VFORK ) {
& && && && && &p->vfork_done = &&
& && && && && &init_completion(&vfork) ;
& && &&&spin_lock_init( &p->alloc_lock );
& && &&&p->sigpending = 0 ;
& && &&&init_sigpending( &p->pending );
& && &&&p->it_real_value = p->it_virt_value = p->it_prof_value = 0 ;&//初始化时间数据成员
& && &&&p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0 ;& &&//初始化定时器结构
& && &&&init_timer( &p->real_timer );
& && &&&p->real_timer.data = (unsigned long)p;
& && &&&p->leader = 0 ;
& && &&&p->tty_old_pgrp = 0 ;
& && &&&p->times.tms_utime = p->times.tms_stime = 0 ;& && && && && &&&//初始化进程的各种运行时间
& && &&&p->times.tms_cutime = p->times.tms_cstime = 0 ;
#ifdef CONFIG_SMP& && && && && &&&//初始化对称处理器成员
& && &&&int& && &i;
& && &&&p->cpus_runnable = ~0UL;
& && &&&p->processor = current->
& && &&&for( i = 0 ; i < smp_num_ i++ )
& && && && && &&&p->per_cpu_utime[ i ] = p->per_cpu_stime[ i ] = 0;
& && &&&spin_lock_init ( &p->sigmask_lock );
& && &&&p->lock_depth = -1 ;& && &&&// 注意:这里 -1 代表 no ,表示在上下文切换时,内核不上锁
& && &&&p->start_time =& &// 设置进程的起始时间
& && &&&INIT_LIST_HEAD ( &p->local_pages );
& && &&&retval = -ENOMEM ;
& && &&&if ( copy_files ( clone_flags , p ))& && &//拷贝父进程的 files 指针,共享父进程已打开的文件
& && && && && & goto bad_fork_
& && &&&if ( copy_fs ( clone_flags , p ))& && && &//拷贝父进程的 fs 指针,共享父进程文件系统
& && && && && & goto bad_fork_cleanup_
& && &&&if ( copy_sighand ( clone_flags , p ))& &&//子进程共享父进程的信号处理函数指针
& && && && && & goto bad_fork_cleanup_
& && &&&if ( copy_mm ( clone_flags , p ))
& && && && && & goto bad_fork_cleanup_& && &&&//拷贝父进程的 mm 信息,共享存储管理信息
& && &&&retval = copy_thread( 0 , clone_flags , stack_start, stack_size , p regs );
& && && && && && && && && && && && && && && && &&&//初始化 TSS、LDT以及GDT项
& && &&&if ( retval )
& && && && && & goto bad_fork_cleanup_
& && &&&p->semundo = NULL ;& && && && && && && &&&//初始化信号量成员
& && &&&p->prent_exec_id = p-self_exec_
& && &&&p->swappable = 1 ;& && && && && && && && &//进程占用的内存页面可换出
& && &&&p->exit_signal = clone_flag & CSIGNAL ;
& && &&&p->pdeatch_signal = 0 ;& && && && && && &&//注意:这里是父进程消亡后发送的信号
& && &&&p->counter = (current->counter + 1) >> 1 ;//进程动态优先级,这里设置成父进程的一半,应注意的是,这里是采用位操作来实现的。
& && &&&current->counter >> =1;
& && &&&if ( !current->counter )
& && && && && & current->need_resched = 1 ;& && &&&//置位重新调度标记,实际上从这个地方开始,分裂成了父子两个进程。
& && &&&retval = p->
& && &&&p->tpid =
& && &&&INIT_LIST_HEAD( &p->thread_group );
& && &&&write_lock_irq( &tasklist_lock );
& && &&&p->p_opptr = current->p_
& && &&&p->p_pptr = current->p_
& && &&&if ( !( clone_flags & (CLONE_PARENT | CLONE_THREAD ))) {
& && && && && &&&p->opptr =
& && && && && &&&if ( !(p->ptrace & PT_PTRACED) )
& && && && && && && && & p->p_pptr =
& && &&&if ( clone_flags & CLONE_THREAD ){
& && && && && &&&p->tpid = current->
& && && && && &&&list_add ( &p->thread_group,&current->thread_group );
& && &&&SET_LINKS(p);
& && &&&hash_pid(p);
& && &&&nr_threads++;
& && &&&write_unlock_irq( &tasklist_lock );
& && &&&if ( p->ptrace & PT_PTRACED )
& && && && && && &send_sig( SIGSTOP , p ,1 );
& && &&&wake_up_process(p);& && &&&//把新进程加入运行队列,并启动调度程序重新调度,使新进程获得运行机会
& && &&&++total_&
& && &&&if ( clone_flags & CLONE_VFRK )
& && && && && && &wait_for_completion(&vfork);
& && &&&//以下是出错处理部分
& && &&&fork_out:
& && && && && && &
& && &&&bad_fork_cleanup_mm:
& && && && && && &exit_mm(p);
& && &&&bad_fork_cleanup_sighand:
& && && && && && &exit_sighand(p);
& && &&&bad_fork_cleanup_fs:
& && && && && && &exit_fs(p);
& && &&&bad_fork_cleanup_files:
& && && && && && &exit_files(p);
& && &&&bad_fork_cleanup:
& && && && && && &put_exec_domain( p->exec_domain );
& && && && && && &if ( p->binfmt && p->binfmt->module )
& && && && && && && && && && &&&__MOD_DEC_USE_COUNT( p->binfmt->module );
& && &&&bad_fork_cleanup_count:
& && && && && && &atomic_dec( &p->user->processes );
& && && && && && &free_uid ( p->user );
& && &&&bad_fork_free:
& && && && && && &free_task_struct(p);
& && && && && && &goto fork_
代码是分析完了,有两个方面的体会:
一、这个函数重点是理解进程分裂的部分,其中两次返回 pid 的值是理解的重中之重。
二、尽管我一直不主张在程序中大量使用 goto 语句,不得不承认,那些大牛的 goto 语句用在此处是恰到好处啊。^_^
阅读(6271) | 评论(1) | 转发(15) |
相关热门文章
给主人留下些什么吧!~~
这个貌似不是2.6以上内核的源码。
请登录后评论。linux 编程学习笔记(136)
前面对 fork 函数牛刀小试,相信你已基本掌握了简单的“影分身术”了,不过在篇末,却为各位留下了一些坑位。为了能够说明白一些问题,本篇将讨论有关进程的一些必备知识,以及 fork 函数的底层实现。如此一来,也方便加深对其它有关问题的理解。当然,如果你对此完全不感兴趣,大可跳过。
本文所讨论的范围,限制在 32 位的 linux 操作系统。
1. 进程空间
这里的进程空间,说的就是进程虚拟地址空间。
稍微懂点编程和操作系统的同学都应该知道,每个进程都有自己的 4GB 虚拟地址空间,这具有深远的意义。有个经典的 C 语言的例子,就是进程 A 中往地址 0x 写入一个 int 类型的整数 100,然后在进程 B 的地址0x 写入另一个 int 类型的整数 1000,最后两个进程再各自通过 printf 函数打印这两个地址保存的值,发现进程 A 仍然可以正常打印 100,而进程 B 正常打印出 1000.
这段代码,可以在windows 系统的 visual studio 模拟(没有使用 linux 是因为 linux 不直接提供在指定地址上分配内存的函数,另外在概念上,windows 的进程空间和 linux 是互通的,不会有太大区别)。
进程 A 的代码
int main()
int *buf = (int*)0x;
LPVOID res = VirtualAlloc((LPVOID)buf, sizeof(int), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (res != buf) {
printf("ERROR!\n");
*buf = 100;
printf("A = %d\n", *buf);
Sleep(3000);
进程 B 的代码
int main()
int *buf = (int*)0x;
LPVOID res = VirtualAlloc((LPVOID)buf, sizeof(int), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (res != buf) {
printf("ERROR!\n");
*buf = 1000;
printf("B = %d\n", *buf);
Sleep(3000);
然后开启了两个控制台窗口,同时(只要 A 进程没结束的情况下启动 B 程序就行了)运行这两个程序,结果如下。
形成图 1 中现像的原因在于进程 A 和进程 B 的地址空间是隔离的,尽管它们都在地址 0x 这个位置写了不同的值,但是各自都互不影响。这就好像我在我家菜地的5号地种的土豆,你在你家菜地的5号地种的黄瓜,根本就是谁也挨不着谁。
2 虚拟地址到物理地址的映射
对于进程来说,其实属于进程那一块菜地,是从虚拟的,是假的。这中间有一步到物理地址的映射过程。就好像上面的菜地,你看到的是菜地也是假的。要怎么理解这件事情,难道你看到的不是菜地吗?
2.1 种菜的故事
不妨假设一下,亲自下地种菜的人不是你本人,而是由另一个管家代劳。而你,只负责下达命令就行了。当你来到这个世上,你老爸就告诉你,你有一块的菜地,被分成了16个小块,你可以在任意一块地上随便种,你想种什么,种在哪块地上,和管家说一声就行了,你想从地上摘什么菜,也和管家说就行了。你爸把菜地大概的样子画给你看,就像图 2 中的那样。
除此之外,你还有一个弟弟,你老爸也和你弟说了相同的话,你弟弟也有和你一块一样大小的地,而且你弟弟要种菜,也只要给你家的管家下达命令就行。
就这样,你和你弟弟就在图 2 那样的菜地上,各自快乐的种着菜,生活了几年;可是你并不知道你家菜地真实情况是长啥样,你弟弟也同样不知道。你和你弟弟知道的菜地的样子,仅仅就是你老爸当初给你们画出来的图 2 的样子。
其实对你来说,菜地长啥样都无所谓了。你的菜地上有什么,统统问管家,你想在哪块地上种什么,也由管家代劳。
2.2 菜地长什么样
就这样,5年过去了。种了 5 年菜的你已经不耐烦了,突然有一天,你想亲自看看你的菜地。你不顾一切管家的阻挠,带着你弟弟,一起来到了所谓的菜地,这时候,你们慌了。你们看到的菜地,实际是图 3 这样的。根本就没有你和你弟弟的菜地,你们种的菜,全都只是在图 3 这块地上。
管家气喘吁吁的正好赶来了,你和你弟弟刚好和他对簿公堂。只有一块地,却骗了你们 5 年。管家是如何做到的?再三追问下,管家拿出了他的两本笔记本,说秘密都在这里了。其中一本记录了你的菜地使用情况,另一本记录了你弟弟的菜地使用情况。
管家继续说到,如果你说要去你的 5 号地取土豆,他就会翻翻笔记本,看看你的 5 号地对应实际哪块地,比如现在就对应着实际的 3 号地,管家就会去 3 号地把东西取给你。对你弟弟也是一样,只要拿出相应对应的笔记本就可以很快查到。这就好像是下面这个样子。
2.3 进程的那块地
看完前面的故事,我希望你能够触类旁通,每个进程生来认为自己有 4GB 的虚拟地址,可却被“管家”(操作系统)欺骗了一生,它没有你和你弟那么幸运,最终破解这个分地谜题。进程在自己的土地上心情的挥霍,它意识不到真正的菜地到底有多大(你的物理内存),只要“管家”能够满足它的需求就行了(管家甚至做了内存交换这种事,因为实际的土地只有 0 到 20 号,一共 21 块地,而你和你弟弟的地加起来有 32 块,万一土地不够了,管家就会偷偷的把你们不经常用的土地里的东西取到硬盘上)。
值得一说的是管家的笔记本,对应到进程这里就是页表的概念,有关这一块的知识,不详细展开,对细节感兴趣的同学,请参考我的另一个笔记。
3. fork 干了什么
有了进程空间的概念,就很容易知道 fork 做了什么事。以前面的菜地为例,再假设你没有弟弟。fork 的含义有点类似下面这样:
你告诉你老爸,给我生个弟弟出来。
你告诉你的管家,给我弟弟种一片和我的菜地一模一样的菜地出来。
这一切完成后,菜地应该是这样的。
图 5 你弟弟复制了一份你的菜地
那么再此之后,你种什么,你弟弟种什么就各不相干了。
相对于进程来说,这两个进程的进程空间是一模一样。
4. 上一篇的遗留问题
如果你完成上一篇最后一节总结里的实验,你会惊讶的发现,tmp 文件内容和直接运行 ./myfork 打印在屏幕上的结果是完全不一样的。
tmp 文件里的内容,应该像下面这样:
Hello, I'm father
before fork
I'm father 3542; my child is 3543
before fork
I'm child 3543; my father is 3542
执行 ./myfork 打印到屏幕上的内容,却是这样:
Hello, I'm father
before fork
I'm father 4382; my child is 4383
I'm child 4383; my father is 4382
你比较感兴趣的地方应该在于 tmp 文件里的 before fork 竟然出现了两次!这个谜题在这一篇相信你可以搞清楚,原因在于 printf 这个函数,它是带缓冲区的!!!
当 printf 接收到字符串后,第一件事情是把字符串复制到一个 char 数组(缓冲区)里,当这个数组遇到了特定的字符,比如 ‘\n’ 字符,回车或者装满等等,就会立即把字符写到屏幕终端上。
而当我们把 printf 重定向到文件的时候,如果 printf 函数遇到 ‘\n’ 字符,并不会立即把字符写到文件里,这是 printf 函数将字符定向到屏幕和文件的重要区别。
所以当 ./myfork & tmp 这个进程执行到 fork 的时候,printf 里的缓冲区数据还没来得及被刷新到 tmp 文件里,就被 fork 函数复制了,同时,printf 的缓冲区也被复制了一模一样的一份出来。这也就是为什么子进程里为什么子进程也会输出 before fork 的原因。
5. 关于 fork 函数的返回值
前一节讲了 fork 就是将进程的地址空间完完全全的复制了一份(不是100%复制,除少数几个地方外),实际在复制完后,操作系统会悄悄的修改复制出来的进程空间里的 fork 函数的返回值,把它改成 0(准确的说不是改,而是根本就没有复制原来的值,直接把它赋值为 0)。
这也就是子进程 fork 函数返回了 0 的原因。
6. 写时复制技术
到此 fork 并没结束。早在 linux 0.11 里,fork 函数就实现了 Copy On Write(COW) 机制,也就是写时复制技术。什么意思呢?
你告诉管家给你弟弟种一份一模一样的菜地时,管家并没有照做,他干了下面这样的事情:
图6 读时共享
不得不说,这管家已经懒到家了……但是也不得不说,管家很聪明。
当你弟弟只是问问管家,我的 5 号地种了什么?管家什么也不用管,直接查查笔记就告诉你弟弟,这是土豆就行了。
当你和你弟弟任何一方想挖掉在 5 号地上的土豆了怎么办?这岂不是很糟糕?不会的。聪明的管家早就意识到了这个问题。如果你弟弟需要挖掉他 5 号地,管家这时候才会真正的把你的 5 号地再复制一份出来,然后从真正的土地做一个映射,分配到你弟弟的 5 号地上,你弟弟爱怎么挖怎么挖,都不会影响到你。
图 7 写时复制
理解进程空间的概念,知道进程的地址是虚拟的
知道进程地址空间之间是隔离的
理解进程虚拟地址和物理地址之间的映射关系
理解 fork 函数的工作原理
理解为什么子进程的 fork 函数返回 0
理解写时复制技术
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:41483次
积分:2800
积分:2800
排名:第11482名
原创:245篇
转载:21篇
评论:29条
文章:41篇
阅读:3767
文章:33篇
阅读:9856
阅读:1400
文章:137篇
阅读:12936
(53)(67)(15)(12)(34)(10)(19)(26)(2)(6)(1)(2)(3)(5)(11)  进程和线程是我们平时接触的比较多的两个概念,特别是线程机制,很多语言原生就支持它。前段时间主要演示了下linux下进程和线程的创建,这篇文章对其创建的过程做一个简单的分析,错误之处,还请您斧正。  在linux下,线程其实就是一个轻量级的进程,所以其实现都是通过调用给do_fork函数传入不同的参数实现的。先来看下这几个函数:1&int&sys_fork(struct&pt_regs&*regs)2&{3&&&&&&&&&&return&do_fork(SIGCHLD,&regs-&sp,&regs,&0,&NULL,&NULL);4&}1&int&sys_vfork(struct&pt_regs&*regs)2&{3&&&&&&&&&return&do_fork(CLONE_VFORK&|&CLONE_VM&|&SIGCHLD,&regs-&sp,&regs,&0,4&&&&&&&&&&&&&&&&&&&&&&&&NULL,&NULL);5&}&&&1&sys_clone(unsigned&long&clone_flags,&unsigned&long&newsp,2&&&&&&&&&&&void&__user&*parent_tid,&void&__user&*child_tid,&struct&pt_regs&*regs)3&{4&&&&&&&&&if&(!newsp)5&&&&&&&&&&&&&&&&&newsp&=&regs-&6&&&&&&&&&return&do_fork(clone_flags,&newsp,&regs,&0,&parent_tid,&child_tid);7&}&  上面的代码中,并没有看到fork()函数的实现,其实fork函数的执行过程大致像这样:普通程序调用fork()--&库函数fork()--&系统调用(fork功能号)--&由功能号在 sys_call_table[]中寻到sys_fork()函数地址--&调用sys_fork(),这就完成拉从用户态到内核态的变化过程。所以,实际上,fork函数对应的实现就是sys_fork。   和上面的过程类似,上面的几个函数分别对应与fork,vfork和clone,可以看到,其实际上都是通过一个调用do_fork函数实现的,不同处只是其传入的参数不同。 首先来看看传入的参数的,先看看这些传入的参数分别代表的含义:cloning flags/**&cloning&flags:*/#define&CSIGNAL&&&&&&&&  && 0x000000ff&&&&&&/*&signal&mask&to&be&sent&at&exit&*/#define&CLONE_VM&&&&&&&&&&& 0x&&&&&&/*&set&if&VM&shared&between&processes&*/#define&CLONE_FS&&&&&&&  & 0x&&&&&&/*&set&if&fs&info&shared&between&processes&*/#define&CLONE_FILES&&&&   0x&&&&&&/*&set&if&open&files&shared&between&processes&*/#define&CLONE_SIGHAND& 0x&&&&&&/*&set&if&signal&handlers&and&blocked&signals&shared&*/#define&CLONE_PTRACE&&&&& 0x&&&&&&/*&set&if&we&want&to&let&tracing&continue&on&the&child&too&*/#define&CLONE_VFORK&&&&&& 0x&&&&&&/*&set&if&the&parent&wants&the&child&to&wake&it&up&on&mm_release&*/#define&CLONE_PARENT&&&& 0x&&&&&&/*&set&if&we&want&to&have&the&same&parent&as&the&cloner&*/#define&CLONE_THREAD&&&& 0x&&&&&&/*&Same&thread&group?&*/#define&CLONE_NEWNS&&&&&0x&&&&&&/*&New&namespace&group?&*/#define&CLONE_SYSVSEM& 0x&&&&&&/*&share&system&V&SEM_UNDO&semantics&*/#define&CLONE_SETTLS&&&&&& 0x&&&&&&/*&create&a&new&TLS&for&the&child&*/#define&CLONE_PARENT_SETTID&&&&& 0x&&&&&&/*&set&the&TID&in&the&parent&*/#define&CLONE_CHILD_CLEARTID&&&&0x&&&&&&/*&clear&the&TID&in&the&child&*/#define&CLONE_DETACHED&&&&&&&&&&&&& 0x&&&&&&/*&Unused,&ignored&*/#define&CLONE_UNTRACED&&&&&&&& &&& 0x&&&&&&/*&set&if&the&tracing&process&can't&force&CLONE_PTRACE&on&this&clone&*/#define&CLONE_CHILD_SETTID&&&&&&& 0x&&&&&&/*&set&the&TID&in&the&child&*/#define&CLONE_STOPPED&&&&&&&&&&   0x&&&&&&/*&Start&in&stopped&state&*/#define&CLONE_NEWUTS&&&&&&&&&&&&&&& 0x&&&&&&/*&New&utsname&group?&*/#define&CLONE_NEWIPC&&&&&&&&&&&&&&&& 0x&&&&&&/*&New&ipcs&*/#define&CLONE_NEWUSER&&&&&&&&&&&&& 0x&&&&&&/*&New&user&namespace&*/#define&CLONE_NEWPID&&&&&&&&&&&&&&&& 0x&&&&&&/*&New&pid&namespace&*/#define&CLONE_NEWNET&&&&&&&&&&&&&&& 0x&&&&&&/*&New&network&namespace&*/#define&CLONE_IO&&&&&&&&&&&&&&&&&&&&&&&&& 0x&&&&&&/*&Clone&io&context&*/& 然后来看看do_fork的具体过程: p = copy_process(clone_flags, stack_start, regs, stack_size,
child_tidptr, NULL, trace);  wake_up_new_task(p, clone_flags);    第一步是调用copy_process函数来复制一个进程,并对相应的标志位等进行设置,接下来,如果copy_process调用成功的话,那么系统会有意让新开辟的进程运行,这是因为子进程一般都会马上调用exec()函数来执行其他的任务,这样就可以避免写是复制造成的开销,或者从另一个角度说,如果其首先执行父进程,而父进程在执行的过程中,可能会向地址空间中写入数据,那么这个时候,系统就会为子进程拷贝父进程原来的数据,而当子进程调用的时候,其紧接着执行拉exec()操作,那么此时,系统又会为子进程拷贝新的数据,这样的话,相比优先执行子程序,就进行了一次&#8220;多余&#8221;的拷贝。&    从上面的分析中可以看出,do_fork()的实现,主要是靠copy_process()完成的,这就是一环套一环,所以在看内核的时候,会觉得一下子跳到这,一下子又跳到那,一下子就看晕了的一个很大的原因。不过我觉得这也是linux的一大好处,因为其提高了函数的可重用行,比如本文一开始提到的几个函数的实现,归根到底,都是通过do_fork()实现的。&  接着再来看看copy_process()的实现:p = dup_task_struct(current); 为新进程创建一个内核栈、thread_iofo和task_struct,这里完全copy父进程的内容,所以到目前为止,父进程和子进程是没有任何区别的。检查所有的进程数目是否已经超出了系统规定的最大进程数,如果没有的话,那么就开始设置进程描诉符中的初始值,从这开始,父进程和子进程就开始区别开了。设置子进程的状态为不可被TASK_UNINTERRUPTIBLE,从而保证这个进程现在不能被投入运行,因为还有很多的标志位、数据等没有被设置。复制标志位(falgs成员)以及权限位(PE_SUPERPRIV)和其他的一些标志。调用get_pid()给子进程获取一个有效的并且是唯一的进程标识符PID。根据传入的cloning flags(具体表示上面有)对相应的内容进行copy。比如说打开的文件符号、信号等。父子进程平分父进程剩余的时间片。返回一个指向子进程的指针。  至此,do_fork的工作就基本结束了。 &&&&&
阅读(...) 评论()}

我要回帖

更多关于 forkjoinpool 原理 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信