目录
1.概述
课本上的概念是:
比进程更加轻量化的一种执行流/在进程内部执行的一种执行流
for us:
线程是CPU调度的基本单位/进程是承担系统资源的基本实体
进程的地址空间:
线程的地址空间:
这两个或n 个task_struct
在内存上能共享就就共享,
不能共享就得到自己
该有的一份(将原来的分割)
然后通过页表找到对应的实际物理内存
参与资源的分配
得到的结论:
1.线程创建比进程的创建更简单,创建简单意味着销毁也简单
2.线程在进程的地址空间中运行
linux中的线程机制
OS中存在多个线程,会对多个线程进行管理(创建,暂停,挂起等等情况)
先描述再组织,TCB(线程控制块),T:thread C:control B:block
未来的线程会作为一个链表的形式存放进进程地址块中
对线程的管理就相当于是进程的管理,所有的关于线程的管理操作复用进程的管理形式就好了
windows下创建进程和创建线程都有直接的接口,管理TCB,管理PCB都有自己的对应的方法,
而linux下并不是这样
linux下创建的线程是根据PCB的进行复用,但并不是像创建PCB一样建立地址空间等,
而是直接在PCB内部进行管理
本质
进程来模拟创建线程,但是线程的存在,OS会区分进程和线程吗?
OS只要执行那个对应的代码就可以了,不会管这是进程还是线程
换做以前CPU拿到一个PCB <= 进程, 拿到的是一个进程的执行流,这个进程的执行流是包括当前进程和其他线程
执行流 <= 进程 -->cpu执行一个执行流,这个执行流可能是一个线程,此时是<,可能是一个进程,此时是=.
如何看待以前的进程:
只有一个执行流的进程
如何看到现在的线程:
内部多个执行流的进程
什么叫做进程:
task_struct,虚拟内存地址空间,页表,代码和数据这个整个部分都是进程.进程=内核数据结构+代码和数据
(创建)
即:
所谓进程是承担系统资源的基本实体
linux中并不存在真正的线程,他是用进程模拟的线程.
这样的线程也叫做轻量级进程
即:这个进程的执行流 <= 进程
对于一个进程,里面有多个线程,多个线程之间进行协作,共同完成一件事情
2.理解–原理—看待线程/看待进程------linux上的线程 vs OS的线程
创建线程:
pthread_create
的使用
这个pthread_t 本质就是unsigned long int
知识补充static_cast<>使用
思考:
为什么不允许直接转为string* 类型?
但实际上是支持这样的写法,只是推荐一种更安全的写法X* 变量= static_cast(原变量)
void* a = new string(“haha”); // 动态分配一个 string 对象
string* s = static_cast<string*>(a); // 现在 static_cast 是安全的
这样的写法更安全 (演示写法)
static_cast 主要用于在相关类型之间进行转换,例如基本类型的转换(如 int 到 float)或具有继承关系的类指针之间的转换(如从基类指针到派生类指针)
会报错这个报错需要在编译的时候加一个编译选项:
能同时跑两个循环,且只有一个进程
打印pid查看:
查看线程:
ps -aL 解释:-a—>all L(light)轻量级
LWP:
(Light Weight Process)
OS在调度的时候,是根据LWP进行调度的,LWP与PID相等的那个线程是主线程
多创建几个,查看结果:
每一个线程的创建都会执行一个对应的线程方法,这些线程方法可以相同也可以不同
当多个线程执行同一个方法时,这个函数就一直在被重入
线程在进程内部资源是共享的
让两个执行流看到同一份资源不再像进程那样,线程执行流资源共享更简单,更方便
线程能并发执行代码
线程比进程更加轻量化:
进程的创建需要建立虚拟地址空间,建立页表映射等操作
线程创建则不需要,直接创建一个PCB对象,指向这个进程指向的虚拟地址空间即可
在切换方面:
线程间切换,地址空间页表是不用替换的,更不用像进程那样替换很多的寄存器,只是替换几个局部的寄存器即可
进程间切换,是需要切换所有的寄存器,还要上下文切换等
查看CPU信息
cat /proc/cpuinfo
局部性原理的介绍
计算机中存在局部性原理:
即对于一个可执行程序,CPU可能正在访问第10行,那么未来CPU有很大可能访问第 11 12 13行,为什么是 可能?因为会存在函数跳转,进程替换等情况
基于这样一种情况,CPU会将代码读到cache(硬件级别的缓存)缓存中,这样在未来的访问直接从缓存进行访问,速度就能快很多
这一设计就是利用局部性原理
存在在cache里面的数据叫做热数据
线程间的切换不需要切换cache,因为cache中存的本来就是这个虚拟地址空间的数据
进程间切换,cache会失效重新加载,因为两个进程之间的数据没有关系
这也是线程间切换比进程间切换的效率高的主要原因(寄存器少,cache不需要重新更新)
进程的创建需要时间片,时间片会被一个进程中的各个线程进行划分
1.一个任务的进行从串行进行变成了并行执行,线程创建也更轻量化
2.在linux中,线程是在进程的地址空间中进行
3.进程=内核数据结构+代码和数据,他是承担系统资源分配的基本实体
4.线程可以认为是进程的一个子集
5.linux 的线程创建是进程模拟的
参考下面两张图内的1—>7顺序更好的理解线程
3.再谈地址空间----虚拟–>物理地址的过程
以前页表的认识是不完全正确的,
为什么?
以4GB内存为例,虚拟内存的大小为2的32次方字节
页表中的内容有:虚拟内存地址(4字节)+物理内存地址(4字节)+权限数字等其他(按2字节算)
那么这个页表空间就比内存还大
所以实际虚拟到物理的转化是(大概描述):
一个4GB的物理内存,有4 * 1024 * 1024个kb大小的单位
按4kb分为n个页框
OS对这些页框的管理也会成为对内核数据结构的管理
struct page
{
int flag;//page的使用情况
//page的属性
}
根据物理内存的大小(以4GB为例)维护一个数组来进行管理
struct page pages[1048576];
那么缓冲区的实际指向是指向这个数组
总结:
1.对于一个很大的文件,OS可能会分批将文件加载到内存
2.一个可执行程序,并不会把虚拟内存全部用完,
3.页目录一定会被加载,但是二级页表不一定,当访问的内容小就只会在前几页进行加载,
后续的页表不会创建,当新的内容加载进来,首先发生缺页中断,然后重新创建新的页表,
创建新的映射关系,这更加说明了页表的内容之大,不可能被存满或不足的情况
4.CPU当中有保存页表起始地址的寄存器,整个虚拟到物理的转化是CPU进行的,这样从CPU出来以后,就能通过总线找到物理地址
5.总结上述:给一个线程分配它对应的代码和数据,实际就是在分割页表
6.划分页表的额本质就是划分地址空间
7.类型的本质就是偏移量,语言层面的创建到了汇编就是对应寄存器的偏移量的计算
8.在进程视角,虚拟地址就是资源
本期就到此为止,期末耽误好多,要抓紧补了
喜欢不放来个三连~~