创建子进程
1、 fork() 函数,创建一个新进程
1) 如果创建失败, 出错返回-1
2) 由fork函数创建的进程叫子进程(child proccess)
3) 此函数调用一次,返回两次。分别在子进程和和父进程中返回,子进程中返回0,父进程返回子进程的PID
4) 子进程是父进程的副本,子进程获得父进程的数据空间,堆和栈的副本,但子进程共享父进程的正文段
5) fork之后 父子进程会继续执行。父进程先执行还是子进程先执行不确定
6) fork时,文件描述符也会被复制,那么两个进程可能会共享同一个文件表。
7) fork失败的原因
系统中有太多的进程
实际用户ID的进程总数已经超过系统限制。
8) fork的用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段
一个进程要执行一个不同的程序
fork() 通过复制父进程 创建子进程
9) 使用例子说明
fork() 非常简单的复杂函数。
pid_t fork(void);
2、 代码验证阶段:
例子1: fork()后的效果
#include <stdio.h>
#include <unistd.h>
int main(){
printf("begin ");
pid_t pid = fork();
printf("end:%d ",pid);
}
aiyq195@aiyq195-virtual-machine:~/桌面/uc$ gcc fork.c -o fork
aiyq195@aiyq195-virtual-machine:~/桌面/uc$ ./fork
begin
end:6090
end:0
结果如上:
产生上面的结果的思考,fork函数调用后,才会创建子进程,fork()之前的代码,因为只有父进程在运行,所以,只有一个begin打印出来,而后,fork()函数创建了子进程,这时候父子进程都会执行后续的代码,也就是fork()后面的printf()函数,会执行两遍;产生两个end;
fork()创建子进程后,fork()之前的代码只有父进程执行一次,fork()之后的代码父子进程都执行一次(一共执行二次)。
而返回的ID,fork()函数也会返回2次,父进程返回 子进程pid,子进程返回 0。
例子2: fork()后父子进程的查看
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("begin ");//要求:在父进程加上打印子进程ID
pid_t pid = fork();//在子进程中加上打印父进程ID
if(pid == 0)
{//子进程
printf("子进程pid=%d,父进程pid=%d ",getpid(),getppid());
}
else
{//父进程
printf("父进程pid=%d,子进程pid=%d ",getpid(),pid);
}
}
结果如下:
aiyq195@aiyq195-virtual-machine:~/桌面/uc/day06$ ./fork2
begin
父进程pid=6238,子进程pid=6239
子进程pid=6239,父进程pid=6238
可以看出:
fork()创建子进程后,规范中没有确定谁先运行,父子进程谁先运行 和 操作系统自身的算法有关,Linux中 父子进程谁先运行是 不确定的。
也就是说,这里可能子进程里获取到的父进程id,也有可能是1(即init进程)。
例子3:fork()查看变量
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int i = 10;//全局变量
int main()
{
int i2 = 10;//栈
int* pi = malloc(4);//堆
*pi = 10;
pid_t pid = fork();
int i4 = 10;//不是复制,是父子进程分别定义
if(pid == 0)
{//子进程
i = 20; i2 = 20; *pi = 20; i4 = 20;
printf("child:i=%d,i2=%d,*pi=%d,i4=%d
",i,i2,*pi,i4);
printf("child:%p,%p,%p,%p
",&i,&i2,pi,&i4);
exit(0); //退出子进程
}
sleep(2); //可以确保子进程先进行
printf("father:i=%d,i2=%d,*pi=%d,i4=%d
",i,i2,*pi,i4);
printf("father:%p,%p,%p,%p
",&i,&i2,pi,&i4);
}
结果如下:
aiyq195@aiyq195-virtual-machine:~/桌面/uc/day06$ ./fork3
child:i=20,i2=20,*pi=20,i4=20
child:0x804a030,0xbf954cc0,0x9260008,0xbf954cc4
father:i=10,i2=10,*pi=10,i4=10
father:0x804a030,0xbf954cc0,0x9260008,0xbf954cc4
可以说明:内存有复制;
fork()在创建子进程时,复制父进程的除代码区之外的内存区域,和父进程 完全共享代码区。
其实,上面的内容,通俗点说就是,fork()函数执行后,父进程还在原来的内存区域内,而它产生的子进程呢,会再找一个内存区域,来存放子进程自己的数据,子进程会把父进程的代码内容给拷贝过来,但是,它如果变化;它自己内部修改,它只会修改自己的那片内存区的数据,而父进程,不会因为子进程的改变,而改变;
例子4:fork()关于文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = open("a.txt",O_RDWR|O_CREAT,0666);
if(fd == -1)
{
perror("open"),exit(-1);
}
pid_t pid = fork();//先open()后fork() 一张文件表
if(pid == 0 )
{
write(fd,"abc",3);
close(fd);
exit(0);
}
sleep(1);
write(fd,"123",3);
close(fd);
}
结果如下:
aiyq195@aiyq195-virtual-machine:~/桌面/uc$ cat a.txt
abc123
fork()创建子进程,如果父进程有文件描述符,子进程将复制文件描述符,但不复制文件表。
如果文件在fork()函数之前打开,父子进程只有一个偏移量,也就是只有一张文件表:
也就是说,如果父进程有打开一个文件,子进程跟父进程会共用一个文件描述符,而且是针对一个文件;
例子5:fork()在文件打开之前
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
pid_t pid = fork();//先fork()后open(),两个文件表
int fd = open("a.txt",O_RDWR|O_CREAT,0666);
if(fd == -1)
{
perror("open"),exit(-1);
}
if(pid == 0 )
{
write(fd,"abc",3);
close(fd);
exit(0);
}
sleep(1);
write(fd,"123",3);
close(fd);
}
结果如下:
aiyq195@aiyq195-virtual-machine:~/桌面/uc$ ./fork4
aiyq195@aiyq195-virtual-machine:~/桌面/uc$ ls
a.txt fork2 fork3 fork4 fork5 fork.c
fork fork2.c fork3.c fork4.c fork5.c
aiyq195@aiyq195-virtual-machine:~/桌面/uc$ cat a.txt
123
fork()创建子进程之前,打开的文件,则父进程有文件描述符,子进程将复制文件描述符,但不复制文件表。也就是说这时候,有两个文件描述符,但是只有一个文件表,也就是一个文件偏移量;
如果创建子进程之后,才open的文件;则有两个文件描述符,两张表;
文件偏移量存在文件表中,不存在描述符中;
上面的内容,也就说明了,进程对于文件的操作内容:如果在fork之前,则父子进程共用一个文件表,有两个文件描述符,如果在fork之后,则有两个文件表,两个文件描述符。后执行的会吧先执行的给冲掉;
例子6:孤儿进程
思路:先打印 子进程的父进程,然后子进程sleep(),父进程在sleep()期间结束,子进程sleep
()结束后 再次打印父进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid == 0)
{//子进程满足条件,而父进程判断不满足
printf("pid=%d,ppid=%d
",getpid(),getppid());
sleep(3);
printf("pid=%d,ppid=%d
",getpid(),getppid());
exit(0);
}
sleep(1);
}
结果如下:
aiyq195@aiyq195-virtual-machine:~/桌面/uc/day06$ ./fork5
pid=6463,ppid=6462
pid=6463,ppid=1
因为,父子进程都建立后,子进程的打印开始,也可能是父进程先开始,但是,父进程直接休眠了,然后是子进程开始休眠,父进程先休眠结束,接着结束,然后,子进程再次醒来,发现父进程已经死掉,则认1进程为父进程;