fork()函数介绍和创建多个子进程

文章结构

  • 常用函数
    • fork函数
    • getpid和getppid函数
    • getuid和getgid函数
    • getgid和getegid函数
  • 函数使用
    • 创建1个子进程:
    • 创建N个子进程
  • 总结

常用函数


fork函数

   #include 
   pid_t fork(void);

fork()函数被调用后会立即创建一个子进程,子进程和父进程独立运行互不干扰

返回值:在父进程中返回一个大于0的数,表示创建的子进程的id;在子进程中返回 0;-1表示创建失败。

getpid和getppid函数

   #include 

   pid_t getpid(void); // 获取当前进程的pid
   pid_t getppid(void); // 获取当前进程父进程的pid

getuid和getgid函数

	#include 

   	uid_t getuid(void); // 获取当前进程实际使用的用户id
   	uid_t geteuid(void); // 获取当前进程原本有效的用户id

补充:
区分一个函数是“系统函数”还是“库函数”依据:
① 是否访问内核数据结构
② 是否访问外部硬件资源
二者有任一 则为系统函数,二者均无 则为库函数;

getgid和getegid函数

   #include 

   gid_t getgid(void); // 获取当前进程使用用户组id
   gid_t getegid(void); // 获取当前进程原本有效用户组id

函数使用


创建1个子进程:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main(void)
  5 {
  6     pid_t pid = fork();
  7     if(pid == 0){
  8         printf("I'm subProcess, my pid = %d, ppid = %d\n", getpid(), getppid());
  9     }else if(pid > 0){
 10         printf("I'm parentProcess, my pid = %d, ppid = %d\n", getpid(), getppid());
 11         sleep(1); // Avoid father dying in front of son
 12     }else{
 13         printf("fork error!\n");
 14     }
 15     return 0;
 16 }

执行结果:

I'm parentProcess, my pid = 12050, ppid = 11620
I'm subProcess, my pid = 12051, ppid = 12050

创建N个子进程

下面以创建6个子进程为例:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 int main(void)
  6 {
  7     int i = 0;
  8     pid_t pid;
  9 
 10     printf("I'm start parent, pid = %d, ppid = %d\n", getpid(), getppid());
 11     for(;i < 6; i++)
 12     {
 13         pid = fork();
 14         if(pid == 0)
 15         {
 16             //printf("Subprocess do something. I'm pid = %d, ppid = %d\n", getpid(), getppid());
 17             break; // 要创建6个子进程这里就必须break跳出,否则子进程又会继续创建子进程,结果就会创建 2^6 - 1个子进程
 18         }else if(pid <0){
 19             perror("fork error!");
 20             exit(1);
 21         }
 22         //printf("I'm parent, pid = %d, ppid = %d, cpid = %d\n", getpid(), getppid(), pid);
 23         //sleep(1); // 防止父进程先结束,因为父进程先结束的话子进程的ppid就会等于1
 24     }
 25 	// 上面注释掉的语句与下面26-30行的语句等效,只是移除循环执行而已
 26     if(i < 6)
 27         printf("Subprocess do something. I'm pid = %d, ppid = %d\n", getpid(), getppid());
 28     if(i == 6)
 29         printf("I'm parent, pid = %d, ppid = %d\n", getpid(), getppid());
 30     sleep(1);
 31     return 0;
 32 }

执行结果:

I'm start parent, pid = 12115, ppid = 11620
I'm parent, pid = 12115, ppid = 11620
Subprocess do something. I'm pid = 12121, ppid = 12115
Subprocess do something. I'm pid = 12119, ppid = 12115
Subprocess do something. I'm pid = 12120, ppid = 12115
Subprocess do something. I'm pid = 12118, ppid = 12115
Subprocess do something. I'm pid = 12117, ppid = 12115
Subprocess do something. I'm pid = 12116, ppid = 12115

总结


  • 调用fork()函数会创建出一个进程,区分当前进程是父进程还是子进程可以根据fork函数的返回值来判断,如果返回值为0则当前进程是子进程,大于0则当前进程是父进程且这个大于0的数就是被创建的子进程的id。
  • 父子进程的相同和不同:
    1). 相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式
    2). 不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
  • 子进程复制了父进程0-3G即用户空间内容,以及父进程的PCB,但pid不同。但不是每fork创建一个子进程都要将父进程的0-3G用户空间地址空间完全拷贝一份,然后在映射至物理内存,而是遵循读时共享写时复制的原则,也就是如果子进程后面的代码不会修改程序数据,而只是读操作,则共享通一份物理地址空间,否则复制出单独的一块空间出来(注意:无论是读还是写内核空间都是共享的但pcb的id不同)。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
  • 父子进程不能通过全局变量进行数据传递,因为两个进程写时内存地址是共享同一块空间的,父子进程是相互独立的。

补充:
当我们在对含有fork的程序进行调试的时候是跟踪父进程还是子进程呢?
默认是跟踪父进程,如果需要跟踪子进程则需要**gdb调试时在调用fork函数之前**执行下面相应的命令:

set follow-fork-mode child  # 设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent # 设置跟踪父进程。

注意,一定要在fork函数调用之前设置才有效。

你可能感兴趣的:(多进程,c++,操作系统,linux)