770 likes | 865 Views
进程. xlanchen@2006.6.8. 主要内容. 进程描述符 进程切换 进程的创建和删除 进程调度. 进程的概念. 进程是执行程序的一个实例 进程和程序的区别 几个进程可以并发的执行一个程序 一个进程可以顺序的执行几个程序. 进程描述符. 为了管理进程,内核必须对每个进程进行清晰的描述。 进程描述符提供了内核所需了解的信息 include/linux/sched.h struct task_struct. 进程描述符. 进程状态. 可运行状态 (TASK_RUNNING) 可中断的等待状态 (TASK_INTERRUPTIBLE)
E N D
进程 xlanchen@2006.6.8
主要内容 • 进程描述符 • 进程切换 • 进程的创建和删除 • 进程调度 Embedded Operating Systems
进程的概念 • 进程是执行程序的一个实例 • 进程和程序的区别 • 几个进程可以并发的执行一个程序 • 一个进程可以顺序的执行几个程序 Embedded Operating Systems
进程描述符 • 为了管理进程,内核必须对每个进程进行清晰的描述。 • 进程描述符提供了内核所需了解的信息 • include/linux/sched.hstruct task_struct Embedded Operating Systems
进程描述符 Embedded Operating Systems
进程状态 • 可运行状态(TASK_RUNNING) • 可中断的等待状态(TASK_INTERRUPTIBLE) • 不可中断的等待状态(TASK_UNINTERRUPTIBLE) • 暂停状态(TASK_STOPPED) • 僵死状态(TASK_ZOMBIE) Embedded Operating Systems
进程状态转换图 Embedded Operating Systems
标识一个进程 • 使用进程描述符地址 • 进程和进程描述符之间有非常严格的一一对应关系,使得用32位进程描述符地址标识进程非常方便 • 使用PID (Process ID,PID) • 每个进程的PID都存放在进程描述符的pid域中 Embedded Operating Systems
进程描述符 • Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构: • 进程描述符 • 进程的内核堆栈 进程处于内核态时使用不同于用户态堆栈 内核控制路径所用的堆栈很少,因此对栈和描述符来说,8KB足够了 Embedded Operating Systems
Task_union • C语言允许用如下的一个union结构来方便的表示这样的一个混合体 =2048 Embedded Operating Systems
current宏进程描述符 • 从刚才看到的进程描述符和内核态堆栈之间的配对,内核可以很容易的从esp寄存器的值获得当前在CPU上运行的进程的描述符指针 • 因为这个内存区是8KB=213大小,内核必须做的就是让esp有13位的有效位,以获得进程描述符的基地址 • 这个工作由current宏来完成 8191=8192-1=0x2000-1=0x1fff取反:0xffffe000(最后13位为0) Embedded Operating Systems
Current宏的使用 • Current宏可以看成当前进程的进程描述符指针在内核中直接使用 • 比如current->pid返回在CPU上正在执行的进程的PID Embedded Operating Systems
进程链表 • 为了对给定类型的进程(比如所有在可运行状态下的进程)进行有效的搜索,内核维护了几个进程链表 • 所有进程链表 在进程描述符中: Embedded Operating Systems
SET_LINKS和REMOVE_LINKS宏用来分别在进程链表中插入和删除一个进程描述符。SET_LINKS和REMOVE_LINKS宏用来分别在进程链表中插入和删除一个进程描述符。 Embedded Operating Systems
for_each_task宏扫描整个进程链表 Embedded Operating Systems
TASK_RUNNING状态的进程链表 • 当内核调度程序寻址一个新的进程在cpu上运行时,必须只考虑可运行进程,因为扫描整个进程链表效率很低 • 引入了可运行状态的双向循环链表,也叫运行队列 • 进程描述符使用用来实现运行队列 Embedded Operating Systems
对可运行队列的一些操作函数 增加/删除一个可运行进程 可运行队列的长度,可运行进程的个数 唤醒一个进程,使一个进程可运行 … Embedded Operating Systems
pidhash表及链接表 • 在一些情况下,内核必须能从进程的PID得出对应的进程描述符指针。例如kill系统调用 • 为了加速查找,引入了pidhash散列表 • 用pid_hashfn宏把PID转换成表的索引 Embedded Operating Systems
pidhash表及链接表 Embedded Operating Systems
进程之间的亲属关系 • 程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系 Embedded Operating Systems
等待队列 • 当要把除了TASK_RUNNING状态之外的进程组织在一起时,linux使用了等待队列 • TASK_STOPPED和TASK_ZOMBIE不在专门的链表中 • TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程再分成很多类,每一类对应一个特定的事件。在这种情况下,进程状态提供的信息满足不了快速检索,因此,内核引进了另外的进程链表,叫做等待队列 • 等待队列在内核中有很多用途,尤其是对中断处理、进程同步和定时用处很大 Embedded Operating Systems
等待队列使得进程可以在事件上的条件等待,并且当等待的条件为真时,由内核唤醒它们等待队列使得进程可以在事件上的条件等待,并且当等待的条件为真时,由内核唤醒它们 • 等待队列由循环链表实现 • 在等待队列上内核实现了一些操作函数 Add_wait_queue remove_wait_queue Embedded Operating Systems
等待队列的链表 Embedded Operating Systems
进程等待 • 等待一个特定事件的进程能调用下面几个函数中的任一个 • sleep_on • sleep_on_timeout • interruptible_sleep_on • interruptible_sleep_on_timeout • 进程等待由需要等待的进程自己进行(调用) Embedded Operating Systems
sleep_on Embedded Operating Systems
进程的唤醒 • 利用wake_up或者wake_up_interruptible等一系列的宏,都让插入等待队列中的进程进入TASK_RUNNING状态 Embedded Operating Systems
进程切换(process switching) • 为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换,任务切换,上下文切换 Embedded Operating Systems
进程上下文 • 包含了进程执行需要的所有信息 • 用户地址空间包括程序代码,数据,用户堆栈等 • 控制信息进程描述符,内核堆栈等 • 硬件上下文 Embedded Operating Systems
硬件上下文 • 尽管每个进程可以有自己的地址空间,但所有的进程只能共享CPU的寄存器。 • 因此,在恢复一个进程执行之前,内核必须确保每个寄存器装入了挂起进程时的值。这样才能正确的恢复一个进程的执行 • 硬件上下文:进程恢复执行前必须装入寄存器的一组数据 • 包括通用寄存器的值以及一些系统寄存器 • 通用寄存器如eax,ebx等 • 系统寄存器如eip,esp,cr3等等 Embedded Operating Systems
在linux中 • 一个进程的硬件上下文主要保存在thread_struct中 • 其他信息放在内核态堆栈中 Embedded Operating Systems
thread_struct Embedded Operating Systems
上下文切换 • switch_to宏执行进程切换,schedule()函数调用这个宏一调度一个新的进程在CPU上运行 • 演示:在schedule()中找到调用switch_to宏的位置 • switch_to利用了prev和next两个参数: • prev:指向当前进程 • next:指向被调度的进程 Embedded Operating Systems
当前进程仍然是prev 这个push操作针对的是 当前进程的堆栈 保存esi,edi,ebp 保存esp到当前进程的上下文中 保存esp到%0中 %0是什么? 从next的上下文中取出堆栈的位置,将其作为当前堆栈 堆栈被切换 在prev进程的上下文中设置返回地址,返回到下面标号为1处 从next进程的上下文中取得该进程的返回地址,放入堆栈中 调用__switch_to函数 嵌入式汇编中 用这种方法表示输入、输出参数,可以从 0开始编号 Embedded Operating Systems
进程切换的关键语句 • 堆栈的切换从此,内核对next的内核态堆栈操作,因此,这条指令执行从prev到next真正的上下文切换,因为进程描述符和内核态堆栈紧密联系在一起,改变内核态堆栈就意味改变当前进程 Embedded Operating Systems
什么时候next进程真正开始执行呢? • call=保存返回地址+跳转到target处执行 • ret=从堆栈上获得返回地址,并跳转到该返回地址处执行 • ?当__switch_to正常返回时,发生了什么事情? Embedded Operating Systems
标号为1的执行代码处 • 一个进程被正常切换出时,保存的eip总是标号为1的那个位置 • 当这个进程再次被调度运行时,恢复在堆栈上的返回地址总是这个1 1: popl %ebp popl %edi popl %esi Embedded Operating Systems
__switch_to • __switch_to用来处理其他上下文的切换 • 此时,使用的堆栈是next进程的堆栈,这个堆栈上没有__switch_to需要的参数prev和next • 怎么传参呢? • 演示:找到__switch_to的函数定义和函数声明 • 找到FASTCALL的定义 Embedded Operating Systems
__switch_to的关键操作 • unlazy_fpu() 处理数学协处理器 • 保存和恢复fs、gs • 等等 Embedded Operating Systems
?哪里切换了进程的地址空间 Embedded Operating Systems
进程的创建 • 许多进程可以并发的运行同一程序,这些进程共享内存中程序正文的单一副本,但每个进程有自己的单独的数据和堆栈区 • 一个进程可以在任何时刻可以执行新的程序,并且在它的生命周期中可以运行几个程序 • 又如,只要用户输入一条命令,shell进程就创建一个新进程 Embedded Operating Systems
Linux提供了几个系统调用来创建和终止进程,以及执行新程序Linux提供了几个系统调用来创建和终止进程,以及执行新程序 • Fork,vfork和clone系统调用创建新进程 • exec系统调用执行一个新程序 • exit系统调用终止进程(进程也可以因收到信号而终止) Embedded Operating Systems
fork • fork系统调用创建一个新进程 • 调用fork的进程称为父进程 • 新进程是子进程 • 子进程几乎就是父进程的完全复制。它的地址空间是父进程的复制,一开始也是运行同一程序。 • fork系统调用为父子进程返回不同的值 Embedded Operating Systems
exec • 很多情况下,子进程从fork返回后很多会调用exec来开始执行新的程序 • 这种情况下,子进程根本不需要读或者修改父进程拥有的所有资源。 • 所以fork中地址空间的复制依赖于Copy On Write技术,降低fork的开销 Embedded Operating Systems
写时复制技术 • 写时复制技术允许父子进程能读相同的物理页。 • 只要两者有一个进程试图写一个物理页,内核就把这个页的内容拷贝到一个新的物理页,并把这个新的物理页分配给正在写的进程 Embedded Operating Systems
使用fork和exec的例子 If (result = fork() == 0){ /* 子进程代码 */ … if (execve(“new_program”,…)<0) perror(“execve failed”); exit(1); }else if (result<0){ perror(“fork failed”) } /* result==子进程的pid,父进程将会从这里继续执行*/ … Embedded Operating Systems
分开这两个系统调用是有好处的 • 比如服务器可以fork许多进程执行同一个程序 • 有时程序只是简单的exec,执行一个新程序 • 在fork和exec之间,子进程可以有选择的执行一系列操作以确保程序以所希望的状态运行 • 重定向输入输出 • 关闭不需要的打开文件 • 改变UID或是进程组 • 重置信号处理程序 • 若单一的系统调用试图完成所有这些功能将是笨重而低效的 • 现有的fork-exec框架灵活性更强 • 清晰,模块化强 Embedded Operating Systems
do_fork • 不论是fork,vfork还是clone,在内核中最终都调用了do_fork Embedded Operating Systems
阅读do_fork,了解大致程序流程 • ???子进程从哪里开始执行,它的返回值是什么? Embedded Operating Systems
注意:childregs指针指向哪里 eax寄存器用作返回值,这里强制为0 在上下文中,设置用户态堆栈指针 设置内核态堆栈指针 强制从ret_from_fork返 回用户态 此后,由于子进程处于调度队列上,因此在合适的时候会被调度, 调度时根据这里设置的上下文返回 用户态 Embedded Operating Systems