TASK_KILLABLE:Linux 中的新进程状态【转】

转自:https://www.ibm.com/developerworks/cn/linux/l-task-killable/index.html

新的睡眠状态允许 TASK_UNINTERRUPTIBLE 响应致命信号

Linux kernel 2.6.25 引入了一种新的进程状态,名为 TASK_KILLABLE,用于将进程置为睡眠状态,它可以替代有效但可能无法终止的 TASK_UNINTERRUPTIBLE 进程状态,以及易于唤醒但更加安全的 TASK_INTERRUPTIBLE 进程状态。2002 年,OpenAFS 文件系统驱动程序在阻塞所有信号之后等待事件中断时遇到了问题,而 TASK_KILLABLE 就是因此而被推出的。这种新的睡眠状态允许 TASK_UNINTERRUPTIBLE 响应致命信号。在本文中,作者将介绍这方面的内容,并结合 2.6.26 和早期版本 2.6.18 中的示例来讨论 Linux 内核发生的相关变化以及这些变化带来的新 API。

0评论

Avinesh Kumar, 系统软件工程师, EMC

2008 年 10 月 20 日

  • 内容


在 IBM Bluemix 云平台上开发并部署您的下一个应用。

开始您的试用

类似于文件,进程是任何 UNIX 操作系统的基本元素。进程是执行可执行文件的指令的动态实体。除了执行其指令之外,进程有时还会管理 打开文件、处理器上下文、地址空间以及与程序相关的数据等。Linux 内核将关于进程的完整信息保存在进程描述符 中,它的结构被定义为 struct task_struct。您可以在 Linux 内核源文件 include/linux/sched.h 中看到 struct task_struct 的各个字段。

关于进程状态

在进程的生命周期内,可能会经历一系列互斥的状态。内核将进程的状态信息保存在 struct task_structstate 字段中。图 1 展示了进程状态之间的转换。

图 1. 进程状态转换


我们先来了解一下各种进程状态:

  • TASK_RUNNING:进程当前正在运行,或者正在运行队列中等待调度。
  • TASK_INTERRUPTIBLE:进程处于睡眠状态,正在等待某些事件发生。进程可以被信号中断。接收到信号或被显式的唤醒呼叫唤醒之后,进程将转变为 TASK_RUNNING 状态。
  • TASK_UNINTERRUPTIBLE:此进程状态类似于 TASK_INTERRUPTIBLE,只是它不会处理信号。中断处于这种状态的进程是不合适的,因为它可能正在完成某些重要的任务。 当它所等待的事件发生时,进程将被显式的唤醒呼叫唤醒。
  • TASK_STOPPED:进程已中止执行,它没有运行,并且不能运行。接收到 SIGSTOPSIGTSTP 等信号时,进程将进入这种状态。接收到 SIGCONT 信号之后,进程将再次变得可运行。
  • TASK_TRACED:正被调试程序等其他进程监控时,进程将进入这种状态。
  • EXIT_ZOMBIE:进程已终止,它正等待其父进程收集关于它的一些统计信息。
  • EXIT_DEAD:最终状态(正如其名)。将进程从系统中删除时,它将进入此状态,因为其父进程已经通过 wait4()waitpid() 调用收集了所有统计信息。

有关进程状态转换的详细信息,请参阅 参考资料 一节中的 UNIX 操作系统设计

如前所述,进程状态 TASK_UNINTERRUPTIBLETASK_INTERRUPTIBLE 都是睡眠状态。现在,我们来看看内核如何将进程置为睡眠状态。

回页首

内核映射

Linux 内核提供了两种方法将进程置为睡眠状态。

将进程置为睡眠状态的普通方法是将进程状态设置为 TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE 并调用调度程序的 schedule() 函数。这样会将进程从 CPU 运行队列中移除。如果进程处于可中断模式的睡眠状态(通过将其状态设置为 TASK_INTERRUPTIBLE),那么可以通过显式的唤醒呼叫(wakeup_process())或需要处理的信号来唤醒它。

但是,如果进程处于非可中断模式的睡眠状态(通过将其状态设置为 TASK_UNINTERRUPTIBLE),那么只能通过显式的唤醒呼叫将其唤醒。除非万不得已,否则我们建议您将进程置为可中断睡眠模式,而不是不可中断睡眠模式(比如说在设备 I/O 期间,处理信号非常困难时)。

当处于可中断睡眠模式的任务接收到信号时,它需要处理该信号(除非它已被屏弊),离开之前正在处理的任务(此处需要清除代码),并将 -EINTR 返回给用户空间。再一次,检查这些返回代码和采取适当操作的工作将由程序员完成。因此,懒惰的程序员可能比较喜欢将进程置为不可中断模式的睡眠状态,因为信号不会唤醒这类任务。但需要注意的一种情况是,对不可中断睡眠模式的进程的唤醒呼叫可能会由于某些原因不会发生,这会使进程无法被终止,从而最终引发问题,因为惟一的解决方法就是重启系统。一方面,您需要考虑一些细节,因为不这样做会在内核端和用户端引入 bug。另一方面,您可能会生成永远不会停止的进程(被阻塞且无法终止的进程)。

现在,我们在内核中实现了一种新的睡眠方法!

回页首

新睡眠状态:TASK_KILLABLE

Linux Kernel 2.6.25 引入了一种新的进程睡眠状态,TASK_KILLABLE:当进程处于这种可以终止的新睡眠状态中,它的运行原理类似于 TASK_UNINTERRUPTIBLE,只不过可以响应致命信号。清单 1 给出了内核 2.6.18 与内核 2.6.26 进程状态(定义在 include/linux/sched.h 中)之间的比较:

清单 1. 2.6.18 和 2.6.26 进程状态之间的比较
Linux Kernel 2.6.18                    Linux Kernel 2.6.26
=================================      ===================================
#define TASK_RUNNING            0      #define TASK_RUNNING            0
#define TASK_INTERRUPTIBLE      1      #define TASK_INTERRUPTIBLE      1
#define TASK_UNINTERRUPTIBLE    2      #define TASK_UNINTERRUPTIBLE    2
#define TASK_STOPPED            4      #define __TASK_STOPPED          4
#define TASK_TRACED             8      #define __TASK_TRACED           8
/* in tsk->exit_state */            /* in tsk->exit_state */
#define EXIT_ZOMBIE             16     #define EXIT_ZOMBIE             16
#define EXIT_DEAD               32     #define EXIT_DEAD               32
/* in tsk->state again */           /* in tsk->state again */
#define TASK_NONINTERACTIVE     64     #define TASK_DEAD               64
                                    #define TASK_WAKEKILL           128

注意,状态 TASK_INTERRUPTIBLETASK_UNINTERRUPTIBLE 并未修改。 TASK_WAKEKILL 用于在接收到致命信号时唤醒进程。

清单 2 展示了状态 TASK_STOPPEDTASK_TRACED 的修改之处(以及 TASK_KILLABLE 的定义):

清单 2. 内核 2.6.26 中的新状态定义
#define TASK_KILLABLE   (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED    (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED     (TASK_WAKEKILL | __TASK_TRACED)

换句话说,TASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE

回页首

使用 TASK_KILLABLE 的新内核 API

关于 完成 的一些信息

完成机制的适用情况是:您希望将某个任务置为睡眠状态,但随后需要在某些事件完成时唤醒它。它提供了一种简单的、无竞态条件的同步机制。例程 wait_for_completion(struct completion *comp) 将使调用任务处于不可中断睡眠状态,除非完成已经发生。它要求通过 complete(struct completion *comp)complete_all(struct completion *comp) 函数来唤醒进程。

除了 wait_for_completion_killable() 之外,其他正在等待的例程包括:

  • wait_for_completion_timeout()
  • wait_for_completion_interruptible()
  • wait_for_completion_interruptible_timeout()

有关完成结构的定义,请参阅 include/linux/completion.h。

让我们来看看这种新状态中的新函数。

  • int wait_event_killable(wait_queue_t queue, condition);
    该函数定义在 include/linux/wait.h 中;它将处于可终止睡眠状态的调用进程置于 queue 中,直到 condition 等于 true
  • long schedule_timeout_killable(signed long timeout);
    该函数定义在 kernel/timer.c 中;该例程主要用于将当前任务的状态设置为 TASK_KILLABLE 并调用
    schedule_timeout(),它用于让调用任务睡眠 timeoutjiffies。(在 UNIX 系统中,jiffy 主要表示两个连续时钟计时单元之间的时间。
  • int wait_for_completion_killable(struct completion *comp);
    此例程定义在 kernel/sched.c 中,它用于等待进程在事件完成后变为可终止状态。如果没有等待的致命信号,该函数将调用
    schedule_timeout() 维持
    MAX_SCHEDULE_TIMEOUT(指定为等于 LONG_MAX)个 jiffies 时间。
  • int mutex_lock_killable(struct mutex *lock);
    定义在 kernel/mutex.c 中,该例程用于获取互斥锁。但是,如果锁不可用并且任务正在等待获得锁,与此同时又接收到一个致命信号,则该任务将从等待互斥锁以处理信号的等待者列表中删除。
  • int down_killable(struct semaphore *sem);
    定义在 kernel/semaphore.c 中,它用于获取信号量 sem。如果信号量不可用,它将被置为睡眠状态;如果向它传递了一个致命信号,则会将它从等待者列表中删除,并且需要响应此信号。获取信号量的另外两种方法是使用例程 down()
    down_interruptible()down() 函数现在已不建议使用。您应该使用 down_killable()
    down_interruptible() 函数。

回页首

NFS 客户机代码中的变化

NFS 客户机代码也使用了这种新进程状态。清单 3 显示了 Linux 内核 2.6.18 和 2.6.26 在 nfs_wait_event 宏方面的差异。

清单 3. nfs_wait_event 因 TASK_KILLABLE 而发生的变化
Linux Kernel 2.6.18                          Linux Kernel 2.6.26
==========================================   =============================================
#define nfs_wait_event(clnt, wq, condition)  #define nfs_wait_event(clnt, wq, condition)
 ({                                           ({
  int __retval = 0;                            int __retval =
                                                   wait_event_killable(wq, condition);
    if (clnt->cl_intr) {                        __retval;
     sigset_t oldmask;                        })
     rpc_clnt_sigmask(clnt, &oldmask);
     __retval =
     wait_event_interruptible(wq, condition);
       rpc_clnt_sigunmask(clnt, &oldmask);
    } else
        wait_event(wq, condition);
        __retval;
 })

清单 4 显示了 nfs_direct_wait() 函数在 Linux Kernels 2.6.18 与 2.6.26 中的定义

清单 4. nfs_direct_wait() 因 TASK_KILLABLE 而发生的变化
Linux Kernel 2.6.18
=================================
static ssize_t nfs_direct_wait(struct nfs_direct_req *dreq)
{
  ssize_t result = -EIOCBQUEUED;                              

  /* Async requests don't wait here */
 if (dreq->iocb)
      goto out;                                                    

 result = wait_for_completion_interruptible(&dreq->completion);

 if (!result)
   result = dreq->error;
 if (!result)
   result = dreq->count;                                        

out:
  kref_put(&dreq->kref, nfs_direct_req_release);
  return (ssize_t) result;
}                                                               

Linux Kernel 2.6.26
=====================
static ssize_t nfs_direct_wait(struct nfs_direct_req *dreq)
{
  ssize_t result = -EIOCBQUEUED;
  /* Async requests don't wait here */
  if (dreq->iocb)
    goto out;

  result = wait_for_completion_killable(&dreq->completion);
  if (!result)
    result = dreq->error;
  if (!result)
    result = dreq->count;
out:
   return (ssize_t) result;
 }

要了解 NFS 客户机中的更多变化,以便于更好地掌握这种新功能,请参阅 参考资料 一节中的 Linux Kernel Mailing List 条目。

早期的 NFS 挂载选项 intr 可以帮助解决 NFS 客户机进程中断并等待某些事件的问题,但它允许所有中断,而不仅仅是通过致命信号(如 TASK_KILLABLE)。

回页首

结束语

尽管此特性是对现有选项的改进 — 毕竟,它是解决死进程的另一种方法 — 但它要得到普遍应用还有待时日。记住,除非真的非常有必要 禁止显式唤醒呼叫(通过传统的 TASK_UNINTERRUPTIBLE)之外的任何中断,否则请使用新的 TASK_KILLABLE

参考资料

学习

获得产品和技术

  • 使用可直接从 developerWorks 下载的 IBM 试用软件 构建您的下一个 Linux 开发项目。

讨论

时间: 2024-09-29 23:26:22

TASK_KILLABLE:Linux 中的新进程状态【转】的相关文章

linux中挂载新硬盘到目录,并开机自动挂载例子

 挂载硬盘对于linux系统来讲是一个比较常见的问题了,下面我们来看看linux中挂载新硬盘到目录,并开机自动挂载例子紧,     今天,在整理图片时,发现根目录硬盘空间已经所剩无几了,而网站和所有相关文件主要放置在根下data目录(/data)里,但/home下是有空间,而又不想把文件放到/home下面去,只有再加一个新硬盘,对以后/data目录的文件管理也比较好. 那么就先关掉主机,再加上新硬盘(其它电脑使用过的500G硬盘),插上后再启动电脑. 接下来,就开始工作了: 1. 查看硬盘信息:

linux中挂载新硬盘到home目录的配置(开机自动挂载)

Linux的硬盘识别: 2.6 kernel以后,linux会将识别到的硬件设备,在/dev/下建立相应的设备文件.如: sda        表示第1块SCSI硬盘.# V, z   b, }2 F7 u' | hda        表示第1块IDE硬盘(即连接在第1个IDE接口的Master口上) scd0        表示第1个USB光驱. 当添加了新硬盘后,在/dev目录下会有相应的设备文件产生.cciss的硬盘是个例外,它的 设备文件在/dev/cciss/目录下.一般使用"fdis

Linux中如何启动新进程

有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些方法及它们之间的区别. 一.system函数调用 system函数的原型为: #include <stdlib.h> int system (const char *string); 它的作用是,运行以字符串参数的形式传递给它的命令并等待该命令的完成.命令的执行情况就如同在shell中执行命令:s

在Linux中添加普通新用户

  在Linux中添加普通新用户 ,超级用户(也称为"root")是一个具有修改系统中任何文件权力的特别账号.在日常工作中,最好不要使用超级用户账号进入系统,因为任何错误操作都可能导致巨大的损失.由于超级用户账号是系统建立后提供的惟一一个账号,因此,您需要建立和使用一个一般用户账号进行日常工作. 超级用户可以创建新的用户账号,下面的命令将建立一个名为joe的新用户: # adduser joe # passwd joe (键入joe的口令) Linux采用了将系统管理员和一般用户分开的

Linux中利用两个现存文件,生成一个新的文件

Linux中利用两个现存文件,生成一个新的文件的方法 前提条件:每个文件中不得有重复行 1. 取出两个文件的并集(重复的行只保留一份) cat file1 file2 | sort | uniq 2. 取出两个文件的交集(只留下同时存在于两个文件中的文件) cat file1 file2 | sort | uniq -d 3. 删除交集,留下其他的行 cat file1 file2 | sort | uniq –u

操作系统-mint17写类新的linux中没有sys.c吗

问题描述 mint17写类新的linux中没有sys.c吗 新版的linux中都没有sys.c文件吗?比如mint17.没有的话如果想要自己写一个系统调用怎么办?? 解决方案 linux编写系统调用,

Linux中crontab-定时任务命令

有些东西很久不用就会忘记,所以为了不至于总是找资料,干脆就做一次深刻的笔记,这样既能加深记忆,又能深入理解. linux服务器很久没接触了,出了一些新的版本,Ubuntu已经出了11.10了,之前对于Ubuntu的了解只是出于对桌面版的好奇,可以跟win7一样炫,现在的Ubuntu已正式进入了Liunx爱好者的视线,应用用于后台大型服务器,选择它的主要原因还是出于对稳定性的考虑.用习惯了Centos,突然转移到Ubuntu上还是有些不习惯,毕竟Centos与传统的Redhat没什么区别.不过,总

linux中各类压缩包的整理

  这是网上提供的方法: .tar.gz 解压:tar zxvf FileName.tar.gz 压缩:tar zcvf FileName.tar.gz DirName1 DirName2 大致总结了一下linux下各种格式的压缩包的压缩.解压方法.但是部分方法我没有用到,也就不全,希望大家帮我补充,我将随时修改完善,谢谢! .tar 解包:tar xvf FileName.tar 打包:tar cvf FileName.tar DirName (注:tar是打包,不是压缩!) --------

Linux中echo命令实例

echo是一种最常用的与广泛使用的内置于Linux的bash和C shell的命令,通常用在脚本语言和批处理文件中来在标准输出或者文件中显示一行文本或者字符串. echo命令的语法是: echo [选项][字符串] 1. 输入一行文本并显示在标准输出上 $ echo Tecmintis a community of LinuxNerds 会输出下面的文本: Tecmintis a community of LinuxNerds 2. 输出一个声明的变量值 比如,声明变量x并给它赋值为10. $