fork与vfork(未完,正在每天更新中。。。。。)

一、fork系统调用

1、函数的声明:

#include <unistd.h>

pid_t fork(void);

2、返回值:

    fork函数调用一次,将会返回两次(返回给主进程为新创建的子进程的进程ID,返回给子进程的是0)。当进程创建失败时候,fork返回值为-1。

  • 因为父进程种可能有多个子进程,但没有一个函数可以获得所有子进程的进程ID,所以我们通过fork调用时候将新创建的进程的进程ID返回给主进程。
  • 由于应用程序中创建的进程的进程ID不可能为0,且我们知道进程ID是一个非负值,所以为了在后续代码中区分当前进程是主进程还是新创建的子进程,我们需要通过返回值来区分,因此fork将在子进程中返回0;

3、函数详细说明

1、子进程是父进程的副本   

子进程通过fork创建成功以后。子进程和父进程都将继续执行fork调用之后l的指令,子进程是父进程的副本(子进程获得父进程的数据空间:堆、栈副本)。这里我们特别要注意的是,子进程获得的是父进程的副本,而不是父子进程共享数据空间。但父子进程将会共享正文段。如下程序所示:

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

int main()
{
	int a = 9;
        pid_t pid = -1;

	if((pid = fork()) < 0)
	{
		//如果进程ID小于零则报错,退出
		printf("fork error!!\n");
		return -1;
	}
	else if(pid == 0)
	{
         //我们在子进程中将会获得主进程中自动变量a的一个副本,我们在子进程中a++操作将操作的是子进程中的副本而不是父进程中的自动变量a的值。 因此父进程中的变量a的值将不会改变。
	    a++;
	}
	return 0;
}

2、缓冲区被复制引发的问题

    当我们在调用 fork之前如果使用了标准I/O(带缓冲的I/O),则在使用 fork之前最好刷新(或冲洗)标准输出流。因为在 fork创建子进程后,系统会为子进程复制父进程数据空间以及标准输出缓冲区。而如果不刷新标准输出缓冲区,则调用 fork时候,之前通过标准输出流输出的数据还在缓冲区中,则子进程也就拥有了一份缓冲区的副本。如下程序所示:
#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid = -1;

	//printf 带缓冲的标准输出,当输出以后,数据将仍就存留在缓冲区中。随后,子进程会复制缓冲区中的数据。所以在子进程中将会再次输出: before fork
	printf("before fork!!\n");

	if((pid = fork()) < 0)
	{
		//如果进程ID小于零则报错,退出
		printf("fork error!!\n");
		return -1;
	}
	else if(pid == 0)
	{
       //子进程中我们什么都没有做
	}

	return 0;
}

如果没有改变标准输出(默认为终端)运行该程序,输出将为(该输出为调用fork之前,主进程中的printf输出):

before fork!!

而如果我们将该程序的标准输出重定向到文件中,我们将得到以下结果(第一个为调用 fork之前,主进程中的printf输出,第二个为缓冲区复制后在子进程中的输出):

before fork!!
before fork!!

3、标准输出重定向:

如果我们重定向了父进程的标准输出,那么子进程的标准输出也会被重定向。即:fork会将父进程中所有打开的文件描述符都复制到子进程中。

4、文件共享

从上面所述,我们已经了解到调用fork后,系统会将父进程中所有打开的文件描述符复制到子进程中。父进程与子进程将会共享所打开的文件表项,则父进程与子进程共享同一个文件偏移量。如果我们要满足子进程与父进程之间文件访问的同步,则在fork调用之后处理文件描述符有两种常见的情况:

    • 父进程等待子进程完成。
    • 父子进程各自执行不同的程序段。即,虽然子进程会复制父进程的文件描述符,但可以在子进程程序段开始关闭这些打开但不再需要的文件描述符,则可以避免父子进程之间的互相影响。

5、fork的两种用法:

    • 一个父进程希望自己被复制,使得父子进程同时执行不同的程序段,这在网络程序设计中是常见的。例如:父进程一直等待客户端的连接请求。当请求到达时候,父进程调用fork,使子进程处理刚到达的请求,而父进程继续等待下一个请求的到达。
    • 一个进程要执行一个不同的程序,这对shell是常见的。子进程从fork返回后立即调用exec。

6、另外的一点话:

某些操作系统讲fork与exec合并在一起,并称其为spawn。UNIX将其分开,可以使在fork和 exec之间做一些其他的必要的操作。如,重定向标准输出、设置用户ID等。另外有些时候,在我们调用fork之后没有必要执行exec。所以,Unix系统中并没有将fork与exec合并(UNIX在高级实现时候,选项组中确实包括了spawn接口,但是该接口并不打算代替fork和exec)。

二、vfork函数

1、函数声明:

       #include <sys/types.h>
       #include <unistd.h>

       pid_t vfork(void);

2、返回值:

vfork函数的返回值与fork函数返回值相同,参见fork函数返回值部分的说明。

3、函数详细说明:

1、创建进程的目的

vfork函数用于创建一个新进程,新进程的目的是执行(exec)一个新程序。

2、与fork函数一样都创建一个子进程。但它并不将父进程的地址空间完全复制到子进程中。因为子进程会立即调用exec函数,于是也就没有必要保存父进程的地址空间了。

3、vfork函数创建的子进程中,在调用exec之前,子进程是在父进程空间中执行的。因此,子进程会与父进程共享堆、栈,子进程中改变父进程中的变量会引起父进程中变量值的修改,如下:

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

int main()
{
	int a = 9;
    pid_t pid = -1;

	if((pid = vfork()) < 0)
	{
		//如果进程ID小于零则报错,退出
		printf("vfork error!!\n");
		return -1;
	}
	else if(pid == 0)
	{
		//我们在子进程中修改a的值,会改变父进程中自动变量a的值
		a++;
		printf("child process: a = %d  ppid = %d\n",a,getppid());
		_exit(0);
	}
	else
	{
		//我们在父进程中打印变量a的值。
		printf("parent process: a = %d  child_pid = %d\n",a,pid);
	}

	return 0;

}


输出结果如下:
child process: a = 10  ppid = 2893
parent process: a = 10  child_pid = 2894


从上面例子我们可以看出父进程中自动变量a的值被改变了。

4、vfork与fork之间的另外一个区别:

前面我们已经提到 vfork与fork之间的一个区别(是否会复制父进程的地址空间)。 vfork与fork之间的另外一个区别是, vfork会保证子进程首先运行,在调用exec或者exit之后,父进程才可能被调度运行。如果在这些函数(exec、exit)之前有依赖于父进程的进一步动作,则会 导致死锁

5、子进程中exit的使用




你可能感兴趣的:(unix,shell,网络,终端)