您的位置:首页 > 其它

进程、轻量级进程(LWP)、线程

2012-04-12 20:06 260 查看
进程、轻量级进程(LWP)、线程

进程、轻量级进程(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里去取。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: