【Linux】线程基础

🔥个人主页Quitecoder

🔥专栏linux笔记仓

目录

01.背景知识


OS进行内存管理,不是以字节为单位的,而是以内存块为单位的,默认大小为4kb,我们也学过,系统和磁盘文件进行IO的基本单位是4kb--8个扇区

可执行文件要加载到内存中,我们前面知道,文件系统中文件是以块为单位进行存储的,加载到内存中,也以块的方式加载访问,所以才有了IO的基本单位是4kb

内存块是操作系统进行内存管理的基本单位,称为页(page),操作系统为文件分配内存空间,以页为单位

操作系统现在对内存的管理及转为对一个个页的管理,先描述,再组织

c 复制代码
struct page
{
	int flag;//是否被占用,是否是脏页,是否被锁定
	int mode;
	....
}
struct page memory[1048576];

每一个页帧用数组管理

页表我们学过,是虚拟地址到物理地址的映射,详细讲解

虚拟地址就是二进制构建的32个比特位的数据

虚拟地址被拆分为3部分,10位10位12位,页表也不是只有一张,页表开始以前十个bit位,表示的范围是2的十次方

页目录索引(Page Directory Index) :高 10 位,用于定位 页目录表(Page Directory) 中的条目。

页表索引(Page Table Index) :中间 10 位,用于定位 页表(Page Table) 中的条目。

页内偏移(Page Offset):低 12 位,用于定位页内的具体字节。

页表中放的是指向页框的起始地址

虚拟地址最低十二位,范围【0,4095】,页内偏移,刚好就是一个页的大小,用于定位页内具体字节的部分,例如一个整数,我就连续读取四个字节

还有一部分page不通过页表映射,通过struct file,缓冲区


每行代码都有地址,函数是连续的代码地址构成代码块,一个函数对应一批连续的虚拟地址

虚拟地址到物理地址的转换过程

以下是通过二级页表将虚拟地址转换为物理地址的过程:

虚拟地址分解

假设虚拟地址为 0x00401234,其二进制表示为:

复制代码
0000 0000 0100 0000 0001 0010 0011 0100

分解为:

• 页目录索引:0000 0000 01(高 10 位,值为 1

• 页表索引:00 0000 0001(中间 10 位,值为 1

• 页内偏移:0010 0011 0100(低 12 位,值为 0x234

  1. CR3 寄存器获取页目录表的基地址
  2. 使用页目录索引(1)找到页目录表中的条目,获取页表的基地址。
  3. 使用页表索引(1)找到页表中的条目,获取物理页帧的基地址。
  4. 将物理页帧基地址与页内偏移(0x234)相加,得到物理地址。

页表项(PTE)的结构

每个页表项(PTE)的大小为 4 字节(32 位),包含以下字段:

物理页帧地址(Physical Page Frame Address) :20 位(实际使用 20 位,支持 4GB 物理内存)。

标志位(Flags) :12 位,包括:

有效位(Present Bit) :指示页是否在内存中。

可写位(Writeable Bit) :指示页是否可写。

用户位(User Bit) :指示用户程序是否可以访问该页。

脏位(Dirty Bit) :指示页是否被修改过。

访问位(Accessed Bit):指示页是否被访问过。


页表的大小

页目录表 :1024 个条目,每个条目 4 字节,总大小为 4KB。

页表 :1024 个条目,每个条目 4 字节,总大小为 4KB。

总大小 :页目录表和所有页表的总大小取决于进程的虚拟地址空间使用情况。在最坏情况下,需要 1024 个页表,总大小为:
4 KB + 1024 × 4 KB = 4 MB + 4 KB ≈ 4 MB 4\text{KB} + 1024 \times 4\text{KB} = 4\text{MB} + 4\text{KB} \approx 4\text{MB} 4KB+1024×4KB=4MB+4KB≈4MB

02.线程概念

线程:在进程内部运行,是CPU调度的基本单位

以前我们知道,每次创建一个进程,都要创建一个地址空间和页表

现在不想给"进程"重新创建地址空间加载数据,直接让你新的pcb和父进程指向同一个地址空间,正文部分拆成多份由不同task_struct执行,这一部分task_struct就叫做Linux中的线程

上面一个整体为一个进程,进程=内核数据结构+进程代码和数据

线程实现基于 ​轻量级进程(LWP)​

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是"一个进程内部的控制序列"
  • 一切进程至少都有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

cpu对task_struct是进程还是线程不做区分,cpu看到的执行流为进程,Linux中的执行流:轻量级进程

Linux是用进程模拟的线程

OS要单独设计线程,先描述再组织,现在给线程专门设计结构TCB,可以复用PCB,用PCB统一表示执行流,这样的话,我们就不需要为线程单独设计数据结构和调度算法了

简单使用线程

c 复制代码
int pthread_create(pthread_t *restrict thread,
        const pthread_attr_t *restrict attr,
        void *(*start_routine)(void *),
        void *restrict arg);

参数分别为:

  • 线程标识符tid
  • 用于设置线程的属性,传入NULL使用默认属性
  • 指定线程启动后执行的函数
  • 传递给 start_routine 函数的参数
c 复制代码
#include<iostream>
#include<pthread.h>
#include<unistd.h>

using namespace std;
//新线程
void *threadStart(void *args)
{
    while(true)
    {
        sleep(1);
        cout<<"new thread run..."<<endl;
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,threadStart,(void*)"thread-new");

   //主线程
    while(true)
    {
        sleep(1);
        cout<<"main thread run.."<<endl;
    }
    return 0;
}

注意这里的makefile里编译那一步必须链接thread库

bash 复制代码
testthread:testthread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f testthread

这里系统只有一个进程,我们还可以让线程输出它的pid


LWP:light weight process 轻量级进程,lwp就是轻量级进程的id

我们发现,有一个lwp与pid相同,为主线程。OS调度的时候,用的是LWP

进程创建成本非常高,创建线程,只需要创建pcb,然后把进程的资源全部给线程即可

与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多,页表,地址空间都不需要切换,线程的调度成本低

删除一个线程成本也低

但线程也有劣势,一个线程出错可能会影响整个进程。这是因为线程是进程内的执行单元,它们共享进程的资源(如内存地址空间、文件描述符等)。如果某个线程出现错误(如非法内存访问、未捕获的异常等),可能会导致整个进程崩溃或进入不可预期的状态

线程调度成本更低

进程上下文切换:

  • 进程拥有独立的虚拟地址空间,切换时需要切换页表,切换页表需要刷新 CPU 的 ​TLB(Translation Lookaside Buffer)​,这是一个耗时的操作
  • 进程上下文需要保存和恢复CPU寄存器状态
  • 还需要处理其他资源(文件fd,信号处理函数等)

线程共享进程的虚拟地址空间和资源,切换时不需要切换页表,也不需要分配和释放fd,内存等的资源,硬件只需要关注线程的私有数据,线程上下文切换也需要保存和恢复CPU寄存器状态

CPU 缓存 的影响:线程共享进程的内存地址空间,因此线程切换时 CPU 缓存(Cache)的命中率较高。缓存中的数据可以继续被新线程使用,减少了内存访问的延迟
进程拥有独立的内存地址空间,因此进程切换时 ​CPU 缓存的命中率较低

线程私有的部分:一组寄存器:硬件上下文数据--线程可以动态运行

栈:线程在运行的时候,会形成各种临时变量,临时变量会被每个线程保存在自己的栈区

某一个线程将来也会被页表映射到物理内存,以4kb为单位的代码块

相关推荐
IT缺脑干44 分钟前
Linux centos 7 vsftp本地部署脚本
linux·运维·centos
一拳一个呆瓜2 小时前
【银河麒麟系统常识】命令:dotnet --list-sdks(列出已安装的 .NET SDK 版本)
linux·.net·kylin
一拳一个呆瓜2 小时前
【银河麒麟系统常识】需求:安装.NET SDK
linux·.net·kylin
Gold Steps.5 小时前
运维网络排查工具介绍与使用
linux·运维·网络
IT大卡拉咪6 小时前
Linux网站搭建(新手必看)
linux·运维·服务器
YX1417 小时前
在linux部署网站
linux·运维·服务器
we19a0sen9 小时前
在kali linux中配置hive(mysql元数据库)
linux·数据库·hive
基德爆肝c语言9 小时前
Linux的一些常见指令
linux·运维·服务器
漫谈网络10 小时前
网络命名空间之iptables规则解析
linux·网络·iptables·网络命名空间·netns
还没想好取啥名10 小时前
Linux 网络编程(二)——套接字编程简介
linux·c语言·网络协议