pid_t fork(void);
函数返回类型pid_t实质是int类型
fork函数会新生成一个进程,调用fork函数的进程为父进程,新生成的进程为子进程。在父进程中返回子进程的PID,在子进程中返回0,失败返回-1
fork是把已有的进程复制一份,当然把PCB也复制了一份,然后申请一个PID,子进程的 PID(父进程的)+1;
子进程的PID=父进程的PID+1;
如果父子进程想要做不同的事情,那么我们通过返回值来判断;
#include
#include
#include
#include
#include
int main()
{
char *s=NULL;
int n=0;//控制父子进程执行的次数
pid_t id=fork();
assert(id!=-1);
if(id==0)//子进程
{
s="child";
n=3;
}
else
{
s="parent";
n=7;
}
//父子进程一起运行
int i=0;
for(;i
getppid:得到一个进程的父进程的PID;
getpid:得到当前进程的PID;
for(;i
父子程序交换打印,并不是先打印完其中一个再进行下一个,显然,父子进程在并发进行
扩展知识:
UNIX中的进程创建是通过内核系统调用fork()实现的。当一个进程产生一个fork请求时,操作系统执行以下功能:
1)为新进程在进程表中分配一个空项。
2)为子进程赋一个唯一的进程标识符。
3)做一个父进程上下文的逻辑副本,不包括共享内存区。
4)增加父进程拥有的所有文件的计数器,以表示有一个另外的进程现在也拥有这些文件。
5)把子进程置为就绪态。
6)向父进程返回子进程的进程号;对子进程返回零。
所有这些操作都在父进程的内核态下完成。
在当内核完成这些功能后可以继续下面三种操作之一,它们可以认为是分派器例程的一个部分:
- 在父进程中继续执行。控制返回用户态下父进程进行fork调用处。
- 处理器控制权交给子进程。子进程开始执行代码,执行点与父进程相同,也就是说在fork调用的返回处。
- 控制转交给另一个进程。父进程和子进程都置于就绪状态。
这种创建进程的方法中父进程和子进程都执行相同的代码。其区别在于:当从fork中返回时,测试返回参数,如果值为零,则它是子进程,可以转移到相应的用户程序中继续执行;如果值不为零,则它是父进程,继续执行主程序。
物理内存是系统硬件提供的内存大小,是真正的内存,相对于物理内存,在Linux下还有一个虚拟内存的概念。虚拟内存的存在就是为了满足物理内存的不足而提出的策略,它利用磁盘空间虚拟出的一块逻辑内存。用作虚拟内存的磁盘空间被称为交换空间。
进程中看到的地址都是逻辑地址;
虚拟内存提供的三个重要的能力:
1) 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,根据需要在磁盘和主存之间来回传送数据,使得能够运行比内存大的多的进程。
2) 它为每个进程提供了一致的地址空间,从而简化了存储器管理;
3) 它保护每个进程的地址空间不被其他进程破坏 ;
传统的fork系统调用直接把所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟糕的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。
Linux的fork()使用写时拷贝页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。
只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,知识以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。在页根本不会被写入的情况下(举例:fork()后立即调用exec())他们就无需复制了。
fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆数据)。由于Unix强调进程快速执行的能力,所以这个优化是很重要的。
不采用写时拷贝技术,第一,复制开销比较大;第二,占用内存空间;
所以我们对fork复制进程的过程做了一个优化:-------写时拷贝技术。
引入了写时拷贝技术,就可以延迟页面的拷贝,甚至免除页面的拷贝
还有一个需要注意的地方:写时拷贝是以页为单位的,哪怕这个页中只有一个字节被修 改了,我们也需要将整个页面都复制出来一份
(1)打印几个A?产生了几个进程?
#include
#include
#include
int main()
{
int i=0;
for(;i<2;i++)
{
fork();
printf("A\n");
}
exit(0);
}
打印6个A,共有4个进程。
i=0时,父进程产生一个子进程1,父进程输出一个A,子进程1中继续fork以下代码,输出一个A;父进程i=1,子进程1i=1,此时父进程再产生一个子进程2,输出一个A,子进程1再产生一个子进程(孙进程),输出一个A,i=2程序结束。
(2)打印几个A?
#include
#include
#include
int main()
{
int i=0;
for(;i<2;i++)
{
fork();
printf("A");
}
exit(0);
}
打印8个A。
i=0时,父进程产生一个子进程1,父进程在缓冲区里放一个A,子进程1中继续fork以下的代码,缓冲区里存放一个A;父进程中i=1,子进程1中i=1,此时父进程再产生一个子进程2(这个子进程2将父进程缓冲区中里的A也复制过去),此时父进程i=2判定函数结束,输出两个A,子进程2,也输出两个A(有一个A是从父进程缓冲区复制过去的);子进程1产生子进程(孙进程),孙进程把子进程1的缓冲区中的A也复制过去,此时子进程1判定结束,输出两个A,孙进程判定结束输出两个A,所以一共是8个A。
(3)打印几个A?
#include
#include
#include
int main()
{
fork()||fork();
printf("A\n");
exit(0);
}
3个A。
首先在父进程中fork出一个子进程,父进程中第一个fork()返回大于0,||后就不用判定,此时父进程输出一个A;在子进程第一个fork()返回0,继续判定||后的fork(),此时子进程又产生一个相对于自己的子进程(孙进程),在子进程中第二个fork()返回大于0,输出一个A,孙进程中输出一个A。
(4) 打印几个A?
#include
#include
#include
int main()
{
fork()&&fork();
printf("A\n");
exit(0);
}
共创建了3个进程
打印3个A。
(5)打印结果是什么?
#include
#include
#include
int main()
{
printf("A");
write(1,"B",1);
fork();
exit(0);
}
打印结果是BAA
父进程在缓冲区中存放一个A,write向程序中写入一个B,父进程fork产生一个子进程(子进程将父进程缓冲区中里的A也复制过去),进程结束,再将缓冲区中的内容输出,所以打印结果为“BAA”。