为什么busybox中的crond是linux 僵尸进程程

2176人阅读
busybox里面的僵尸进程很多是有目共睹的,为什么呢?这要从僵尸进程的概念说起,所谓僵尸进程实际上就是没有人回收的进程,什么也没有了,只剩下 task_struct这个空壳子了,task_struct里面的字段都没有了,都被释放了但是task_struct本身还在,占据着 sizeof(struct task_struct)大小的空间,其空虚的task_struct仍然在全局的task_struct链表中挂着,这样遍历整个系统的进程的时候仍然 可以找到它,在用户空间ps的时候仍然可以看到僵尸进程。但是为何会有这种进程呢?这得从进程回收说起。进程在以下情况下被回收:
1.父进程调用wait系统调用等待子进程;
2.系统在父进程显式忽略SIGCHLD信号的时候进行回收。
那么在别的情况下,该进程就会成为僵尸进程,这怎么理解呢?一般情况下,当一个进程结束的时候都要向其父进程发送SIGCHLD信号,什么情况呢?就是父进程没有将SIGCHLD信号设置为SIG_IGN并且没有设置为SIG_DFL,满足以上条件的话,父进程收到信号后必须调用wait进行回收,如果没有wait,那么该子进程就会变成僵尸进程,如果父进程将信号设置为SIG_DFL,那么退出进程照样向父进程法信号,只不过父进程不处理,子进程会成为僵尸,这是情况一;情况二就是父进程将SIGCHLD信号设置为SIG_DFL,这样的话当子进程结束时不会向父进程发送SIGCHLD信号,而且内核也 不会帮着回收,这样的话该结束的子进程一定会变成僵尸进程;情况三就是父进程显式乎略了SIGCHLD信号,即设置为SIG_IGN,这样的话内核会回收 子进程,故该子进程一定不会变成僵尸进程。为何如此复杂呢?呵呵,这是posix的约定,问他们去吧。我们可以从内核源代码看个究竟,当进程exit的时候,调用就到了do_exit:
asmlinkage NORET_TYPE void do_exit(long code)
struct task_struct *tsk =
profile_task_exit(tsk);
tsk->flags |= PF_EXITING;
del_timer_sync(&tsk->real_timer);
exit_notify(tsk);
//这个函数告知了僵尸进程产生的原因
schedule();
/* Avoid "noreturn function does return".
for (;;) ;
//不可能到这里了,因为进程永远不会从schedule返回了
static void exit_notify(struct task_struct *tsk)
struct task_struct *t;
struct list_head ptrace_dead, *_p, *_n;
INIT_LIST_HEAD(&ptrace_dead);
forget_original_parent(tsk, &ptrace_dead);
BUG_ON(!list_empty(&tsk->children));
BUG_ON(!list_empty(&tsk->ptrace_children));
t = tsk->real_
if (tsk->exit_signal != -1 && thread_group_empty(tsk)) {
int signal = tsk->parent == tsk->real_parent ? tsk->exit_signal : SIGCHLD;
do_notify_parent(tsk, signal);
//告诉父进程这个进程退出了,如果可能,那么向父进程发送子进程退出信号
} else if (tsk->ptrace) {
do_notify_parent(tsk, SIGCHLD);
//这个是跟踪调试相关的,暂不讨论,可以参考我前面的关于调试的文章《关于linux内核调试的实现》
state = TASK_ZOMBIE;
//默认情况下进程就是僵尸进程,呵呵
if (tsk->exit_signal == -1 && tsk->ptrace == 0)
state = TASK_DEAD;
//如果没有父进程wait,就将进程状态转为TASK_DEAD了,内核负责回收
tsk->state =
if (state == TASK_DEAD)
release_task(tsk);
//内核回收了TASK_DEAD状态的进程
preempt_disable();
tsk->flags |= PF_DEAD; // 注意release_task并没有真正将task_struct的内存释放,因为do_exit中最后还要调用schedule,而 schedule 里还要用到该退出进程的task_struct,真正内存释放在schedule里面的finish_task_switch,该函数 将 task_struct的计数器减一,如果为0,那么释放内存。
我们下面看一下do_notify_parent:
void do_notify_parent(struct task_struct *tsk, int sig)
struct sighand_struct *
info.si_signo =
info.si_errno = 0;
info.si_pid = tsk->
info.si_uid = tsk->
info.si_utime = tsk->utime + tsk->signal->
info.si_stime = tsk->stime + tsk->signal->
psig = tsk->parent->
spin_lock_irqsave(&psig->siglock, flags);
if (sig == SIGCHLD &&
(psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||
(psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {
//如果父进程SIG_IGN了SIGCHLD,那么就设置一些标志,然后由内核进行回收,见上面的函数
tsk->exit_signal = -1;
if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)
if (sig > 0 && sig <= _NSIG)
//如果没有SIG_IGN,那么向父进程发送信号,父进程SIG_DFL信号时也发送,只是父进程不处理,不wait,子进程当然成为了僵尸进程
__group_send_sig_info(sig, &info, tsk->parent);
__wake_up_parent(tsk, tsk->parent);
spin_unlock_irqrestore(&psig->siglock, flags);
上面的函数说明足以说明僵尸进程产生的原因,但是还有一个有意思的事情就是forget_original_parent函数,该函数就是把退出进程的孩 子们过继给一个选择出来的新的进程,典型的不养老不送终,父亲到死还要照顾儿子,而僵尸进程就是典型的白发送黑发的惨剧,那么过继给谁呢?一般是过继给本线程组的另外一个进程,如果没有就过继给一个全局变量child_reaper,该变量在内核初始化的时候设置为1号init进程,具体就是在 rest_init函数中设置的,而rest_init就是start_kenenl函数fork出来的1号init进程的前身,1号进程一切初始化完毕 后就会exec成/sbin/init,具体代码很清晰就不多说了,为什么说这个呢?因为init进程负责着回收大多数僵尸进程的重任,很多进程过继给了 init进程,按照道理讲,init进程必须有wait子进程的调用,也就是说必须设置SIGCHLD信号处理器,然后在该处理器里面wait子进程,要 么就是init进程SIG_IGN了SIGCHLD信号,但是如果init进程SIG_DFL了信号那就麻烦了,init进程将不会回收子进程,造成大量 僵尸进程的产生。下面我们就看看busybox的init进程是怎么做的:busybox的init进程从init_main函数开始,注意它没有 main函数,这是busybox体系决定的,在busybox中所有进程都是busybox,不同的参数决定执行不同的进程,具体研究一下就明白了,这 里就不多说了,看一下init_main:
int init_main(int argc, char **argv)
...//前面主要就是解析/etc/inittab然后运行初始化脚本,和system v的init没有本质区别,所以掠过
while (1) {
/* Wait for a child process to exit */
wpid = wait(NULL);
//看到这里,你还把busybox的僵尸进程多的原因推卸给busybox的init吗?
while (wpid > 0) {
/* Find out who died and clean up their corpse */
for (a = init_action_ a = a->next) {
if (a->pid == wpid) {
/* Set the pid to 0 so that the process gets
* restarted by run_actions() */
a->pid = 0;
message(LOG, "Process '%s' (pid %d) exited.
"Scheduling it for restart.",
a->command, wpid);
/* see if anyone else is waiting to be reaped */
wpid = waitpid(-1, NULL, WNOHANG);
//如果还不明白就看一下内核的sys_wait4调用吧,该系统调用里回收了所有状态为“僵尸”的子进程,如果系统将没有父亲的进程都过继给了init,在busybox里面是没有任何问题的,这里全部被回收了。
既然不是init惹的祸,那么是谁呢?想象一下linux里面的老大级别的除了内核,init进程还有谁?答案是shell,我们知道当你得到一个 shell,那么该shell下面的所有的进程都是该shell的子进程,如果shell不wait的话,僵尸还是会出现的,那就看看shell吧,我们 看msh.c文件,通篇查找没有找到wait(-1,...)的,倒是有wait调用,全是wait特定pid的进程的,也就是wait它的直接子进程,那么就是不管过继给它的子进程了,因此如果将一个进程过继给了msh,那么就别指望msh回收了,它不管这种事。过继给shell的可能性极大,毕竟 shell是很多进程的父进程,认祖父为父在linux里面是再正常不过的了(内核的意思是认叔叔为父,这个还比较正常)。
于是真相大白了,busybox里的僵尸进程很大部分是shell设计的问题,但是也不一定,我敢肯定的是大多是是这样的,因为我调试shell的时候事实就是如此,可能还有别的凶手,我懒得找了。
也许有些较真的看了以上文字会去看一下子进程过继的相关代码,那么我还是具体说一下好了,不就是forget_original_parent嘛:
static inline void forget_original_parent(struct task_struct * father, struct list_head *to_release)
struct task_struct *p, *reaper =
struct list_head *_p, *_n;
reaper = next_thread(reaper);
//在本线程组找新父亲,就是找一个叔叔
if (reaper == father) {
reaper = child_
//如果没有再过继给init进程
} while (reaper->state >= TASK_ZOMBIE);
//就此打住,再往后说就没完了,只要明白这里的reaper是所有退出进程的子进程们的新父亲就可以了。
那 么按照上述推理,busybox的shell就应该和它的子进程是同一线程组的了(它显然不是init进程),那么就看看msh.c文件吧,里面只要 fork新进程,通篇用vfork,所谓vfork就是和当前进程共用虚存空间,在sys_vfork里明确指示要CLONE_VM标志,这样shell 不一定和子进程在同一线程组,但是和父进程关系甚密。vfork在调用exec之前完全在父进程的空间运行,这样可以减少复制开销,直到exec才和父进程分道扬镳,但是却还是和父进程关系甚密。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:4826724次
积分:61509
积分:61509
排名:第26名
原创:1324篇
评论:2269条
(11)(6)(17)(14)(9)(12)(1)(1)(2)(5)(1)(8)(2)(22)(9)(8)(2)(7)(10)(12)(14)(21)(7)(1)(2)(9)(13)(10)(12)(11)(10)(5)(11)(14)(14)(1)(16)(12)(7)(15)(11)(5)(1)(5)(2)(11)(10)(6)(7)(6)(16)(4)(15)(15)(16)(8)(22)(25)(7)(13)(10)(13)(13)(19)(19)(9)(11)(29)(10)(16)(9)(19)(28)(26)(28)(27)(39)(7)(414)(1)(10)Unix&#47;Linux系统中僵尸进程是如何产生的?有什么危害?如何避免_百度知道3577人阅读
crond是一个服务,一个守护进程。crond 是linux用来定期执行程序的命令,crond命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。
crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类&#20284;使用者的时程表。
按照网上查找的资料执行时经常会碰到这种错误:
crontab: chdir(/var/spool/cron/crontabs): No such file or directory
所以首先创建这样一个目录吧!
#mkdir -p&/var/spool/cron/crontabs
然后编辑要执行的任务
#crontabs -e
0 */1 * * * /sbin/ntpdate 192.168.1.67 &/dev/null 2&&1 每隔一个小时更新下时间,不打印任何信息
最后,执行下
此时你会发现/var/spool/cron/crontabs下有一个跟用户名相同的文件,保存着要执行的任务。
ps下会发现cron进程已经启动!
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:458078次
积分:4314
积分:4314
排名:第5124名
原创:44篇
转载:71篇
评论:54条
(1)(2)(3)(1)(5)(3)(6)(2)(8)(5)(7)(6)(10)(14)(23)(19)嵌入式Linux下使用BusyBox的crond服务的方法_Linux教程_Linux公社-Linux系统门户网站
你好,游客
嵌入式Linux下使用BusyBox的crond服务的方法
来源:Linux社区&
作者:jason6765599
crond是一个服务,一个守护进程。crond 是linux用来定期执行程序的命令,crond命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。
crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类似使用者的时程表。
按照网上查找的资料执行时经常会碰到这种错误:
crontab: chdir(/var/spool/cron/crontabs): No such file or directory
所以首先创建这样一个目录吧!
#mkdir -p /var/spool/cron/crontabs
然后编辑要执行的任务
#crontabs -e
0 */1 * * * /sbin/ntpdate 192.168.1.67 &/dev/null 2&&1 每隔一个小时更新下时间,不打印任何信息
最后,执行下
此时你会发现/var/spool/cron/crontabs下有一个跟用户名相同的文件,保存着要执行的任务。
ps下会发现cron进程已经启动!
以上内容参见文章:&
实践中发现问题如下:
在主机端开发板NFS目录下var里新建的文件夹在开发板的/var下面查看,并未找到文件夹,同样在开发板端/var目录下新建的文件夹或者文件,在主机端也没有出现,最后感谢王师傅,得到了解决方案与原因:
var下面很多文件一直在刷新,电脑硬盘无所谓,嵌入式存储用的flash,擦写多了严重影响寿命,所以var目录下的内容实际为内存虚拟的,并不存在与flash之中。
【解决方案】
在开发板/etc/crontabs/下建立crontab的配置文件root:
* * * * * /myupdate/update
每分钟执行update一次
然后在/etc/init.d/rcS修改,让开发板每次启动之后自动执行:
mkdir -p /var/spool/cron/crontabs#guoshufanADDcp -p /etc/crontabs/root /var/spool/cron/crontabs#guoshufanADD
chmod 777 /var/spool/cron/crontabs/root#guoshufanADD
crond#guoshufanADDecho "cp cron config file to /var/spool/cron/crontabs"#guohufanADD
即新建crontab配置文件目录,然后将配置文件按拷贝至新建目录下,完美解决问题,不过要记得执行chmod命令,以后只需要修改root中的内容就可以改变crontab配置,不过临时修改配置还是需要去/var/spool/cron/crontabs内的rooot文件,永久修改是修改/etc/crontabs/root,重启生效。
相关资讯 & & &
& (01月25日)
& (08/08/:57)
& (02/09/:32)
& (10/25/:58)
& (03/23/:13)
& (01/28/:23)
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款2773人阅读
C/C++(8)
Linux(2)
什么僵尸进程
这里简单说一下,详细的到网上搜一下就知道了:
僵尸进程就是指子进程退出了,而父进程尚未退出,并且没有对子进程进行wait,致使子进程的资源得不到释放,依然占据在内存中,从而变成了像"僵尸"一样的进程(僵尸不能动,却占据着身体;进程不能执行了,却占据着内存等资源)。
这种进程因为不再活动了,不会对信号进行处理,使用kill向它发送信号是没有用的,也就是它变成了杀不死的进程。
wait是干什么的
wait不是来打酱油的,看一下man手册的描述:
All of these system calls are used to wait
the calling process, and obtain information about the child whose state has changed.
A state change
considered
t the child was or the child was resumed by a signal.
In the case of a terminated child, performing
system to release the resources assoc if a wait is not performed, then the terminated
remains in a "zombie" state (see NOTES below).
这段话的意思是说:wait是当前进程等待它的子进程的状态变化,并且能够取得子进程状态变化的一些信息。这种状态变化通常指的是子进程的结束或者恢复。当子进程结束的时候,wait函数将会告知系统释放该子进程的资源,如果没有使用wait函数,那么结束的子进程将会变成"僵尸"状态。
这种僵尸状态会一直保持,直到父进程退出,被过继到老祖宗init(pid为1)进程,由init进程负责释放它们的资源。
僵尸进程的避免
网上搜一搜,基本上就是这样的三种方式:
signal(SIGCHLD, SIG_IGN),忽略SIGCHLD信号,这样子进程结束后,就不需要父进程来wait和释放资源
fork两次,第一次fork的子进程在fork完成后直接退出,这样第二次fork得到的子进程就没有爸爸了(真可怜。。。),它会被自动过继给老祖宗init进程,init会负责释放它的资源,这样就不会由"僵尸"产生了
对子进程进行wait,释放它们的资源。但是父进程一般没工夫在那里守着,等着子进程的退出,所以,一般使用信号的方式来处理,在收到SIGCHLD信号的时候,再临时用wait操作来释放它们的资源。
从个人角度,简单评价一下这三种方式:
老爸不管儿子死活。父进程无法知晓子进程的退出情况。
儿子自杀了,爷爷不管孙子。跟1一样,父进程无法知晓子进程的退出情况。
老爸算是尽职了,儿子死了会给它火化(释放资源)。父进程可以知晓子进程的退出情况,不过处理比1和2麻烦。
个人推荐第3种解决方法,这也引出了下面这样的一个问题。
在《UNIX环境高级编程》10.8章节中有这样一段话:
What happens if a blocked signal is generated more than once before the process unblocks the signal? POSIX.1 allows the system to deliver the signal either once or more than once. If the system delivers the signal more than once, we say that the signals are queued. Most UNIX systems, however, do not queue signals unless they support the real-time extensions to POSIX.1. Instead, the UNIX kernel simply delivers the signal once.
这段话的意思是说:如果相同的信号在被在该进程解除对它的阻塞之前发生了多次,多数UNIX系统并不对该信号进行排队处理,也就是说,该信号将会被只递交一次。
这就是Linux中信号处理有一个特点,那就是,同一个信号被递交多次,如果第一个信号还在处理,那么后面的信号都会被丢弃,而不会进入队列中等待处理。如果我们只是简单的对这个信号处理,必然会丢失对后面相同信号的处理。
这个问题是在我写一个File Server的时候遇到的。这个文件服务器每接收一个Client请求,就fork一个进程对它进行处理。为了对这个Server进行压力测试,我在Client端生成了非常多的文件传输请求,同时为了测试Server的容错能力,我在这些请求生成后,按下Ctrl+C键中断了这些文件传输,发现每次执行这样的操作,Server端都会有大量的僵尸进程生成。研究了一整天,查阅了很多资料,并测试了多次,终于发现,原来这些僵尸进程的SIGCHLD信号被父进程给忽略了!没有对他们进行wait,从而导致了他们变成了僵尸进程!
下面举个简单的例子:
* main.cpp
Created on: Jun 17, 2011
Author: boyce
#include &stdio.h&
#include &stdlib.h&
#include &unistd.h&
#include &signal.h&
#include &wait.h&
#include &errno.h&
int num_clients = 0;
int dead_clients = 0;
void sig_chld_handler(int sig) {
if (sig == SIGCHLD) {
pid = wait(NULL);
printf("A child dead, current child number: %d, id: %d/n", ++dead_clients, pid);
int main(int argc, char **argv) {
signal(SIGCHLD, sig_chld_handler);
for (int i = 0; i & 30; i++) {
if ((pid = fork()) == 0) {
} else if (pid & 0) {
printf("A child created, current child number: %d, id: %d/n", ++num_clients, pid);
sleep(10);
这段代码将同时创建30个进程,这些进程将几乎在同一时间退出,退出之时都会向父进程发送SIGCHLD信号,按照前面说的,如果上一个信号尚且在处理中,那么当前信号就会被丢弃。如果来一个信号,我们等待一个子进程,那么程序运行时,将会发现,总会有那么一些进程没有被父进程等到而变成僵尸进程。
就像下图所示,30个进程里面居然有9个进程变成了僵尸进程(defunct进程)!这些进程占据着系统资源不肯释放,连kill都不能杀死他们,天杀的,这样都杀不死你!
不能捕捉到所有的信号,是不是就表示我们不能等到所有的子进程退出呢?当然不是了,通过man查询wait函数手册,我们看到这样一段话:
RETURN VALUE
wait(): on success, returns the process ID of
on error, -1 is returned.
ECHILD (for wait()) The calling process does not have any unwaited-for children.
Wait函数在失败的时候将返回-1,这个都是废话。在失败的时候,如果错误码为ECHILD,对于wait函数而言,这说明当前进程没有需要等待的子进程,这说明什么?这说明我们可以不用管来了多少个信号,那都是浮云,信号来了,只能说明一件事:那就是有子进程退出了。至于是哪个子进程要退出了,有多少个,这个等了就知道。于是乎,就有了下面这样一段代码。
* main.cpp
Created on: Jun 17, 2011
Author: boyce
#include &stdio.h&
#include &stdlib.h&
#include &unistd.h&
#include &signal.h&
#include &wait.h&
#include &errno.h&
int num_clients = 0;
int dead_clients = 0;
void sig_chld_handler(int sig) {
if (sig == SIGCHLD) {
while (1) {
pid = wait(NULL);
if (pid & 0 && errno == ECHILD) {
printf("A child dead, current child number: %d, id: %d/n", ++dead_clients, pid);
int main(int argc, char **argv) {
signal(SIGCHLD, sig_chld_handler);
for (int i = 0; i & 30; i++) {
if ((pid = fork()) == 0) {
} else if (pid & 0) {
printf("A child created, current child number: %d, id: %d/n", ++num_clients, pid);
sleep(10);
好,来看看测试结果:
哈哈,看见了吧,儿子全死了,老爸开心了(o(╯□╰)o)
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:239929次
积分:2641
积分:2641
排名:第10295名
原创:30篇
评论:127条
(1)(3)(2)(2)(1)(9)(5)(5)(1)(2)(1)}

我要回帖

更多关于 两个crond进程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信