五.多进程图像的引出
L8: CPU管理的直观想法
不仅要切换PC指针,还需要对应的寄存器环境
L9: 多进程图像
六、 线程引出与实现
L10:用户级线程
10.1 需要两个栈用于线程切换
几个线程就需要几个栈
10.2 用户主动切换
只在调用Yield处进行切换
yield, thread_create都是自己写的,linux 0.11源码中没有
10.3 进程和线程概念区分
进程 和 线程 都是动态概念
进程 = 资源 (包括寄存器值,PCB,内存映射表)+ 指令序列
线程 = 指令序列
线程 的资源是共享的,
进程 间的资源是分隔独立的,内存映射表不同,占用物理内存地址是分隔的
线程 的切换只是切换PC,切换了指令序列
进程 的切换不仅要切换PC,还包括切换资源,即切换内存映射表
用户级线程:调用Yield函数,自己主动让出cpu,内核看不见,内核只能看见所属进程而看不见用户级线程,所以一个用户级线程需要等待,内核会切到别的进程上,不会切到该进程下的其他用户级线程!!!
内核级线程: 内核能看见内核级线程,一个线程需要等待,内核会切到所属进程下的其他内核级线程。
L11:内核级线程
11.1 基本概念
只有内核级线程才能发挥多核性能,多个CPU共用一套MMU设备情况下,可以一个CPU执行一个内核级线程,在内核级线程切换的时候,不需要切换内存映射关系,代价小很多,因为本来一个进程中MMU映射关系就是一样的
进程 无法发挥多核性能,因为只有一套MMU,进程切换时MMU映射关系也得跟着切换,即切换内存映射表,切换内存映射表代价比较大
一个内核级线程的切换需要两套栈: 用户栈 + 内核栈
注意
linux 0.11中本身不支持内核级线程切换,只有进程切换,但实际上只是多了切换内存映射表那部分。
11.2 完整的系统调用中断过程:
1) INT中断自动压栈的有下一条指令,以及用户级线程SS:SP,就是下图五个参数
2) _system_call 把寄存器保护压栈是压到内核栈中,需要手动压栈
3) 系统调用,(有可能是_sys_fork,其实就是根据标号找到的系统调用),结束之后继续执行,要执行reschedule,先push $ret_from_sys_call,让其在_schedule之后返回到ret_from_sys_call, _schedule为c函数,结束右括号会把ret_from_sys_call pop出来,返回到这里执行,即执行ret_from_sys_call;
这里注意call 和 jmp的区别!!!
4)在ret_from_sys_call中pop出_system_call时保护的寄存器内容,然后中断返回!!!
5)中断返回是在最后,中断返回会把SS:SP 以及用户态的下一条指令 POP出来,即把5个寄存器pop出来!!!这样就会返回到用户栈,运行用户态的下一条指令!!!
代码:
reschedule:
pushl $ret_from_sys_call
jmp _schedule //如果用call就是先将下一条指令压栈,然后jmp,这样只能顺序往下走,但是用push+jmp则可以改变跳转地址!!!
11.3 switch_to五段论
这里要注意,中断出口这里已经经过了前面的switch_to,中断的iret已经不是原先的中断返回了,是切换后的新中断的执行返回!!!这样返回以后就来到了引发该新中断的用户态代码来执行
L12 核心级线程实现实例
12.1 fork函数经典调用
if (!fork()) //关键在于 INT 80 后面的指令 mov res, %eax;父进程和子进程都会执行这个代码,但是%eax的值不一样
{
子进程
}
else
{
父进程
}
fork新建了内核栈,但没有新建用户栈,即子进程和父进程共用了用户栈,这样不会出现问题么???
//Main.c
static inline _syscall0(int,fork)
//宏定义
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
//宏展开
int fork()
{
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_fork)); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
12.2 sys_fork解析
sys_fork:
call find_empty_process
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call copy_process
addl $20,%esp
1: ret
12.3 find_empty_process
find_empty_process返回非0值,要么为正整数,要么为负数,并且将返回值保存到寄存器eax中,即父进程的eax值为非0<