LINUX 操作系统 第3章 进程管理

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
15
3.1.4 设置当前进程状态


使用set_task_state(task, state)函数(在 sched.h中定义) set_task_state(task, state); /* 将任务task的状态设置为state*/ 方法set_current_state(state)与 set_task_state(current, state)作用相同。
18
获取当前进程的父进程的进程描述符

代码如下:
struct task_struct *my_parent = current->parent;
19
依次访问当前进程的子进程
struct task_struct *task; struct list_head *list; list_for_each (list, &current->children) { task=list_entry(list, struct task_struct, sibling); } 其中,list_for_each函数在include/linux/list.h中定义, 代码如下: #define list_for_each(pos, head) \ for (pos = (head)->next, prefetch(pos->next); \ pos != (head); \ pos = pos->next, prefetch(pos->next)) 20
25

fork( )与clone( )的区别



fork( )是全部复制,父进程所有的资源全 都通过数据结构的复制“遗传”给子进程。 clone( )则可以将资源有选择地复制给子进 程,而没有复制的数据结构则通过指针的 复制让子进程共享。在极端的情况下,一 个进程可以clone( )出一个线程。 fork( )是无参数的, clone( )是带有参数的。
4
3.1 进程描述符及任务结构



内核把进程放在一个双向循环链表(叫做 任务队列)中。见17页图3-1。 链表中的每一项都是类型为task_struct、 称为进程描述符的结构。 task_struct中包含了一个具体进程的所有 信息,它在include/linux/sched.h中定义。
5
3.1.1 分配进程描述符
TASK_INTERRUPTIBLE

TASK_UNINTERRUPTIBLE

TASK_STOPPED

挂起状态,主要用于调试目的
进程已经结束,但资源未释放,进程结构还在(进程已经“去 世”但“户口”尚未注销)
13
TASK_ZOMBIE

进程状态转换
14
进程的管理状态

task_struct结构中的flags反映进程的状态, 这些状态用于管理,在sched.h中定义。
struct thread_info { 指向该任务实 struct task_struct *task; 际的task_struct struct exec_domain *exec_domain; 的指针。 unsigned long flags; unsigned long status; _ _u32 cpu; _ _s32 preempt_count; mm_segment_t addr_limit; struct restart_block restart_block; unsigned long previous_esp; _ _u8 supervisor_stack[0]; 7 };
演示所有进程之间的继承关系
struct task_struct *task;
for (task=current; task!=&init_task; task=task->parent) ; /* task现在指向init*/
21
获取下一个 (或前一个) 进程

通过两个宏来实现,它们在 include/linux/sched.h中定义,如下:
struct task_struct *parent; /* parent process */ struct list_head children; /* list of my children */ struct list_head sibling; /* linkage in my parent's children list */
#define next_task(p) \ list_entry((p)->tasks.next, struct task_struct, tasks) #define prev_task(p) \ list_entry((p)->tasks.prev, struct task_struct, tasks)
#define #define #define #define #define #define #define #define …… PF_ALIGNWARN PF_STARTING PF_EXITING PF_FORKNOEXEC PF_SUPERPRIV PF_DUMPCORE PF_SIFNALED PF_MEMALLOC //仅486使用 //正在创建中 //正在shut down //创建但未执行 //使用超级用户权限 //dump核心 //被信号杀死 //正在分配内存
22
依次访问整个任务队列

通过在include/linux/sched.h中定义的一个 宏来实现,如下:
#define for_each_process(p) \ for (p = &init_task ; \ (p = next_task(p)) != &init_task ; )
23
for_each_process( ) 应用举例

第一步:从已经存在的“父进程”复制出一
个“子进程”。复制出来的子进程有自己的 task_struct和系统空间堆栈,但与父进程共享 其它所有的资源。 Linux为此提供了两个系统调用:fork( )和 clone( )。 第二步:目标程序的执行。 Linux为此提供了 一个系统调用:execve( )。
第3章 进程管理
本章主要内容

进程描述符及任务结构 进程创建、执行、终结

2
进程四要素
一般来说,Linux中的进程都具备以下四要素: 有一段程序供其执行。 有起码的“私有财产”,这就是系统专用的系统堆 栈空间。 有“户口”,这就是在内核中的一个task_struct数 据结构(在操作系统教科书中常称为PCB)。 有独立的存储空间,意味着拥有专有的用户空间。
26
vfork( )


vfork( )是后来增设的一个系统调用。 与fork( )相比,vfork( )也不带参数,但是 除task_struct和系统空间堆栈以外的资源 全都通过数据结构指针的复制“遗传”, 所以vfork( )出来的是线程而不是进程。
27
创建子进程后父进程的选择

创建子进程后,父进程可以有三种选择: 父进程不受影响,继续执行,也称“异步”方式。 父进程停下来,等待子进程完成后,父进程再继 续执行,也称“同步”方式。用系统调用wait3( ) 和 wait4( )实现。 wait3( )是等待任何一个子进程 完成; wait4( )等待特定的子进程完成。 父进程创建了子进程后,马上结束自己。用系统 调用exit( )实现。其实是第一种选择的特例。


Linux通过slab分配器分配task_struct结构。 由于使用slab分配器动态生成task_struct, 故只需在栈底创建一个新的结构 thread_info,这个结构能使在汇编代码中 计算其偏移量变得相当容易。
6
thread_info结构

在/include/asm/thread_info.h中定义
9

在x86系统上,通过current_thread_info( ) 函数完成。汇编代码如下:
static inline struct thread_info *current_thread_info(void)
{
struct thread_info *ti; _ _asm_ _("andl %%esp, %0; " :"=r" (ti) : "0" (~(THREAD_SIZE - 1))); return ti;



在内核中,访问进程通常需要获得指向其 task_struct的指针。 通过current宏查找到当前正在运行进程的 进程描述符。 硬件体系结构不同,current宏的实现也不 同。对x86体系结构,在内核栈的尾端创建 thread_info结构,通过计算偏移间接地查 找task_struct结构。
struct task_struct *task; for_each_process(task) { /*打印每个任务的名称和PID*/ printk(“%s[%d]\n”,task->comm, task->pid); } printk函数见教材第12页。
24
3.2 进程创建

Linux将进程的创建与目标程序的执行分成 两步:
28
3.2.1 写时拷贝 (copy_on_write)


传统的fork( )系统调用直接把所有的资源复制给 新创建的进程。 ——缺点:效率低下 Linux的fork( )使用写时拷贝来实现。 写时拷贝是一种可以推迟甚至免除拷贝数据的技 术。 新创建进程时,内核并不复制整个进程地址空间, 而是让父进程和子进程共享同一个拷贝;只有在 需要写入的时候,数据才会被复制,从而使各个 进程拥有各自的拷贝。
注:缺了其中任何一条就不成其为“进程”。如果只具备了前面3 条而缺第4条,那就称为“线程”。特别地,如果完全没有用户空 间,就称为“内核线程”,而如果共享用户空间则就称为“用户 线程”。
3
task与process
Linux系统中的“进程” (process) 和“任 务” (task)是同一个意思,在内核代码中 也常混用这两个名词。
16
Байду номын сангаас
3.1.5 进程家族树


Linux系统中的所有进程构成一棵进程家族 树,所有进程都是PID为1的init进程的后 代。 系统中的每个进程必有一个父进程,每个 进程也可以拥有零个或多个子进程。拥有 同一个父进程的所有进程被称为兄弟。
17
进程家族树

进程间的关系存放在进程描述符中,每个 task_struct都包含一个指向其父进程的 parent指针,和一个叫做children的子进程 链表。其定义如下:
11
进程状态

系统中的每个进程都必然处于五种进程状 态之一。其具体定义见sched.h。
#define TASK_RUNNING #define TASK_INTERRUPTIBLE 0 1
#define TASK_UNINTERRUPTIBLE
#define TASK_STOPPED #define TASK_TRACED
}
10
3.1.3 进程状态

task_struct结构中的state域描述了进程的 当前状态。
struct task_struct { volatile long state; struct thread_info *thread_info; atomic_t usage; unsigned long flags; unsigned long ptrace; …… };
3.1.2 进程描述符的存放

内核通过PID(其值是唯一的)来标识每个 进程。PID是pid_t类型的,实际上是int型。 PID在task_struct结构中定义,如下: pid_t pid;

为了与老版本的Linux兼容,PID的最大值 默认设置为32768。
8
访问进程——获取task_struct指针
2
4 8
12
进程状态

TASK_RUNNING

可执行状态,表示这个进程可以被调度执行而成为当前进程。 当进程处于这样的可执行状态时,内核就将该进程的task_struct 结构通过其队列头run_list挂入一个“运行队列”。 进程睡眠,可因“信号到来”而被唤醒
进程深度睡眠,不受信号干扰

29
写时拷贝


写时拷贝技术使地址空间上的页的拷贝被 推迟到实际发生写入的时候,在页根本不 会被写入的情况下(如fork( )后立即调用 exec( )),它们就无需复制了。 一般情况下,进程创建后都会马上运行一 个可执行的文件,这种优化可以避免拷贝 大量根本就不会被使用的数据。
相关文档
最新文档