Chinaunix首页 | 论坛 | 博客
  • 博客访问: 512559
  • 博文数量: 68
  • 博客积分: 5011
  • 博客等级: 大校
  • 技术积分: 806
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-30 22:06
文章分类
文章存档

2011年(1)

2009年(8)

2008年(59)

我的朋友

分类: LINUX

2008-10-09 19:51:03

进程、轻量级进程(LWP)、线程
  • 进程:程序执行体,有生命期,用来分配资源的实体
  • 线程:分配CPU的实体。
    •   用户空间实现,一个线程阻塞,所有都阻塞。
    •   内核实现,不会所用相关线程都阻塞。用LWP实现,用线程组表示这些线程逻辑上所属的进程。

进程描述符
  • 进程描述符(简称pd, process descriptors),结构体是:task_struct
    •   数据较多,存放在kenerl的动态内存空间。
    •   pd的引用放在thread_info中,
      •    thread_info与内核栈,放在一个8K空间(它的地址8K对齐)。内核程序使用的栈空间很小。
      •    thread_info在底部,内核栈在顶部向下增长。
        •     好处:多CPU时方便,每个CPU根据自己的栈指针就可以找到当前的pd (以后用current表示当前CPU运行的进程描述符)。
          •      esp(内核栈指针)低8位置零,就是thread_info地址。
      •    每进程有自己的thread_info, (分配释放函数: alloc_thread_info, free_thread_info)

  • 描述符的内容
    •   相关的ID (一个4元素数组)
      •    进程ID (PID)
        •     PID按创建顺序连续增长,到最大值后从最小值开始。
        •     0号进程:交换进程(swapper)
        •     有PID可用位图,表示那一个PID可用,至少占一个页。
      •    线程组ID(tgid),用LWP实现多线程支持
        •     多进程时,进程id,就是线程组id, 也就是组长的pid(LWP)。 getpid() 取的是线程组的id(tgid), 也是组长的pid.
        •     单线程时,pid = gid。所以getpid,也是真正的pid.
      •    进程组ID(pgrp)。
      •    回话的ID(session).
        •     组ID,都是组长的PID。FIXME: 但pb也有各组长的PID
          •      线程组长:tgid
          •      进程组长:signal->pgrp ,
          •      会话长:signal->session
      •    管理ID数据结构——哈希表管理 (利用id找到所用相关的pd,方便)。
        •     一个哈希表数组(pid_hash),存放四个哈希表, 每一个表代表一类id (pid, tgid, pgrp, session)
        •     每个哈希表的由数组(索引为哈希值)和二维链表(嵌入到进程描述符内的pids中)实现
          •      第一维链表:哈希冲突链表。
          •      第二维链表:要查找的值相同的链表, 叫per-PID list(同一组的所有线程,同一组的所有进程,同一会话的所有进程);
      •    进程组ID(pgrp), 回话ID(session)在共享信号的数据结构里。因为同一进程内的所有LWP,这两个ID都是一样的


    •   家族关系:由pd里的链表(下级)和pd指针(上级)实现
      •    关系:
        •     亲生父亲:创建自己的进程,或是托孤进程(创建自己的进程死了)。
        •     父亲:自己死时要发信号告知的。一般是亲生父亲,有时是监控自己的进程 (调用ptrace)
        •     孩子:
        •     兄弟:
      •    监控(自己起的名字,类似于监护。由于管理方式相同,也归为家族关系)
        •     监控的进程列表:ptrace_children
        •     被监控的其他进程:ptrace_list (类似于被监控的兄弟)
      •    在链表里为了管理方便:
        •     最大儿子的兄弟是父亲
        •     最小儿子的弟弟也是父亲
        •     父亲保管最大儿子,和最小儿子

    •   进程资源及资源限制:
      •    CPU相关:
        •     占用CPU总时间
        •     用户的最大进程数
      •    内存相关:
        •     进程地址空间
        •     锁住内存大小
        •     进程页数 (只有记录,没有限制)
        •     堆大小,栈大小
      •    资源相关:
        •     文件:
          •      core dump大小
          •      最大文件大小
          •      打开文件个数
        •     进程同步与通信
          •      锁数目,
          •      悬挂信号数据
          •      在消息列队中占的大小
      •    相关数据结构 和 处理流程
        •     pd->sigal->rlim 是一个表示进程资源使用情况以及限制的结构的数组。
        •     表示进程资源使用情况以及限制的结构:包含当前值,最大值两个数值。
      •    只有超级用户才能增大资源限制。
      •    一般用户登陆时:
        •     kernel创建root进程,减少limit,
        •     建一个 shell子进程,继承limit.
        •     把shell进程的用户,改成登陆的那个用户

    •   进程状态(state)
      •    运行,TASK_RUNNING
        •     组织pd的结构:就绪进程链:
          •      一个CPU一组链表,每个链表表示一种优先级。
      •    阻塞
        •     可中断阻塞,TASK_INTERRUPTIBLE
          •      可被硬件中断,“释放资源”事件,信号唤醒。
        •     不可中断阻塞,TASK_UNINTERRUPTIBLE
          •      可被硬件中断,“释放资源”事件,唤醒。
          •      但不能被信号唤醒。可用于驱动程序中。
        •     组织pb的结构:等待列队: 每一类事件一个列队,用内嵌链表实现(虽然没列出内嵌链表节点)
          •      列队头:
            •       自旋锁:防止有一个主函数和中断函数同时操作列队。
          •      列队节点:
            •       独占标志:表示该进程是否要独占资源 (不再唤醒别的进程)
            •       指向pd的指针
            •       用于唤醒进程的回调函数。(提供进程的执行机会,是否操作等待列队由用户决定)
      •    停止
        •     停止TASK_STOPPED
          •      被信号停止
        •     追踪TASK_TRACED
          •      该进程被一个调试进程监控以后,收到任何一个信号就进入该状态
        •     组织pb的结构:FIXME: 信号的等待列队?
      •    退出
        •     退出_僵尸EXIT_ZOMBIE
          •      进程终止,资源没有被回收(父进程要用,没有调wait系列函数)
        •     退出_死亡EXIT_DEAD
          •      进程终止,资源正在被回收(父进程要用,没有调wait系列函数)。
          •      一旦资源回收完成,进程描述符也就被回收了。
          •      它防止该进程再次被wait.
        •     组织pb的结构:不挂到队列上,只在家族关系中,等待父进程收回资源


程控制
  • 阻塞(current阻塞到某个列队上):
    •   基本流程
      •    临时生成一个列队节点,初始化。
      •    改变current的状态,放入节点,挂到列队上。
      •    调度 (=====》至此,阻塞完成。 一旦被别的进程唤醒====》从调度函数中返回)
      •    从等待列队上摘除节点。
    •   变化:
      •    将挂列队、调度、从列队删除三步拆开,便于灵活处理。
      •    可中断的、限时、独占的函数类似。只不过进程状态、调度函数、独占标志不同。
      •    非独占的从列队开始添加,独占的从末尾添加。(但一个列队内既有独占的,又有非独占的等待进程,很少见)

  • 唤醒:
    •   基本流程
      •    唤醒一个进程:调用节点里的回调函数
      •    唤醒的时候从列队开头依次唤醒,直到唤醒一个独占的后停止。
    •   变化
      •    是否只唤醒可中断的进程. (_interruptible后缀)
      •    唤醒的独占进程的数目(1个,多个(_nr后缀),所有(_all后缀))
      •    唤醒后是否不检查优先级,马上给予CPU (有_sync的不检查优先级)。


  • 进程切换
    •   切换pgd (全局页目录),此章不讨论。
    •   切换内核栈,硬件上下文
      •    硬件上下文,就是CPU的寄存器。
        •     一部分(大多数CPU寄存器(除了通用寄存器))在pd中保存(task_struct->thread, 类型是thread_struct),
        •     一部分(通用寄存器)保存在内核栈中.
      •    原来用硬件指令()保存CPU信息。后来改成软件(一个个MOV指令)
        •     容易控制,可以挑选信息保存,便于优化。不保存的做其他用(如:进程间传递)
          •           far jmp:跳至目标进程的TSSD。而linux是每个CPU一个TSS,不是每进程一个
        •     对于一些寄存器(ds、es)可以检查值。
        •     与用硬件指令保存时间差不多。
    •   switch_to 宏
      •    三个参数:
        •     prev: 要换走的进程,一般是当前进程
        •     next: 要换到的进程。
        •     last: 传出参数。当前进程再次被换到时,最后一个占用CPU的进程。(prev指向的进程 就是 next指向的进程 的last)
      •    步骤:
        •     栈切换, 完成后就是在新进程的上执行了:
          •      保存prev(放在eax)
          •      eflags,ebp入内核栈;
          •      保存并装载新的esp (旧的esp放到prev->thread.esp,新的esp是next->thread.esp)
            •       此时current就是新的esp所指的thread_info内的task指针
        •     设置返回地址:
          •      prev进程以后得到执行时的__switch_to的返回地址: __switch_to后的第一条指令, 放入prev->thread.eip,
          •      准备next进程的从__switch_to返回的地址: next->thread.eip入栈.
        •     调用__switch_to ()函数,该函数动作如下:
          •      更新CPU的相关信息(tss和gdt):
            •       存next->thread.esp0(内核栈低)到本地TSS.esp0中。
            •       所在CPU的全局段表里的TLS段, 设成next进程的.
            •       更新tss的I/O位图.
          •      更新CPU的寄存器(pd->thread (tss) 与 CPU寄存器交换数据):
            •       保存FPU, MMX, XMM寄存器, 先不装载以后需要时通过中断装载(TODO: )
            •       保存prev的fs, gs寄存器. 装载next的
            •       装载next的debug寄存器(debug寄存器一个8个, 进程切换时只需6个)
          •      返回
            •       prev放入eax (prev就是新进程的last)
            •       ret
        •     ret返回的地址: (__switch_to之前被存入栈中, __switch_to ret时进入eip)
          •      如果是next新进程, next->thread.eip是iret_from_fork.
          •      如果next不是新进程:
            •       弹出ebp, elfags
            •       把eax放入last变量 (prev就是next进程的last)

  • 任务状态段(一个存CPU状态的数组,tss_struct init_tss[])
    •     每个CPU用段上的一个元素。(FIXME: 用于:用户模式要进入内核模式时,设置相应寄存器)
      •       TSS上存内核栈地址。CPU上的程序从用户模式转到内核模式,设置esp。
      •       TSS存I/O端口许可位图。用户模式程序用到I/O时,检查有无权限
      •       所以,进程切换时,要保存的寄存器在pd->thread中。
        •         thread_struct不是thread_info。thread_info中只有少量的数据或指针, 用于通过esp快速定位数据
    •     进程切换时,更新TSS上的信息。
      •       CPU控制单元再从TSS上取需要的信息。
      •       即反应了CPU的当前进程情况,又不需要维护所有进程的状态数据。
    •     TSS的描述符在GDT里。
      •       TSSD:任务状态段描述符 (其实应该叫任务状态描述符,每个TSSD,表示一个CPU的状态, FIXME: :具体以源码为准)
      •       CPU原始设计,每个进程一个TSS元素。
      •       linux设计,每个CPU一个TSS元素。
      •       cpu里的tr寄存器,保存着自己的TSSD(即init_ttss[cpu_id]),不用总上gdt里去取。
阅读(3183) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~