运行中的程序在进行切换时, 可以分为以下两类:
- 线程: 只涉及指令的切换, 硬件资源没有切换;
- 进程: 包含指令的切换,以及硬件资源的切换, 其中映射表便是一种内存资源。
这一讲中, 只会涉及指令的切换,暂不考虑资源的切换。
本质就是 映射表不改变, pc 指针改变
1.用户级线程
1.1 线程 thread
由前面的进程之间的切换可知,
不同的进程之间在进行切换时, 需要切换各自进程所对应的PCB, 以及 各个进程对应的 映射表;
由于不同的进程对应不同的映射表, 切换时,代价较大;
为了避免这种代价, 在切换时, 使得不同的程序之间共享同一张 映射表;
线程: 保留了程序并发的有点;
减少了进程切换的代价。
1.2 线程的实例
浏览器的使用:
-
一个线程用来从服务器接受数据
-
一个线程用来显示文本;
-
一个线程用来处理图片(如解压缩)
-
一个线程用来显示图片;
线程之间的资源共享:
- 将从服务器中接受的数据, 如文本,图片数据放在缓冲区中, 而其他的进程需要从映射表中读数据;
而如果使用进程完成上述任务,
进程之间,地址是分离的。
由于不同的进程,会有不同的映射表, 那么这些映射表将会存放在内存的不同区域, 这样同一份数据占用了更多的内存,这没有必要。
一个线程在下载数据时, 这个过程中,切换到其他线程, 用来显示文本数据;
当文本数据显示完成之后, 在重新切换到下载进程。
1.3 线程的切换
切换时需要是个什么样子,
create
函数, 要创造出第一次切换时的样子;
pthread_create()
创建多个线程,
在各个线程执行的过程中,增加一些内容,
Yield ()
函数 实现线程之间的切换,即 可以从一个线程中切换出去,又切换回来。
1.4 两个线程共用一个栈
线程内部的函数调用,
-
当线程中调用一个函数B时, 所以需要将函数B的返回地址进行压栈,
-
当执行调用函数B时, 执行调用的函数中,会执行
Yield
函数. -
执行
Yield
函数, 切换到另外一个线程。 -
当PC 指针执行到
204
地址处, 会遇到右侧花括号}
, 该花括号的作用翻译成汇编指令ret
是弹栈. -
此时,弹栈是 弹出404, 跳到404 处继续执行。 而 实际上, 经过
ret
应该返回到 104 的地方, -
一个线程内部的函数调用, 无论是 压栈 和弹栈, 都应该 只保留住当前线程的压栈和弹栈信息, 而不应该多个线程共用一个栈;
多个线程执行的过程中,不应该共用一个栈,
发生函数调用是在一个指令序列内部发生的事情 , 一个函数调用, 每个线程中,应该使用自己的栈。
1.5 每个线程使用独立的栈
线程通过Yield
切换时, 应该将 首先将该线程对应的栈切换回去。
栈 切换回去, 需要有一个地方存放栈的指针, ;
即需要一个地方存放栈的信息, 称作是 TCB
, thread control block
,
栈切换回去, 即更新当前的esp 寄存器 中的信息即可以,
栈切换完成之后, PC 指针更着切换,
从栈中弹出的 指令或者 是地址,都需要执行,
1.6 重复执行了204
Yield
的下一句话是204,即是弹栈,
使用Yield
的返回的右括号,完成了线程切换,
2. 两个线程的执行
两个TCB, 两个栈, 切换的PC在栈中
ThreadCreate 的核心就是用程序做出这三样东西;
void ThreadCreate (A)
{
TCB *tcb = malloc(); // 申请一段内存, 作为tcb;
*stack = malloc(); // 申请一段内存,作为栈。
*stack = A; // 100 在栈中存放内容,程序的初始地址;
tcb.esp = stack; // 栈和TCB 关联;
}
2.1 用户级线程
Yield
都是用户程序
如果进程1的某个线程进入内核并阻塞,
内核则会执行另外一个进程2,
如果此时,除了进程1 没有进程2, 则当进程1中的某个线程阻塞, 则此时进程1中的其他线程,也会卡在那里, 导致CPU空转,
2.2 核心级线程
ThreadCreate
是系统调用, 会进入内核, 内核知道TCB;
核心级线程的创建,会进入到内核;
核心的并发性更改, 因为此时进程中某个线程阻塞,并不会影响到其他线程的运行。