日期:2014-05-16 浏览次数:20900 次
Linux 是一种动态系统,能够适应不断变化的计算需求。Linux 计算需求的表现是以进程 的通用抽象为中心的。进程可以是短期的(从命令行执行的一个命令),也可以是长期的(一种网络服务)。因此,对进程及其调度进行一般管理就显得极为重要。
在用户空间,进程是由进程标识符(PID)表示的。从用户的角度来看,一个 PID 是一个数字值,可惟一标识一个进程。一个 PID 在进程的整个生命期间不会更改,但 PID 可以在进程销毁后被重新使用,所以对它们进行缓存并不见得总是理想的。
在用户空间,创建进程可以采用几种方式。可以执行一个程序(这会导致新进程的创建),也可以在程序内,调用一个fork
或
exec
系统调用。fork
调用会导致创建一个子进程,而 exec
调用则会用新程序代替当前进程上下文。接下来,我将对这几种方法进行讨论以便您能很好地理解它们的工作原理。
在本文中,我将按照下面的顺序展开对进程的介绍,首先展示进程的内核表示以及它们是如何在内核内被管理的,然后来看看进程创建和调度的各种方式(在一个或多个处理器上),最后介绍进程的销毁。
在 Linux 内核内,进程是由相当大的一个称为 task_struct
的结构表示的。此结构包含所有表示此进程所必需的数据,此外,还包含了大量的其他数据用来统计(accounting)和维护与其他进程的关系(父和子)。对task_struct
的完整介绍超出了本文的范围,清单 1 给出了
task_struct
的一小部分。这些代码包含了本文所要探索的这些特定元素。task_struct
位于 ./linux/include/linux/sched.h。
struct task_struct { volatile long state; void *stack; unsigned int flags; int prio, static_prio; struct list_head tasks; struct mm_struct *mm, *active_mm; pid_t pid; pid_t tgid; struct task_struct *real_parent; char comm[TASK_COMM_LEN]; struct thread_struct thread; struct files_struct *files; ... };
在清单 1 中,可以看到几个预料之中的项,比如执行的状态、堆栈、一组标志、父进程、执行的线程(可以有很多)以及开放文件。我稍后会对其进行详细说明,这里只简单加以介绍。state
变量是一些表明任务状态的比特位。最常见的状态有:TASK_RUNNING
表示进程正在运行,或是排在运行队列中正要运行;TASK_INTERRUPTIBLE
表示进程正在休眠、TASK_UNINTERRUPTIBLE
表示进程正在休眠但不能叫醒;TASK_STOPPED
表示进程停止等等。这些标志的完整列表可以在 ./linux/include/linux/sched.h 内找到。
flags
定义了很多指示符,表明进程是否正在被创建(PF_STARTING
)或退出(PF_EXITING
),或是进程当前是否在分配内存(PF_MEMALLOC
)。可执行程序的名称(不包含路径)占用comm
(命令)字段。
每个进程都会被赋予优先级(称为 static_prio
),但进程的实际优先级是基于加载以及其他几个因素动态决定的。优先级值越低,实际的优先级越高。
tasks
字段提供了链接列表的能力。它包含一个 prev
指针(指向前一个任务)和一个next
指针(指向下一个任务)。
进程的地址空间由 mm
和 active_mm
字段表示。mm
代表的是进程的内存描述符,而active_mm
则是前一个进程的内存描述符(为改进上下文切换时间的一种优化)。
thread_struct
则用来标识进程的存储状态。此元素依赖于 Linux 在其上运行的特定架构,在 ./linux/include/asm-i386/processor.h 内有这样的一个例子。在此结构内,可以找到该进程自执行上下文切换后的存储(硬件注册表、程序计数器等)。
现在,让我们来看看如何在 Linux 内管理进程。在很多情况下,进程都是动态创建并由一个动态分配的task_struct
表示。一个例外是
init
进程本身,它总是存在并由一个静态分配的 task_struct
表示。在 ./linux/arch/i386/kernel/init_task.c 内可以找到这样的一个例子。
Linux 内所有进程的分配有两种方式。第一种方式是通过一个哈希表,由 PID 值进行哈希计算得到;第二种方式是通过双链循环表。循环表非常适合于对任务列表进行迭代。由于列表是循环的,没有头或尾;但是由于init_task
总是存在,所以可以将其用作继续向前迭代的一个锚