首页 > 系统相关 >Linux TLB 刷新的懒惰模式【转】

Linux TLB 刷新的懒惰模式【转】

时间:2023-09-28 10:59:55浏览次数:40  
标签:懒惰 cpu TLB 地址 内核 Linux CPU 页表

转自:https://blog.csdn.net/Henzox/article/details/41963271

 我们都知道,在切换页表时会刷新 TLB,这样就可以使用新的地址空间,那什么是 TLB 刷新的懒惰模式呢?

        TLB 是什么这里不作多的解释,可以简单理解为,为了加快 MMU 对虚拟地址的转换而增加的缓存,它记录了一个虚拟地址对应的内存页的物理地址。其实就是根据虚拟地址的前 20 位,来建立一个个条目,对应记录通过查找页表来记录的内存页的物理地址。 
        既然有缓存,那么被缓存的内容改变时,就涉及到缓存的刷新,就是 TLB 的刷新问题,当一个页表结构发生变化时,使用该页表节构的 CPU 就应该刷新自己的 TLB。
        显然进程切换时,由于地址空间发生了变化,TLB 应该得到刷新,然而,内核进程只访问内核空间的地址范围,而每个进程的内核空间的地址范围相同,所以如果 CPU 从一个用户进程切换到一个内核进程,由于用户进程和内核进程的内核地址空间部分相同,其实是不用切换页表的,内核进程依然可以使用前一个用户进程的内核部分的地址空间。这样就省去了刷新 TLB 带来的性能损耗。其实内核进程并不拥有自己的页表集。
        想像着是挺完美的,但是在 SMP 构架下,这将带来一些问题,例如,在某一核上 CPU0 刚从一用户进程切换到内核进程,该内核进程沿用该用户进程的地址空间,但它只访问内核空间部分,这不会有问题,然而,如果该用户进程在另一个 CPU1 核上被调度,并且在 CPU0 用它的地址空间时,它在 CPU1 上修改了自己内核空间的页表,此时,若 CPU0 如果访问它的地址空间是非常危险的,不管是被缓存的地址还是未被缓存的地址都将可能带来意想不到的严重后果。
        那么,难道这种美好的事情就要被上面的情况的发生扼杀,而每次都要刷新 TLB,重新加载页表么。显然还是有补救办法的,如果在 CPU0 上的内核进程执行期间,它所引用的用户进程的地址空间没有被调度并执行完毕的情况还是非常多的,这种不刷新 TLB 带来的性能提升还是可以利用一下的,谁让 Linux 是一个精打细算的内核呢。
        如何办到这一点,其实很简单,当内核空间页表集在其它 CPU 上更改时,会调用 flush_tlb_all,它会让每个 CPU 去刷新自己的 TLB。

        那么当用户空间的页表集在其它 CPU 上更改时,由于 CPU0 虽然引用了相同的地址空间,但由于它是一个内核进程,它不会去访问用户空间的地址,那么,那些失效和 TLB 项也不会造成危险,也就是它可以不去立即刷新 TLB。所以此时其它的 CPU 往往会发送一个 IPI 给其它引用该地址空间的 CPU,以通知他们自己更改了用户空间的页表,其它  CPU 就会根据自己的状态作出相应的处理,如果是懒惰模式,就不用刷新 TLB。

        这就引入了 TLB 刷新的懒惰模式。
        Linux 为每一个 CPU 创建了一个节构,它是一个每 CPU 数据,所以不需要加锁,每个 CPU 只访问自己的节构,它记录了该 CPU 的状态,TLBSTATE_OK 表示非懒惰模式, TLBSTATE_LAZY 表示懒惰模式。它还记录该 CPU 引用的地址空间节构,是一个 mm_struct 类型的节构体指针,它记录了一个进程的地址空间的所有信息,mm_struct 有一个成员 cpu_vm_mask, 是一个默认 32 位的掩码,如果某个 CPU 在使用这个地址空间,则相应位置会被置位,显然,它将支持最多 32 个 CPU。这样情况就简单了,当一个 CPU 从一个用户进程调出,调用一个内核进程时,它会设置自己的进入 TLBSTATE_LAZY 模式,并且把它引用的用户进程的 mm_struct 中相应的位置位,此时并不切换页表节构,即不加载内核空间的页目录,而如果其它 CPU 更改了用户空间的页表,它会发送一个 IPI 消息,此时使用该地址空间的 CPU 都会收到这个消息,消息的响应函数为 smp_invalidate_interrupt,代码如下:

void smp_invalidate_interrupt(struct pt_regs *regs)
{
    unsigned long cpu;
 
    cpu = get_cpu();
 
    if (!cpu_isset(cpu, flush_cpumask))
        goto out;
        /*
         * This was a BUG() but until someone can quote me the
         * line from the intel manual that guarantees an IPI to
         * multiple CPUs is retried _only_ on the erroring CPUs
         * its staying as a return
         *
         * BUG();
         */
 
    if (flush_mm == per_cpu(cpu_tlbstate, cpu).active_mm) {
        if (per_cpu(cpu_tlbstate, cpu).state == TLBSTATE_OK) {
            if (flush_va == TLB_FLUSH_ALL)
                local_flush_tlb();
            else
                __flush_tlb_one(flush_va);
        } else
            leave_mm(cpu);
    }
    ack_APIC_irq();
    smp_mb__before_clear_bit();
    cpu_clear(cpu, flush_cpumask);
    smp_mb__after_clear_bit();
out:
    put_cpu_no_resched();
    __get_cpu_var(irq_stat).irq_tlb_count++;
}

        其它 CPU 会比较,看自己引用的地址空间是否是正在销毁的地址空间,如果引用了相同地址空间,再判断自己的状态,如果不是懒惰模式,说明它在运行一个用户进程,此时需要刷新 TLB 以同步用户空间的页表,如果是懒惰模式,那么查看自己是否是懒惰模式,如果是懒惰模式,则调用 leave_mm ,代码如下:
void leave_mm(int cpu)
{
    if (per_cpu(cpu_tlbstate, cpu).state == TLBSTATE_OK)
        BUG();
    cpu_clear(cpu, per_cpu(cpu_tlbstate, cpu).active_mm->cpu_vm_mask);
    load_cr3(swapper_pg_dir);
}

        它会把自己从地址空间的掩码中清除,这样就不会再次接收到第二次 IPI 消息。也不用刷新 TLB。但你可能发现,最后调用了 load_cr3,它会引起 tlb 的刷新,其实在 x86 上的这种实现与 intel 的预取队列有关,在这里加载 cr3 虽然感觉没有实现完全的懒惰,但是不会有任何问题的,详细原因这里不谈。
————————————————
版权声明:本文为CSDN博主「Henzox」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Henzox/article/details/41963271

 

标签:懒惰,cpu,TLB,地址,内核,Linux,CPU,页表
From: https://www.cnblogs.com/sky-heaven/p/17735156.html

相关文章

  • 深入Linux内核(进程篇)—进程切换之ARM体系架构【转】
    转自:https://blog.csdn.net/liyuewuwunaile/article/details/106773630进程切换一、context_switch二、switch_mm2.1刷新I-CACHE2.2ASID和TLB2.3页表转换基址切换三、switch_to进程切换由两部分组成:切换页全局目录安装一个新的地址空间;切换内核态堆栈及硬件上下文。一、conte......
  • “国产双系统”出炉,RK3568J非对称AMP:Linux+RTOS/裸机
    “非对称AMP”双系统是什么AMP(AsymmetricMulti-Processing),即非对称多处理架构。“非对称AMP”双系统是指多个核心相对独立运行不同的操作系统或裸机应用程序,如Linux+RTOS/裸机,但需一个主核心来控制整个系统以及其它从核心。每个处理器核心相互隔离,拥有属于自己的内存,既可各......
  • linux命令之系统操作历史
    history显示代时间戳的记录exportHISTTIMEFORMAT='%F%T'history|more更多参考 https://blog.51cto.com/leonkuo/950208who查看当前在线who查看历史登录who/var/log/wtmp......
  • Linux centos7内核升级 简单快速的方法
     Linux是支持多版本内核共存的,无非是系统启动的时候应用哪个版本内核而已。关于内核:Linux内核分两种:官方内核(通常是内核开发人员用)和各大Linux发行版内核(一般用户常用)。 关于Linux内核版本号:例如:[root@centos7~]#uname-r3.10.0-1127.19.1.el7.x86_64查询得到的版本号......
  • Linux 操作另一台服务器
    服务器信任在运维场景中,如果需要在一台服务器操作另一台服务器,就需要目标服务器(下面称为B服务器)信任当前服务器(下面称为A服务器)。在A服务器生成证书。ssh-keygen-trsa一直回车结束。会在/root/.ssh目录下生成id_rsa和id_rs.pub两个文件:id_rsa为私钥。id......
  • 6-Linux操作系统 用户与用户组
    一、介绍说明  Linux系统是一个多用户多任务的操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。  用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面也可以帮助用户组......
  • Linux中C程序的编译与运行
    C程序编译的完整过程如下图所示1、编译C文件,生成可执行文件gccmine.c-omine2、运行程序./mine3、编译并运行gccmine.c-omine&&./mine4、编译C程序,生成目标文件gcc-cmine.c-omine.o5、链接目标文件,生成可执行文件,同编译C文件一样。gccmine.o-omine......
  • linux 中批量输出指定目录的磁盘占用和文件数目
     001、磁盘占用(base)[root@pc1test1]#lstest1test2test3(base)[root@pc1test1]#find$PWD-typed##查出所有目录/home/test1/home/test1/test1/home/test1/test1/test/home/test1/test2/home/test1/test3(base)[root@pc1test1]#find$PWD......
  • linux-Shell将命令行终端输出结果写入保存到文件中
    (一)将输出与错误写到同一个文件(1)方法1#!bin/bashjava-jarhbase-example.jar2>&1|teehbase_log.txt说明:0,1,2:在linux分别表示标准输入、标准输出和标准错误信息输出。tee默认为写入覆盖,-a参数表示追加内容。#!bin/bashjava-jarhbase-example.jar2>&1|tee-ahbase_......
  • linux中查询端口占用情况
    1、lsof-i:端口号用于查看某一端口的占用情况,比如查看8000端口使用情况,lsof-i:8000可以看到8000端口已经被轻量级文件系统转发服务lwfs占用2、netstat-tunlp|grep端口号,用于查看指定的端口号的进程情况,如查看8000端口的情况,netstat-tunlp|grep8000说明一下几个参数的含义:......