线程的切换
在看进程切换前,我们先来看线程的切换吧。
这一篇主要说的是用户级线程的切换。
因为 进程的切换=资源切换+指令执行序列切换。
将资源和指令序列分开看,如果只是从一个执行指令序列切换到另一个执行指令序列,那么这就是线程的切换。
线程保留了并发(一个cpu上交替的执行多个程序)的优点,避免了进程切换代价,不需要切资源(映射表),只是切执行指令序列。线程切换的实质就是映射表不变而PC指针变。
用户级线程的切换
一个网页浏览器
一个线程用来从服务器接收数据
一个线程用来显示文本
开始实现这个游览器…
void WebExplorer()
{
char URL[]="http://cms.hit.edu.cn";
char buffer[1000];
pthread_create(...,GetData,URL,buffer);
pthread_create(...,Show,buffer);
}
void GetData(char* URL,char *p){...};
void Show(char* p){...};
我们下载了一段时间的数据后,切出去执行另一个线程,显示文本后,切回来继续下载。
Yield与Create
pthread_create()让多个线程同时触发,yield()能完成线程的切换,使线程交替执行。
现在有两个执行序列,我们想要其中执行了一段时间后,跳到另一个去执行,之后又切回来。(线程的切换)
Yiled从100跳到300
//B中的Yield
void Yield()
{
找到300;
jmp 300;
}
//D中的Yield
void Yield()
{
找到204;
jmp204;
}
两个执行序列与一个栈…
从100开始执行,在A函数中遇到B函数的调用,B的返回地址即下一句指令的地址104压栈,又在B中遇到Yield()调用,Yield()的返回地址204压栈。在B中的Yield()jmp到300,执行C函数,在C中遇到D()调用,304压栈,执行D(),遇到D中的Yield()调用,Yield()的返回地址404压栈。
D中的Yield()执行后,跳到204
200:B()
{
Yield();
204:
}
204开始执行遇到”}”,会弹栈,栈顶元素404被弹出执行404,这里就出现问题了,我们刚才已经回到100那个线程了,现在却又切到了300那个线程。
所以两个序列一个栈是不行的。
一个栈到两个栈
每个序列对应一个栈,Yield切换先切栈。
//D中的Yield
void Yield()
{
TCB2.esp=esp;
esp=TCB1.esp;
jmp 204;
}
现在两个栈的情况是:
执行D中的Yield,使栈完成了切换,esp=1000。但是遇到jmp 204,就跳到204,那么Yield()就永远不能返回了。
//D中的Yield
void Yield()
{
TCB2.esp=esp;
esp=TCB1.esp;
//jmp 204;
}
把jmp 204去掉,此时栈已经完成了切换esp=1000,然后Yield执行遇到“}”,弹栈,此时弹栈弹出栈顶元素 204,执行204(“}”相当于ret),再ret,弹栈就是104了。
所以两个线程的样子:两个TCB、两个栈、切换的PC在栈中。
而ThreadCreate的核心就是申请一个栈,一个TCB(线程控制块),再将栈与TCB关联。
void ThreadCreate(A)
{
TCB* tcb=malloc();
*stack=malloc();
*stack=A;//100 执行线程的起始地址
tcb.esp=stack;//栈和TCB关联
}
综上
void ThreadCreate(func,arg1)
{
申请栈;
申请TCB;
func等入栈;
关联TCB与栈;
}
void GetData(char* URL,char *p)
{
连接URL;
下载;
Yield();
...
}
void Yield()
{
压入现场;
esp放在当前TCB中;
Next();//调度函数,对系统影响很大,可优先调度Show
从下个TCB取出esp;
弹栈切换线程;
}
用户级线程
因为这里的Yield,Create是用户程序,没有进入内核,所以说是用户级线程。
用户级线程对于内核是不可见的。
如果进程的某个线程进入内核并阻塞,对于内核来说,它是看不到那个进程里面有其他线程的,所以会直接切到别的进程去执行。此时用户级线程的并发性就没有什么用了。
例如
GetData()
{
连接URL发起请求;
等待网卡IO...
进程阻塞
}
Show()
{
显示文本和连接;
...
}
用户级线程只能在用户级切来切去,不进入内核。