用户级线程
举栗子
先显示文本,再显示图像
不适用线程,要等到所有资源(文本,图像)下载完成,才能显示,一开始全部空白,交互性差
使用线程,线程1下载完之后,切到线程2显示文本,再切到线程3处理图片,然后切到线程4显示图片
Yield切换函数
Yield()是用户编写的函数,用来切换用户级线程
每一个线程需要一个栈
需要借助TCB,TCB是全局的数据结构
切换的时候,在TCB找到栈的指针esp,然后放到esp寄存器,完成切换
esp是CPU里的寄存器
而且,
需要每次在 } 处弹栈,所以jmp指令应该去掉
ThreadCreate创建函数
申请内存作为:
1、TCB
2、栈
3、程序初始地址,也就是这个函数放到栈
4、栈和TCB相关联
所有内容即:
用户级线程进入内核并阻塞
例如
进程阻塞,内核看不到Show函数
那么所有用户级线程都停止
浏览器每打开一个标签都是一个用户级线程,假如一个线程卡了,其他线程也会卡
核心级线程
多核——使用核心级线程,大家共用一套内存空间
多进程——一套MMU,需要来回切换
多用户级线程——操作系统识别不到,也无法分配CPU
最大区别:
每一个线程需要一套栈——用户栈+内核栈
switch_to切换
INT中断
IRET中断返回
SS:存放栈的段地址;
SP:堆栈寄存器SP(stack pointer)存放栈的偏移地址;
CS:IP 两个寄存器指示了 CPU 当前将要读取的指令的地址,其中 CS 为代码段寄存器,而 IP 为指令指针寄存器
ThreadCreate
1、申请内存,作为TCB
2、申请内存,作为内核栈
3、内核栈和用户栈相关联
4、TCB关联内核栈
源码分析
切换五段论
中断入口
首先INT,把SS,SP,ret等数据自动存到核心栈,紧接着调用system_call,把剩下用户态数据保存到寄存器edx等。
最重要的是调用sys_call_table,例如sys_fork,假设没有中断,继续往下执行,cmpl state判断当前PCB(即:current)的state非阻塞,cmpl counter时间片非0,则返回到用户态reschedule
看下reschedule函数具体内容
压栈ret_from_sys_call
schedule是c函数,作用是调度,遇到}时,自动弹栈,弹出ret_from_sys_call,此时执行ret_from_sys_call,返回用户态线程。
中断出口
ret_from_sys_call,push出所欲偶寄存器内容,最后执行iret函数,把ss,sp内容也给弹出,成功切换到用户态
结合源码
切换
next调度暂时不讲
主要看swich_to
TTS是一个段
TR寄存器,作用是根据TTS描述符找到TSS段
n是下一个线程next
linux0.11用TSS方式来切换,较慢
核心指令是ljmp
这段汇编代码意义是,把TSS(n)赋值到ljmp操作数中。由图可知,一旦跳转成功,ESP,EIP等也已经切换成功,无需其他操作
创建sys_fork
创建线程,资源不变,拷贝父进程,内核栈的内容全部作为copy_process的参数,这样就知道了父进程长什么样
注意!!
内核栈是重新创建的
共用用户栈的内容
靠eax区分,叉子两头
父进程%eax不是0,
子进程%eax置0,
if(!fork ){
exec(cmd);
}
exec执行子进程代码,没有进入exec之前,父子进程执行同样的代码
要让子进程执行自己的代码
那么首先得把子进程文件入口entry告诉系统,也就是压到核心栈的ret(ret里存的是eip,把eip告诉pc)。 通过iret命令即可弹出。
eax存的是eip的地址,把eip的地址
流程:读文件,可执行程序在磁盘上读进来,有一个文件头
接着置给内核栈
完成之后,弹出