fork函数
fork描述:创建进程 fork函数一次调用,两次返回 。父进程中,fork返回子进程的PID。子进程中,fork返回0。若出错则返回-1。 每次用户向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能创建新进程,并在这个新进程的上下文中运行他们自己的代码或其他应用程序。 父进程通过调用fork函数创建一个新的运行的子进程。 调用:
#include <sys/types.h>
#include <unistd.h>
pid_t fork ( void ) ;
getpid、getppid
getpid函数返回调用进程的PID。getppid函数返回它的父进程的PID。 getpid、getppid函数返回一个类型为pid_t的整数值,在Linux系统上它在types.h中被定义为int。 调用:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid ( void ) ;
pid_t getppid ( void ) ;
waitpid
一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。 如果成功,返回子进程的PID。如果WNOHANG,则返回0。如果为其他错误,则返回-1。(WNOHANG、WUNTRACED等常量是由系统头文件定义的。例如WNOHANG、WUNTRACED由wait.h头文件(间接)定义)。 如果调用进程没有子进程,那么waitpid返回-1,并设置errno为ECHLD。如果waitpid函数被一个信号中断,那么它返回-1并设置errno为EINTR。 调用:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid ( pid_t pid, int * statusp, int options) ;
检查已回收子进程的退出状态
WIFEXITED(status) :如果子进程通过调用exit或者一个返回(return)正常终止 ,就返回真。WEXITSTATUS(status) :返回一个正常终止的子进程的退出状态。只有在WIFEXITED()返回为真时,才会定义这个状态。WIFSIGNALED(status) :如果子进程是因为一个未被捕获的信号 终止的,就返回真。WTERMSIG(status) :返回导致子进程终止的信号的编号。只有在WIFSIGNALED()返回为真时,才会定义这个状态。WIFSTOPPED(status) :如果引起返回的子进程当前是停止的,就返回真。WSTOPSIG(status) :返回引起子进程停止的信号的编号。只有在WIFSTOPPED()返回为真时,才会定义这个状态。WIFCONTINUED(status) :如果子进程收到SIGCONT信号重新启动,则返回真。
程序实例
代码(一):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main ( ) {
pid_t pid;
int x = 1 ;
pid = Fork ( ) ;
if ( pid == 0 ) {
printf ( "child : x=%d\n" , ++ x) ;
exit ( 0 ) ;
}
printf ( "parent: x=%d\n" , -- x) ;
exit ( 0 ) ;
}
运行结果:
结果分析:
fork函数被父进程调用一次,返回两次。一次返回到父进程,一次返回到新创建的子进程。 pid=0时,为子进程,输出child : x = 2(即执行printf(“child : x=%d\n”, ++x);)。为父进程时输出parent: x=0(即执行printf(“parent : x=%d\n”, --x);)。 并发执行 。父进程和子进程是并发运行的独立进程,内核能够以任意方式交替执行他们的逻辑控制流中的指令。即:在此处我们运行代码(一)的程序时,父进程先完成它的printf语句,然后是子进程(即运行结果为parent: x=0 child : x=2)。然而,在不同的系统上,可能是子进程先运行(运行结果为child : x=2 parent: x=0)。代码(一)对应进程图:
代码(二):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void fork7 ( )
{
if ( fork ( ) == 0 ) {
printf ( "Terminating Child, PID = %d\n" , getpid ( ) ) ;
exit ( 0 ) ;
} else {
printf ( "Running Parent, PID = %d\n" , getpid ( ) ) ;
while ( 1 ) ;
}
}
运行结果:
结果分析:
由于父进程在printf后却有一个while(1)的循环,所以在子进程终止后父进程是无法结束的,导致shell无法出来。此时使用Ctrl+Z为挂起(Ctrl+C为终止)。 ps命令是最基本的进程查看命令 。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵尸、哪些进程占用了过多的资源等等。ps显示瞬间进程的状态,并不动态连续;如果想对进程实行监控应该用top命令。ps -ef :查看全格式的全部进程 ps -ax :查看全部进程 ps -ef|grep <进程名> :查看并筛选 跟进程名有关的进程,该进程名可以是进程的全部或者部分。 僵死进程(zombie):一个终止了但还未被回收的进程。 使用命令 kill -9 加上父进程的ID可杀死该僵死进程。 函数kill是发送信号的函数,它并不能直接杀死进程,它位于头文件#include <sys/types.h>、#include <signal.h>中。函数的第一个参数pid若大于零,那么kill函数发送信号号码sig给进程pid;若pid等于零,kill发送sig给调用进程所在进程组中的每个进程,包括调用进程自己;若pid小于零,那么kill发送信号sig给进程组中pid的绝对值中的每个进程。 代码(二)对应进程图:
代码(三):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void fork9 ( )
{
int child_status;
if ( fork ( ) == 0 ) {
printf ( "HC: hello from child\n" ) ;
exit ( 0 ) ;
} else {
printf ( "HP: hello from parent\n" ) ;
wait ( & child_status) ;
printf ( "CT: child has terminated\n" ) ;
}
printf ( "Bye\n" ) ;
}
运行结果:
结果分析:
wait函数是waitpid函数的简单版本。调用wait( &status )等价于调用waitpid( -1 , &status , 0 )。 wait函数调用:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait ( int * statusp) ;
父进程在执行完第一条printf后遇到wait函数,转去执行子进程,子进程执行完后再返回去执行父进程。 代码(三)对应进程图:
代码(四):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void int_handler ( int sig)
{
printf ( "Process %d received signal %d\n" , getpid ( ) , sig) ;
exit ( 0 ) ;
}
void fork13 ( )
{
pid_t pid[ N] ;
int i;
int child_status;
signal ( SIGINT, int_handler) ;
for ( i = 0 ; i < N; i++ )
if ( ( pid[ i] = fork ( ) ) == 0 ) {
while ( 1 )
;
}
for ( i = 0 ; i < N; i++ ) {
printf ( "Killing process %d\n" , pid[ i] ) ;
kill ( pid[ i] , SIGINT) ;
}
for ( i = 0 ; i < N; i++ ) {
pid_t wpid = wait ( & child_status) ;
if ( WIFEXITED ( child_status) )
printf ( "Child %d terminated with exit status %d\n" , wpid, WEXITSTATUS ( child_status) ) ;
else
printf ( "Child %d terminated abnormally\n" , wpid) ;
}
}
运行结果:
结果分析:
signal函数:进程可以通过使用signal函数修改和信号相关联的默认行为。 signal函数调用:
#include <signal.h>
typedef void ( * sighandler_t) ( int ) ;
sighandler_t signal ( int signum , sighandler_t handler) ;
signal函数通过调用以下三种方式之一来改变和信号signum相关联的行为:
如果handler是SIG_IGN,那么忽略类型为signum的信号。 如果handler是SIG_DFL,那么类型为signum的信号行为恢复默认行为。 否则,handler就是用户自定义的函数的地址,这个函数被称为信号处理器 ,只要进程收到一个类型为signum的信号就会调用这个程序。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序 。调用信号处理程序被称为捕获信号 。执行信号处理程序被称为处理信号 。
代码(五):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void cleanup ( void ) {
printf ( "Cleaning up\n" ) ;
}
void fork6 ( )
{
atexit ( cleanup) ;
fork ( ) ;
exit ( 0 ) ;
}
运行结果:
结果分析:
atexit函数:注册终止函数 。用来注册程序正常终止(也就是通过exit(0)、_exit(0)或return结束的程序)时要被调用的函数。位于头文件:#include<stdlib.h>中。 用 法: void atexit(void (*func)(void));。 exit调用这些注册函数的顺序与它们 登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次。 代码(五)对应进程图:
代码(六):
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void fork16 ( )
{
if ( fork ( ) == 0 ) {
printf ( "Child1: pid=%d pgrp=%d\n" , getpid ( ) , getpgrp ( ) ) ;
if ( fork ( ) == 0 )
printf ( "Child2: pid=%d pgrp=%d\n" , getpid ( ) , getpgrp ( ) ) ;
while ( 1 ) ;
}
}
运行结果:
结果分析:
getpgrp函数返回调用进程的进程组 ID。 getpgrp函数调用:
#include <unistd.h>
pid_t getpgrp ( void ) ;
每个进程都是只属于一个进程组,进程组是由一个正整数进程组ID来标识的。