嵌入式 Linux 的僵尸进程是什么?

简介: 嵌入式 Linux 的僵尸进程是什么?

1、什么是僵尸进程?

 首先内核会释放终止进程(调用了 exit 系统调用)所使用的所有存储区,关闭所有打开的文件 等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程 ID,进程的终止状 态,以及该进程使用的 CPU 时间,所以当终止子进程的父进程调用 wait 或 waitpid 时就可以得 到这些信息。

 而僵尸进程就是指:一个进程执行了 exit 系统调用退出,而其父进程并没有为它收尸(调用 wait 或 waitpid 来获得它的结束状态)的进程。 任何一个子进程(init 除外)在 exit 后并非马上就消失,而是留下一个称外僵尸进程的数据结构, 等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一 个 SIGCHLD 信号。

2、僵尸进程的目的

 设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包 括进程 ID,进程的终止状态,以及该进程使用的 CPU 时间,所以当终止子进程的父进程调用 wait 或 waitpid 时就可以得到这些信息。如果一个进程终止,而该进程有子进程处于僵尸状态,那么 它的所有僵尸子进程的父进程 ID 将被重置为 1(init 进程)。继承这些子进程的 init 进程将清 理它们(也就是说 init 进程将 wait 它们,从而去除它们的僵尸状态)。

3、怎么避免僵尸进程?

1.通过 signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不 想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略 SIGCHLD 信号,该信号是子进程退出的时候向父进程发送的。

2.父进程调用 wait/waitpid 等函数等待子进程结束,如果尚无子进程退出 wait 会导致父进 程阻塞。waitpid 可以通过传递 WNOHANG 使父进程不阻塞立即返回。

3.如果父进程很忙可以用 signal 注册信号处理函数,在信号处理函数调用 wait/waitpid 等待子进程退出。

4.通过两次调用 fork。父进程首先调用 fork 创建一个子进程然后 waitpid 等待子进程退出, 子进程再 fork 一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父 进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由 init 进程接管,孙进程结束后,init 会等待回收。

第一种方法忽略 SIGCHLD 信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常 fork 很多子进程,子进程终结之后需要服务器进程去 wait 清理资源。如果将此信号的处理方式 设为忽略,可让内核把僵尸子进程转交给 init 进程去处理,省去了大量僵尸进程占用系统资源。

4、僵尸进程的处理方法

4.1 wait()连接

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

进程一旦调用了 wait,就立即阻塞自己,由 wait 自动分析是否当前进程的某个子进程已经退出,如果让它找 到了这样一个已经变成僵尸的子进程,wait 就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找 到这样一个子进程,wait 就会一直阻塞在这里,直到有一个出现为止。 参数 status 用来保存被收集进程退出时的一些状态,它是一个指向 int 类型的指针。但如果我们对这个子进程 是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可 以设定这个参数为 NULL,就象下面这样:

pid = wait(NULL);

如果成功,wait 会返回被收集的子进程的进程 ID,如果调用进程没有子进程,调用就会失败,此时 wait 返回 -1,同时 errno 被置为 ECHILD。

  1. wait 系统调用会使父进程暂停执行,直到它的一个子进程结束为止。
  2. 返回的是子进程的 PID,它通常是结束的子进程
  3. 状态信息允许父进程判定子进程的退出状态,即从子进程的 main 函数返回的值或子进程中 exit 语句的退 出码。
  4. 如果 status 不是一个空指针,状态信息将被写入它指向的位置

4.2 waitpid()函数

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

参数:

status:如果不是空,会把状态信息写到它指向的位置,与 wait 一样

options:允许改变 waitpid 的行为,最有用的一个选项是 WNOHANG,它的作用是防止 waitpid 把调用者的执行挂起

返回值:如果成功返回等待子进程的 ID,失败返回-1

对于 waitpid 的 pid 参数的解释与其值有关:

pid == -1 等待任一子进程。于是在这一功能方面 waitpid 与 wait 等效。

pid > 0 等待其进程 I D 与 p i d 相等的子进程。

pid == 0 等待其组 I D 等于调用进程的组 I D 的任一子进程。换句话说是与调用者进程同在一个组的进程。

pid < -1 等待其组 I D 等于 p i d 的绝对值的任一子进程 wait 与 waitpid 区别:

 在一个子进程终止前, wait 使其调用者阻塞,而 waitpid 有一选择项,可使调用者不阻塞。

 waitpid 并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。

 实际上 wait 函数是 waitpid 函数的一个特例。waitpid(-1, &status, 0);

示例:如下代码会创建100个子进程,但是父进程并没有等待他们结束,所以在父进程退出前会有100个僵尸进程;

#include <stdio.h> 
#include <unistd.h> 
int main() { 
 int i; 
 pid_t pid; 
 for(i=0; i<100; i++) { 
 pid = fork(); 
 if(pid == 0) 
 break; 
 } 
 if(pid>0) { 
 printf("press Enter to exit..."); 
 getchar(); 
 } 
 return 0; 
}
其中一个解决方法即是编写一个SIGCHLD信号处理程序来调用wait/waitpid来等待子进程返回。
#include <stdio.h> 
#include <unistd.h> 
#include <signal.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
void wait4children(int signo) { 
 int status; 
 wait(&status); 
} 
int main() { 
 int i; 
 pid_t pid; 
 signal(SIGCHLD, wait4children); 
 for(i=0; i<100; i++) { 
 pid = fork(); 
 if(pid == 0) 
 break; 
 } 
 if(pid>0) { 
 printf("press Enter to exit..."); 
 getchar(); 
 } 
 return 0; 
}

所以最佳的方案如下:

#include <stdio.h> 
#include <unistd.h> 
#include <signal.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
void wait4children(int signo) { 
 int status; 
 pid_t pid;
 while(pid=waitpid(-1, &status, WNOHANG) > 0){
//if (WIFEXITED(status))
 // printf("------------child %d exit %d\n", pid, WEXITSTATUS(status));
 // else if (WIFSIGNALED(status))
 // printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
} 
} 
int main() { 
 int i; 
 pid_t pid; 
 signal(SIGCHLD, wait4children); 
 for(i=0; i<100; i++) { 
 pid = fork(); 
 if(pid == 0) 
 break; 
 } 
 if(pid>0) { 
 printf("press Enter to exit..."); 
 getchar(); 
 } 
 return 0; 
}

这里使用 waitpid 而不是使用 wait 的原因在于:我们在一个循环内调用 waitpid,以获取 所有已终止子进程的状态。我们必须指定 WNOHANG 选项,它告诉 waitpid 在有尚未终止的子进程 在运行时不要阻塞。我们不能在循环内调用 wait,因为没有办法防止 wait 在正运行的子进程尚 有未终止时阻塞。


相关文章
|
13天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
26天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
75 34
|
9天前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
27 7
|
8天前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
30 5
|
8天前
|
Linux 应用服务中间件 nginx
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep &lt;进程名&gt;` 查找特定进程;`ps -p &lt;PID&gt;` 查看指定 PID 的进程详情。终止进程可用 `kill &lt;PID&gt;` 或 `pkill &lt;进程名&gt;`,强制终止加 `-9` 选项。
19 3
|
1月前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
65 16
|
2月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
177 20
|
2月前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
130 15
|
3月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
135 13
|
消息中间件 缓存 Unix
[面试必备]嵌入式Linux内核开发必须了解的三十道题
[面试必备]嵌入式Linux内核开发必须了解的三十道题