linux进程(三)——如何终止进程?

一、如何终止进程?

终止一个进程大致有以下几种情况:

(1)函数执行完毕,main函数正常return;

(2)使用linux提供的退出进程相关的函数:exit()、_exit()、atexit()、on_exit()

exit()的作用是退出当前进程,并且尽可能释放当前进程占用的资源

_exit()的作用也是退出当前进程,但是不试图释放当前进程占用的资源,而且_exit()终止进程的时候不调用atexit()、on_exit()等注册的回调函数!

atexit()、on_exit()作用都是程序退出是指定调用用户的代码(可以理解为设置回调函数),区别在于on_exit()可以为设定的用户函数设定参数。

实例1、函数退出回调函数例程

#include 

void exit(int status);

int atexit(void (*function)(void));

The atexit() function registers the given function to be called at normal process termination, either via exit(3) or via return from the program's main().  

#include 
#include 
#include 
void bye(void)
{
   printf("That was all, folks\r\n");
}

void bye1(void)
{
   printf("That was bye1, folks\r\n");
}


int main(void)
{
   long a;
   int i;

   a = sysconf(_SC_ATEXIT_MAX);
   printf("ATEXIT_MAX = %ld\r\n", a);

   i = atexit(bye);
   if (i != 0) 
   {
       fprintf(stderr, "cannot set exit function\r\n");
       exit(EXIT_FAILURE);//EXIT_FAILURE=1  返回给操作系统作为exit status
   }
   
   i = atexit(bye1);
   if (i != 0) 
   {
       fprintf(stderr, "cannot set exit function\r\n");
       exit(EXIT_FAILURE);//EXIT_SUCCESS=0  返回给操作系统作为exit status
   }

   exit(EXIT_SUCCESS);
}

我们看到bye是先注册的,但是确是bye1先执行的,这是因为atexit()函数是按照栈方式向系统注册的,所以后注册的函数会先调用。

 

二、僵尸进程与孤儿进程

1、定义

在每个进程退出的时候,内核自动释放该进程所有的资源,包括打开的文件,占用的内存,malloc申请的内容没有free时等。 但是仍然为其保留一定的信息,主要是task_struct和栈内存,(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放(也就是需要父进程来收尸)。 但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 

僵尸进程:即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程。

孤儿进程:一个父进程退出, 而它的一个或几个子进程仍然还在运行,那么这些子进程就会变成孤儿进程,孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集的工作。

2、问题与危害:

孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

三、使用wait / waitpid回收进程资源

#include 
#include 

pid_t wait(int *status);

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

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
/* This is the glibc and POSIX interface; see NOTES for information on the raw system call.*/

1、使用wait回收进程

形参status用来返回子进程结束时的状态,父进程通过wait得到status后就可以知道子进程的一些结束状态信息。wait的返回值pid_t,这个返回值就是本次wait回收的子进程的PID。当前进程有可能有多个子进程,wait函数阻塞直到其中一个子进程结束wait就会返回,wait的返回值就可以用来判断到底是哪一个子进程本次被回收了。若父进程没有任何子进程则wait返回错误。

原理:父进程调用wait函数后阻塞,等待子进程的SIGCHILD信号。子进程结束时,系统向其父进程发送SIGCHILD信号,父进程被SIGCHILD信号唤醒然后去回收僵尸子进程。父子进程之间是异步(各自双方不知道对方是什么状态)的,SIGCHILD信号机制就是为了解决父子进程之间的异步通信问题,让父进程可以及时的去回收僵尸子进程。

#include 
#include 
#include   
#include 
#include 

int main(void)
{
	pid_t pid = -1;
	pid_t ret = -1;
	int status = -1;
	
	pid = fork();
	if (pid > 0)
	{
	//因为wait函数是阻塞的,当父进程先执行,子进程没有结束的情况下,父进程会在wait函数这个地方阻塞住
		printf("parent.\n");
		ret = wait(&status);
		
		printf("子进程已经被回收,子进程pid = %d.\n", ret);
		printf("子进程已经被回收,子进程status = %d.\n", status);
	}
	else if (pid == 0)
	{
		// 子进程
		printf("child pid = %d.\n", getpid());
		//while(1);//子进程不可能结束,所以wait函数被阻塞住,子进程不可能被回收
	}
	else
	{
		perror("fork");
		return -1;
	}
	
	return 0;
}

我们还可以通过使用相关宏来判断子进程的返回状态,可以参考man手册对wait函数的解释:

WIFEXITED(status)
              returns true if the child terminated normally, that is, by call‐
              ing exit(3) or _exit(2), or by returning from main().

WEXITSTATUS(status)
              returns  the  exit  status  of  the child.  This consists of the
              least significant 8 bits of the status argument that  the  child
              specified  in  a  call to exit(3) or _exit(2) or as the argument
              for a return statement in main().  This macro should be employed
              only if WIFEXITED returned true.

WIFSIGNALED(status)
              returns true if the child process was terminated by a signal.

WTERMSIG(status)
              returns  the  number of the signal that caused the child process
              to terminate.  This macro should be employed only if WIFSIGNALED
              returned true.

WCOREDUMP(status)
              returns  true  if  the  child  produced a core dump.  This macro
              should be employed only  if  WIFSIGNALED  returned  true.   This
              macro  is  not specified in POSIX.1-2001 and is not available on
              some UNIX implementations (e.g., AIX,  SunOS).   Only  use  this
              enclosed in #ifdef WCOREDUMP ... #endif.

WIFSTOPPED(status)
              returns  true  if the child process was stopped by delivery of a
              signal; this is possible only if the call was  done  using  WUN‐
              TRACED or when the child is being traced (see ptrace(2)).

WSTOPSIG(status)
              returns the number of the signal which caused the child to stop.
              This macro should be employed only if WIFSTOPPED returned true.
WIFCONTINUED(status)
              (since Linux 2.6.10) returns  true  if  the  child  process  was
              resumed by delivery of SIGCONT.

2、使用waitpid回收进程

基本功能一样,都是用来回收子进程,waitpid可以回收指定PID的子进程,waitpid可以阻塞式或非阻塞式两种工作模式。

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

 

pid 意义
pid>0 只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
options 意义
WNOHANG 若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED 返回终止子进程信息和因信号停止的子进程信息
WCONTINUED 返回收到SIGCONT信号而恢复执行的已停止子进程状态信息

 

(1)使用waitpid实现wait的效果

ret = waitpid(-1, &status, 0);          -

1表示不等待某个特定PID的子进程而是回收任意一个子进程,0表示用默认的方式(阻塞式)来进行等待,返回值ret是本次回收的子进程的PID

(2)ret = waitpid(pid, &status, 0);           

   等待回收PID为pid的这个子进程,如果当前进程并没有一个ID号为pid的子进程,则返回值为-1;如果成功回收了pid这个子进程则返回值为回收的进程的PID

(3)ret = waitpid(pid, &status, WNOHANG);

这种表示父进程要非阻塞式的回收子进程。此时如果父进程执行waitpid时子进程已经先结束等待回收则waitpid直接回收成功,返回值是回收的子进程的PID;如果父进程waitpid时子进程尚未结束则父进程立刻返回(非阻塞),但是返回值为0(表示回收不成功)。

ref:

https://www.cnblogs.com/Anker/p/3271773.html

https://blog.csdn.net/csdn_kou/article/details/81091191

你可能感兴趣的:(嵌入式Linux应用编程)