fork()的一些测试

这篇随笔也是看到陈皓的这篇文章自己做了一些测试和学习,http://coolshell.cn/articles/7965.html

1. 两个fork程序 

关于fork,先看我们的第一个fork测试程序

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 int main(){
 4     for(int i=0; i<2; i++){ 
6
int result = fork(); 7 if(result == 0){ 8 printf("child process -----"); 9 }else if(result > 0){ 10 printf("parent process: child id = %d---------", result); 11 } 12 printf("ppid : %d---pid : %d---i=%d\n", getppid(), getpid(), i); 13 } 14 int n; 15 sleep(10); 16 }

如果fork()成功,在父进程中,fork()返回子进程的pid,在子进程中返回0, 失败则返回-1。程序的最后sleep了10s, 在这10s当中,其实几乎是4个进程同时在sleep。这时用pstree查看一下,可以看到如下情况

bash是我运行./fork的那个shell,可以去想像一下这个过程,在i为0和1的时候,分别是到了哪个情况。    运行结果如下

fork()的一些测试_第1张图片

可以看到各个进程交替执行的情况是怎样的。

 

我们再来看一下第二个fork()程序,做了一些改动

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 int main(){
 4     for(int i=0; i<2; i++){
 5         int result = fork();
 6         printf("& %d  ", getpid());
 7     }
 8     sleep(5);
 9     printf("\n");
10 }

猜猜会打出几个&, 不会是6个,实际上是8个。因为printf是有buffer的,而一个进程的buffer也会随着fork被复制的(库函数的buffer是在user-space中的),直到第9行遇到 "\n"时,进程的buffer才会被flush掉。 运行的结果是在sleep之后才会打印出所有的东西,也说明了这一点。  以下是运行结果

对照着sleep时pstree的情况来看

对着代码去想一想那个过程,./for一运行起来,最终会产生4个进程,上面那个图右边那一栏就是这4个进程的id, 可以看到后两行的左边的那个&就是父进程(可以看到打出&时的进程id)打出的,通过buffer复制过来的。

 

2。设备文件(device file),有缓存的(buffered)和没有缓存的(unbuffered)

device file分为块设备文件(block device)和字符设备文件(character device file),设备文件的定义这里引用一下wiki上的定义,http://en.wikipedia.org/wiki/Device_file。device file or special file is an interface for a device driver that appears in a file system as if it were a file。也就是程序可以用标准的IO system call和device driver进行交互。字符设备文件没有缓存,块设备文件是有缓存的.

c标准库中定义的stderr就是一个字符设备的FILE*, 而stdout是一个块设备的FILE*,在C库中,FILE是一个struct,它用来标识一个stream并保存这个stream相关的信息,如pointer to its buffer,position indicator等(注意stream即流的概念并不是属于语言的)。stderr没有缓存,stdout有缓存,通过下面的程序可以验证。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 int main()  
 4 {
 5         int i = 0;
 6         while(1)
 7         {
 8                 fprintf(stdout,"hello-std-out");
 9                 fprintf(stderr,"hello-std-err\n");
10                 sleep(1);
11                 if(i++ == 5){
12                     i = 0;
13                     fflush(stdout);
14                 }
15         }
16         return 0;
17 }

13行调用的fflush(FILE*)是stdio.h中声明的函数,如果没有这个调用,8行中打到stdout缓存中的内容都不会被flush出来的。

在C++的标准io库中,也有类似的情况,标准库中定义的cout,cerr都是类ostream的实例,它们对应于c库中的stdout,stderr。在<iostream>中有如下的声明,但是我没有再在其它的头文件中找到stdout,stderr定义的信息了

1 extern ostream cout;      /// Linked to standard output
2 extern ostream cerr;      /// Linked to standard error (unbuffered)

通过如下的C++程序可以验证cout是buffered, 而cerr是unbuffered。

 1 #include<iostream>
 2 using namespace std;
 3 int main(){
 4     char ch[5] = {'a','b','c','d','e'};
 5     char ch2[5] = {'e','r','r','o','r'};
 6     int i = 0;
 7     while(true){
 8  //       cerr.write(ch2, 5);
 9         cout.write(ch, 5);
10         cout.put(' ');
11         sleep(1);
12         if(i++ == 3){
13             //cout.flush();
14             cout.put('\n');
15             i = 0;
16         }
17     }
18 }

可以用这个程序测试,对于cout,如果没有13,14行的cout.flush()或者给它一个换行符它是不会从缓存中打到输出的。对于cerr就不会有缓存了,但是比较奇怪的是,好像cerr一有输出,cout的缓存就会被清空一下,这个和c中的stdout,stderr是不一样的,没有明白这是怎么回事。

 

3,unix的一点东西

附上apue中描述unix结构的一幅图,重点是库函数(library of common functions)和系统调用(system call)的关系

fork()的一些测试_第2张图片

可以看到,内核就是实现系统调用所要的功能,而system call的使用方式就是普通的函数调用,在使用上跟自己定义的函数及库函数没有区别。可能这也是为什么POSIX中相关的标准,只规定了头文件以及头文件要提供的接口(函数),并不区分这个接口是system call还是system call之上的库函数。注意POSIX标准是包括了C标准库的。

POSIX(http://en.wikipedia.org/wiki/POSIX)是IEEE定义的一系列的标准,用于维护操作系统间的兼容性。对于unix系统,早期的posix只定义了core programming interface,也就是操作系统要提供的服务(函数),现在则扩展了很多,包括command line和scriping interface(基于Korn shell),一些用户级别的程序如awk, 还有一个标准的thread library API,大多数现代操作系统都支持了。这些东西现在都汇集于一个文档 IEEE Std 1003.1-2008  或是叫  POSIX.1-2008

posix定义的unix系统必需的头文件(required headers)如下, 摘自apue2.2.1。我们上面的fork程序只使用了 <unistd.h>。

fork()的一些测试_第3张图片

 

参考:

POSIX wiki : http://en.wikipedia.org/wiki/POSIX

你可能感兴趣的:(fork)