L10_用户级线程
线程与进程
:::info
- 进程 = 资源 + 指令执行序列
- 将资源和指令执行分开
- 一个资源 + 多个指令执行序列
- 线程: 保留了并发的优点, 避免了进程切换代价
- 实质就是映射表不变而PC指针变
切换线程——指令切换(PC指针切换),资源不切换(不更换映射表) :::
一个例子:网页浏览器
void WebExplorer() {
char URL[] = “http://cms.hit.edu.cn”;
char buffer[1000];
pthread_create(..., GetData, URL, buffer); //GetData
pthread_create(..., Show, buffer); //Show
}
void GetData(char *URL, char *p){...};
void Show(char *p){...};
如何切换?
跳转时会将当前程序下一个程序的地址压入栈中
程序结束遇到
}
会将栈中的地址弹出,转到该地址; :::danger 若是两个程序只是用一个栈,会发生混乱; :::Yield
切换时要先切换栈,进程1的地址压在TCB1的栈,进程2的地址压在TCB2的栈;ThreadCreate
线程的核心便是TCB、栈、切换的PC;因此在创建线程时,需要创建这三者;
void ThreadCreate(A)
{
TCB *tcb=malloc();
*stack=malloc();
*stack = A;//100
tcb.esp=stack;
}
用户级线程和核心级线程的差异
用户级线程仍会出现阻塞的现象;
某个线程进入内核并阻塞,CPU并不知道TCB的信息,也不会调用其他的线程,因此会卡住;
- 与之区别的是核心级线程,由系统调用,会进入内核,知道TCB(但是不用跳转进程)
L11_内核级线程
内核级线程与用户级线程的差别
:::info
- 用户级:两个栈
内核级:两套栈 ::: :::info 用户栈会和内核栈组成一种关联的关系;
*当用户调用INT中断时,会进入内核; :::如何切换
用户调用int指令,进入中断
- 中断处理,启动磁盘读或是时钟中断:
schedule()
引发切换 - schedule内使用swith to函数,实现内核切换
- 切换后执行一段中断函数,最后应该右iret指令,从中断弹出,进入切换后的线程
:::info
直观上讲,有两级切换,先是内核级的切换,从线程S的代码段切到线程T的代码段
然后中断结束回到线程T(因为栈也换了)
:::
Summar
L12_内核级线程的实现
用户栈进入内核
main(){
A();
B();
}
A(){
fork();
}
//遇到fork系统调用,通过中断进入内核
mov %eax,__NR_fork
INT 0x80
mov res,%eax
从main()
进入A()
时,用户栈会压入下一条指令的地址:ret=B
从内核进中断
调用中断进入内核后,内核栈要和用户栈联系起来,栈寄存器指向用户栈,因为要去执行中断函数,因此也要保存内核下一条指令的地址,即mov res,%eax
的地址。然后调用中断处理函数system_call
_system_call:
push %ds..%fs
pushl %edx... //首先保存现场,此时保存的这些内容还是当前的用户栈的
call sys_fork //调用fork创建一个子进程
//========================
pushl %eax
movl _current, %eax
cmpl $0,state(%eax)
jne reschedule
cmpl $0, counter(%eax)
je reschedule
//========================
ret_from_sys_call:
:::info
系统判断当前进程是否发生阻塞、需要切换到新的进程是通过上面的两个cmpl
来进行的;
- 第一个比较状态信息是否为非零,若是非0则说明阻塞了,执行
schedule
切换 -
切换到其他进程
这里先解释切换五段论中的最后一个,如何从内核回到用户栈。
reschedule:
pushl $ret_from_sys_call
jmp _schedule
reschedule
首先在当前内核栈中保存返回后的下一条指令,即弹栈ret_from_sys_call
然后才执行切换线程(PCB的切换)
void schedule(void){
next=i;
switch_to(next);
}
:::tips
switch_to
切换到下一个就绪的进程,实际上是PCB的切换!也就是说现在内核栈换了!switch_to
末尾的ret
会执行弹栈,这时因为已经换成新的内核栈了,ret后会跳到新的用户栈。 :::ret_from_sys_call:
popl %eax //返回值
popl %ebx.h
pop %fs...
iret //返回到哪里呢?INT 0x80后面执行!
//res的值是返回值,实际上是%eax的值
:::info 若是从schedule出来的话也会执行弹栈,弹栈后会执行
ret_from_sys_call
ret_from_sys_call
会执行弹栈操作,因为内核栈已经切换了,所以这时候弹栈实际上是弹的另一个进程的用户栈,执行结束后iret
也会进入到新进程的用户栈 :::fork创建线程