拥有梦想是一种智力,实现梦想是一种能力。

 

概述

并发程序是应用开发中非常重要的一部分内容,如何实现程序的并发?包括多进程编程、进程间通信机制、多线程编程、线程间同步和异步机制等等。本次介绍多进程编程:

  • fork创建进程
  • exit/_exit结束进程
  • exec函数族让进程执行指定程序
  • wait/waitpid回收一个已经结束了的进程
  • 如何创建一个守护进程

 

系统调用fork允许一个进程(父进程)创建一个新进程(子进程)。通过fork,子进程几乎是父进程的复制版本,子进程获得父进程的栈、数据段、堆和执行文本段的拷贝。通常,调用fork产生子进程后,子进程随便会调用execve函数簇执行新的任务,随后执行exit相关函数退出。而父进程则通常会调用wait函数等待子进程终止。此外,我会通过程序的方向来介绍,如何让一个子进程脱离它原有的终端成为一个守护进程运行。

 

fork()创建一个子进程

 pid_t  fork(void);

  • 创建新的进程,失败时返回-1
  • 成功时父进程返回子进程的进程号,子进程返回0
  • 通过fork的返回值区分父进程和子进程

 

示例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> 

int main()
{
  pid_t  pid;

  if ((pid = fork()) < 0) {
     perror(“fork”);  
     return -1;
  }
  else  if (pid == 0) {//子进程执行
     printf(“child  process :  my pid  is %d\n”, getpid());
  }
  else {//父进程执行
     printf(“parent  process :  my pid  is  %d\n”, getpid());
  }  

  return 0;
}

此时,子进程fork()函数之后,而不是程序的开始处进行。由于子进程继承了父进程的所有内容,所以两个进程的执行没有差分。

 

exit/_exit结束进程

 void  exit(int  status);
 void  _exit(int  status);

  • 结束当前的进程并将status返回

exit与_exit的区别是,exit结束进程时会刷新(流)缓冲区。

 

示例

#include <stdio.h>
#include <stdlib.h>

  int main(void) 
  {
     printf("this process will exit");//
     exit(0);
     printf("never  be  displayed");
  }

由于shell终端是行缓冲输出,printf输出的字符串又没有换行,所以 "this process will exit" 会驻留在缓冲区。exit()操作会刷新缓冲区,终端显示   "this process will exit"。

 

exec函数族让进程执行指定程序

  #include  <unistd.h>
  int execl(const char *path, const char *arg, …);
  int execlp(const char *file, const char *arg, …);
 

  •  成功时执行指定的程序;失败时返回EOF
  •  path   执行的程序名称,包含路径
  •  arg…  传递给执行的程序的参数列表
  •  file   执行的程序的名称,在PATH中查找
     

 进程调用exec函数族执行某个程序,进程当前内容被指定的程序替换。从而实现让父子进程执行不同的程序。

 

示例

#include <stdio.h>
#include <unistd.h>

int main()
{
  if  (execl(“/bin/ls”, “ls”, “-a”, “-l”, “/etc”, NULL) < 0) 
  {
     perror(“execl”);
  }

  return 0;
}  

 

问:如何让父子进程执行不同程序?

答:fork()创建一个子进程,然后exec()让进程执行其他程序,这样就达到了父子进程执行不同程序目的。

 

wait/waitpid回收一个已经结束了的进程

如果子进程结束了但是没有被回收的话,此时进程进入死亡态,叫做僵尸进程。僵尸进程的进程控制块依然存在(PCB),会占用资源,务必对其进行回收。通过父进程对其回收。

  #include  <unistd.h>
  pid_t wait(int *status);
   

  •  成功时返回回收的子进程的进程号;失败时返回EOF
  •  若子进程没有结束,父进程一直阻塞
  •  若有多个子进程,哪个先结束就先回收
  •  status 指定保存子进程返回值和结束方式的地址
  •  status为NULL表示直接释放子进程PCB,不接收返回值

 

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  int status;
  pid_t pid;

  if ((pid = fork()) < 0) {
     perror(“fork”);  
	 exit(-1);
  }
  else if (pid == 0) {
     sleep(1);  
	 exit(2);
  }
  else {
     wait(&status);  
	 printf(“%x\n”, status);
  }

  return 0;
}  

wait()函数被父进程执行,父进程便进入阻塞态(一直等待),直到子进程结束。status保存子进程的返回值和结束方式地址。

 

 

如何创建一个守护进程

守护进程通常在系统启动时运行,系统关闭时结束。Linux很多服务程序以守护进程形式运行。其特点

  • 始终在后台运行
  • 独立于任何终端
  • 周期性的执行某种任务或等待处理特定事件

Linux以会话(session)、进程组的方式管理进程。例如,我在打开shell(一个会话建立),运行一个脚本程序,然后关闭sell终端(会话关闭),程序也随之结束。而守护进程是不会随着终端关闭而结束的!!!

 

示例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>  
  
int  main() {
	pid_t pid;
	FILE *fp;
	time_t  t;
	int  i;

	if ((pid = fork()) < 0) 
	{
	  perror(“fork”);  
	  exit(-1);
	}
	else if (pid > 0) 
	{
	   exit(0);
	}
	setsid();
	umask(0);
	chdir(“/tmp”);
	for (i=0; i< getdtablesize(); i++) 
	{
	   close(i);
	}
	if ((fp = fopen(“time.log”, “a”)) == NULL) 
	{
	   perror(“fopen”); 
	   exit(-1); 
	}
	while  ( 1 ) 
	{
	   time(&t);
	   fprintf(fp, “%s”, ctime(&t));
	   fflush(fp);
	   sleep(1);
	}   
	 
	return 0;
}

 

过程分析

 

1.创建子进程,让子进程变成孤儿进程(杀死父进程)

    if ((pid = fork()) < 0) 
    {
      perror(¡°fork¡±);  
      exit(-1);
    }
    else if (pid > 0) 
    {
       exit(0);
    }


2.使子进程脱离愿终端(原来子进程是建立在终端上的),切换终端操作

    setsid();

3.重设文件掩码(推测是给进程创建文件的权限)

    umask(0);


4.设定工作目录(让进程创建的文件一直存在)

chdir()


5.关闭子进程从 父进程那边 `继承`过来的文件

    for (i=0; i< getdtablesize(); i++) 
    {
       close(i);
    }