原文地址:http://hi.baidu.com/ikaruga11/blog/item/fb6d75725a8d8d148701b080.html
APUE上的一个例子:

example1 (forkt.c ):

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

int glob = 5;

int main()
{
        int var=10;
        pid_t pid;

        printf("befork vfork\n");
        if((pid = fork()) < 0){
                printf("error\n");
                exit(1);
        }else if(pid == 0){
                glob++;
                var++;
                _exit(0);
        }
        printf("pid = %d, glob = %d, var = %d",getpid(), glob,var);
        exit(0);
}

执行
./forkt.c
./forkt.c > temp.out
将分别输出什么呢?

为了找出为什么会输出如上内容,以及fork vfork exit _exit的区别,于是改造上面的代码
example2:(省略了其他同上相同的代码)

        printf("befork vfork\n");
        if((pid = fork()) < 0){
                printf("error\n");
                exit(1);
        }else if(pid == 0){
                glob++;
                var++;
                exit(0);
        }
        printf("pid = %d, glob = %d, var = %d",getpid(), glob,var);
        _exit(0);
执行
./forkt.c
./forkt.c > temp.out
将分别输出什么呢?


example3:(省略了其他同上相同的代码)

        printf("befork vfork\n");
        if((pid = vfork()) < 0){
                printf("error\n");
                exit(1);
        }else if(pid == 0){
                glob++;
                var++;
                _exit(0);
        }
        printf("pid = %d, glob = %d, var = %d",getpid(), glob,var);
        exit(0);

执行
./forkt.c
./forkt.c > temp.out
将分别输出什么呢?



example4:(省略了其他同上相同的代码)

        printf("befork vfork\n");
        if((pid = vfork()) < 0){
                printf("error\n");
                exit(1);
        }else if(pid == 0){
                glob++;
                var++;
                exit(0);
        }
        printf("pid = %d, glob = %d, var = %d",getpid(), glob,var);
        _exit(0);

./forkt.c
./forkt.c > temp.out
将分别输出什么呢?


先把几个概念缕一缕:
   fork: 子进程拥有父进程的数据段、堆和栈的副本,父进程和子进程共享正文段。但现在很多实现却并不是将父进程的数据段、堆栈段进行完全拷贝,而是采用写时复制(copy-on-write),内核将其标记为只读,(典型的页式虚存)只有父进程或子进程对这些区域进行修改时内核才真正将那一页进行拷贝,从物理上分离开。
   vfork:由于在vfork后经常是跟着一个exec执行一个新的程序不会在用到原来的地址空间,所以vfork的子进程在调用exec或exit之前是在父进程的空间里运行的,这样对于页式虚存效率很高。另外,vfork的子进程总是先与父进程执行,但是子进程不能依赖与父进程的执行否则产生死锁。

exit(0):根据实现的不同而不同,一般是刷新I/O缓冲区,关闭所有I/O标准流(APUE上如是说,但是我在linux下验证的结果应该是没有关闭),一般现在的I/O库函数在关闭I/O流方面不自找麻烦了。

_exit(0):不刷新I/O缓冲区


标准I/O库:
标准I/O库是带缓存的,如果标准输出是连接到终端设备,则它是行缓冲的,否则是全缓冲的。行缓冲在接收到一个换行符才进行刷新,而全缓冲在缓冲区满或者程序在执行exit退出后在执行缓冲区刷新



好了 ,现在我们来看看上面的例子到底输出什么东东了

example1:

执行:./forkt.c
交互方式执行,则是行缓冲, befork vfork后跟一个换行符\n,则刷新缓冲区输出 befork vfork,然后fork一个子进程,父子进程分别运行在不同的存储空间,子进程执行_exit()退出,不刷新缓冲区,父进程执行最后一个printf,但由于没有遇到\n,所以并不立即输出,在执行exit(0)后刷新缓冲区,此时输出
pid =6724, glob = 5, var =10

执行:./forkt.c > temp.out
非交互方式,则是全缓冲,首先执行printf("befork vfork\n")此时并不输出,而是缓存在缓冲区,然后fork一个子进程,父子进程分别运行在不同的存储空间,拷贝一份父进程中缓冲区的befork vfork\n 到子进程,子进程执行_exit()退出,不刷新缓冲区,父进程执行最后一个printf,也缓存起来并不立即输出,在执行exit(0)后刷新缓冲区,此时输出全部输出
cat > temp.out 输出如下:
befork vfork
pid =6731, glob = 5, var =10


example2:

执行:./forkt.c
交互方式执行,则是行缓冲, befork vfork后跟一个换行符\n,则刷新缓冲区输出 befork vfork,然后fork一个子进程,父子进程分别运行在不同的存储空间,子进程执行exit()刷新缓冲区退出,父进程执行最后一个 printf,但由于没有遇到\n,所以并不立即输出,在执行_exit(0)后由于不刷新缓冲区而退出,所以最后一个printf内容并不输出。所以
执行:./forkt.c
befork vfork


执行:./forkt.c > temp.out
非交互方式,则是全缓冲,首先执行printf("befork vfork\n")此时并不输出,而是缓存在缓冲区,然后fork一个子进程,父子进程分别运行在不同的存储空间,同时拷贝一份父进程中缓冲区的befork vfork\n 到子进程,子进程执行exit()刷新缓冲区退出,此时输出befork vfork\n ,父进程执行最后一个printf,也缓存起来并不立即输出,在执行_exit(0)后由于不刷新缓冲区而退出,因此输出
cat > temp.out 输出如下:
befork vfork


example3:


执行:./forkt.c
交互方式执行,则是行缓冲, befork vfork后跟一个换行符\n,则刷新缓冲区输出 befork vfork,然后vfork一个子进程,父子进程运行在相同的存储空间,子进程执行_exit()不刷新缓冲区退出,父进程执行最后一个 printf,但由于没有遇到\n,所以并不立即输出,在执行exit(0)后刷新缓冲区而退出,所以最后一个printf内容输出。所以
执行:./forkt.c
befork vfork
pid =6802, glob = 6, var =11


执行:./forkt.c > temp.out
非交互方式,则是全缓冲,首先执行printf("befork vfork\n")此时并不输出,而是缓存在缓冲区,然后vfork一个子进程,父子进程运行在相同的存储空间,子进程执行_exit()不刷新缓冲区退出,父进程执行最后一个printf,也缓存起来并不立即输出,在执行exit(0)刷新缓冲区而退出,因此输出
cat > temp.out 输出如下:
befork vfork
pid =6808, glob = 6, var =11


example4:
执行:./forkt.c
befork vfork
pid =6802, glob = 6, var =11


执行:./forkt.c > temp.out

cat > temp.out 输出如下:
befork vfork
pid =6808, glob = 6, var =11

 

另外

简单的说,exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容将刷新定义,并调用所有已刷新的“出口函数”(由atexit定义)。
_exit:该函数是由Posix定义的,不会运行exit handler和signal handler,在UNIX系统中不会flush标准I/O流。
简单的说,_exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。
共同:
不管进程是如何终止的,内核都会关闭进程打开的所有file descriptors,释放进程使用的memory!
note:
在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是 因为使用它会导致标准输入输出的缓冲区被清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建在系统临时目录下,文件名由系统随机生成)。
在C++程序中情况会更糟,因为静态目标(static objects)的析构函数(destructors)可以被错误地执行。
还有一些特殊情况,比如守护程序,它们的父进程需要调用‘_exit()’而不是子进程;适用于绝大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。

在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响 父进程的状态

posted on 2009-09-07 19:42  Myhsg  阅读(1021)  评论(0编辑  收藏  举报