2011年(17)
分类: LINUX
2011-12-27 20:38:18
pit_t fork(void)
注意点:
1.当fork()结束后,会有两个进程存在。在父进程中,fork()返回子进程的ID,错误返回-1。在子进程中,fork()返回0。
2.两个进程执行相同的程序段,但是栈、堆、数据段是各自拥有的(最初是共享的)。子进程的栈、堆、数据段最初都是从父进程拷贝的。
3.进程可以调用getpid()得到自己的ID,调用getppid()获得父进程的ID。
4.fork()返回-1很有可能是该用户拥有的进程到达上限,或整个系统的进程到达上限。
5.fork()执行后,不能确定哪个进程会被先调度到(和线程相同)。像在父进程中使用sleep()来使子进程先运行的这种方法是不可靠的。
6.fork()的典型使用方法:
父进程和子进程文件的共享
当调用fork()后,子进程会复制父进程的文件描述符。该描述符包含了打开的文件和当前文件的偏移量(read(),write(),lseek()会修改该offset),和文件相关的标志。
父进程和子进程内存的共享
在早期linux中, fork()在创建子进程时会整个拷贝父进程的虚拟页表,这种方式是相当费时的,特别是调用fork()后,紧接着再调用会替代子进程整个空间的exec()函数,这时拷贝虚拟页表就显得多余了。
现代linux采用下面两种技术来避免在创建子进程时就马上拷贝虚拟页表:
1.内核把进程的代码段设置为只读,父进程和子进程都共享该段。
2.对于在数据段,堆,栈中的页(pages),内核使用写时复制(copy-on-write)。在最开始,内核让父子进程共享这些段中的页,并把这些页设置为可读。当任意一个进程要修改其中一个页时,内核负责把该页复制,然后让要修改该页的进程中的page-table entry指向复制后的页。
pid_t vfork(void)(该函数已被废弃)
注意点:
1.这是BSD实现的系统调用。它的出现是为了弥补fork()(未实现copy-on-write)性能上的缺失。通常在vfork()之后,会继续调用exec()。
2.vfork()函数不会对父进程的页表和页进行复制,父子进程共享相同的页。当子进程调用exec()或_exit()时,子进程才会创建自己的页表。如果子进程在vfork()和exec()/_exit()之间返回了,这将会影响父进程(父进程中的资源和堆栈都被清除了)。
4.文件描述表在vfork()中会被复制。这就意味着子进程在vfork()和exec()之间调用操作文件描述符的API不会影响到父进程。
5.父进程在执行vfork()后,会一直挂起,直到子进程执行了exec()或_exit()。这保证了子进程在父进程之前被调度。
创建进程后的竞争
先调度父进程还是子进程?
fork()后到底是先调度父进程(parent first after fork)还是子进程(children first after fork),这个没有定论。children first after fork是基于以下考虑:当fork()后,子进程紧接着调用exec()。而父进程在fork()后,马上修改数据。如果先调度父进程,那么内核会因为父进程需要修改共享的数据页,而把该页先复制,然后让父进程的页表中某一项指向该页。在完成上面一系列操作后,父进程才能修改数据。接下来调度子进程,exec()会把子进程的整个空间用其他程序替代。所以,如果先调度子进程,那么父进程中的复制根本没必要。
parent first after fork是基于以下考虑:当父进程执行fork()后,进程的状态都已经存在于CPU中,内存的管理信息也已近被缓冲进了TLB。如果这时切换到子进程,那么所有在TLB的数据很有可能无效,这样CPU又会重新装载TLB,这会浪费时间。所以,先调度父进程比较好。
Linux为了满足这两种情况,提供了/proc/sys/kernel/sched_child_runs_first参数来让管理员手动修改。
用信号进行同步
注意:我们在写程序时,始终认为fork()之后的父子进程调度顺序都是随机的。如果必须在程序中保持某种顺序,我们必须使用同步技术(Synchronization Technique)。同步技术有很多种比如信号量(Semaphores),文件锁(File Locks),用Pipes发送信息。在这里将展示怎么用信号来进行进程同步。