精华内容
下载资源
问答
  • fork函数

    2020-12-27 00:00:30
    fork函数fork函数简介实验前的准备1.安装虚拟机VMware,配置系统环境2.学习Linux的命令使用3.拷入代码,运行产生实验结果分析fork函数fork1()fork6()总结 fork函数简介 fork()函数将运行着的程序分成2个(几乎)完全...

    fork函数简介

    fork()函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。我把这两个进程分别称之为父进程和子进程。

    实验前的准备

    1.安装虚拟机VMware,配置系统环境

    具体安装过程只需稍微百度一下便可知晓,这里就不过多赘述。

    2.学习Linux的命令使用

    为了运行forks函数的代码,我们需要提前了解Linux的命令使用

    1.命令:cd 目录

    cd / 切换到根目录
    cd /usr 切换到根目录下的usr目录
    cd …/ 切换到上一级目录 或者 cd …
    cd ~ 切换到home目录
    cd - 切换到上次访问的目录

    2.命令:find 目录 参数 文件名称

    示例:find /usr/tmp -name ‘a*’ 查找/usr/tmp目录下的所有以a开头的目录或文件

    3.命令:kill pid 或者 kill -9 pid(强制杀死进程) pid:进程号

    3.拷入代码,运行产生实验结果

    注:我的电脑不能直接将原电脑的文件直接拖入虚拟机,所以是通过QQ邮箱来发送和接受代码文件,产生这种情况的原因尚未知晓。

    分析fork函数

    fork2()


    实验结果:
    在这里插入图片描述
    分析:在这里插入图片描述
    如图所示的进程图即可说明,每当其运行到fork()函数时,就会分出子进程和父进程,L0在第一个fork()之前,所以只打印一次,而L1在两个fork()之间,则打印两次,而bye在两个fork()后面,所以打印四次。
    注:因为子进程和父进程运行随机,不同时间运行时,打印出的结果会不同,打印次序会变化。

    fork1()

    在这里插入图片描述
    显示结果:
    在这里插入图片描述
    分析:子进程时pid==0,打印"child …"部分,++x得x == 2,父进程时,pid == 1,打印"parent …"部分,–x得x == 0,而无论是父进程还是子进程,执行玩if语句后都会执行后续的printf(bye …),所以最终的显示结果为四行语句,且显示顺序可以不同。

    fork9()

    在这里插入图片描述
    显示结果为:
    在这里插入图片描述
    分析:fork()函数的父进程和子进程的执行顺序随机,图中所示情况为先执行父进程,打印了HP,而后出现了wait()语句,意思为等待子进程结束,所以此时子进程随比父进程满,它的打印仍然是HC先于父进程的CT,最后进程结束后打印bye,下图为流程示意图。
    在这里插入图片描述

    fork7()

    在这里插入图片描述
    显示结果:
    在这里插入图片描述
    分析:从结果上看不出有什么问题,但程序并未终止,父进程有一个无限循环,子进程结束后,如果父进程一直不结束,会变为僵死进程,这时需要杀死它的父进程才能杀死僵死进程。

    fork8()

    在这里插入图片描述
    显示结果:
    在这里插入图片描述
    分析:和fork7()一样,虽然结果显示看起来并无问题,但其实程序运行后没有结束,观察程序可知,子进程中有一个无限循环,这时可用kill直接杀死。

    fork14()

    在这里插入图片描述
    实验结果:
    在这里插入图片描述
    而后进程会卡死。
    分析:孩子进程正常退出时,会向父进程发送 SIGCHLD 信号,signal 函数接收到该信号,调用 handler 函数处理handler 函数等待一个子进程结束,然后 ccount 会减一父进程由于 ccount = N,一开始会陷入死循环,直到子进程全部被回收为止但是如果已有一个子进程先 sleep 结束向父进程发送信号,到父进程处理该信号之前,所有的子进程发送的 SIGCHLD 信号都会被弃,因此这个函数的输出是不确定的,并且如果子进程未全部结束,父进程会一直持续下去,此时挂起当前进程后使用 ps 命令查看则会看到已经结束的子进程会变成僵死进程。

    总结

    上述的几个fork()函数涉及到几个不同的语句,fork2()是最基本的fork()函数进程,fork1()涉及if()语句,fork7()和fork8()涉及无限循环语句,fork9()涉及wait()函数,而fork16()则与信号有关。通过对上述fork()函数的运行和分析,自己对fork()函数有了一个清晰的认识,懂得了wait()以及信号的理解方式,对今后的学习铺好了基础。

    展开全文
  • Fork函数

    2019-11-07 22:14:12
    父进程通过调用fork函数来创建一个新的运行的子进程。 父进程和子进程之间最大的区别就是PID不同 1)在父进程中,fork返回新创建子进程的PID; 2)在子进程中,fork返回0; 3)如果出现错误,fork返回一个负值 fork...

    fork()的基础知识
    父进程通过调用fork函数来创建一个新的运行的子进程。
    父进程和子进程之间最大的区别就是PID不同
    1)在父进程中,fork返回新创建子进程的PID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值
    fork()的特点
    调用一次,返回两次
    一次只在调用进程(父进程)中,fork返回子进程的PID。
    一次是在新创建的子进程中,fork返回0。

    并发执行
    父进程和子进程是并发运行的独立进程。
    内核能够以任意方式交替执行他们的逻辑控制流中的指令。

    相同但是独立的地址空间
    父进程和子进程会有相同的用户栈、相同的本地变量值、相同的堆、全局变量以及代码。
    但是,父进程和子进程都是独立的进程,他们都有自己的私有地址空间。

    共享文件
    子进程可以读写父进程中打开的任何文件

    有关fork()代码分析
    在看代码之前我们要了解一下有关 进程图 的知识:

    进程图是刻画程序语句偏序的一种简单的前驱图,每个顶点a对应于一条程序语句的执行
    有向边a—>b代表a发生在语句b之前,边上可以标记一些信息,例如一个变量的当前值。

    画出 进程图 方便我们理解fork调用程序的情况。

    1.几个基本的嵌套循环的例子

    void fork1()
    {
       int x = 1;
       pid_t pid = fork();
    
       if (pid == 0) {
       printf("Child has x = %d\n", ++x);
       } 
       else {
       printf("Parent has x = %d\n", --x);
       }
       printf("Bye from process %d with x = %d\n", getpid(), x);
    }
    

    运行结果和流程图:
    在这里插入图片描述
    子进程将x+1输出,父进程将x-1输出,二者的值没有相互影响,这是为什么呢?这个时候要提及到两个概念,一个是逻辑地址(虚拟地址),一个是物理地址。

    逻辑地址:CPU所生成的地址。
    根据内存管理和分页机制,CPU产生的逻辑地址分为:页号,它包含每个页在物理内存中的基址,用来做页表的索引;页偏移,同基址相结合,用来确定送入内存设备的物理内存地址。

    物理地址:内存单元中存储数据的实际地址。
    用户程序看不到物理地址。用户程序只生成逻辑地址。逻辑地址与物理地址呈现一一映射的关系。

    fork函数会产生一个和父进程完全相同的子进程,一般情况下,子进程会调用exec函数族去执行新的程序,这个时候子进程就会有新的栈、堆、数据段和代码段。Linux系统经过不断发展,从效率的角度出发,引入了写时复制技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

    写时复制:
    在fork之后exec之前两个进程用的是相同的物理空间(内存区),
    子进程的代码段、数据段、堆栈都是指向父进程的物理空间,
    也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。
    当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,
    如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),
    而代码段继续共享父进程的物理空间(两者的代码完全相同)。
    而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

    子进程在执行过程中改变了x的值,这个时候系统发现了这个操作,就会给子进程分配新(新指物理地址上的新)的栈、堆和数据段,并且将父进程中fork之前的变量拷贝一份(拷贝的值和父进程中的值就没有任何关系了),然后子进程再对这些值进行操作,所以二者虽然用的是相同的变量名,但是在物理地址上已经完全不相同了。

    /*
     * fork2 - Two consecutive forks
     * Both parent and child can continue forking
     * Ordering undetermined
     */
    void fork2()
    {
        printf("L0\n");
        fork();
        printf("L1\n");    
        fork();
        printf("Bye\n");
    }
    dida@dida-VirtualBox:~/code$ ./f
    L0
    L1
    Bye
    dida@dida-VirtualBox:~/code$ Bye
    L1
    Bye
    Bye
    

    在这里插入图片描述
    .使用waitpid函数
    在介绍waitpid函数之前,我们先来了解一下有关 回收子进程 的相关知识

    回收子进程
    当一个进程由于某种原因终止时,内核并不是立即把他从系统中清除。相反,进程被保持一种已终止状态中,直到被他的父进程回收。一个终止了但还未被回收的进程称为僵死进程。
    即使僵死子进程没有运行,他们仍然消耗系统的内存资源。
    如果父进程没有回收它的僵死进程就终止了,那么内核会安排init进程去回收他们。
    但是,一些长时间运行的程序,例如shell或服务器,总应该回收他们的僵死子进程,不然会对系统造成不小的负担。
    所以,如何使我们的父进程来等待它的子进程终止或者停止,来将僵死子进程所占用的内存资源释放掉呢?

    这时就需要我们的waitpid函数登场了,首先我们先来看下他的函数结构

    #include <sys/types.h>
    #include <sys/wait.h>
    
    pid_t waitpid(pid_t pid, int *statusp, int options);
    ``
    我们这里仅对第一个参数pid进行解释,另外两个请大家自行百度
    
    如果pid>0,那么等待集合就是一个单独的子进程,它的进程ID等于pid。
    如果pid=-1,那么等待集合就是由父进程所有的子进程组成的。
    我们可以通过控制pid的值来指定子进程进行回收,这也是与wait函数的区别。大家可以自行运行一下代码中的fork10和fork11观察下输出有何不同。
    
    .使用fflush清除缓存
    我们先来看一下fflush的函数结构
    
    

    #include<stdio.h>
    int fflush(FILE * stream);

    函数说明:
    
    fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中,如果参数stream为NULL,fflush()会将所有打开的文件数据更新。
    返回值:成功返回0,失败返回EOF,错误代码存于errno中。
    fflush()也可用于标准输入(stdin)和标准输出(stdout),用来清空标准输入输出缓冲区。
    stdin是standard input的缩写,即标准输入,一般是指键盘;标准输入缓冲区即是用来暂存从键盘输入的内容的缓冲区。
    stdout是standard output 的缩写,即标准输出,一般是指显示器;标准输出缓冲区即是用来暂存将要显示的内容的缓冲区。
    我们在fork函数中要注意的也就是输入输出缓冲区。
    
    

    include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main()
    {
    int i;
    for (i=0; i<2; ++i) {
    fork();
    printf(“A”);
    //fflush(stdout);
    //printf("\n");
    }
    return 0;

    
    
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,924
精华内容 4,369
关键字:

fork函数