fock 的意思是复制进程, 就是把当前的程序再加载一次, 不同之处在,加载后,所有的状态和当前进程是一样的(包括变量)。 fock 不象线程需提供一个函数做为入口, fock后,新进程的入口就在 fock的下一条语句。
一个现存进程调用f o r k函数是U N I X内核创建一个新进程的唯一方法(这并不适用于前节提及的交换进程、i n i t进程和页精灵进程。这些进程是由内核作为自举过程的一部分以特殊方式创建的。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
**返回:子进程中为0,父进程中为子进程ID,出错为-1

由f o r k创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程 I D。将子进程I D返回给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以获得其所有子进程的进程I D。f o r k使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用g e t p p i d以获得其父进程的进程I D (进程ID 0总是由交换进程使用,所以一个子进程的进程I D不可能为0 )。
 

子进程和父进程继续执行f o r k之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程所拥有的拷贝。父、子进程并不共享这些存储空间部分。如果正文段是只读的,则父、子进程共享正文段(见7 . 6节)。


现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在 f o r k之后经常跟随着e x e c。作为替代,使用了在写时复制( C o p y - O n - Write, COW)的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的“页”,做一个拷贝。
 

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


这种共享文件的方式使父、子进程对同一文件使用了一个文件位移量。考虑下述情况:一个进程f o r k了一个子进程,然后等待子进程终止。假定,作为普通处理的一部分,父、子进程都向标准输出执行写操作。如果父进程使其标准输出重新定向 (很可能是由s h e l l实现的),那么子进程写到该标准输出时,它将更新与父进程共享的该文件的位移量。在我们所考虑的例子中,当父进程等待子进程时,子进程写到标准输出;而在子进程终止后,父进程也写到标准输出上,
并且知道其输出会添加在子进程所写数据之后。如果父、子进程不共享同一文件位移量,这种形式的交互就很难实现。
如果父、子进程写到同一描述符文件,但又没有任何形式的同步(例如使父进程等待子进
程),那么它们的输出就会相互混合(假定所用的描述符是在 f o r k之前打开的)。
在f o r k之后处理文件描述符有两种常见的情况:
(1) 父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进
程终止后,它曾进行过读、写操作的任一共享描述符的文件位移量已做了相应更新。
(2) 父、子进程各自执行不同的程序段。在这种情况下,在f o r k之后,父、子进程各自关闭
它们不需使用的文件描述符,并且不干扰对方使用的文件描述符。这种方法是网络服务进程中
经常使用的。
除了打开文件之外,很多父进程的其他性质也由子进程继承:
* 实际用户I D、实际组I D、有效用户I D、有效组I D。
* 添加组I D。
* 进程组I D。
* 对话期I D。
* 控制终端。
* 设置-用户- I D标志和设置-组- I D标志。
* 当前工作目录。
* 根目录。
* 文件方式创建屏蔽字。
* 信号屏蔽和排列。
* 对任一打开文件描述符的在执行时关闭标志。
* 环境。
* 连接的共享存储段。
* 资源限制。
父、子进程之间的区别是:
* fork的返回值。
* 进程I D。
* 不同的父进程I D。
* 子进程的t m s _ u t i m e , t m s _ s t i m e , t m s _ c u t i m e以及t m s _ u s t i m e设置为0。
* 父进程设置的锁,子进程不继承。
* 子进程的未决告警被清除。
* 子进程的未决信号集设置为空集。
其中很多特性至今尚末讨论过,我们将在以后几章中对它们进行说明。
使f o r k失败的两个主要原因是:( a )系统中已经有了太多的进程(通常意味着某个方面出了问
题),或者( b )该实际用户I D的进程总数超过了系统限制。回忆表2 - 7,其中C H I L D _ M A X规定了
每个实际用户I D在任一时刻可具有的最大进程数。
f o r k有两种用法:
(1) 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程
中是常见的——父进程等待委托者的服务请求。当这种请求到达时,父进程调用 f o r k,使子进程处理此请求。父进程则继续等待下一个服务请求。
(2) 一个进程要执行一个不同的程序。这对 s h e l l是常见的情况。在这种情况下,子进程在从f o r k返回后立即调用e x e c (我们将在8 . 9节说明e x e c )。
某些操作系统将( 2 )中的两个操作( f o r k之后执行e x e c )组合成一个,并称其为s p a w n。U N I X将这两个操作分开,因为在很多场合需要单独使用 f o r k,其后并不跟随e x e c。另外,将这两个操作分开,使得子进程在f o r k和e x e c之间可以更改自己的属性。例如I / O重新定向、用户I D、信号排列等。在第1 4章中有很多这方面的例子。
#include <unistd.h>
#include <stdio.h>
int main()
{
        int pid;
        int pid2;
        //printf("%d ",getppid());
        printf("=============\n");
        printf("LLLLLLLLLLLLL");
        if((pid=fork())==0){
                printf("This is the child process:%d\n",pid);
        }
        else
        {
                if((pid2=fork())==0)
                        printf("This is another child process:%d\n ",pid2);
                else
                        printf("This is the parent process:%d  %d\n",pid,pid2);
        }
        printf("--------\n");
        return 1;
}
输出结果为
=============
LLLLLLLLLLLLLThis is the child process:0
--------
LLLLLLLLLLLLLThis is another child process:0
 --------
LLLLLLLLLLLLLThis is the parent process:22180  22181
--------

#include <unistd.h>
#include <stdio.h>
int main()
{
        int pid;
        int pid2;
        //printf("%d ",getppid());
        //printf("=============\n");
        printf("LLLLLLLLLLLLL");
        if((pid=fork())==0){
                printf("This is the child process:%d\n",pid);
        }
        else
        {
                if((pid2=fork())==0)
                        printf("This is another child process:%d\n ",pid2);
                else
                        printf("This is the parent process:%d  %d\n",pid,pid2);
        }
        printf("--------\n");
        return 1;
}

输出结果为:
LLLLLLLLLLLLLThis is the child process:0
--------
LLLLLLLLLLLLLThis is another child process:0
 --------
LLLLLLLLLLLLLThis is the parent process:22212  22213
--------

原因是 子进程同时复制父进程的缓存内容 printf()进行行缓存 在exit 之后才将数据写回标准输出文件中
对fork函数的体会 进程的创建
创建一个进程的系统调用很简单.我们只要调用fork函数就可以了.
#i nclude <unistd.h>
pid_t fork();
当一个进程调用了fork以后,系统会创建一个子进程.这个子进程和父进程不同的地方只有他的进程ID和父进程ID,其他的都是一样.就象符进程克隆(clone)自己一样.当然创建两个一模一样的进程是没有意义的.为了区分父进程和子进程,我们必须跟踪fork的返回值. 当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用.对于父进程fork返回子进程的ID,而对于fork子进程返回0.我们就是根据这个返回值来区分父子进程的. 父进程为什么要创建子进程呢?前面我们已经说过了Linux是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源.有时进程为了早一点完成任务就创建子进程来争夺资源. 一旦子进程被创建,父子进程一起从fork处继续执行,相互竞争系统的资源.有时候我们希望子进程继续执行,而父进程阻塞,直到子进程完成任务.这个时候我们可以调用wait或者waitpid系统调用。
 

 

 

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

int main(void)
{
        pid_t           pid;
        int             stat;

        pid = fork();
        if(pid < 0){
                perror("fork");
                return 1;
        }

        else if(pid == 0){
                printf("I am child process.\n");
                sleep(10);
                return 0;
        }

        else{
                printf("Send signal to child process (%d)\n", pid);
                sleep(1);
                kill(pid, SIGKILL);
                wait(&stat);
                if(WIFSIGNALED(stat)){//<sys/wait.h>定义的检查wait和waitpid所返回的 终止状态的宏,其中:WIFSIGNALED(stat),如果是异常终止子进程返回的状态则为真,WTERMSIG(stat)取使自3进程异常终止的信号编号。
                        printf("Child process received signal (%d).\n", WTERMSIG(stat));
                }
        }

        return 0;
}