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

下次自动登录
现在的位置:
& 综合 & 正文
linux之crond服务
安装crontab:
[root@CentOS ~]# yum install vixie-cron
[root@CentOS ~]# yum install crontabs
vixie-cron软件包是cron的主;
crontabs软件包是用来安装、卸装、或列举用来驱动 cron 守护进程的表格的程序。
//+++++++++++++++++++++++++++++++++++
cron 是linux的内置服务,但它不自动起来,可以用以下的方法启动、关闭这个服务:
/sbin/service crond start //启动服务
/sbin/service crond stop //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload //重新载入配置
查看crontab服务状态:service crond status
手动启动crontab服务:service crond start
查看crontab服务是否已设置为开机启动,执行命令:ntsysv
加入开机自动启动:
chkconfig --level 35 crond on
1,crontab命令
功能说明:设置计时器。
语  法:crontab [-u &用户名称&][配置文件] 或 crontab [-u &用户名称&][-elr]
补充说明:cron是一个常驻服务,它提供计时器的功能,让用户在特定的时间得以执行预设的指令或程序。只要用户会编辑计时器的配置文件,就可以使用计时器的功能。其配置文件格式如下:
Minute Hour Day Month DayOFWeek Command
参  数:
 -e  编辑该用户的计时器设置。
 -l  列出该用户的计时器设置。
 -r  删除该用户的计时器设置。
 -u&用户名称&  指定要设定计时器的用户名称。
2,crontab 格式
基本格式 :
*  *  *  *  command
分 时 日 月 周  命令
第1列表示分钟1~59 每分钟用*或者 */1表示
第2列表示小时1~23(0表示0点)
第3列表示日期1~31
第4列表示月份1~12
第5列标识号星期0~6(0表示星期天)
第6列要运行的命令
crontab文件的一些例子:
30 21 * * * /usr/local/etc/rc.d/lighttpd restart
上面的例子表示每晚的21:30重启apache。
45 4 1,10,22 * * /usr/local/etc/rc.d/lighttpd restart
上面的例子表示每月1、10、22日的4 : 45重启apache。
10 1 * * 6,0 /usr/local/etc/rc.d/lighttpd restart
上面的例子表示每周六、周日的1 : 10重启apache。
0,30 18-23 * * * /usr/local/etc/rc.d/lighttpd restart
上面的例子表示在每天18 : 00至23 : 00之间每隔30分钟重启apache。
0 23 * * 6 /usr/local/etc/rc.d/lighttpd restart
上面的例子表示每星期六的11 : 00 pm重启apache。
* */1 * * * /usr/local/etc/rc.d/lighttpd restart
每一小时重启apache
* 23-7/1 * * * /usr/local/etc/rc.d/lighttpd restart
晚上11点到早上7点之间,每隔一小时重启apache
0 11 4 * mon-wed /usr/local/etc/rc.d/lighttpd restart
每月的4号与每周一到周三的11点重启apache
0 4 1 jan * /usr/local/etc/rc.d/lighttpd restart
一月一号的4点重启apache
*/30 * * * * /usr/sbin/ntpdate 210.72.145.44
每半小时同步一下时间
查看软件包是否已安装
判定该软件包是否已安装,使用命令。
判定服务是否在运行
判定该服务是否在运行使用命令
启动、关闭和重启、重新载入配置
执行以下命令:
重新载入配置
创建保存备份文件的目录
这个是建立在目录下面的,备份文件将会存放在文件夹之下。
执行以下命令:
直接添加需要执行的命令,保存退出。
原文件的存放目录
备份文件的存放目录。
原文件的名称
是备份文件的名称年月日,是文件类型
后两条命令同上,只是文件名称不同而已。最后保存退出。
修改文件属性,使其可执行
执行以下命令:
执行以下命令或用文件编辑器打开:
在下面添加如下内容:
表示每天的分执行备份
执行以下命令:
完成,这样就会在每天的自动进行数据备份了。
首先进入到根目录之下
然后选择要执行的命令:
、恢复执行:
、恢复执行:
、恢复执行:
最后重启,数据恢复完成。
busybox下crond服务:
crond是一个服务,一个守护进程。crond 是linux用来定期执行程序的命令,crond命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。
crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类似使用者的时程表。
在busybox下的usr/sbin执行下crond提示: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 每隔一个小时更新下时间,不打印任何信息
最后,执行下
#./crond &
此时你会发现/var/spool/cron/crontabs下有一个跟用户名相同的文件,保存着要执行的任务。
ps下会发现cron进程已经启动!
&&&&推荐文章:
【上篇】【下篇】博客访问: 612339
博文数量: 133
博客积分: 0
博客等级: 民兵
技术积分: 3916
注册时间:
认证徽章:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
Linux系统中,应用程序以进程的方式存在,调度也以进程为单位,有关进程的概念就不多说了,参考教科书。
本文主要关注进程状态、偶然会见到的僵尸进程(Z状态)、以及很少见过的X状态进程。
每个进程都有相应的状态,如平常常见的R、S和D状态,也有在出现问题时见到的Z状态,即僵尸状态,还有极少见到的X状态,这也是本文重点分析和关注的。
首先需要简单介绍下几种基本的进程状态的相关概念:
R即Running状态为可运行状态,但并不代表该进程正在运行,只有处于R状态的进程才可以被调度器选中运行,也就是说R状态的进程有机会得到调度运行,其它状态进程不行,当R状态进程被调度器选中运行时,其才正在开始运行。
S即Sleeping状态为可中断睡眠状态,对应于内核中的INTERRUPTIBLE状态,可中断的意思时,处于S状态的进程可以处理信号,可以被信号中断和唤醒。这是系统中最常见的进程状态了。
D为不可中断睡眠状态,对应于内核中的UNINTERRUPTIBLE状态,不可中断的意思是,处于D状态的额进程不能处理信号,不能被信号中断和唤醒,处于该状态的进程大多在等待IO完成后将其唤醒,其它方式不能唤醒,处于D状态的进程由于不处理信号,所以无法被kill,这也是平常遇到的比较头疼的问题。在处理问题时经常见D状态进程,无法kill,也没有其它办法处理,有人想把它强制kill掉,但没有办法,及时有办法(比如内核模块),但其实这样做也不妥。首先如果进程长期处于D状态不退出的话,那此时该进程或系统肯定有问题了,D状态通常等待IO完成,完成后会自动唤醒退出,不应该长期处于D状态,如果是这样,要么是系统IO挂住了(通常scsi等层都有超时机制的,所以通常不会导致长期D),要么是内核中产生死锁了(这种可能性比较大),要么内核出其它问题了。内核中针对长期处于D状态的进程也有相应的检测手段,俗称hungtask检测机制,基本原理是定期检测处于D状态的进程,如果D状态持续时间超过120s,就打印相应的堆栈及错误信息,也可以通过配置使内核直接panic,这样可以通过kdump搜集vmcore做详细分析。好像说太多了,说来话太长,这里就不继续了。
4、Z状态(僵尸进程)
僵尸进程产生的原理为:当进程退出时,默认会向其父进程发送SIGCLD信号,同时将自己设置为Z状态(僵尸状态),父进程在收到SIGCLD信号后,标准做法需要在SIGCLD信号的处理中,调用wait(或类似接口)函数对其子进程占用的剩余资源(如进程描述符)进行回收,回收后,进程彻底退出并消失。
也就是说Z状态(僵尸状态),其实是进程退出过程中的一个正常的中间状态,正常情况下该状态持续的时间应该比较短,应该会很快被回收并退出。当发现一个进程长期处于僵尸状态时,可能的原因有:
&&&&1)父进程的SIGCLD信号处理函数中没有调用wait。
&&&&2)父进程SIGCLD信号处理函数中调用wait执行过程中阻塞,可能由于资源回收过程中发生阻塞,见过的案例有:wait过程中,需要等待进程的所有子线程退出,而子线程处于D状态不返回,导致wait无法继续。
&&&&3)内核出问题了。
那如果父进程在收到SIGCLD信号之前先退出了,是否会导致僵尸进程呢?
答案是不会。因为父进程退出后,子进程会变成孤儿进程,孤儿进程会由init进程自动接管,而init进程会定期通过wait回收其正在退出过程中处于僵尸状态的进程,所以正常情况下,是不会出现这样的情况的。
5、X状态(Dead状态)
X即Dead状态,跟Z状态是密切相关的。如前面所述,进程退出时,默认会向父进程发送SIGCLD信号,但在发送之前,会先对父进程的sighand进行检查,当父进程忽略了SIGCLD信号时,就不会发送信号了,此时会将进程的退出状态设置为EXIT_DEAD,即X状态,此时进程的资源不由父进程回收,进程也不会进入僵尸状态。这种情况下,进程的资源需要自己回收,实际上是在内核调度到下一个进程开始执行时进行回收,回收后,进程消失,X状态也随之消失。
所以,X状态其实也是退出过程中的一个正常的中间状态,正常情况下该状态持续的时间应该比较短,应该会很快被回收并退出。当发现一个进程长期处于X状态时,那应该也有问题了,但这种情况很少见。
1、内核中定义的进程状态
在3.10内核中,定义了如下进程状态:
/*进程状态*/
#define TASK_RUNNING&&&&0 /*可运行状态,被调度的对象*/
#define TASK_INTERRUPTIBLE&&&&1 /*可中断睡眠状态,即平时见的S状态*/
#define TASK_UNINTERRUPTIBLE&&&&2 /*不可中断睡眠状态,即平时见的D状态*/
#define __TASK_STOPPED&&&&4
#define __TASK_TRACED&&&&8 /*调试使用状态,比如gdb attach时*/
#define TASK_DEAD&&&&64 /*进程退出后,等待资源回收时的状态*/
#define TASK_WAKEKILL&&&&128
#define TASK_WAKING&&&&256
#define TASK_PARKED&&&&512
#define TASK_STATE_MAX&&&&1024
组合状态:
/* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE&&&&(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED&&&&(TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED&&&&(TASK_WAKEKILL | __TASK_TRACED)
/* Convenience macros for the sake of wake_up */
#define TASK_NORMAL&&&&(TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
#define TASK_ALL&&&&(TASK_NORMAL | __TASK_STOPPED | __TASK_TRACED)
/* get_task_state() */
#define TASK_REPORT&&&&(TASK_RUNNING | TASK_INTERRUPTIBLE | \
TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
__TASK_TRACED)
为何没有我们熟知的ZOMBIE(僵尸)状态?不是常见(或偶见)僵尸进程么?也没有X状态?
答案:进程状态中确实没有ZOMBIE(僵尸)状态,ZOMBIE(僵尸)在内核中只是一种exit_status,即退出状态,也就是进程退出时的一种状态,具体定义如下:
/* in tsk->exit_state */
#define EXIT_ZOMBIE
#define EXIT_DEAD
可见,退出状态中,除了ZOMBIE,还有另一种叫DEAD的状态,该状态其实就是X状态。
2、用户看到的进程状态
那我们在ps、top或cat /proc//status中看到的Z状态()的进程是如何产生的呢?
ps、top或cat /proc//status中看到进程状态来源于内核中如下的定义
&* The task state array is a strange "bitmap" of
&* reasons to sleep. Thus "running" is zero, and
&* you can test for combinations of others with
&* simple bit tests.
static const char * const task_state_array[] = {
"R (running)",&&&&/* 0 */
"S (sleeping)",&&&&/* 1 */
"D (disk sleep)",&&&&/* 2 */
"T (stopped)",&&&&/* 4 */
"t (tracing stop)",&&&&/* 8 */
"Z (zombie)",&&&&/* 16 */
"X (dead)",&&&&/* 32 */
"x (dead)",&&&&/* 64 */
"K (wakekill)",&&&&/* 128 */
"W (waking)",&&&&/* 256 */
"P (parked)",&&&&/* 512 */
以/proc//status中显示的状态为例,看看这个状态是如何获取并显示的。
cat /proc//status示例:
[root@A ~]# cat /proc/115/status
Name:&&&&crypto/7
State:&&&&S (sleeping)
Tgid:&&&&115
Pid:&&&&115
PPid:&&&&2
TracerPid:&&&&0
Uid:&&&&0&&&&0&&&&0&&&&0
Gid:&&&&0&&&&0&&&&0&&&&0
Utrace:&&&&0
FDSize:&&&&64
Threads:&&&&1
SigQ:&&&&2/30472
SigPnd:&&&&0000
ShdPnd:&&&&0000
SigBlk:&&&&0000
SigIgn:&&&&ffffffffffffffff
SigCgt:&&&&0000
CapInh:&&&&0000
CapPrm:&&&&ffffffffffffffff
CapEff:&&&&fffffffffffffeff
CapBnd:&&&&ffffffffffffffff
Cpus_allowed:&&&&80
Cpus_allowed_list:&&&&7
Mems_allowed: ,,,,,,,,,,,,,,,
Mems_allowed_list:&&&&0
voluntary_ctxt_switches:&&&&2
nonvoluntary_ctxt_switches:&&&&0
/proc//status其中的进程状态获取是通过如下调用路径:
proc_pid_status()
&&&&->task_state()
&&&&&&&&->get_task_state()
/*获取进程状态,实际是根据根据task_struct->state和exit_state来确认的*/
static inline const char *get_task_state(struct task_struct *tsk)
/*将tsk->state通过TASK_REPORT过滤后,再组合exit_state,形成最终的状态*/
unsigned int state = (tsk->state & TASK_REPORT) | tsk->exit_state;
/*获取进程状态列表中的第一种状态*/
const char * const *p = &task_state_array[0];
BUILD_BUG_ON(1 + ilog2(TASK_STATE_MAX) != ARRAY_SIZE(task_state_array));
/*取进程状态中的最低位作为返回的状态,比如如果Z(Zombie)位为1,那就不管后面的DEAD位了*/
while (state) {
state >>= 1;
return *p;
可见,/proc中反应的进程状态实际为tsk->status和exit_state的组合。那需要继续分析这两种状态的设置情况。
3、Z(僵尸)状态和X(Dead)状态的形成
Z(僵尸)状态和X(Dead)状态的形成原理前面已经描述,这里主要关注Z(Zombie)和X状态形成的相关流程。基本流程为:
进程退出必经do_exit入口,其中调用exit_notify通知父进程,如果父进程未忽略SIGCLD信号,则设置进程的退出状态(exit_state)为EXIT_ZOMBIE(即为Z状态);如果父进程忽略了SIGCLD信号,则设置进程的退出状态(exit_state)为EXIT_DEAD(即为X状态)。
do_exit():
/*进程退出时必经入口,完成相应处理*/
void do_exit(long code)
struct task_struct *tsk = current;
int group_dead;
profile_task_exit(tsk);
WARN_ON(blk_needs_flush_plug(tsk));
/*不能在中断上下文中退出进程。*/
if (unlikely(in_interrupt()))
panic("Aiee, killing interrupt handler!");
/*不能kill idle(pid=0)进程*/
if (unlikely(!tsk->pid))
panic("Attempted to kill the idle task!");
/*退出通知,其中完成向父进程发SIGCLD信号*/
exit_notify(tsk, group_dead);
/* causes final put_task_struct in finish_task_switch(). */
/*设置进程状态为DEAD.Fixme:跟僵尸进程和ZOMBIE状态有何关系?*/
tsk->state = TASK_DEAD;
tsk->flags |= PF_NOFREEZE;&&&&/* tell freezer to ignore us */
schedule();
/*不应该再回来了。Fixme:task_struct会在finish_task_switch中清理?那SIGCLD信号谁来发?*/
/* Avoid "noreturn function does return". */
cpu_relax();&&&&/* For when BUG is null */
exit_notify():
static void exit_notify(struct task_struct *tsk, int group_dead)
bool autoreap;
* This does two things:
&&* A. Make init inherit all the child processes
* B. Check to see if any process groups have become orphaned
*&&&&as a result of our exiting, and if they have any stopped
*&&&&jobs, send them a SIGHUP and then a SIGCONT. (POSIX 3.2.2.2)
forget_original_parent(tsk);
write_lock_irq(&tasklist_lock);
if (group_dead)
kill_orphaned_pgrp(tsk->group_leader, NULL);
if (unlikely(tsk->ptrace)) {
int sig = thread_group_leader(tsk) &&
thread_group_empty(tsk) &&
!ptrace_reparented(tsk) ?
tsk->exit_signal : SIGCHLD;
autoreap = do_notify_parent(tsk, sig);
} else if (thread_group_leader(tsk)) {
/*autoreap表示当父进程忽略了SIGCLD信号时,需要进程self-reap相应的资源*/
autoreap = thread_group_empty(tsk) &&
do_notify_parent(tsk, tsk->exit_signal); /*通过信号(通常是SIGCLD)通知父进程*/
autoreap = true;
&&&&&&&&* 设置进程退出状态,父进程忽略了SIGCLD信号时,需要进程self-reap,
&&&&&&&&* 此时autoreap==1,则退出状态为EXIT_DEAD,否则为EXIT_ZOMBIE。
&&&&&&&&* 父进程只会负责EXIT_ZOMBIE状态的子进程的资源回收,EXIT_DEAD的进程
&&&&&&&&* 自行处理。
tsk->exit_state = autoreap ? EXIT_DEAD : EXIT_ZOMBIE;
/* mt-exec, de_thread() is waiting for group leader */
if (unlikely(tsk->signal->notify_count < 0))
wake_up_process(tsk->signal->group_exit_task);
write_unlock_irq(&tasklist_lock);
/* If the process is dead, release it - nobody will wait for it */
if (autoreap)
release_task(tsk);
do_notify_parent():
&&* 通知父进程自己要退出了,其实就是向父进程发送SIGCLD信号,
&&* 如果父进程处理SIGCLD信号,则通常会在信号处理函数中调用wait()相关接口,
&&* 回收子进程最后的资源(比如task_struct?);如果父进程忽略该信号,则子进程
&&* 需要自行回收(self-reaping)。Fixme:可能会变僵尸进程?
bool do_notify_parent(struct task_struct *tsk, int sig)
struct siginfo info;
unsigned long flags;
struct sighand_struct *psig;
bool autoreap = false;
cputime_t utime, stime;
BUG_ON(sig == -1);
&&&&&/* do_notify_parent_cldstop should have been called instead. */
&&&&&BUG_ON(task_is_stopped_or_traced(tsk));
BUG_ON(!tsk->ptrace &&
&&&&&&(tsk->group_leader != tsk || !thread_group_empty(tsk)));
if (sig != SIGCHLD) {
* This is only possible if parent == real_parent.
* Check if it has changed security domain.
if (tsk->parent_exec_id != tsk->parent->self_exec_id)
sig = SIGCHLD;
info.si_signo = sig;
info.si_errno = 0;
* We are under tasklist_lock here so our parent is tied to
* us and cannot change.
* task_active_pid_ns will always return the same pid namespace
* until a task passes through release_task.
* write_lock() currently calls preempt_disable() which is the
* same as rcu_read_lock(), but according to Oleg, this is not
* correct to rely on this
rcu_read_lock();
info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent));
info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns),
&&&&&&task_uid(tsk));
rcu_read_unlock();
task_cputime(tsk, &utime, &stime);
info.si_utime = cputime_to_clock_t(utime + tsk->signal->utime);
info.si_stime = cputime_to_clock_t(stime + tsk->signal->stime);
info.si_status = tsk->exit_code & 0x7f;
if (tsk->exit_code & 0x80)
info.si_code = CLD_DUMPED;
else if (tsk->exit_code & 0x7f)
info.si_code = CLD_KILLED;
info.si_code = CLD_EXITED;
info.si_status = tsk->exit_code >> 8;
/*获取父进程的sighand*/
psig = tsk->parent->sighand;
spin_lock_irqsave(&psig->siglock, flags);
&* 如果发送信号为SIGCHLD且父进程忽略了SIGCHLD信号或者设置了SA_NOCLDWAIT标记,则设置autoreap,
&* 即子进程自己回收资源,不由父进程通过wait来回收。
if (!tsk->ptrace && sig == SIGCHLD &&
&&&(psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN ||
&&&&(psig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDWAIT))) {
* We are exiting and our parent doesn't care. POSIX.1
* defines special semantics for setting SIGCHLD to SIG_IGN
* or setting the SA_NOCLDWAIT flag: we should be reaped
* automatically and not left for our parent's wait4 call.
* Rather than having the parent do it as a magic kind of
* signal handler, we just set this to tell do_exit that we
* can be cleaned up without becoming a zombie. Note that
* we still call __wake_up_parent in this case, because a
* blocked sys_wait4 might now return -ECHILD.
* Whether we send SIGCHLD or not for SA_NOCLDWAIT
* is implementation-defined: we do (if you don't want
* it, just use SIG_IGN instead).
autoreap = true;
if (psig->action[SIGCHLD-1].sa.sa_handler == SIG_IGN)
if (valid_signal(sig) && sig)
/*向父进程发送信号(SIGCLD)*/
__group_send_sig_info(sig, &info, tsk->parent);
&* 唤醒父进程。
&* Fixme:如果上面的if不成立,不发送信号,此时还唤醒父进程来干嘛?
&* 答案:见上面注释:(?)&&&&
&* Note that we still call __wake_up_parent in this case, because a
&* blocked sys_wait4 might now return -ECHILD.
__wake_up_parent(tsk, tsk->parent);
spin_unlock_irqrestore(&psig->siglock, flags);
return autoreap;
4、Z(僵尸)状态进程回收
如之前所说,僵尸进程的资源由父进程,在SIGCLD信号处理中,通过wait接口回收,回收代码流程如下:
sys_wait4()
& & ->do_wait()
& &&&&& ->do_wait_thread()
& &&&&& &&&&->wait_consider_task()
& & &&&&&&&&&&&&->wait_task_zombie()
& &&&&&&&&&&&&&&&&& ->release_task()
5、X(Dead)状态进程回收
X状态进程资源不由父进程回收,需要自己回收(autoreap==1),其回收是在内核调度到下一个进程开始运行时进行的。
这里就涉及到进程上下文切换的问题,调度产生后必然会进行进程上下文切换,上下文切换后问题变得相对复杂一些,有关进程上下文切换相关的知识请参见另一篇blog:http://blog.chinaunix.net/uid--id-4740294.html
内核调度的代码路径如下:
schedule()
& & ->__schedule()
& &&&&& ->context_switch()
& &&&&&&&&& ->switch_to(宏)
实际的上下文切换发生在switch_to宏中。
这里需要分两种情况:
1)当调度时,被选中的next进程已经经历过调度时,上下文切换后其会继续从switch_to宏的“标号1”处继续执行:
&&* 上下文切换,在schedule中调用,current进程调度出去,当该进程被再次调度到时,重新从__switch_to后面开始执行
&&* prev:被替换的进程
&&* next:被调度的新进程
&&* last:当切换回原来的进程(prev)后,被替换的另外一个进程。
#define switch_to(prev, next, last)&&&&\
* Context-switching clobbers all registers, so we clobber&&&&\
* them explicitly, via unused output variables.&&&&\
* (EAX and EBP is not listed because EBP is saved/restored&&&&\
* explicitly for wchan access and EAX is the return value of&&&&\
* __switch_to())&&&&\
unsigned long ebx, ecx, edx, esi, edi;&&&&\
asm volatile("pushfl\n\t"&&&&/* save
flags */&&&&/*将eflags寄存器值压栈*/\
&&&&"pushl %%ebp\n\t"&&&&/* save
EBP */&&&&/*将EBP压栈*/\
/*将当前栈指针(内核态)保存到prev进程的thread.sp中*/
&&&&"movl %%esp,%[prev_sp]\n\t"&&&&/* save
&&&&/*将next进程的栈指针(内核态)装载到ESP寄存器中*/
&&&&"movl %[next_sp],%%esp\n\t"&&&&/* restore ESP */ \
&&&&/*保存"标号1"的地址到prev进程的thread.ip,以便当prev进程重新被调度运行时,可以从"标号1处"重新开始执行*/
&&&&"movl $1f,%[prev_ip]\n\t"&&&&/* save
EIP */&&&&\
&&&&&&&&&* 将next进程的IP(通常都是"标号1"的地址,因为通常都是经历过这里的调度过程的,上一行代码中即保存了这个IP)
&&&* 压入当前的(即next进程的)堆栈中。结合后面的jmp指令(注意:不是call指令)一起理解,当__switch_to执行完ret返回时,
&&&* 会自动从当前的堆栈中弹出该地址作为函数的返回地址接着执行,如此即可实现新进程的运行。
&&&&&&&&&&&&&&&&&&&&&&&*/
&&&&"pushl %[next_ip]\n\t"&&&&/* restore EIP */&&&&\
&&&&__switch_canary&&&&\
&&&&&&&&&*jmp到__switch_to函数执行,当此函数返回时,自动跳转到[next_ip]开始执行,实现新进程的调度。注意不是call,jmp指令
&&&&&&&&&* 不会自动将当前地址压栈,call会自动压栈
&&&&&&&&&*/
&&&&"jmp __switch_to\n"&&&&/* regparm call */&&&&\
&&&&/*当prev进程再次被调度到时,从这里开始执行*/
&&&&"1:\t"&&&&\
&&&&/*恢复EBP*/
&&&&"popl %%ebp\n\t"&&&&/* restore EBP */&&&&\
&&&&/*恢复eflags*/
&&&&"popfl\n"&&&&/* restore flags */&&&&\
&&&&/* output parameters */&&&&\
&&&&/*输出参数*/
&&&&: [prev_sp] "=m" (prev->thread.sp),&&&&\
&&&&&&[prev_ip] "=m" (prev->thread.ip),&&&&\
&&&&&&"=a" (last),&&&&\
&&&&&&/* clobbered output registers: */&&&&\
&&&&&&"=b" (ebx), "=c" (ecx), "=d" (edx),&&&&\
&&&&&&"=S" (esi), "=D" (edi)&&&&\
&&&&&&__switch_canary_oparam&&&&\
&&&&&&/* input parameters: */&&&&\
&&&&&&/*输入参数*/
&&&&: [next_sp] "m" (next->thread.sp),&&&&\
&&&&&&[next_ip] "m" (next->thread.ip),&&&&\
&&&&&&/* regparm parameters for __switch_to(): */&&&&\
&&&&&&/*将prev和next分别存入ecx和edx,然后作为参数传入到__switch_to函数中*/
&&&&&&[prev] "a" (prev),&&&&\
&&&&&&[next] "d" (next)&&&&\
&&&&&&__switch_canary_iparam&&&&\
&&&&: /* reloaded segment registers */&&&&\
"memory");&&&&\
} while (0)
退出switch_to宏后,会返回到context_switch函数中继续执行:
&* context_switch - switch to the new MM and the new
&* thread's register state.
static inline void
context_switch(struct rq *rq, struct task_struct *prev,
&&&&&&struct task_struct *next)
/* Here we just switch the register state and the stack. */
/*切换到新的进程上下文*/
switch_to(prev, next, prev);
/*屏障,防止乱序*/
barrier();
* this_rq must be evaluated again because prev may have moved
* CPUs since it called schedule(), thus the 'rq' on its stack
* frame will be invalid.
&* 上下文切换后,会继续到这里执行,但这里已经是新的进程上下文了
&* 在新的上下文中,清理掉上一个被调度进程prev的相关资源(比如DEAD状态的进程占用的资源)。
finish_task_switch(this_rq(), prev);
&* Fixme:本函数执行完成后,返回到哪里? 这里已经是新的进程上下文了,
&* 进程的内核栈已经切换,所以,内核栈中该函数的返回地址也已经
&* 切换了,因此,不可能再返回上一个进程的上下文中的__schedule函数了。
&* 但是新的进程上下文该函数的上级函数(该返回的函数)也必然是__schedule函数,
&* 因为每个进程的调度都需要经历相同的过程和函数调用,所以实际上,
&* 这里还是返回__schedule函数,只是在新的进程上下文中运行而已。
X状态(EXIT_DEAD)的进程在finish_task_switch函数中被回收:
static void finish_task_switch(struct rq *rq, struct task_struct *prev)
__releases(rq->lock)
struct mm_struct *mm = rq->prev_mm;
long prev_state;
rq->prev_mm = NULL;
* A task struct has one reference for the use as "current".
* If a task dies, then it sets TASK_DEAD in tsk->state and calls
* schedule one last time. The schedule call will never return, and
* the scheduled task must drop that reference.
* The test for TASK_DEAD must occur while the runqueue locks are
* still held, otherwise prev could be scheduled on another cpu, die
* there before we look at prev->state, and then the reference would
* be dropped twice.
*&&&&Manfred Spraul <manfred@colorfullife.com>
prev_state = prev->state;
vtime_task_switch(prev);
finish_arch_switch(prev);
perf_event_task_sched_in(prev, current);
finish_lock_switch(rq, prev);
finish_arch_post_lock_switch();
fire_sched_in_preempt_notifiers(current);
mmdrop(mm);
/*判断DEAD状态(即X状态)的进程,如果是的话,需要对齐占用的资源(比如进程描述符)进行回收*/
if (unlikely(prev_state == TASK_DEAD)) {
task_numa_free(prev);
* Remove function-return probe instances associated with this
* task and put them back on the free list.
kprobe_flush_task(prev);
put_task_struct(prev);
tick_nohz_task_switch(current);
2)当进程被fork创建后首次运行
当进程被fork创建后首次运行时,在进程上下文切换后,switch_to宏中应该返回到ret_from_fork(entry_32.S汇编代码中定义)处开始执行(具体原理参见另一篇blog)
/*fork返回,单独处理*/
ENTRY(ret_from_fork)
CFI_STARTPROC
pushl_cfi %eax
/*进行调度收尾处理,包括回收DEAD状态(X状态)的进程*/
call schedule_tail
GET_THREAD_INFO(%ebp)
popl_cfi %eax
pushl_cfi $0x0202&&&&# Reset kernel eflags
jmp syscall_exit
CFI_ENDPROC
END(ret_from_fork)
其中,schedule_tail会调用finish_task_switch回收X状态进程。
阅读(2449) | 评论(0) | 转发(3) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。}

我要回帖

更多关于 僵尸进程怎么杀 的文章

更多推荐

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

点击添加站长微信