精华内容
下载资源
问答
  • fork函数创建一个进程

    千次阅读 2011-02-12 22:31:00
    在linux中创建一个进程的唯一方法是使用fork函数,fork()执行一次但有两个返回值。在父进程中,返回值是子进程进程号;在子进程中,返回值为0。因此可通过返回值来判断当前进程是父进程还是子进程使用fork函数...

    在linux中创建一个新进程的唯一方法是使用fork函数,fork()执行一次但有两个返回值。

     

    在父进程中,返回值是子进程的进程号;在子进程中,返回值为0。因此可通过返回值来判断当前进程是父进程还是子进程。

     

    使用fork函数得到的子进程是父进程的一个复制品,它从父进程处复制了整个进程的地址空间,包括进程上下文,进程堆栈,内存信息,打开的文件描述符,信号控制设定,进程优先级,进程组号,当前工作目录,根目录,资源限制,控制终端等。而子进程所独有的只是它的进程号,资源使用和计时器等。可以看出,使用fork函数的代价是很大的,它复制了父进程中的代码段,数据段和堆栈段里的大部分内容,使得fork函数的执行速度并不快。

     

    头文件:

     

    #include <unistd.h>

    函数定义:

    int fork( void );

    返回值:

     

    子进程中返回0,父进程中返回子进程ID,出错返回-1

     

    一个简单的fork程序:

     

    #include <stdio.h>

    #include <stdlib.h>

    #include <unistd.h>

     

    int main()

    {

    pid_t id; //定义一个进程号变量

    int i=0;

    printf("start fork/n");

    id = fork(); //调用fork函数新建一个进程

    i ++;

    printf("end fork/n");

    //判断当前进程

    if(id < 0){ //出错

    perror("fork failed/n");

    exit(1);

    }

    else if(id == 0){ //子进程

    printf("In child/n");

    printf("i = %d/n", i++);

    exit(0);

    }

    else{ //父进程

    printf("In father/n");

    printf("i = %d/n", i++);

    exit(0);

    }

     

    return 0;

    }

     

    运行结果:
    [root@localhost Process]# ./fork
    start fork
    end fork
    In child
    i = 1
    end fork
    In father
    i = 1
    可知:
    1.子进程是从调用fork函数处的下一条语句开始执行的。
    2.子进程中的局部变量i不同于父进程中的i,是父进程的复制。
    下面写一个小实验(包括了fork, exec, waitpid等函数的使用):
    实验要求:
    该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停5s之后异常退出,父进程并不阻塞自己,并等待子进程的退出信息,带收集到该信息,父进程就返回。
    程序如下:
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    int main(){
    pid_t child1, child2, child;
    child1 = fork(); //创建子进程1
    child2 = fork(); //创建子进程2
    if(child1 < 0){
    perror("fork child1 failed/n");
    exit(1);
    }
    else if(child1 == 0){
    printf("In child1: execute 'ls -l'/n"); //子进程1执行"ls -l"
    if(execlp("ls", "ls", "-l", NULL) < 0){
    perror("execlp failed/n");
    }
    }
    if(child2 < 0){
    perror("fork child2 failed/n");
    exit(1);
    }
    else if(child2 == 0){
    printf("In child2: sleep for 5 sec and exit/n");
    sleep(5); //子进程2睡眠5s
    exit(1);
    }
      else{
    printf("In father process:/n");
    do{ //父进程每隔1s接受一次子进程2的信号
    child = waitpid(child2, NULL, WNOHANG);
    if(child == 0){
    printf("The child2 process has not exited!/n");
    sleep(1);
    }
    }while( child == 0);
    if(child == child2){ //接受到信号
    printf("Get child2/n");
    }
    else{
    perror("Error/n");
    }
    }
    return 0;
    }
    运行结果:
    [root@localhost Process]# ./exp1
    In child1: execute 'ls -l'
    In child1: execute 'ls -l'
    In child2: sleep for 5 sec and exit
    In father process:
    The child2 process has not exited!
    总计 15
    -rwxrwxrwx 1 root root 5903 02-12 19:44 dameon
    -rwxrwxrwx 1 root root  812 02-12 19:43 dameon.c
    -rwxrwxrwx 1 root root 5712 2011-02-12 exp1
    -rwxrwxrwx 1 root root 1024 2011-02-12 exp1.c
    -rwxrwxrwx 1 root root 5300 02-12 22:19 fork
    -rwxrwxrwx 1 root root  508 02-12 22:19 fork.c
    -rwxrwxrwx 1 root root  510 02-12 22:18 fork.c.bak
    -rwxrwxrwx 1 root root 6026 02-12 19:57 sys_dameon
    -rwxrwxrwx 1 root root  919 02-12 19:57 sys_dameon.c
    总计 15
    -rwxrwxrwx 1 root root 5903 02-12 19:44 dameon
    -rwxrwxrwx 1 root root  812 02-12 19:43 dameon.c
    -rwxrwxrwx 1 root root 5712 2011-02-12 exp1
    -rwxrwxrwx 1 root root 1024 2011-02-12 exp1.c
    -rwxrwxrwx 1 root root 5300 02-12 22:19 fork
    -rwxrwxrwx 1 root root  508 02-12 22:19 fork.c
    -rwxrwxrwx 1 root root  510 02-12 22:18 fork.c.bak
    -rwxrwxrwx 1 root root 6026 02-12 19:57 sys_dameon
    -rwxrwxrwx 1 root root  919 02-12 19:57 sys_dameon.c
    The child2 process has not exited!
    The child2 process has not exited!
    The child2 process has not exited!
    The child2 process has not exited!
    Get child2

     

     

    展开全文
  • linux系统如何使用fork函数创建进程

    万次阅读 多人点赞 2017-09-16 15:48:00
    系统调用函数fork()是创建一个进程的唯一方式,fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程

    大家都知道linux是多进程的系统。可是,在linux中,进程是如何创建并运行的呢?

    在linux系统中创建进程有两种方式:一是由操作系统创建,二是由父进程创建进程(通常为子进程)。系统调用函数fork()是创建一个新进程的唯一方式,fork()函数是Linux系统中一个比较特殊的函数,其一次调用会有两个返回值。

    fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。父进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程(父进程)的所有值都复制到新的新进程(子进程)中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

    我们来看一个例子:

    #include <unistd.h>
    #include <stdio.h>
    
    int main ()
    {
        pid_t fpid;
    
        fpid = fork();
    
        if (fpid < 0) {
            printf("error in fork!");
        } else if (fpid == 0) {
            printf("this is child process, pid %d/n",getpid());
        } else {
            printf("this is parent process, pid %d/n",getpid());
        }
    
        return 0;
    }

    运行结果是:

    this is child process, pid 5574
    this is parent process, pid 5573

    在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if (fpid<0)……

    为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值;

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

    fork出错可能有两种原因:
    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2)系统内存不足,这时errno的值被设置为ENOMEM。

    创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

    每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

    参考资料:
    1、http://blog.csdn.net/jason314/article/details/5640969
    2、http://blog.csdn.net/cywosp/article/details/27316803

    展开全文
  • fork函数中最重要的是:fork()函数被调用次,却会被返回两次,返回两次的区别,子进程返回的是0,而父进程返回的是子进程的ID。若调用错误则返回-1。 1.Fork.c 1.Fork.c #include "csapp.h" /* $begin fork */...

    fork函数中最重要的是:fork()函数被调用一次,却会被返回两次,返回两次的区别,子进程返回的是0,而父进程返回的是子进程的ID。若调用错误则返回-1。

    1.Fork.c

    1.Fork.c
    #include "csapp.h"
    
    /* $begin fork */
    /* $begin wasidefork */
    int main(int argc, char *argv[]) 
    {
        pid_t pid;
        int x = 1;
    
        pid = fork(); //line:ecf:forkreturn
        if (pid == 0) {  /* Child */
    	printf("child : x=%d\n", ++x); //line:ecf:childprint
    	fflush(stdout);
    	return 0;
        }
    
        /* Parent */
        printf("parent: x=%d\n", --x); //line:ecf:parentprint
        fflush(stdout);
        return 0;
    }

    进程图如下:

    运行结果如下:

    由运行结果可知:一般来说,fork()之后是父进程先执行还是子进程先执行是不确定的,这取决与内核的调度算法。但是由我的运行结果可知,在子进程之前运行是因为父进程是当前shell命令执行的,父进程结束后就返回了shell,然后子进程才开始执行。

    2.fork1().c

    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);
    }

    由程序可画出进程图:

    运行结果如下:

    由运行结果可知:当执行到fork()函数之后,就创建了一个子进程,然后根据父进程和子进程返回值不同执行相应的代码。因为系统把原来的进程的所有代码,数据都复制到了一个新的进程,所以父进程和子进程都会执行之后的一句代码,所以最后一句代码会打印两次。getpid()函数返回的是调用进程的ID。

    3.fork2().c

    void fork2()
    {
        printf("L0\n");
        fork();
        printf("L1\n");    
        fork();
        printf("Bye\n");
    }

    由程序画出的进程图如下:

    运行结果如下:


    4.进程中有wait函数

    void fork9()
    {
        int child_status;
    
        if (fork() == 0) {
    	printf("HC: hello from child\n");
            exit(0);
        } else {
    	printf("HP: hello from parent\n");
    	wait(&child_status);
    	printf("CT: child has terminated\n");
        }
        printf("Bye\n");
    }

    由程序画出的进程图如下:

    运行结果如下:

    由代码可知,该代码调用了wait函数,而进程一旦调用了wait函数,就会立即阻塞自己,由wait自动分是否当前的某个子进程已经退出,如果它已经找到这样一个已经变成僵尸子进程,wait就会收集这个子进程的信息,并把它彻底摧毁后返回;如果没有找到这样一个子进程,那么wait就会一直阻塞在这里,直到有这样一个子进程出现为止。

    5.进程中有fflush(stout)

    #include "csapp.h"
    
    /* $begin fork */
    /* $begin wasidefork */
    int main(int argc, char *argv[]) 
    {
        pid_t pid;
        int x = 1;
    
        pid = fork(); //line:ecf:forkreturn
        if (pid == 0) {  /* Child */
    	printf("child : x=%d\n", ++x); 
    	printf("child : x=%d\n", ++x); 
    	fflush(stdout);
    	return 0;
        }
    
        /* Parent */
        printf("parent: x=%d\n", --x);
        printf("parent: x=%d\n", --x);
        fflush(stdout);
        return 0;
    }
    /* $end fork */
    /* $end wasidefork */

    运行结果如下:

    fflush(stout)是把输出缓冲区里的东西打印到标准的输出设备上来,从运行结果可以看出,当执行父进程时,就将父进程里面Printf函数中的东西全部都输出来了,同理当执行子进程也是。

     

    展开全文
  • 创建进程fork函数:fork函数干什么? fork函数与vfork函数的区别在哪里?为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?
  • fork一个在Linux系统环境下专有的函数,现有的进程调用fork后将会创建一个新的进程,这里我们就来看一下Linux中使用C语言的fork()函数创建进程的实例教程
  • 18-用fork函数创建进程

    千次阅读 多人点赞 2018-09-05 14:29:02
    1. 程序和进程   简单来说,程序是一组存储在磁盘的机器语言指令集合(本质上是一个...  如果同一个程序多次运行,每次都在内存中创建出不同的进程,每个进程都有自己的代码空间和数据空间,且进程间彼此独立,...

    1. 浅谈程序和进程

      简单来说,程序是一组存储在磁盘的机器语言指令集合(本质上是一个二进制文件),不占用cpu、内存等系统资源。

      而进程是一个可执行程序的运行实例,说白了就是运行着的程序,每一个运行着的程序都可以看做是一个独立的进程,会占用系统资源。

    2. 感受进程

      如果同一个程序多次运行,每次都会在内存中创建出不同的进程,每个进程都有自己的代码空间和数据空间,且进程间彼此独立,互不影响。

      比如编译完process.c文件,在磁盘中生成的process的可执行文件就是一个二进制程序,且process程序只占用磁盘的存储空间,当使用执行命令./process执行时,shell终端就会产生一个属于process.out文件的进程在运行,并占用了cpu,内存等系统资源,这时候process.out就是一个进程。

    例如,当多次执行./process时,每次执行./process命令都会产生一个process进程,如下图所示:

    这里写图片描述
    图1-感受进程

      产生的这三个process进程彼此间独立,互不影响。



      还可以通过ps -u test u命令可以帮助查看当前系统属于test用户的所有进程,最后的u表示显示格式,这里以用户格式显示。ps 后面的选项以短破拆号-开头和没有它开头的含义是不同的。一般来说,有 - 开头的表示的是”UNIX options”,没有-开头的叫 “BSD options”,而以双破折号 - - 开头的叫 “GNU long options”。

    这里写图片描述
    图2-查看进程

    图2中列出了当前用户test的进程。每一列的含义如下:

    名称 含义
    USER 进程的属主
    PID 进程的 id 号
    %CPU 进程占用的 CPU 百分比
    %MEM 占用的内存百分比
    VSZ 进程虚拟大小
    RSS 驻留页的数量
    TTY 终端 id 号
    STAT 进程状态(D、R、S、T、W、X、Z、<、N、L、s 等)
    START 进程开始运行时间
    TIME 进程累积使用的CPU时间
    COMMAND 使用的命令



    以下是进程状态值的含义( 从这里也可以看出,进程它是有状态的):

    名称 含义
    D 不可中断睡眠
    R 运行或就绪态
    S 休眠状态
    T 停止或被追踪
    W 进入内存交换(从内核2.6开始无效)
    X 死掉的进程
    Z 僵尸进程
    < 优先级高的进程
    N 优先级较低的进程
    L 有些页被锁进内存
    s 进程的领导者(在它之下有子进程)


    3. 程序和进程之间的特点

      无论是C/C++,还是java语言编写的源码程序经过编译器编译成一个可执行的二进制机器指令文件,然后交给计算机运行,当这个程序的运行起来就是进程(为了方便,我们可以理解为进程是程序的运行状态)。

      它们的区别在于程序作为一个静态的二进制可执行文件永久存储在磁盘空间中,没有执行的意义。而进程是由操作系统创建,调度运行,分配系统资源,完成任务后销毁等等。整个过程进程是处于动态的,由操作系统维护管理。

    总体来说可以从以下方面来理解:
      程序是静态的文件,进程是处于动态运行的程序。

      程序是一组指定的集合,无执行意义,只占用磁盘存储空间,而进程占会一定的系统资源,进程是有状态的(休眠,运行,僵死等),有一定的生命周期(从进程创建到进程结束)。

      同一个程序运行不同的数据集就是不同的进程,进程间是独立的,数据集不相同。比如:同时开两个终端,各自都有一个bash进程,但彼此的bash进程pid不同。


    4. 身份标识——进程pid

      前面在图2中还看了pid,pid就是进程的id,每个进程都有一个非负整数且唯一的进程id。用于唯一标识系统的某个进程。对于系统调用来说,进程id可以作为参数传入,也可以作为函数返回值。

      比如接下来要讲的神奇的fork系统调用,它的神奇之处在于调用成功后会有两个返回值。


    5. 神奇的fork函数

    fork函数用于创建一个新的进程。

    pid_t fork(void);  

    返回值:fork调用成功后,子进程返回0,父进程返回子进程pid,fork出错返回-1

      返回值pid_t类型表示进程ID,但为了表示- 1,它是有符号整型。注意fork的返回值,由于fork调用成功后子进程也复制了一个fork函数,父子进程中各自有一个fork函数,父进程的fork返回值大于0(子进程pid),子进程的fork则返回0,如果fork调用失败,当前进程返回-1。



    fork程序示例:

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(){
    
        pid_t pid; 
        pid = fork();   //当fork调用成功后,子进程开始从fork后开始执行
    
        //创建进程失败
        if(-1 == pid){
            perror("fork failed");
            return -1;
    
        //父进程
        }else if(pid > 0){
                //pid用于接收当前进程id, ppid用于接收父进程id
                printf("parent : pid = %d , ppid = %d\n", getpid(), getppid());
                sleep(1);
    
        //子进程
        }else if(pid == 0){
                printf("child: pid = %d, ppid = %d\n", getpid(), getppid());
                sleep(1);
        }
        return 0;
    }



    执行结果如下:
    这里写图片描述

      通过程序的执行结果我们发现,父子进程各自打印了自己的pid,同时也打印了父进程的pid。注意,一般来说调用fork创建子进程成功后,父进程和子进程谁先执行是不确定的,这取决于内核的调度算法。


    6. 关于fork的2个返回值

    这里写图片描述
    图3-fork的返回值

      父进程一行一行代码执行,调用fork( )函数时会产生一个子进程,且复制了同一份代码给子进程,有些小伙伴可能会很奇怪为啥调用一次fork却返回2个返回值? 真实的情况是:这2个返回值是由父进程和子进程各自返回的

       如图3所示,对于父进程来说,父进程调用fork函数成功产生一个子进程,子进程会从父进程“复制”一份数据空间,堆和栈作为副本(注意:并不是完全的复制,但是接近于97%的复制,子进程会修改自己的数据空间中的某些变量值),这两个进程拥有相同的代码文本,但各自拥有不同的栈段,数据段等。

       父进程中的fork返回值是子进程pid (如果调用fork失败则返回 -1),fork成功调用后,会把子进程中的fork的返回值直接修改为0,然后子进程会从fork后开始执行,这样我们可以通过fork函数的返回值来区分并且控制父子进程的代码流程

       当然,为了能深入了解fork函数的返回值,可以去看看读共享写复制机制,然后再回过头来看fork函数的返回值,你就非常清楚了。


    7. 父子进程间的共享问题

      大家有没有想过这么一个问题,父进程在fork后,父子进程之间的数据共享的问题。比如哪些数据共享,哪些是不共享的,但至少从前面的学习中来看,我们知道父子进程的进程id,fork返回值等是不共享的,好了,还是来看下面这个示例程序吧。


    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    //全局变量
    int var = 100;
    
    int main(void) {
        pid_t pid;
        pid = fork();
        if (pid == -1) {
            perror("fork error");
            exit(1);
        } else if (pid > 0) {
            //这是写操作,修改的是父进程中的全局变量var
            var = 288;
            printf("parent, var = %d\n", var);
            printf("I'am parent pid= %d, getppid = %d\n", getpid(), getppid());
        } else if (pid == 0) {
            //写操作,修改的是子进程中全局变量var
            var = 200;
            printf("I'am child pid= %d, ppid = %d\n", getpid(), getppid());
            printf("child, var = %d\n", var);       
        }
        printf("------------finish---------------\n");
        return 0;
    }



    程序执行结果:

    这里写图片描述
    图4-进程间共享问题

      父进程打印var = 288,子进程打印var = 200,说明父子进程在写时是不共享全局变量的,父子进程是修改各自的全局变量。

      从上面这个例子来看,父子进程的运行时间也是不共享的。

      例如fork之后父子进程谁先运行,运行时间多久都是不确定,因为父进程和子进程都会争取CPU的执行权,谁先抢到谁就执行,这取决于内核所使用的调度算法。

       如图4所示,pid为2591进程的父进程是bash进程,bash进程一启动,2591进程开始运行,然后bash进程就转到后台去了,把前台让给了2591进程去执行,那bash进程什么时候回到前台呢?

       按理说bash进程把前台让给2591进程执行,当2591执行完毕应该把前台让回来给bash进程,所以bash进程恢复的时机就是判断2591进程是否执行完毕,bash进程恢复的标记就是把终端提示符(test@test-virtual-machine:)打印出来,但是bash进程并不知道2591进程还有子进程(即2592进程)。因此2592进程还会占着前台继续执行,所以当2592进程运行结束,bash进程才会回到前台打印终端提示符。


    8. 进程的虚拟地址空间

       为了深入了解进程间共享问题,我们先来了解下进程的地址空间的概念。所谓的地址空间说的是进程虚拟地址空间。就是每个进程都有自己的4GB虚拟地址空间。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    int g_v = 30;  //全局变量
    int main(void){
            int a_v = 30;   //局部变量
            static int s_v = 30;  //静态变量
            pid_t pid;
            printf("pid = %d\n", getpid());
            pid = fork();
            //父进程
            if(pid > 0){
                    //父进程针对这三个变量进行修改
                    g_v = 40; a_v = 40; s_v =40;
                    //打印值
                    printf("father = %d : g_v = %d , a_v = %d , s_v = %d\n", getpid() , g_v , a_v , s_v);
                    //打印地址
                    printf("father = %d : g_v = %p , a_v = %p , s_v = %p\n", getpid() , &g_v , &a_v , &s_v);
              //子进程
             }else if(0 == pid){
                    //子进程针对这三个变量进行修改
                    g_v = 50; a_v = 50; s_v = 50;
                    //打印值
                    printf("child = %d : g_v = %d , a_v = %d , s_v = %d\n", getpid() , g_v , a_v , s_v);
                    //打印地址
                    printf("child = %d : g_v = %p , a_v = %p , s_v = %p\n", getpid() , &g_v , &a_v , &s_v);
            }else{
                    perror("fork");
                    return -1;
            }
            //最后子进程打印数据,父进程也打印数据
            printf("pid = %d : g_v = %d , a_v = %d , s_v = %d\n", getpid() , g_v , a_v , s_v);
            wait(NULL);   //回收子进程
            //sleep(1);
            return 0;
    }



    程序执行结果:
    这里写图片描述

      通过程序运行时发现,父子进程打印的全局,静态,局部变量值不一样的,但是它们的地址是一样的。所以我们可以确定父进程在fork子进程时,子进程几乎把整个父进程复制了过去(包括0-4G虚拟地址空间)

      在修改数据时,虽然父子进程的数据的虚拟地址相同,但是虚拟地址实际映射到的物理地址却是不同的。

      换句话说,虚拟地址在映射到物理内存的地址时,系统会在物理内存中找一块还没有用,空闲的物理内存,把这个虚拟地址映射到这块空闲的内存的物理地址


    9. 读共享写复制

    fork之后,父子进程在进行读写操作时各自的数据空间发生了以下变化:

    这里写图片描述
    图5-虚拟地址到物理地址的映射过程

      父子进程的虚拟地址空间如图5所示,也就是说在父子进程的数据空间中,虚拟地址空间是一样的,但是这并不意味着父子进程的物理地址空间就是一样的。



    父子进程数据空间分析:
       从图5可以看出父子进程打印出来的数据时不同的,这意味着子进程的数据空间在进行写操作前并没有额外的开辟物理内存映射,而是和父进程共享的同一块物理内存(也间接说明了虚拟内存空间是共享的)。换句话说,当父子进程任何一个进程发生写操作的时候,都会先针对部分写操作的数据开辟新的物理内存,然后把复制的数据映射到物理内存当中。



    虚拟地址到物理地址的映射过程:
      实际上在系统中有一个MMU单元,主要负责虚拟地址到物理地址的映射(感兴趣的同学可以去看看操作系统哈)。

      首先它会根据虚拟地址在物理内存中找一块还没有被使用,空闲的内存块,然后把虚拟地址映射到这块物理内存中。那么进程是怎么找到MMU的呢?答案是三级页表,那么这个页表的映射过程又得另说了,可以确定的是这个映射过程实际上是非常复杂的(这里只是方便理解,简化了一下,有兴趣的可以参考这位大佬的OS笔记:OS 学习笔记导航)。



      我们可以得出一个结论,对于读操作,父子进程间是共享的;对于写操作,父子进程间是不共享的。这种机制就是写时复制机制(copy on write)。


    10. 写时复制(copy on write)机制

      也就是说写时复制机制(copy on write)是一种推迟或免除复制数据的方法,此时内核并不去复制进程的整个地址空间的数据,而是让父进程和子进程共享同一数据,当进程A调用fork创建出子进程B时,由于子进程B实际是进程A的拷贝,所以进程B会拥有和进程A同一物理页面,也是为了达到节约内存和提高创建进程效率的目标,fork函数实际只会以只读的形式让子进程B共享进程A的物理页面

      同时父进程A也对这些页面设置为只读权限,也就是对此共享物理页面进行了写保护,这样一来,只有A,B任何一个进程对这些共享物理页面进行写操作时都会产生页面异常中断,此时CPU会对此异常进行处理,取消对共享物理页面的写操作,然后为执行写操作的进程复制一块新的物理页面,使A,B进程各自拥有一块相同的物理页面,这才真正的执行了复制操作(其实只复制了这一块物理页面),然后将这块复制的物理页面标记改为可写状态(原先是只读的),因此,在对进程间虚拟地址空间范围内执行写操作时,才会触发写时复制操作。

      在复制之前,会申请一块物理页面来存放复制的物理页面,然后将此物理页面取消共享,并标记的读状态改为可写状态(因为共享属性和读写属性也复制了,所以必须把这些属性改掉),这块物理页面只属于当前执行写操作的进程,其他进程不能对此物理页面进行读写操作,同时在复制时也只会复制针对部分写操作的数据,而不是复制整个数据空间,因此其他部分还是共享的,这样做的目的是为了高效。(以上来自linux 0.11内核版本)


    11. 总结

    1 . 了解进程和程序的区别

    2 . 理解进程空间等相关概念

    3 . 掌握fork函数的使用和返回值

    4 . 理解写时复制(copy on write)机制

    展开全文
  • 一个现有的进程可以调用fork函数创建一个新的进程。由fork创建的新进程被称为子进程。 关于fork的返回值 fork函数被调用一次,但是它的返回值有两个。子进程的返回值为0,父进程的返回值则是新创建的子进程的...
  • fork函数是Linux下一个近乎专有的C语言函数,因为使用时需要调用unistd.h这个头文件,这里我们就在Linux环境下举例讲解C语言的fork()函数创建进程的用法,需要的朋友可以参考下
  • 使用fork()函数得到的子进程是父进程一个复制品,它从父进程处继承了整个进程的地址空间,包括进程的上下文、代码段、进程堆栈、内存信息、打开的文件描述符、符号控制设定、进程优先级、进程组号、当前工作目录、...
  • 在Linux中创建一个进程的唯一方法是使用fork()函数fork()函数是Linux中一个非常重要的函数,和以往遇到的函数有一些区别,因为fork()函数看起来执行一次却返回两个值。 fork()函数用于从已存在的进程创建一个...
  • 此题为刷题过程中遇到了题目,比较绕,就下下来记录一下。...首先程序执行,创建一个进程,作为第一个进程进程的代码如下: 1进程 int main(void) { fork();//位置1 fork();//位置2 fork();//...
  • fork一个进程fork()函数fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程一个进程调用fork()函数后,系统先给新的进程...
  • 1.一个现有进程可以调用 fork 函数创建一个进程。 2.fork 函数被调用一次,但返回两次, 两次返回的唯一区别是子进程的返回值是 0, 而父进程的返回值是新子进程的 PID。 3.子进程和父进程继续执行 fork 调用之后的...
  • linux下fork函数创建进程

    千次阅读 2014-01-01 11:51:00
    有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls -l”指令,另一个子进程在暂停5s后异常退出。父进程先用阻塞方式等待第一个进程的结束,然后用非阻塞方式等待另一个子进程...
  • Linux中fork()函数创建进程

    千次阅读 2019-05-09 22:05:09
    Linux系统中学习fork函数创建进程前言.准备工作二.任务三.感想 前言    最近学习到操作系统原理中的进程同步的知识点时,为了加深对进程的了解,就实践了一下在Linux系统中fork()函数的使用.准备工作 ...
  • fork函数用于创建一个子进程 用法 #include <unistd.h> pid_t fork(void); 对于父进程来说,fork函数返回成功返回子进程id,对于子进程来说,fork函数成功返回0;失败返回-1; C/C++中的函数返回值只能有一个...
  • 2、编写段程序,使用系统调用函数fork( )创建两个子进程,再用系统调用函数signal( )让父进程捕捉信号SIGINT(用kill命令来触发),当捕捉到中断信号后,父进程用系统调用函数kill( )向两个子进程发出信号,子进程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 86,021
精华内容 34,408
关键字:

使用fork函数创建一个子进程