`
memorymyann
  • 浏览: 271805 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

linux系统调用fork, vfork, clone

阅读更多

fork,vfork,clone都是linux的系统调用,用来创建子进程的(确切说vfork创造出来的是线程)。

先介绍下进程必须的4要点:

a.要有一段程序供该进程运行,就像一场戏剧要有一个剧本一样。该程序是可以被多个进程共享的,多场戏剧用一个剧本一样。

b.有起码的私有财产,就是进程专用的系统堆栈空间。

c.有“户口”,既操作系统所说的进程控制块,在linux中具体实现是task_struct

d.有独立的存储空间。

当一个进程缺少d条件时候,我们称其为线程。

1.fork 创造的子进程复制了父亲进程的资源,包括内存的内容task_struct内容(2个进程的pid不同)。这里是资源的复制不是指针的复制。下面的例子可以看出

[root@liumengli program]# cat testFork.c
#include"stdio.h"

int main() {
        int count = 1;
        int child;

        if(!(child = fork())) { //开始创建子进程
                printf("This is son, his count is: %d. and his pid is: %d\n", ++count, getpid());//子进程的内容
        } else {
                printf("This is father, his count is: %d, his pid is: %d\n", count, getpid());
        }
}
[root@liumengli program]# gcc testFork.c -o testFork
[root@liumengli program]# ./testFork
This is son, his count is: 2. and his pid is: 3019
This is father, his count is: 1, his pid is: 3018
[root@liumengli program]#
从代码里面可以看出2者的pid不同,内存资源count是值得复制,子进程改变了count的值,而父进程中的count没有被改变。有人认为这样大批量的复制会导致执行效率过低。其实在复制过程中,子进程复制了父进程的task_struct,系统堆栈空间和页面表,这意味着上面的程序,我们没有执行count++前,其实子进程和父进程的count指向的是同一块内存。而当子进程改变了父进程的变量时候,会通过copy_on_write的手段为所涉及的页面建立一个新的副本。所以当我们执行++count后,这时候子进程才新建了一个页面复制原来页面的内容,基本资源的复制是必须的,而且是高效的。整体看上去就像是父进程的独立存储空间也复制了一遍。

 

其次,我们看到子进程和父进程直接没有互相干扰,明显2者资源都独立了。我们看下面程序

[root@liumengli program]# cat testFork.c
#include"stdio.h"

int main() {
        int count = 1;
        int child;

        if(!(child = fork())) {
                int i;
                for(i = 0; i < 200; i++) {
                        printf("This is son, his count is: %d. and his pid is: %d\n", i, getpid());
                }
        } else {
                printf("This is father, his count is: %d, his pid is: %d\n", count, getpid());
        }
}
[root@liumengli program]# gcc testFork.c -o testFork
[root@liumengli program]# ./testFork
...

This is son, his count is: 46. and his pid is: 4092
This is son, his count is: 47. and his pid is: 4092
This is son, his count is: 48. and his pid is: 4092
This is son, his count is: 49. and his pid is: 4092
This is son, his count is: 50. and his pid is: 4092
This is father, his count is: 1, his pid is: 4091
[root@liumengli program]# This is son, his count is: 51. and his pid is: 4092
This is son, his count is: 52. and his pid is: 4092
...

(运气很衰,非要200多个才有效果,郁闷)从结果可以看出父子2个进程是同步运行的。这和下面的vfork有区别。

 

2.vfork创建出来的不是真正意义上的进程,而是一个线程,因为它缺少了我们上面提到的进程的四要素的第4项,独立的内存资源,看下面的程序

[root@liumengli program]# cat testVfork.c
#include "stdio.h"

int main() {
        int count = 1;
        int child;

        printf("Before create son, the father's count is:%d\n", count);
        if(!(child = vfork())) {
                printf("This is son, his pid is: %d and the count is: %d\n", getpid(), ++count);
                exit(1);
        } else {
                printf("After son, This is father, his pid is: %d and the count is: %d, and the child is: %d\n", getpid(), count, child);
        }
}
[root@liumengli program]# gcc testVfork.c -o testVfork
[root@liumengli program]# ./testVfork
Before create son, the father's count is:1
This is son, his pid is: 4185 and the count is: 2
After son, This is father, his pid is: 4184 and the count is: 2, and the child is: 4185
[root@liumengli program]#
从运行结果可以看到vfork创建出的子进程(线程)共享了父进程的count变量,这一次是指针复制,2者的指针指向了同一个内存,所以子进程修改了count变量,父进程的 count变量同样受到了影响。另外由vfork创造出来的子进程还会导致父进程挂起,除非子进程exit或者execve才会唤起父进程,看下面程序:

[root@liumengli program]# cat testVfork.c
#include "stdio.h"

int main() {
        int count = 1;
        int child;

        printf("Before create son, the father's count is:%d\n", count);
        if(!(child = vfork())) {
                int i;
                for(i = 0; i < 100; i++) {
                        printf("This is son, The i is: %d\n", i);
                        if(i == 70)
                                exit(1);
                }
                printf("This is son, his pid is: %d and the count is: %d\n", getpid(), ++count);
                exit(1);
        } else {
                printf("After son, This is father, his pid is: %d and the count is: %d, and the child is: %d\n", getpid(), count, child);
        }
}
[root@liumengli program]# gcc testVfork.c -o testVfork
[root@liumengli program]# ./testVfork
...

This is son, The i is: 68
This is son, The i is: 69
This is son, The i is: 70
After son, This is father, his pid is: 4433 and the count is: 1, and the child is: 4434
[root@liumengli program]#
从这里就可以看到父进程总是等子进程执行完毕后才开始继续执行。

 

3.clone函数功能强大,带了众多参数,因此由他创建的进程要比前面2种方法要复杂。clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

这里fn是函数指针,我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。下面是flags可以取的值

标志                    含义

  CLONE_PARENT   创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”

  CLONE_FS           子进程与父进程共享相同的文件系统,包括root、当前目录、umask

  CLONE_FILES      子进程与父进程共享相同的文件描述符(file descriptor)表

  CLONE_NEWNS   在新的namespace启动子进程,namespace描述了进程的文件hierarchy

  CLONE_SIGHAND   子进程与父进程共享相同的信号处理(signal handler)表

  CLONE_PTRACE   若父进程被trace,子进程也被trace

  CLONE_VFORK     父进程被挂起,直至子进程释放虚拟内存资源

  CLONE_VM           子进程与父进程运行于相同的内存空间

  CLONE_PID          子进程在创建时PID与父进程一致

  CLONE_THREAD    Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

下面的例子是创建一个线程(子进程共享了父进程虚存空间,没有自己独立的虚存空间不能称其为进程)。父进程被挂起当子线程释放虚存资源后再继续执行。

[root@liumengli program]# cat test_clone.c
#include "stdio.h"
#include "sched.h"
#include "signal.h"
#define FIBER_STACK 8192
int a;
void * stack;
int do_something(){
        printf("This is son, the pid is:%d, the a is: %d\n", getpid(), ++a);
        free(stack); //这里我也不清楚,如果这里不释放,不知道子线程死亡后,该内存是否会释放,知情者可以告诉下,谢谢
        exit(1);
}
int main() {
        void * stack;
        a = 1;
        stack = malloc(FIBER_STACK);//为子进程申请系统堆栈
        if(!stack) {
                printf("The stack failed\n");
                exit(0);
        }

        printf("creating son thread!!!\n");

        clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);//创建子线程
         printf("This is father, my pid is: %d, the a is: %d\n", getpid(), a);
         exit(1);
}
[root@liumengli program]# gcc test_clone.c -o test_clone
[root@liumengli program]# ./test_clone
creating son thread!!!
This is son, the pid is:7326, the a is: 2
This is father, my pid is: 7325, the a is: 2
[root@liumengli program]#

读者可以试试其它的资源继承方式。

分享到:
评论
2 楼 klose 2011-05-15  
我引用你的文章,并做了简单的分析:这里贴出url:http://blog.sina.com.cn/s/blog_4a1f59bf0100rykp.html
1 楼 klose 2011-05-15  
你上面提到的问题:free的问题。首先你可能疏忽了,stack变量在全局和局部都有定义的时候,子进程使用的是全局的变量stack。这样stack指针就与实际申请heap的stack不一致了。
使用Valgrind查看内存使用:
==14022== HEAP SUMMARY:
==14022==     in use at exit: 8,192 bytes in 1 blocks
==14022==   total heap usage: 1 allocs, 0 frees, 8,192 bytes allocated

相关推荐

    详解linux中fork、vfork、clone函数的区别

    在Linux操作系统中,`fork()`、`vfork()` 和 `clone()` 都是用于创建新进程的函数,但它们在实现机制和使用场景上存在显著差异。 1. **fork()** `fork()` 是最传统的进程创建方法。它创建一个与父进程完全独立的新...

    Linux常见系统调用列表介绍

    Linux 系统调用列表介绍 本文列出了大部分常见的 Linux 系统调用,并附有简要中文说明。 Linux 系统调用是操作系统提供给应用程序的接口,用于控制进程、文件系统、网络、设备等资源。以下是 Linux 系统调用的一个...

    Linux系统调用列表

    ### Linux系统调用详解 #### 一、进程控制 Linux系统调用是操作系统与应用程序之间的桥梁,它们允许用户空间的应用程序请求内核执行特定的任务。下面详细介绍与进程控制相关的系统调用。 - **fork()**: 创建一个...

    linux内核 do_fork 函数源代码浅析

    do_fork 函数被 sys_fork()、sys_clone() 和 sys_vfork() 等系统调用所调用,以实现进程的复制与创建。 #### 二、do_fork 函数的工作原理 do_fork() 函数主要负责创建一个新的进程,并对其进行初始化。该过程可以...

    Linux系统调用列表[定义].pdf

    Linux系统调用是操作系统提供给应用程序的接口,允许用户空间的程序执行底层操作,如进程控制、文件操作、设备交互等。这份"Linux系统调用列表[定义].pdf"文档详细列举并解释了大量常见的Linux系统调用,对于软件...

    linux fork 简单使用

    在Linux系统编程中,`fork()`, `exit()` 和 `exec()` 这三个系统调用是构建多进程程序的关键组成部分。它们允许程序创建新的进程、结束当前进程以及替换当前进程的执行上下文,从而实现复杂的功能和服务。 ### `...

    系统调用表可以作为索引使用

    在本篇内容中,我们将详细探讨《系统调用表可以作为索引使用》这一主题的核心知识点,特别关注于Linux系统调用表的结构、功能以及如何作为索引工具来帮助程序员更好地理解和使用这些系统调用。 #### 一、系统调用的...

    分析Linux内核创建一个新进程的过程

    除了fork()函数外,Linux内核还提供了vfork()和clone()系统调用。vfork()函数与fork()函数类似,但它不允许子进程执行exec()函数,直到父进程调用wait()函数或者exit()函数。clone()函数则可以创建一个新的进程,...

    Linux系统调用函数

    ### Linux系统调用函数知识点详解 #### 一、进程控制 **fork** - **功能**:创建一个新进程。 - **参数**:无。 - **返回值**:在父进程中返回子进程的PID,在子进程中返回0。 - **用途**:用于创建一个与父进程...

    linux进程创建详解

    `sys_vfork()` 同样调用 `do_fork()`,但传递了 `CLONE_VFORK | CLONE_VM | SIGCHLD` 的组合标志。`CLONE_VFORK` 用于确保子进程在执行 `exec` 或退出之前不会运行;`CLONE_VM` 表明子进程将继承父进程的虚拟内存,...

    Linux操作系统实验总结分析报告.pdf

    整个进程创建过程可能涉及到如下函数:fork()/vfork()/_clone-----------&gt;clone()---------&gt;do_fork()----------&gt;copy_process() 进程的切换:进程切换又称为任务切换、上下文切换。它是一种行为,为了控制进程的...

    linux内核源代码情景分析

    4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件系统 ...

    Linux系统编程.zip

    2. **进程管理**:包括进程创建(`fork`、`vfork`、`clone`)、进程间通信(管道、消息队列、共享内存、信号量、套接字等)、进程同步与互斥、进程调度策略等。这些概念对于编写多线程或多进程应用程序至关重要。 3...

    vfork() 函数详解示例代码

    在现代Linux系统中,`vfork()`通常被实现为`clone()`函数的一个特殊标志集,它仍然保留了地址空间共享的特点。 在实际开发中,由于`vfork()`的复杂性和潜在问题,一般推荐使用更安全、更稳定的`fork()`函数。然而,...

    Linux内核源代码情景分析.pdf

    第4章进程与进程调度深入探讨了进程的四要素,进程创建、执行与消亡的过程,以及系统调用fork、vfork、clone、execve、exit、wait的使用。文档还分析了进程的调度与切换、强制性调度、系统调用nanosleep和pause、...

    结合 linux 0.11 源码分析为什么 fork 函数会返回两个值

    在Linux操作系统中,`fork()`函数是一个非常关键的系统调用,它被用来创建一个新的进程——也就是我们常说的子进程。这个过程被称为进程复制,因为子进程会继承父进程的大部分属性,包括打开的文件、内存空间、信号...

    LINUX内核源代码情景分析(上).part1.rar

    4.3 系统调用fork()、vfork()与clone() 4.4 系统调用execve() 4.5 系统调用exit()与wait4() 4.6 进程的调度与切换 4.7 强制性调度 4.8 系统调用nanosleep()和pause() 4.9 内核中的互斥操作 第5章 文件...

    操作系统实验指导书-实验三(发布版).pdf

    当一个进程调用fork后,系统会复制当前进程(父进程)的大部分信息(PCB以及地址空间等)来创建一个新的进程(子进程)。子进程是父进程的副本,除了进程标识号(PID)和资源统计信息(如CPU使用时间等)之外,几乎...

Global site tag (gtag.js) - Google Analytics