1. fork函数
fork函数用于创建一个新进程,但返回两个值:子进程返回0,而父进程返回的是子进程的ID。但子进程是父进程的副本,并不共享存储空间,只共享正文段。
#include <stdio.h> #include <unistd.h> int glob = 6; char buf[] = "a write to stdout\n"; int main( void ) { int var; pid_t pid; var = 88; if ( write( STDOUT_FILENO, buf, sizeof( buf ) - 1 ) != sizeof( buf ) - 1 ) printf("write error\n"); printf("before fork\n"); if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ glob++; var++; } else{ sleep( 2 ); } printf("pid=%d,glob=%d,var=%d\n", getpid(), glob, var); return 0; }程序输出:
重定向到终端,则缓冲区会被flush,而重定向到文件,则不会。
2. fork函数的文件共享:
#include <stdio.h> #include <unistd.h> int main( void ) { FILE *file = fopen("temp", "w+"); pid_t pid; char buf1[] = "abcdefghi\n"; char buf2[] = "ABCDEFGHI\n"; if ( fputs( buf1, file ) == EOF ){ printf("write error\n"); } if ( ( pid = fork() ) < 0 ){ printf("fork error\n"); } else if ( pid == 0 ){ if ( fputs( "child:\n", file ) == EOF ){ printf("child write error\n"); } if ( fputs( buf2, file ) == EOF ){ printf("child write error\n"); } } else{ sleep( 2 ); if ( fputs("parent:\n", file ) == EOF ){ printf("parent write error\n"); } if ( fputs( buf1, file ) == EOF ){ printf("write error\n"); } } return 0; }
备注:这里注意child和parent两个字符出现的位置。子进程完全是父进程的副本!
fork有下面两种用法:
1) 一个父进程希望复制自己,使父子进程同时执行不同的代码段。(网络编程的应用)
2) 一个进程要执行一个不同的程序
3. vfork函数
与fork函数有以下两点不同:
1) vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec或exit,于是也就不会访问该地址空间。
2) vfork保证子进程先运行
#include <stdio.h> #include <unistd.h> int glob = 6; int main( void ) { int var; pid_t pid; var = 88; printf("before vfork\n"); if ( ( pid = vfork() ) < 0 ){ printf("vfork error\n"); }else if ( pid == 0 ){ glob++; var++; _exit( 0 ); } printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var ); return 0; }
4. wait和waitpid函数
内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID,该进程的终止状态,以及该进程使用的CPU时间总量。
当调用wait或waitpid的进程可能会发生如下情况:
1) 如果其所有子进程都还在运行,则阻塞
2) 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
3) 如果它没有任何子进程,则立即出错返回
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main( void ) { pid_t pid; int status; if ((pid = fork()) < 0) printf("fork error\n"); else if ( pid == 0 ){ printf("wait函数会阻塞,直到子进程终止,并捕捉子进程的终止信息!这样也可以保证子进程比父进程先执行。\n"); sleep( 2 ); } if ( wait(&status) != pid ) printf("wait error\n"); if ( pid > 0 ){ printf("main done\n"); } return 0; }
waitpid的优势:
1) waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态。
2) waitpid提供了一个wait的非阻塞版本。
3) waitpid支持作业控制
#include <sys/wait.h> #include <unistd.h> int main( void ) { pid_t pid1; pid_t pid2; if ( ( pid1 = fork()) < 0 ){ printf("fork error\n"); } else if ( pid1 == 0 ){ printf("fork 1....\n"); exit( 0 ); } if ( ( pid2 = fork() ) < 0 ){ printf("fork error\n"); } else if ( pid2 == 0 ){ printf("fork 2....\n"); exit( 0 ); } if ( waitpid( pid2, NULL, 0 ) != pid2 ){ printf("fork 2 exit error\n"); } else{ printf("fork 2 exit ok\n"); } if ( waitpid( pid1, NULL, 0 ) != pid1 ){ printf("fork 1 exit error\n"); } else{ printf("fork 1 exit ok\n"); } return 0; }
5. 竞争条件
父进程/子进程之间的相互竞争:
#include <stdio.h> #include <sys/wait.h> static void charatatime( char * ); int main( void ) { pid_t pid; if ( ( pid = fork() ) < 0 ){ printf("fork error\n"); } else if ( pid == 0 ) charatatime("output from child\n"); else{ charatatime("output from parent\n"); } return 0; } static void charatatime( char *str ) { char *ptr; int c; setbuf( stdout, NULL ); for ( ptr = str; ( c = *ptr++ ) != 0; ) putc( c, stdout ); }
6. exec函数
exec函数通常用于执行可执行文件,所以在fork后直接运行较好。我们先编写一个用于exec执行的程序echoall.c:
#include <stdio.h> int main( int argc, char *argv[] ) { int i; char **ptr; extern char **environ; for ( i = 0; i < argc; i++ ) printf("argv[%d]:%s\n", i, argv[ i ] ); for ( ptr = environ; *ptr != 0; ptr++ ) printf("%s\n", *ptr ); return 0; }然后编译如下:
cc -o echoall echoall.c
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL }; int main( void ) { pid_t pid; if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ if ( execle( "/home/leichaojian/echoall","echoall","myarg1","MY ARG2", ( char * )0, env_init ) < 0 ) printf("execle error\n"); } if ( waitpid( pid, NULL, 0 ) < 0 ) printf("wait error\n"); if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ if ( execlp( "/home/leichaojian/echoall","echoall","only 1 arg",( char * )0 ) < 0 ){ printf("execlp error\n"); } } return 0; }其中/home/leichaojian可以通过指令pwd来得到。程序运行如下:
7. 解释器文件
用一个程序来解释脚本语言(PS:找段时间学习shell编程)
echoall.c:
#include <stdio.h> int main( int argc, char *argv[] ) { int i; char **ptr; extern char **environ; for ( i = 0; i < argc; i++ ) printf("argv[%d]:%s\n", i, argv[ i ] ); // for ( ptr = environ; *ptr != 0; ptr++ ) // printf("%s\n", *ptr ); return 0; }
cc -o echoall echoall.c
shell.c:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main( void ) { pid_t pid; if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ if ( execl("/home/leichaojian/testinterp", "testinterp", "myarg1","MY ARG2",(char *)0 ) < 0 ) printf("execl error\n"); } if ( waitpid( pid, NULL, 0 ) < 0 ) printf("waitpid error\n"); return 0; }然后程序输出如下: