进程的创建,终止,等待

本文参考博客链接:
https://blog.csdn.net/zz709196484/article/details/54770017

###进程创建
进程的创建主要是通过调用fork( )函数和vfork( )函数来实现的。

####fork()
fork()函数的基本形式如下:

#include
pid_t fork(void){}

pid_t实际上就是short类型,这个在sys/types.h中定义:

typedef short pid_t;     

当fork()创建子进程成功后,父进程返回的是子进程的pid,子进程返回0值,如果创建失败,父进程返回-1.

来看一段简单的代码:

  1 #include
  2 #include
  3 #include
  4 
  5 
  6 int main()
  7 {
  8     printf("before:%d\n",getpid());
  9     pid_t pid = fork();
 10     if (pid < 0)
 11     {
 12         printf("fork error!!\n");
			 return -1;
 13     }
 14     printf("after:%d\n",getpid());
 15     sleep(1);
 16     return 0;
 17 }

运行一下:
进程的创建,终止,等待_第1张图片

我们发现,在fork( ) 之前的before只打印了一句,而fork( )之后的after却打印了两句,且3句中有2句是在进程5988内打印的,很明显,5988是父进程的pid,5989是子进程的pid,为什么子进程不打印before呢?

这里我们就得讲一下父子进程的概念了,子进程是如何创建的呢?他与父进程有什么样的关系呢?

解释:每个进程都有自己的进程数据块PCB,操作系统正是通过管理PCB来管理进程的,而子进程的PCB是由父进程拷贝而来的。我们都知道,PCB中有一个程序计数器,用来存储下一句要执行指令的地址,子进程自然也拷贝了父进程的程序计数器,那子进程在创建好后,cpu就会按照程序计数器,让子进程直接执行fork()后面一句指令,这也就是为什么子进程没有打印before那一句的原因。

####fork()与写时拷贝
上面已经说过,子进程的PCB源自于父进程拷贝而来,所以父子进程共享数据段和代码段,但是,创建子进程的目的终究是想让子进程做一些不同于父进程的东西,所以,当改变子进程内数据时,系统就会为子进程的数据重新开辟空间,与父进程分开存储,这就和写时拷贝是一样的(不修改就赚了,不会另外开辟空间)。

下图是关于子进程修改数据后,系统为其开辟空间的图解:

进程的创建,终止,等待_第2张图片


####vfork()
vfork()函数与fork()函数极为相似,都是用于创建新进程,他和fork()的不同之处在于:

1.由vfork()函数创建的子进程是与父进程共享数据段和代码段的,不论修补修改子进程内的数据,都不另外开辟空间,这是真正的**“共享”**。

2.vfork()函数会保证子进程优先运行,且父进程会阻塞在vfork()函数后,直到子进程调用exec或exit后才会继续运行。
(注意!如果子进程没有调用exec或者exit,直接return,会释放父进程的资源,但注意此时父进程还阻塞在vfork()之后。这会造成严重的后果!linux下会造成调用栈混乱…亲测)

来用代码测试一下:

  1 #include
  2 #include
  3 #include
  4 
  5 int i=10;//这里的i定义或者定义在vfork之前都可以,不可以定义在vfork之后,
			 //倘若定义在vfork之后,父进程先阻塞,子进程创建变量i并修改程20,子进程退出,但     
			 //不释放资源,父进程此刻从vfork后面指令开始运行,运行到"int i = 10"就会把i重
			 //新赋值成10
  6 int main()
  7 {
  8     pid_t pid = vfork();
  9     if (pid < 0)
 10     {
 11         perror("vfork error:");
 12         return -1;
 13     }
 14     else if (pid == 0)//child
 15     {
 16         i=20;
 17         printf("this is child process:%d i=%d\n  ",getpid(),i);
 18         exit(EXIT_SUCCESS);
 19     }
 20     else//parent
 21     {
 22         printf("this is parent process:%d i=%d\n",getpid(), i);
 23     }
 24 
 25     return 0;
 26 }


进程的创建,终止,等待_第3张图片


###进程终止

进程退出的情况有如下3中:
1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.程序异常退出

##常见的进程退出方法

  1. _exit
#include
void _exit(int status)//status定义了进程的终止状态,父进程通过wait来获取该值.
					  //虽然status是int,但是仅有低9位可以被父进程所用,所以在_exit(-1)
					  //时,返回值是255	

_exit又叫暴力退出,直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;

2.exit

#include
void exit(int status)

exit() 函数在_exit上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。

###exit与_exit()之间的区别
进程的创建,终止,等待_第4张图片
简单的说,_exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。而exit关闭文件,清空输出缓存,调用用户的清理函数.
(exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O缓冲"。 )

####为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?
‘exit()’与‘_exit()’有不少区别在使用‘fork()’,特别是‘vfork()’时变得很突出。

‘exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构(user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序 (自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对应,_exit函数只为进程实施内核清除工作。

在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是 因为使用它会导致标准输入输出(stdio: Standard Input Output)的缓冲区被清空两次,而且临时文件被出乎意料的删除(临时文件由tmpfile函数创建在系统临时目录下,文件名由系统随机生成)。在C++程序中情况会更糟,因为静态目标(static objects)的析构函数(destructors)可以被错误地执行。

(还有一些特殊情况,比如守护程序,它们的父进程需要调用‘_exit()’而不是子进程;

3.return

return是很常见的退出进程的方式,但只有在main( )函数中调用return才能结束进程.
其实,return (n)和exit( n )是一样的,因为调用main的运行时函数会将main的返回值做为exit的参数.


###进程等待

####进程为什么要等待?
学过进程的人都知道,在父进程创建子进程后,父进程要等待子进程退出,读取他的退出信息,释放他的资源,否则就会产生僵尸进程,而进程等待就是这么一回事 : 防止僵尸进程的产生

####进程等待的方法

###1.wait( )

#include
#include

pid_t wait(int *status)
//返回值:成功则返回被等待进程的pid,失败则返回-1
//status:输出型参数,获取子进程的退出状态,如果不关心,可以设置成NULL

####wait函数的效果

  1. 如果有子进程再运行,那么当前父进程就处于阻塞状态(可以理解为什么事情都没有做,一直在检测子进程是否在运行);

  2. 如果子进程都已经终止,那么wait可立即获得子进程的终止状态(退出码,退出信息),子进程的终止状态是体现在status参数上的,另外wait还会返回所终止的子进程的标识符;

  3. 如果当前进程没有任何子进程,那么wait会立即出错返回(此时返回值为-1);

  4. 如果有一个子进程终止,那么wait便返回;

###waitpid()

#include
#include

pid_t waitpid(pid_t pid, int *status, int options);

####waitpid函数的参数

  1. pid_t pid : 调用waitpid可以等待标识符为pid的进程,该参数也可以取-1,即标识等待所有子进程,就和wait一样了.
  2. int *status :
    WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    WEXITSTATUS(status) :若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  3. options:
    WHOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不子以等待。若正常结束,返回该子进程的ID.

###关于参数status
我们发现wait( )函数和waitpid( )函数中都有一个status参数.

  1. 这是一个输出型参数,获取子进程的退出状态,如果不关心,可以设置成NULL,否则,他将接收子进程的退出码并反馈给父进程,也就是说,父进程就是通过status这个参数来获得子进程的退出信息的,可见他的重要性.

  2. 虽然他的参数类型是int * ,但是真正有效的只有低16位,也就是低2个字节,在这低16位中:

	   高8为存储的是退出码,程序运行完毕退出才会有,此时低8位为0
       低8位存储的是引起异常退出的信号值,当程序异常退出时,此时高8位为0

大家可以结合这两张图来理解:
进程的创建,终止,等待_第5张图片
进程的创建,终止,等待_第6张图片

最后附上wait函数和waitpid函数的测试代码
###wait

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 
  7 
  8 int main()
  9 {
 10     pid_t pid;
 11     pid =fork();
 12     if (pid < 0)
 13     {
 14         perror("fork error:");
 15         return -1;
 16     }
 17     else if (pid==0)
 18     {
 19         //child
 20         printf("child process:%d\n",getpid());
 21         sleep(15);
 22         exit (10);
 23     }
 24     else//父进程
 25     {
 26         int i;//i用来接收wait函数的返回值,用来判断是否等待到子进程
 27         int j;//j用来接收子进程退出码,等同于wait函数的status参数
 28 
 29         i=wait(&j);
 30         if (i>0 && (j&0x7F)==0)//0x7F代表16进制的127,即二进制中低7为全为1,其余为0
 31         {
 32             //正常退出
 33             printf("the child process pid is: %d\n",i);
 34 
 35             printf("the exit num is :%d\n",(j>>8)&0x7F);
 36 
 37         }
 38         else if (i>0)//低8位不全为0,说明异常退出,此时打印退出码
 39         {
 40             printf("the child process exit error:%d\n",j&0x7F);
 41 
 42         }
 43 
 44     }
 45 
 46     return 0;
 47 }

运行一下:
进程的创建,终止,等待_第7张图片

倘若在别的进程kill掉3225进程:
进程的创建,终止,等待_第8张图片

###waitpid

*waitpid.c*/
#include
#include
#include
#include
#include

int main()
{
 pid_t pc,pr;
 pc=fork();
 if(pc<0)
 {
  printf("error fork!\n");
  exit(0);
 }
 else if(pc==0) /*子进程*/
 {
  /*子进程暂停5s*/
  sleep(5);
  /*子进程正常退出*/
  exit(0);
 }
 else /*父进程*/
 {
  /*循环测试子进程是否退出*/
  do 
  {
   /*调用waitpid,且父进程不阻塞*/
   pr=waitpid(pc,NULL,WNOHANG);/*pc是指定的子进程,只要指定的子进程没结束就会一直等下去*/
   /*若子进程还未退出,则父进程暂停1s*/
   if(pr==0)
   {
    printf("the child process has not exited\n");
    sleep(1);
   }
  }while(pr==0);
  /*若发现子进程退出,打印出相应信息*/
  if(pr==pc)
  {
   printf("get child exit code:%d\n",pr);
  }
  else
   printf("some error occured\n");
 }
 return 0;
}

运行一下:

进程的创建,终止,等待_第9张图片

你可能感兴趣的:(操作系统)