多线程
进程内进行资源划分
之前说过页表有用户级页表和内核级页表,现在再来扩展一下。
这里也能解释为什么对于常量字符串类型为什么不能修改了,因为要修改的时候会从虚拟地址转化成物理地址,然后检查权限是否可以修改等等。
如何看待地址空间和页表呢?
1.比如进程看到的堆区,栈区等等各种的资源窗口。
2.页表决定进程真正拥有资源的情况。(因为虚拟地址空间根本无法决定实际情况)
3.合理的对地址空间+页表进行资源划分,我们就可以对一个进程所有资源进行分类。(比如虚拟地址空间中的堆区,栈区,内核区,通过页表映射到不同的物理地址内存)
从虚拟地址到物理地址是怎么样通过页表访问的呢?
如果是32位地址的OS,就有232个地址,那么页表是不是就要有232个条目呢?一个条目算上物理地址,是否命中,读写权限等等就需要很多的内存,总共算下来需要的内存是非常庞大的,这样显然是不可能的。
其实物理内存早就被划分好了区域,每一块叫做数据页。(也叫做页框)
每一个页框也被管理:
struct Page { //内存属性——4KB }
然后通过struct Page类型的数组来管理。
并且磁盘中是数据也是被划分成4KB大小一块。
然后再说说虚拟地址空间的地址:32个比特位都有相对应的含义。
首先前十个对应的是页目录记录页表的地址,后面的10个是页表记录物理内存的地址,最后面的12个是物理内存起始地址中的偏移量。
页表当中储存的就是页框的起始地址,最后通过偏移量来确定在物理内存中的实际大小,212也正好就是4KB。
也就是说其实在查找的时候OS其实只会创建一个页目录和一个页表,其他暂时不用的页表就先不用,也就是说不需要多少内存。
什么是线程
之前对于进程的概念是内核数据结构+进程对应的代码和数据。
之前创建一个子进程室友自己的独立性的,如果今天创建多个进程,和第一个进程指向同一个PCB,看到是同一块虚拟地址空间,然后让每个这种“进程”执行虚拟地址空间中的部分代码,这些“进程”就叫做线程。
在Linux下,创建的线程其实就是PCB而已。
因为通过进行资源划分,单个“进程”的执行粒度一定比之前的进程更细。
CPU不会看是不是线程,只会去处理每个线程或者是进程。
OS也需要去管理这些线程。
专门管理线程的叫做TCB。
在windows操作系统就是这么设计的,CPU会先找到某个进程,然后进入这个进程中再去找线程。
(这样的设计是很复杂的,也不好维护)
从被执行的角度来看,进程和线程的区别并不是很大。
这就是为什么Linux中的线程只是复用PCB,用PCB来表示“线程”。
线程其实就是进程的一个执行流:
线程在进程的内部运行,线程在进程的地址空间内运行,拥有该进程的一部分资源。
也就是说:
这里整体才算是一个完整的进程。
内核视角:承担分配系统资源的基本实体。(创建进程所需要的各种资源)
如果按照概念来说,相对比之前说的进程只有一个执行流,现在的进程是拥有多个执行流。
在Linux中,什么是线程呢?是CPU调度的基本单位。
在Linux中,一个线程被称为轻量级进程。
总结:
1.Linux内核中没有真正意义上的线程,是用PCB来模拟线程的,是一种完全属于自己的一套线程方案。
2.站在CPU的角度,每一个PCB都可以被叫做轻量级进程
3.Linux线程是CPU调度的基本单位,而进程是承担分配系统资源的基本单位。
4.进程是整体申请资源,线程是向进程申请资源。
Linux线程的优点是什么呢?
比Windows操作系统的线程简单,维护成本低,可靠,高效。
线程的具体作用呢?
就像迅雷的边播放边下载。
Linux无法直接提供创建线程的系统调用,只能提供创建轻量级进程的接口。
进一步理解线程
先来用一份代码来看看线程:
pthread_create函数介绍
第一个参数是线程id,第二个参数是线程属性(大部分情况设置为nullptr),第三个参数是回调函数,让该线程执行这个函数。第四个参数是第三个参数的回调函数的参数。
成功返回0,失败返回错误码。
并且这个函数是第三方库的内容:pthread。
这是因为Linux没有真正意义上的线程。
#include <iostream> #include <pthread.h> #include <cassert> #include <unistd.h> using namespace std; void *pthread_routine(void* args) { while(true) { cout << "我是新线程" << endl; sleep(1); } } int main() { pthread_t tid; int n = pthread_create(&tid, nullptr, pthread_routine, (void *)"111111");//参数记得强制转换成void* assert(n == 0); (void)n; while(true) { cout << "我是主线程" << endl; sleep(1); } return 0; }
任何Linux操作系统都必须默认携带这个库,这个库叫做原生线程库。
运行的时候发现只有一个进程。
终止之后就没有了。
然后用ps -al查看轻量级进程。
这里有两个执行流,PID相同,说明属于同一个进程,旁边的LWP不同,这个就是轻量级进程的id。
两个id相同的是主线程,不同的是新线程。
这里也说明CPU进行调度的时候是以LWP为标准特定执行流的。
并且,主线程还可以给新线程发送内容:
#include <iostream> #include <pthread.h> #include <cassert> #include <unistd.h> using namespace std; void *pthread_routine(void* args) { const char* p = (const char*)args; while(true) { cout << "我是新线程" << p << endl; sleep(1); } } int main() { pthread_t tid; int n = pthread_create(&tid, nullptr, pthread_routine, (void *)"111111");//参数记得强制转换成void* assert(n == 0); (void)n; while(true) { cout << "我是主线程,新线程id是:" << tid << endl;//这里顺便打印新线程的id sleep(1); } return 0; }