进程-(2)

创建子进程

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\n");

  pid_t pid = fork();

  printf("end:%d\n",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\n");//要求:在父进程加上打印子进程ID

  pid_t pid = fork();//在子进程中加上打印父进程ID

  if(pid == 0)

  {//子进程

      printf("子进程pid=%d,父进程pid=%d\n",getpid(),getppid());

  }

  else

  {//父进程

    printf("父进程pid=%d,子进程pid=%d\n",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\n",i,i2,*pi,i4);
    printf("child:%p,%p,%p,%p\n",&i,&i2,pi,&i4);
    exit(0); //退出子进程
  }
  sleep(2); //可以确保子进程先进行
  printf("father:i=%d,i2=%d,*pi=%d,i4=%d\n",i,i2,*pi,i4);
  printf("father:%p,%p,%p,%p\n",&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\n",getpid(),getppid());
    sleep(3);
    printf("pid=%d,ppid=%d\n",getpid(),getppid());
    exit(0);
  }
  sleep(1);
}


结果如下:
aiyq195@aiyq195-virtual-machine:~/桌面/uc/day06$ ./fork5
pid=6463,ppid=6462
pid=6463,ppid=1
因为,父子进程都建立后,子进程的打印开始,也可能是父进程先开始,但是,父进程直接休眠了,然后是子进程开始休眠,父进程先休眠结束,接着结束,然后,子进程再次醒来,发现父进程已经死掉,则认1进程为父进程;

 

posted on 2017-02-22 10:47  aiyq195  阅读(202)  评论(0编辑  收藏  举报

导航