linux终止进程机制,Linux如何终止D状态的进程

碰到这个问题,我第一个反应就是网搜解决方案,后来发现了自己的文章《linux内核模块的强制删除-结束rmmod这类disk sleep进程》(http://www.linuxdiyf.com/linux/26881.html),正好,碰到的也是这类问题。不过本文将介绍一种不触动内核模块本身,而是触动D进程的方案。

声明一下,本文介绍的方法并非常规方法,如果出现了D进程,正常的做法,除了满足它或者重启机器之外,别无其它捷径。

先看一下D进程的成因。

所谓的D进程,就是不可中断的进程,即便你唤醒它,它还是要睡在那里,直到它等待的资源到位,典型的一个D进程的代码情景是:

static void wait_for_zero_refcount(struct module *mod)

{

/* Since we might sleep for some time, release the mutex first */

mutex_unlock(&module_mutex);

for (;;) {

DEBUGP("Looking at refcount...\n");

// 设置为不可中断。

set_current_state(TASK_UNINTERRUPTIBLE);

// 除非引用计数为0,否则不退出循环。

if (module_refcount(mod) == 0)

break;

// 等待期间,出让CPU资源。

schedule();

}

current->state = TASK_RUNNING;

mutex_lock(&module_mutex);

}

几乎所有的D进程在D状态期间都符合上述代码里的场景,明确了这个场景之后,你就应该知道下面的办法并不奏效的原因了:

// 新编写一个模块,在init函数中更改D进程状态,然后唤醒它。

static int __init mymm(void)

{

struct task_struct *p;

if (pid > 0) {

for_each_process(p) {

if (task_pid_vnr(p) == pid) {

// 企图更改其状态为”可中断状态“,然后kill之!

set_task_state(p, TASK_INTERRUPTIBLE);

// 然则一旦被wakeup,根据上述D场景,又会进入那个死结!

wake_up_process(p);

break;

}

}

}

return -ENOMEM;

}

这是一种典型的头痛医头脚痛医脚的方案,你不是状态为D吗?我就把你的状态改成不是D。然而,如果了解了D进程本质,就知道这是没用的。那么怎么办呢?

上述的D进程场景中,很显然,D进程在等待module的refcount变成0,正如我之前的文章中描述的那种方法,编写一个模块找到出问题的module,将其refcount设置为0即可,然而不同的D进程可能在等待不同的资源,100类D进程就有100种解决的办法。

下面的办法用来把D进程本身带出死结怪圈:

void exit_task1()

{

// 这个有点猛,仅为测试。

// emergency_restart();

// TODO:这里要做的事情非常多,类似ret_from_fork那样,要执行schedule_tail后处理之类的事情。

// 首先,你必须preempt_enable_no_resched,不然会锁死,其次,还要考虑schedule_tail的逻辑。

}

static int __init mymm(void)

{

struct task_struct *p;

if (pid > 0) {

for_each_process(p) {

if (task_pid_vnr(p) == pid) {

// 企图更改其状态为”可中断状态“,然后kill之!

set_task_state(p, TASK_INTERRUPTIBLE);

// 修改D进程被切换前保存的PC指针,待它被唤醒后,将其引入别处,脱离那个死循环怪圈。

p->thread.ip = (unsigned long)exit_task1;

// 一旦被唤醒,执行流将开始执行exit_task1。

wake_up_process(p);

break;

}

}

}

return -ENOMEM;

}

以上方案的关键在于修改p->thread.ip的值。如果没有意外,不在运行状态(即current不是它)的进程其p->thread.ip值就是schedule中__switch_to后面的指令地址,意思是其被切换回来后要执行的地址,但是fork新进程时除外,新进程由于没有谁将其切换出,也就不存在切换入的情况了,因此对于fork出来的新进程而言,要手工帮它制造一个被切换出的现场,待其被切换入的时候执行,这个就是ret_from_fork。

受到ret_from_fork的启发,其实我们可以更改任何进程的p->thread.ip指针,从而将其从原来的执行绪中拉出,就好像穿越虫洞一样进入另一个时空。

最后,要说明的是,exit_task1是一个非常复杂的函数,不是想象的那样直接调用do_exit就完事的。它要把schedule函数中从__switch_to冒出来以后一直到结束的逻辑全部执行一遍。另外,要注意的是,上述的代码都是基于32位系统的,如果是64位系统,就会比较麻烦,因为64位的话,不能采用修改p->thread.ip的方法,它完全是另外一套机制。如果想在64系统将D进程拉出死循环,需要动态HOOK switch_to的二进制指令(你看,64位情况下,ret_from_fork是在__switch_to汇编里直接条件跳转的,copy_thread只是设置了一个TIF_FORK标志),学着64位ret_from_fork的样子,在__my_switch_to里面增加条件跳转逻辑没能成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值