Linux:虚拟地址空间进一步认识、线程的概念、线程控制
一、虚拟地址空间的再谈物理内存和磁盘的关系在文件系统中我们也是以一个个块写入或读出数据的那这样的话看来这种的设计是很方便我们做内存管理的页表的进一步认识如图这差不多是页表的完整面目CPU和虚拟地址和页表和物理内存的关系32 位二级页表页目录占 4KB最多可以指向 1024 个页表每个页表 4KB。所有页表全部创建最多占用 4MB。但 OS 采用懒分配不会一次性分配全部页表实际进程的页表占用远小于 4MB。CPU 执行指令给出虚拟地址交给硬件 MMUMMU 通过 CR3 找到当前进程的页目录查询页表完成虚拟→物理地址翻译。若访问的虚拟页面还没有加载到物理内存触发缺页异常内核介入把磁盘上的数据读入物理页更新页表之后程序继续运行。二、线程的概念有了上面的知识铺垫线程的宏观实现我们就能讲清楚了在Linux中: 线程就是一个个PCB结构体它们是共享进程的虚拟地址空间和页表所以严格意义来说,线程在Linux上是叫做轻量级进程(LWP)它没有独立的TCB结构来单独描述线程它是复用进程的相关代码从而节省了需要重新设计调度算法、结构和属性等等LInux 在内核只提供轻量级进程为了给用户提供线程编程模型在用户态封装了 pthread 库底层通过clone系统调用创建 LWP。补充clone的 flags 参数决定创建出来是普通进程还是轻量级进程。-不共享地址空间 → 普通进程fork 底层就是 clone-指定CLONE_VM等标记共享虚拟地址空间 → 轻量级进程线程所以我们现在重谈进程和线程的关系我们以前所学的进程其实是一个单线程进程,就是一个执行流,那现在多线程也就是有多个执行流,每个执行流使用对应一部分资源• 进程是资源分配的基本单位• 线程是调度的基本单位•线程共享进程数据但也拥有⾃⼰的⼀部分私有数据: 线程ID、 ⼀组寄存器、线程的上下⽂数据、栈。三、线程控制线程创建我们使用的接口都是原生线程库,pthread_create();第一个:参数:类似于pid,(其实实际是线程控制块的起始地址后面详细讲),无符号长整型第二个: 设置线程的属性一般默认就行第三个:线程函数也就是回调函数将来线程执行任务的入口,第四个:线程函数的参数因为是void*所以不要局限于传内置类型类类型、函数指针等都可以void*Work(void*arg){while(true){printf(我是new pthread pid:%d tid:0x%lx\n,getpid(),pthread_self());}returnnullptr;}intmain(){idsyscall(SYS_gettid);intthread_num1;pthread_create(tid,nullptr,Work,nullptr);}while(true){printf(我是main pthread pid:%d tid:0x%lx\n,getpid(),pthread_self());sleep(1);}return0;pthread_self()函数就是拿到线程的tid,运行就能发现两个打印消息线程终止和线程等待终止的方式:1、return 结束2、pthread_exit()结束3、一个线程可以调⽤pthread_cancel终⽌同⼀进程中的另⼀个线程。注意:主线程也有自己的tid,如果你强行把主线程退出的话可以但是子线程不会退出,这样就会出现bug,所以这个函数只建议终止子线程线程等待等待原因跟进程等待的原因类似你可以认为会产生类似于僵尸线程的情况(实际是用户层的线程库中的线程控制块需要回收)函数:pthread_join第一个参数:tid第二个参数: 线程函数的返回值(实际线程函数的返回值已经在struct thread中)也就是退出信息看了下面的线程ID和进程地址空间布局,再来重说等待的流程首先当线程函数退出时它会先把退出信息先传入到 该线程的struct thread结构体中先保存然后再在里面触发进程等待wait()先回收LWP然后如果用户自己写了pthread_join时它会把struct thread的退出信息给retval参数然后最后再回收线程库中的申请的线程控制块线程分离它的作用就是:如果不关心线程退出时的返回值就可以使用它系统会帮你回收资源可以是线程组内其他线程对⽬标线程进⾏分离也可以是线程⾃⼰分离:线程ID和进程地址空间布局pthread_t 到底是什么类型呢pthread_t类型的线程ID本质就是⼀个进程地址空间上的⼀个地址。mmap区域就相当于共享区我们通过动态库的知识能知道pthread_create是在库中完成的也就是在库中它会帮你申请好struct pthread然后在里面的把struct pthread起始地址赋值给你传的tid而且我们知道线程的栈也是私有的所以就如图所示它会在动态库中申请线程控制块线程局部存储我们知道在全局域中的资源是共享的一个线程改变了资源另外的线程使用资源是改变后的我们如果要使这部分资源私有化(只限于内置整数类型不包括字符串类类型), 可以__thread 修饰全局变量在各个线程内部使用它时它会新建一份到各自控制块的局部存储区也就是同名不同虚拟地址,比如 __thread int cnt 0;,在各个线程改变它的值只会改变后的结果只在自己的区域生效对于共享资源常见的坑点:资源共享导致数据不一致所以不管是线程任务的参数一般都要在堆上申请让每个线程的资源是独立的