1 进程及进程的创建

在linux编程中,用来创建用户进程的函数时fork。首先来说明什么是进程。

1 进程

什么是进程,引用百度百科的说明:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

通俗的说,一个程序在计算机中运行起来了,就是一个进程。

打个比方:程序好比郭德纲老师相声的剧本,而进程就好比郭德纲老师在台上表演。进程的生命周期就是郭德纲老师从上台开始表演,到表演结束。

2 fork函数

在创建进程之前,需要linux环境安装了gcc。

首先认识fork(),在linux中用man fork查看说明,由于说明很多,这里就大概截取一点,具体的可以自行查阅man说明文档
在这里插入图片描述从图1的说明文档中可以知道,fork函数的功能是创建一个子进程,需要包含的头文件是#include<sys/types.h> #include <unistd.h>,没有参数。还有很多描述,比如子进程拥有独立的进程ID,父子进程运行在独立的内存中等等,可以具体读下man的说明文档。
在这里插入图片描述
从图2,可以看出返回值,如果是父进程返回值为创建的子进程的PID;如果是子进程,返回为0;如果创建失败,返回-1。我们可以通过这一点来判断当前进程是父还是子。

3 进程运行内存说明

在图1中我们看到,父子进程运行的内存是隔离的,也就父子进程运行在相互独立的内存中

当子进程被创建出来的时候,相应的子进程的内存空间也被创建出来了,仿佛克隆了一份父进程的内存空间。

如果是运行在32位的机子上,那么fork后,内存如下图,
在这里插入图片描述
当fork()后,子进程仿佛copy一份父进程的内存空间,作为自己的内存空间。具体有以下内容:

全局变量,.text段,.rodata段,.data段,.bss段,heap,stack,env(环境变量),用户ID, 进程工作目录,信号处理方式等。

那我们计算机的内存都是固定的,比如4G/8G/16G等等。进程的内存如果是独立的,如果有很多进程,怎么够用呢?

哈哈,其实创建子进程的时候不是真正的copy了一份内存空间,而是遵循了读书共享写时复制的原则,这样就可以节省内存开销了。太优秀了!

其实进程用到的虚拟内存到物理内存的映射关系,还涉及到MMU内存管理单元,他实现了我们物理内存到虚拟内存的映射。这里边的关系,后面我单独写个博客来分析下,这里只需要知道父子进程之间内存是相互独立的,以及父子进程遵循读时共享写时复制的原则

4 利用fork()创建进程

这个很简单,直接上code

// creat_process.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
    pid_t pid;
    pid = fork();
    // if creat fail 
    if (pid == -1) {
        perror("creat child process fail!\n");
		exit(1);
    }
    // 利用返回值来判单是父还是子进程
    if (pid == 0){
        printf("I am the child, pid:%d\n", getpid());
    } else {
        printf("I am the parent, pid:%d\n", getpid());
    }

    return 0;
}

直接利用fork函数来创建子进程。

通过返回值的不同来判断是父进程还是子进程。依据上面图2中返回值得说明。

用gcc编译

gcc creat_process.c -o creat_process

然后执行结果如下:
在这里插入图片描述
从结果看,有父进程也有子进程,函数功能ok。

2 利用fork同时创建多个进程

1 错误的方法及分析

如果要创建多个进程,我们很容易想到的就是,直接加个for循环,就可以了。那么很容易想到的写法:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid;
    printf("this is a fork test code\n");
    int i;

    for (i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) {
            printf("i am %d child, pid = %u\n", i + 1, getpid());
        } else {
            printf("I am parent, pid = %u\n", getpid());
		}
    }

    return 0;
}

我们编译执行下:

hxf@hxf-VirtualBox:thread$ gcc fork_test.c -o fork_test             
hxf@hxf-VirtualBox:thread$ ./fork_test
this is a fork test code
I am parent, pid = 26654
I am parent, pid = 26654
I am parent, pid = 26654
I am parent, pid = 26654
I am parent, pid = 26654
i am 1 child, pid = 26655
I am parent, pid = 26655
I am parent, pid = 26655
hxf@hxf-VirtualBox:thread$ I am parent, pid = 26655
I am parent, pid = 26655
i am 2 child, pid = 26660
I am parent, pid = 26660
I am parent, pid = 26660
I am parent, pid = 26660
i am 3 child, pid = 26661
I am parent, pid = 26661
I am parent, pid = 26661
i am 4 child, pid = 26662
I am parent, pid = 26662
i am 2 child, pid = 26656
i am 5 child, pid = 26663
I am parent, pid = 26656
I am parent, pid = 26656
i am 5 child, pid = 26666
I am parent, pid = 26656
i am 4 child, pid = 26671
i am 3 child, pid = 26657
I am parent, pid = 26657
i am 5 child, pid = 26669
I am parent, pid = 26657
i am 5 child, pid = 26672
I am parent, pid = 26671
i am 4 child, pid = 26665
I am parent, pid = 26665
i am 3 child, pid = 26664
I am parent, pid = 26664
I am parent, pid = 26664
i am 5 child, pid = 26676
i am 4 child, pid = 26673
I am parent, pid = 26673
i am 5 child, pid = 26674
i am 4 child, pid = 26658
i am 4 child, pid = 26677
i am 5 child, pid = 26675
I am parent, pid = 26677
i am 5 child, pid = 26680
i am 5 child, pid = 26659
i am 3 child, pid = 26670
I am parent, pid = 26670
I am parent, pid = 26670
I am parent, pid = 26658
i am 5 child, pid = 26682
i am 5 child, pid = 26668
i am 4 child, pid = 26667
i am 5 child, pid = 26679
i am 5 child, pid = 26683
i am 5 child, pid = 26678
I am parent, pid = 26667
i am 4 child, pid = 26681
I am parent, pid = 26681
i am 5 child, pid = 26684
i am 5 child, pid = 26685

发现根本不对,出错在哪里呢?

首先我们想要的结果是,由父进程创建5个子进程,如下图:
在这里插入图片描述

哈哈,这里其实是没有理解创建子进程后,子进程和父进程是同时存在的,并且子进程是从创建的那一刻开始,拥有了父进程的执行程序的一切,比如全局变量,.text段,.rodata段,.data段,.bss段,heap,stack,env(环境变量) 等。所以其实当第一个子进程创建出来后,同样会继续执行fork(),所以子进程会创建出自己的子进程,这个时候子变成了父,生出了自己的子。这样,子生孙,孙生重孙,无穷尽也。

分析上面的结果,我们发现如下规律:

i am 1 child  出现了`1`=  (2^0)
i am 2 child  出现了`2`=  (2^1)
i am 3 child  出现了`4`=  (2^2)
i am 4 child  出现了`8`=  (2^3)
i am 5 child  出现了`16`=  (2^4)
I am parent   出现了`31`=  (2^0) + (2^1) + (2^2) + (2^3) + (2^4)  所有子进程之和

所以每生成一个子进程,就会对应一个父进程。也就是子必有父(感觉像废话,哈哈)。

上面的代码其实是创建了 2 * (1 + 2 + 4 + 8 + ... + 2^(N-1)) 个进程。并不是我们想要的N个进程。
这里用N = 3(上面的例子N=5,5太多啦,这里展示下过程)作为例子画下上面的创建过程:
在这里插入图片描述嗯,这个图,就一目了然了。

2 正确的方法

所以正确的做法是,不要让子再生子了,方法如下:

//fork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid;
    printf("this is a fork test code\n");
    int i;

    for (i = 0; i < 5; i++) {
        pid = fork();
        //不让子再生子了
        if (pid == 0) {
            break;
        }
    }

    if (i < 5) {
        sleep(i);
        printf("i am %d child, pid = %u\n", i + 1, getpid());
    } else {
        sleep(i);
        printf("I am parent, pid = %u\n", getpid());
    }
    return 0;
}

编译然后执行:
图3
从结果看,可以达到我们想要的结果的。

功成!

Logo

更多推荐