Linux进程管理剖析创建、管理、调度和销毁_第1页
Linux进程管理剖析创建、管理、调度和销毁_第2页
Linux进程管理剖析创建、管理、调度和销毁_第3页
Linux进程管理剖析创建、管理、调度和销毁_第4页
Linux进程管理剖析创建、管理、调度和销毁_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、linux进程管理剖析创建、管理.调度和销m. tim jones,顾问工程师,ernulex corp.2009 年 1 月 20 hlinux?的用户空间进程的创建和管理所涉及的原理与unix?有很多共同点,但也有一些 特定于linux的独特之处。在本文中,了解linux进程的生命周期,探索川户进程创建、 内存管理、调度和销毁的内核内幕。linux是一种动态系统,能够适应不断变化的计算需求o linux计算需求的表现是以进程 的 通用抽象为中心的。进程可以是短期的(从命令行执行的一个命令),也可以是k期的(一 种网络服务)。因此,对进程及其调度进行一般管理就显得极为重要。在用户空间,进程是

2、由进程标识符(pid)表示的。从用户的角度來看,一个pid是一个 数字值,可惟一标识一个进程。一个pid在进程的整个牛命期间不会更改,但pid可以 在进程销毁后被重新使用,所以对它们进行缓存并不见得总是理想的。在用户空间,创建进程可以采用儿种方式。可以执行一个程序(这会导致新进程的创建), 也可以在程序内,调用一个fork或exec系统调用。fork调用会导致创建一个子进程,而 exec调川则会川新程序代替当前进程上下文。接下来,我将对这儿种方法进行讨论以便您 能很好地理解它们的工作原理。在本文中,我将按照下面的顺序展开对进程的介绍,首先展示进程的内核表示以及它们是如 何在内核内被管理的,然后

3、來看看进程创建和调度的各种方式(在-个或多个处理器上), 最后介绍进程的销毁。进程表示阅读developerworks上tim jones所撰写的更多文章tim的剖析系列文章在linux内核内,进程是由相当大的一个称为task_struct的结构表示的。此结构包含所有 表示此进程所必需的数据,此外,还包含了人量的其他数据川来统计(accounting)和维护 与其他进程的关系(父和子)。对task.struct的完整介绍超出了本文的范围,清单1给出 了 task.struct的一小部分。这些代码包含了本文所耍探索的这些特定元素。task_s(mct位 于./linux/include/linu

4、x/sched.h<>清单1. task_struct的一小部分 struct task_struct volatile long state; void * stack;unsigned int flags;int prio, static_prio;struct list_head tasks;struct nun_struct *active_mm;pid_t pid;pid_t tgid;struct task_stfuct *real_parent;char comm tas k_com m_len ;struct thread_struct thread;struct

5、files_struct * files;在清单1中,可以看到儿个预料之中的项,比如执行的状态、堆栈、-组标志、父进程、 执行的线程(可以有很多)以及开放文件。我稍后会对其进行详细说明,这里只简单加以介 绍。state变量是一些表明任务状态的比特位。最常见的状态有:task_running表示进 程正在运行,或是排在运行队列中正要运行;taskjnterruptible表示进程正在休眠、 task.uninterruptible表示进程止在休眠但不能叫醒;task_stopped表示进程停 止等等。这些标志的完整列表可以在./linux/include/linux/sched.h内找到。fla

6、gs定义了很多指示符,表明进程是否正在被创建(pf_starting)或斟(pf.exiting), 或是进程当前是否在分配内存(pf.memalloc)o可执行程序的名称(不包含路径)占用 comm (命令)字段。每个进程都会被赋了优先级(称为static_prio),但进程的实际优先级是基于加载以及其他 儿个因索动态决定的。优先级值越低,实际的优先级越高。tasks字段提供了链接列表的能力。它包含一个prev指针(指向前一个任务)和一个next指 针(指向下一个任务)。进程的地址空间由mm和active_mm字段表示。mm代表的是进程的内存描述符,而 active_mm则是前一个进程的内存

7、描述符(为改进上下文切换吋间的一种优化)。thrcad_sti-uct则用来标识进程的存储状态。此元素依赖于linux在其上运行的特定架构, 在./linux/include/asm-i386/processor.h内有这样的一个例子。在此结构内,可以找到该进程 自执行上下文切换后的存储(硬件注册表、程序计数器等)。冋页首进稈管理最人进程数在linux内虽然进程都是动态分配的,但述是需耍考虑故大进程数。在内核内最大进程数 是由一个称为max_threads的符号表示的,它可以在./linux/kernel/fork.c内找到。可以通 过/proc/sys/kernel/threads-max的

8、proc文件系统从用八空间更改此值。现在,让我们來看看如何在linux内管理进程。在很多情况下,进程都是动态创建并山一 个动态分配的task_struct表示。一个例外是init进程本身,它总是存在并山一个静态分配 的 task_struct 表示。在./linux/arch/i386/kernel/init_task.c 内口j以找到这样的一个例子。linux内所有进程的分配有两种方式。第一种方式是通过一个哈希表,由pid值进行哈希 计算得到;第二种方式是通过双链循环表。循环表非常适合于对任务列表进行迭代。由于列 表是循环的,没有头或尾;但是由于init_task总是存在,所以可以将其用作继

9、续向前迭代 的一个锚点。让我们来看一个遍历当前任务集的例子。任务列表无法从用户空间访问,但该问题很容易解决,方法是以模块形式向内核内插入代码。 清单2中所示的是一个很简单的程序,它会迭代任务列表并会提供有关每个任务的少量信 息(name、pid和parent名)。注意,在这里,此模块使用printk來发出结果。要杏看具 体的结果,可以通过 cat 实用 t具(或实时的 tail -f /vai/log/messages)/var/log/messages 文件。next_task函数是sched.h内的一个宏,它简化了任务列表的迭代(返回下一个任务 的 task_struct 引u )。淸单2

10、.发出任务信息的简单内核模块(procsview.c)#include <linux/kernel.h>#include <linux/module.h>#include <linux/schcd.h>int init_module( void)/* set up the anchor point */struct task_struct *task = &init_task;/* walk through the task list, until wc hit the init_task again */ do printk( kernjnfo %

11、s %d parent %sn”, task->comm, task->pid, task->parent->comm); while (task = next_task(task) != &init_task );return 0;void cleanup_module( void)return;可以用清单3所示的makefile编译此模块。在编译时,可以用insmod procsview.ko插入 模块对象,也可以用rmmod procsview删除它。清单3.用来构建内核模块的makefileobj-m += procs view.okdir := /li

12、b/modules/$(shell uname -r)/build pwd := $(shell pwd)default:$(make) -c $(kdir) subdirs=$(pwd) modules插入后,/var/log/messages可显示输出,如下所示。从中可以看到,这里有一个空闲任务(称 为 swapper)和 init 任务(pid 1)。nov 12 22:19:51 mtj-desktop kernel: 8503.873310 * swapper 0 parent swappernov 12 22:19:51 mtj-desktop kernel: 8503.90418

13、2 * init 1 parent swappernov 12 22:19:51 mtj-desktop kernel: 8503.904215 * kthreadd 2 parent swappernov 12 22:19:51 mtj-desktop kernel: 8503.904233 * migration/0 parent kthreadd注意,还可以标识当前正在运行的任务。linux维护一个称为current的符号,代表的是当 前运行的进程(类型是task_struct)0如果在init.module的尾部插入如下这行代码:printk( kern_info, "cur

14、rent task is %s %d, current->comm, cuitent->pid );会看到:nov 12 22:48:45 mtj-desktop kernel: 10233.323662 current task is insmod 6538注意到,当前的任务是insmod,这是因为init_modulc函数是在insmod命令执行的上下 文运行的。cunent符号实际指的是一个函数(ge(_cuirenl)并可在一个与arch有关的头部 中找到(比如./linux/include/asm-i386/current.h 内找到)。回页首进程创建系统调用函数您可能已

15、经看到过系统调用的模式了。在很多情况下,系统调用都彼命名为sys_*并提供 某些初始功能以实现调用(例如错误检查或用户空间的行为)。实际的工作常常会委派给另 外一个名为的函数。让我们不妨亲自看看如何从丿ij户牢间创建一个进程。川户空间任务和内核任务的底层机制是 一致的,因为二者最终都会依赖于一个名为do_fork的药数来创建新进程。在创建内核线 程时,内核会调用一个名为 kemel_thread 的函数(参见./iinux/arch/i386/keme 1/process.c), 此函数执行某些初始化后会调川do.forko创建用户空间进程的情况为此类似。在用户空间,一个程序会调用fork,这

16、会导致对名为 sys_fork的内核函数的系统调用(参见./linux/arch/i386/kcmcl/proccss.c)o函数关系如图1 所示。图1.负责创建进程的函数的层次结构从图1中,可以看至!j do_fork是进程创建的基础。可以在./linux/kemel/fork.c内找到 do_fork函数(以及合作函数copy_process)o do_fork函数首先调用alloc_pidmap,该调用会分配一个新的pid。接下来,do_fork检杳 调试器是否在跟踪父进程。如果是,在clone.flags内设置clone_ptrace标志以做好 执行fork操作的准备。之ju do_f

17、ork函数还会调丿u copy_process,向其传递这些标志、堆 栈、注册表、父进程以及最新分配的pid。新的进程在copy_proccss函数内作为父进程的一个副本创建。此函数能执行除启动进程z 外的所有操作,启动进程在之后进行处理。copy_process内的第一步是验证clone标志 以确保这些标志是一致的。如果不一致,就会返冋einval错误。接下來,询问linux security module (lsm)看当前任务是否可以创建一个新任务。要了解有关lsm在 security七nhanced linux (selinux)上下文中的更多信息,请参见 参考资料 小节。接下来,调用

18、dup_task_stmct 函数(在./iinux/kernel/fork.c 内),这会分配一个新 task_struct 并将当前进程的描述符复制到其内。在新的线稈堆栈设置好后,一些状态信息也会被初始化, 并h会将控制返冋给cesso控制冋到copy_process后,除了其他儿个限制和安全 检查之外,还会执行一些常规管理,包括在新task.struct上的各种初始化。之后,会调川 一系列复制函数来复制此进程的各个方血,比如复制开放文件描述符(copy_files)、复制符 号信息(copy_sighand和copy_signal )、复制进程内存(copy_mm)以及兹

19、终复制线程(copy_thread)o之后,这个新任务会被指定给一个处理程序,同时对允许执行进程的处理程序进行额外的检 查(cpus_allowed)o新进程的优先级从父进程的优先级继承后,执行一小部分额外的常规管 理,而且控制也会被返冋给do_forke在此吋,新进程存在但尚未运行。do.fork函数通过 调用wake_up_new_task来修复此问题。此函数(门1在./linux/kernel/sched.c内找到)初始 化某些调度程序的常规管理信息,将新进程放置在运行队列z内,然后将其唤解以便执行。 最后,一旦返回至do_fork,此pid值即被返回给调用程序,进程完成。冋页首进程调度

20、存在丁 linux的进程也可通过linux调度程序被调度。虽然调度程序超出了本文的讨论范 围 但linux调度程序维护了针对每个优先级别的一组列表,其中保存了 task_struct引用。 任务通过schedule函数(在./linux/kernel/sched.c内)调用,它根据加载及进程执行历史 决定最佳进程。在本文的参考资料小节可以了解有关linux版本2.6调度程序的更多信 息。回页首进程销毁进程销毁可以通过几个事件驳动一通过止常的进程结束、通过信号或是通过对exit函数 的调用。不管进程如何退出,进程的结束都要借助对内核函数do_exit(在./linux/kemel/exit.c

21、内)的调川。此过程如图2所示。图2.实现进程销毁的函数的层次结构 do_exit的目的是将所有对当前进程的引用从操作系统删除(针对所有没有共厚的资源)。销 毁的过程先要通过设置pf.exiting标志来表明进程正在退出。内核的其他方而会利用它 来避免在进程被删除时还试图处理此进程°将进程从它在其牛命期间获得的各种资源分离开 来焰通过一系列调用实现的,比如exit_mm (删除内存页)和exit_keys (释放线程会话和 进程女全键)o do_exit函数执行释放进程所需的各种统计,这z后,通过调用exit.notify执 行一系列通知(比如,告知父进程其子进程正在退出)。最后,进程

22、状态被更改为pf_dead, 并且还会调用schedule函数来选择一个将要执行的新进程。请注意,如果对父进程的通知 是必盂的(或进程正在被跟踪),那么任务将不会彻底消失。如果无需任何通知,就可以调 川release_task来实际收回由进程使用的那部分内存。冋页首结束语linux还在不断演进,其中一个有待进一-步创新和优化的领域就是进程管理。在坚持unix 原理的同时,linux也在不断突破。新的处理器架构、对称多处理(smp)以及虚拟化都将 促使在内核领域内取得新进展。其中的一个例子就是linux版本2.6中引入的新的0(1) 调度程序,它为具有大量任务的系统提供了可伸缩性。另外一个例子就

23、是使用native p0six tliread library (nptl)更新了的线程模型,与之前的linuxthreads模型相比,它带来了更 为有效的线程处理。有关这些创新及其前景的更多信息,请参见参考资料。参考资料学习您可以参阅本文在developerworks全球网站上的英文原文。2.6内核故具创新性的一个方面就是它的0(1)调度程序。它让linux在无需典型负荷的情 况下就能扩展到大量进程。您可以在“linux调度器内幕"(developerworks, 2006年6月) 中,了解2.6内核中0(1)调度程序的更多内容。要很好地了解linux内的內存管理,请参阅mel g

24、onnan的著作understanding the linux virtual memory manager (prentice hall, 2004 年),该书也提供了 pdf 格式。本书详细并 清晰地介绍了 linux的内存管理,述有一个章节专门介绍进程地址空间。要获得关于进程管理的一个很好的介绍,请参阅performance tuning for linux: an introduction to kernels (prentice hall, 2005 年) ibm press 上有其中的一个示例章节。linux提供了用户空间和内核(不同的地址空间)间转换所涉及的系统调用的一种有趣方式。 在“使用linux系统调用的内核命令v (developerworks, 2007年3月)中可以了解更多 关于此内容的信息。在本文中,您看到了内核检查调用程序的安全功能的例子。内核和安全框架间的基本接口称 为linux security moduleo要探索此模块在selinux上下文的情况,请参阅“安全增强 linux (selinux) "(developerworks, 2008 年 4 月)。面向线程的portable operating system interface (posix)标准定义了创建和管理线程所盗 的?准应用程序编程接

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论