1 / 92

Linux 操作系统分析与实践 第三讲:进程管理

Linux 操作系统分析与实践 第三讲:进程管理. 《 Linux 操作系统分析与实践 》 课程建设小组 北京大学 二零零八年春季 *致谢:感谢 Intel 对本课程项目的资助. 本讲主要内容. Linux 中的进程 Linux 进程控制 Linux 的进程调度 Linux 源代码阅读示例 : 进程调度 schedule 部分的阅读. 一、 Linux 中的进程. 进程 是程序执行时的一个实例 从内核的观点来看,进程的目的是担当分配系统资源 (CPU 时间,存储器等 ) 的实体 Linux 中的关于进程的代码大部分是如何管理进程的代码

Download Presentation

Linux 操作系统分析与实践 第三讲:进程管理

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Linux操作系统分析与实践第三讲:进程管理 《Linux操作系统分析与实践》课程建设小组 北京大学 二零零八年春季 *致谢:感谢Intel对本课程项目的资助

  2. 本讲主要内容 • Linux中的进程 • Linux进程控制 • Linux的进程调度 • Linux源代码阅读示例: • 进程调度schedule部分的阅读

  3. 一、Linux中的进程 • 进程 是程序执行时的一个实例 • 从内核的观点来看,进程的目的是担当分配系统资源(CPU 时间,存储器等)的实体 • Linux中的关于进程的代码大部分是如何管理进程的代码 • 每个进程运行的是程序的代码

  4. 轻量级进程 • 线程代表进程的一个执行流,内核无法感知 • Linux使用轻量级进程对多线程应用程序提供更好的支持 • 轻量级进程可以共享资源 • 通过将轻量级进程与线程相关联,内核可以独立调度线程

  5. 进程描述符task_struct (include/linux/sched.h)

  6. 进程描述符(续) Task_struct结构的描述: • 进程标识 • 进程状态(State) • 进程调度信息和策略 • 标识号(Identifiers) • 进程通信有关的信息(IPC) • 进程链接信息(Links) • 时间和定时器信息(Times and Timers) • 文件系统信息(Files System) • 处理器相关的上下文信息

  7. 进程描述符(续) • Linux中每一个进程由一个task_struct数据结构来描述(进程控制块PCB) • 进程描述符放在动态内存中而且和内核态的进程栈放在一个独立的8KB的内存区中 • 好处:通过esp就能引用进程描 述符

  8. current宏 • current宏 • current宏获取当前正在运行的进程描述符的指针,current宏经常作为进程描述符出现在内核代码里,例如current->pid返回当前正在运行的进程的PID值

  9. 进程的状态 • task_struct 中的state 表示进程当前的状态 • Linux中的进程有5个状态:

  10. 状态之间的转换

  11. 进程链表 • task_struct中的 struct task_struct *next_task, *prev_task;

  12. TASK_RUNNING状态的进程链表 • task_struct中的 struct list_head run_list; • list_head是Linux内核当中定义的一个数据结构用来实现双向链表,Linux内核中使用上百个向链表来存放各种数据结(include\list.h)

  13. 进程PID hash • task_struct中的pid • 为了快速的从pid值获得进程描述符。需要有hash表 • hash_pid(),unhashpid()在pidhash表中分别插入和删除一个进程 • find_task_by_pid()查找散列表并返回给定PID的进程描述符指针

  14. 进程之间的父子关系 • task_struct中的 struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; • p_opptr : original parent (process 1 或者创建它的父进程) • p_pptr: parent (父进程,有时候是调试时的调试监管进程) • p_cptr: child (指向自己最年轻的子进程) • p_ysptr:指向比自己年轻的兄弟进程 • p_osptr:指向比自己老的兄弟进程

  15. 进程之间的父子关系(续)

  16. 进程之间的父子关系(续) • Linux中的0号进程,通常称为swapper进程,是所有进程的祖先。由它执行cpu_idle()函数,当没有其他进程处于TASK_RUNNING的时候,调度程序会选择0号进程运行 • 0号进程创建1号进程,通常称为init进程。它创建和监控其他进程的活动

  17. 进程的地址空间 • Linux把进程的线性地址空间组织为一个个线性区 • 每一个线性区对应一组连续的页 • 线性区之间不重叠

  18. 进程的地址空间(续) • task_struct struct mm_struct *mm; 内存描述符 mm_struct 里面有一个字段mmp 指向内存线性区链表的首部。

  19. 进程的地址空间(续) • 每个线性区有一定的访问权限 • 在增加或删除线性区时,Linux尽量合并访问权限相同且相邻的线性区

  20. 进程的地址空间(续)

  21. 进程堆的管理 • 每个进程都拥有一个特殊的线形区:堆 • 内存描述符里面的start_brk和brk字段限定了这个区的开始地址和结束地址 • 常用的C库函数:malloc(),free() • 系统调用brk()用于直接修改堆大小

  22. 二、LINUX进程控制 • Linux进程的创建和执行 • 相关的数据结构和系统调用 • 进程的创建 • 程序的执行 • Linux进程的撤消

  23. 相关的数据结构 • 系统创建进程时,Linux为新进程分配一task_struct结构,进程结束时收回其task_struct结构 • Linux在内存空间中分配了一块空间来存放进程的task_struct结构,并将所有的task_struct结构的指针放在一个task数组中,该数组是在操作系统内核中专门开辟的一块区域,数组大小也就是系统中所能容纳的进程的数目

  24. 相关的数据结构(续) • Task数组的结构: struct task_struct *task[NR_TASKS] ={&init_task}; NR_TASKS是数组的大小,默认是512

  25. 相关的数据结构(续) • Task_struct结构的描述: • 进程标识 • 进程状态(State) • 进程调度信息和策略 • 标识号(Identifiers) • 进程通信有关的信息(IPC) • 进程链接信息(Links) • 时间和定时器信息(Times and Timers) • 文件系统信息(Files System) • 处理器相关的上下文信息

  26. 相关的系统调用 • Fork() 通过复制调用进程来建立新的进程,是最基本的进程建立过程 • Exec 包括一系列系统调用,它们都是通过用一个新的程序覆盖原来的内存空间,实现进程的转变 • Wait() 提供初级的进程同步措施,能使一个进程等待,直到另外一个进程结束为止。 • Exit() 该系统调用用来终止一个进程的运行

  27. 如何区分父进程和子进程的功能? • 父子进程怎样被调度执行? • 父子进程在内存中如何存放? • 子进程如何继承父进程的资源?

  28. 进程的创建过程 新的进程通过克隆旧的进程来建立,当前进程是通过fork()系统调用来建立新的进程 当系统调用结束时,内核在系统的物理内存中为新进程分配新的task_struct结构,并为新进程要使用的堆栈分配物理页和进程标志符 父进程和子进程共享打开的文件 fork()的简单流程

  29. Fork调用执行示意 如上图所示,分别使fork调用前后的两个部分 PC指向当前执行的语句,fork之前,它指向第一个printf语句,fork调用之后进程A、B一起运行,A是父进程,B是子进程-进程A的副本,执行与A一样的程序。两个pc都指向第二个printf语句。也就是AB从程序的相同点开始执行

  30. Fork——内存的变动 Fork()执行的内存变动如下: • 分配1页给task_struct结构 • 分配1页给内核堆栈 • 分配1页给pg_dir并且给page_tables分配一些页

  31. Fork——硬件相关的变化 硬件相关的变化 • SS被置为内核堆栈(0x10) • ESP被置为新分配栈的顶端(kernel_stack_page) • CR3指向新分配的页目录 (由copy_page_table()完成) • Idt=_LDT(task_nr)建立新的局部描述符 • 为新的任务状态段(tss)和局部描述符表(ldt[])装入gdt • 从父进程继承剩下的寄存器

  32. Fork系统调用 Pid_t fork(void); 由fork创建的新进程称为子进程。该函数被调用一次,会返回两次。给子进程的返回值是0,给父进程的返回值是子进程的进程ID。然后子进程和父进程继续执行fork之后的指令。子进程拥有父进程数据空间,堆和栈的拷贝,但是它们并不是共享这些存储空间。这里就用到了 “写时复制”技术

  33. 写时复制 • 写时复制技术(copy_on_write) Linux通过写时复制技术来调入执行的程序 Linux将可写虚拟内存页的页表项标志为只读,当进程向该内存页写入数据时,处理器会发现内存访问中的问题(向只读页中写入),然后会导致操作系统可以捕获的页故障,由操作系统来完成内存页的复制 一个给定的物理页面可以代表多个逻辑页面,当这个页被一个进程从另一个进程处得到共享时,它是逻辑上的拷贝 如前面的fork调用,逻辑拷贝整个进程的地址空间,仅当试图修改页面(产生写错误)才真正的拷贝

  34. 程序的执行 • 用fork创建子进程之后,为了让父进程和子进程执行不同的任务,经常需要调用一种exec函数以执行另一个程序。当进程调用exec函数时,该进程完全由新程序替代。新程序从main开始执行 • exec并不创建新进程,前后进程ID是不变的。它是用另外一个程序替代了当前进程的正文,数据,堆和栈

  35. exec函数 exec执行时的内存变化 • 1页分配给可执行文件头 • 1页或者多页分配给堆栈 硬件相关的变化: • clear_page_tables()移去旧页 • 在新的LDT[]中设置描述符 • 包含argv和envp的“脏”页被分配 • 设置调用者的指针 • 设置调用者的堆栈指针指向建立的堆栈 • 更新内存段的边界

  36. Fork VS Exec 因为fork只能建立相同程序的副本,如果它是程序员唯一可以使用的建立进程的手段,会影响linux的性能 exec系列系统调用把新进程装入调用进程的地址空间,改变调用进程的代码。如果exec成功,调用者进程将被覆盖,从新进程的入口地址开始执行。exec只用新进程取代了原来的进程。并且没有返回数据

  37. 进程的撤销 • 撤销时机 • 主动撤销:执行完代码,通知内核释放进程的资源 • 被动:内核有选择地强迫进程死掉。e.g内核代表进程运行时在内核态产生不可恢复的异常

  38. 进程的撤销 • 进程可能已死,但必须保存它的描述符,在你进程得到通知后才可以删除 • 僵死状态:表明进程已死,但需要等待父进程删除 • 撤销过程分为 • 进程终止:释放进程占有的大部分资源 • 进程删除:彻底删除进程的所有数据结构

  39. 系统调用_exit() • C编译程序总是把exit()插入到main()的最后一条语句之后,exit()调用_exit()系统调用 • _exit() 调用do_exit()释放进程所占资源,终止进程

  40. 进程终止:do_exit() • 删除内核对终止进程的大部分引用: • 信号量队列中的进程描述符 • 删除进程描述符中与分页、文件系统、打开文件描述符和信号处理相关数据结构 • 减小进程所用模块的引用计数 • 将进程描述符的exit_code字段设置为终止代号 • 更新父子进程的亲属关系,强制将自己的子进程作为其它某个进程的子进程,以等待该父进程进行删除 • 调用schedule()进行调度

  41. 进程删除 • 父进程调用wait()类系统调用检查子进程是否终止 • 若子进程包含终止代号,则父进程通过release()释放僵死进程的描述符 • 释放进程id • 从进程链表中删除进程描述符 • 释放存放进程描述符的内存区

  42. 三、Linux的进程调度 • schedule() 决定是否进行进程切换,若要切换,切换到哪个进程 • Linux的调度时机 • 调度策略

  43. 1、进程调度的依据 • 选择一个权值(weight)最大的进程 • 权值的计算goodness(): 综合policy,priority,rt_priority和counter四项计算 • 步骤: 1、区分实时进程和普通进程 实时进程的权值:rt_priority 普通进程的权值:与counter有关 2、权值: 普通进程:weight = p->counter 实时进程:weight = 1000 + rt_priority

  44. 普通进程 SCHED_OTHER 实时进程 SCHED_RR policy SCHED_FIFO priority task_struct rt_priority counter

  45. 普通进程 • 动态优先调度 • 周期性地修改进程的优先级(避免饥饿) • 根据进程的counter值 • 实时进程的优先级是静态优先级

  46. counter • 剩余的时间片 • 时间单位:时钟滴答(10ms) • 初值200ms • do_fork()同时设置父进程和子进程的counter域 • 把父进程的剩余时间片分成相等的两份,父子进程各占一份 • 对于普通进程,counter起作用 • 与priority的关系

  47. 当时间片用完后,用priority重新对counter赋值 • 何时重新赋值——所有处于可运行状态的普通进程的时间片都用完 • 时间片确定 • CPU时间的两个层次:时间段,时间段中时间片 • 基本时间片

  48. need_resched 此标志若为1,调用调度程序schedule() • 选择下一个应该运行的进程 • 调用ret_from_sys_call()恢复所选进程的环境 • 时钟中断:最频繁的调度时机

More Related