您的位置:首页 > 产品设计 > UI/UE

(APUE点滴记录) 进程控制之fork与vfork

2013-03-02 10:59 302 查看
2013-03-02 wcdj

1 fork —— “生个新娃”

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int glob = 6;// external variable in initialized data
char buf[] = "a write to stdout\n";

int
main(int argc, char** argv)
{
	int var;// automatic variable on the stack
	pid_t pid;

	var = 88;

	if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
		printf("write error\n");

	printf("before fork\n");// we do not flush stdout

	if ((pid = fork()) < 0)
	{
		printf("fork error\n");
	}
	else if (pid == 0)// child
	{
		printf("child's parent pid:%d\n", getppid());
		glob++;
		var++;
	}
	else
	{
		sleep(3);// parent
	}

	printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);

	exit(0);
}

/*
output:

mba:APUE gerryyang$ gcc -o fork fork.c 
mba:APUE gerryyang$ ./fork 
a write to stdout
before fork
child's parent pid:574
pid = 575, glob = 7, var = 89
pid = 574, glob = 6, var = 88
mba:APUE gerryyang$ ./fork > tmp.out
mba:APUE gerryyang$ cat tmp.out 
a write to stdout
before fork
child's parent pid:617
pid = 618, glob = 7, var = 89
before fork
pid = 617, glob = 6, var = 88
 */
在UNIX环境下,一个现有进程可以使用fork函数创建一个新的进程(子进程child process)。

#include <unstd.h>

pid_t fork();

需要注意:

(1) 返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1。

(2) fork函数被调用一次,但返回两次。两次返回的唯一区别是:子进程的返回值是0,而父进程的返回值是新子进程的进程ID。

(3) 进程ID 0 总是由内核交换进程使用,所以一个子进程的进程ID不可能为0。

(4) 子进程可以调用getppid以获得其父进程的进程ID。

(5) 子进程和父进程继续执行fork调用之后的指令。

(6) 子进程是父进程的“副本”。例如,子进程获得父进程数据空间、堆和栈的“副本”。注意,父、子进程并不共享这些存储空间部分。父、子进程共享正文段。

注意:由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、堆和栈的完全复制,而使用“写时复制(Copy-On-Write, COW)”技术,这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的,如果父、子进程中任何一个试图修改这些区域,则内核只为修改区域的那块内存***一个副本,通常是虚拟存储器系统中的一“页”。

(7) 一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。如果要求父、子进程之间相互同步,则要求某种形式的IPC。

(8) 缓冲区的复制。write函数是不带缓冲的,但是标准I/O库是带缓冲的。当以交互方式运行时,只得到该printf输出的行一次,其原因是标准输出缓冲区由换行符冲洗;但是当将标准输出重定向到一个文件时,却得到printf输出行两次,其原因是,在fork之前调用了printf一次,但当调用fork时,该行数据仍在缓冲区中,然后在将父进程数据空间复制到子进程中时,该缓冲区也被复制到子进程中。于是那时父、子进程各自有了带该行内容的标准I/O缓冲区。在exit之前的第二个printf将其数据添加到现有的缓冲区中,当每个进程终止时,最终会冲洗其缓冲区中的副本。

(9) 文件共享。在重定向父进程的标准输出时,子进程的标准输出也被重定向。实际上,fork的一个特性是:父进程的所有打开文件描述符都被复制到子进程中。父、子进程的每个相同的打开描述符共享一个文件表项。这种共享文件的方式使父、子进程对同一文件使用了一个文件偏移量。

注意:如果父、子进程写到同一个描述符文件,但又没有任何形式的同步,那么它们的输出就会混乱。

在fork之后处理文件描述符有两种常见的情况:

【1】父进程等待子进程完成。

在这种情况下,父进程无需对其描述符做任何处理,当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件偏移量已执行了相应更新。

【2】父、子进程各自执行不同的程序段。(常用)

在这种情况下,在fork之后,父、子进程各自关闭它们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。

(10) 除了打开文件之外,父进程的很多其他属性也由子进程继承。

(11) fork失败的两个主要原因是:

【1】系统中已经有了太多的进程。

【2】该实际用户ID的进程总数超过了系统限制。CHILD_MAX规定了每个实际用户ID在任一时刻可具有的最大进程数。

(11) fork的两种用法:

【1】一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中常见的,父进程等待客户端的服务请求,当这种请求到达时,父进程调用fork,使子进程处理此请求,父进程则继续等待下一个服务请求到达。

【2】一个进程要执行一个不同的程序。这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec。

2 vfork——“子母同体,用于spawn”

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int glob = 6;// external variable in initialized data

int
main(int argc, char** argv)
{
	int var;// automatic variable on the stack
	pid_t pid;

	var = 88;

	printf("before fork\n");// we do not flush stdout

	if ((pid = vfork()) < 0)// use vfork
	{
		printf("fork error\n");
	}
	else if (pid == 0)// child
	{
		printf("child's parent pid:%d\n", getppid());

		// sleep a well for doing something
		sleep(3);
		printf("child done\n");

		glob++;
		var++;

		_exit(0);// child terminates
	}

	// parent continues here
	printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);

	exit(0);
}

/* 
output:

mba:APUE gerryyang$ gcc -o vfork vfork.c
mba:APUE gerryyang$ ./vfork 
before fork
child's parent pid:1030
child done
pid = 1030, glob = 7, var = 89
*/
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。

#include <unistd.h>

pid_t vfork();

vfork -- spawn new process in a virtual memory efficient way

DESCRIPTION

Vfork() can be used to create new processes without fully copying the address space

of the old process, which is horrendously inefficient in a paged environment. It is

useful when the purpose of fork(2) would have been to create a new system context for

an execve. Vfork() differs from fork in that the child borrows the parent's memory

and thread of control until a call to execve(2) or an exit (either by a call to

exit(2) or abnormally.) The parent process is suspended while the child is using its

resources.

需要注意:

(1) vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。

(2) vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会访问此地址空间。在子进程调用exec或exit之前,它在父进程的空间中运行,这种优化工作方式在某些UNIX的页式虚拟存储器实现中提高了效率。不复制比COW的部分复制要更快一些。

(3) vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

注意:如果在调用这两个函数之前,子进程依赖于父进程的进一步动作,则会导致死锁。

(4) 代码片段中,子进程对变量glob和var进行了改变,结果也改变了父进程中的变量值。因为子进程在父进程的地址空间中运行。

(5) _exit和exit的区别。_exit不执行标准I/O缓冲的冲洗操作。大多数exit的现代实现不再在流的关闭方面自找麻烦,因为进程即将终止,那时内核将关闭在进程中已打开的所有文件描述符,在库中关闭它们,只是增加了开销而不会带来任何益处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: