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
则echoall为可执行文件,然后我们就可以编写exec程序文件:
#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;
}
然后程序输出如下: