linux 如何刷新内核模块中tlb条目?

zz2j4svz  于 2023-06-29  发布在  Linux
关注(0)|答案(1)|浏览(188)

我想在我的内核模块中编写一个函数来使虚拟地址的物理页Map无效。但是,我不知道如何刷新虚拟地址页的tlb条目。我尝试在模块代码中使用“flush_tlb_range”。但是,它报告“函数'flush_tlb_range'的隐式声明”。
下面是我的代码中的一个片段

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>

u64 mypool_get_page(u64 gfn, bool write_fault, bool *writable){
  u64 kvm_hva;
  struct mm_struct *mm = current->mm; 
  struct vm_area_struct *vma = current->mm->mmap;
  unsigned int level;
  pte_t *ptep;

  kvm_hva = map_lookup(index);
  
  ....

  //invalid kvm_hva in page table
  ptep = lookup_address(kvm_hva, &level);
  if (ptep) {
    pte_clear(mm, kvm_hva, ptep);     
    flush_tlb_range(vma,kvm_hva, kvm_hva+PAGE_SIZE);
  }

  ...

}

这是我的Makefile

mypool-objs := pool-main.o maptable.o
obj-m += mypool.o
KERNEL := /lib/modules/$(shell uname -r)/build 
PWD :=$(shell pwd) 
modules :
    $(MAKE) -C $(KERNEL) M=$(PWD) modules
.PHONEY:clean
clean :
    rm -rf *.o *.ko

有人能教我如何在内核模块中使tlb无效吗?
我还尝试使用'flush_tlb_mm_range'。但是,它还报告函数“flush_tlb_mm_range”的隐式声明。

vpfxa7rd

vpfxa7rd1#

有多种方法可以实现目标,这取决于你想要实现的目标。

第一选项

如果您使用的是x86,则可以使用以下命令刷新当前运行的CPU的TLB条目

asm volatile("invlpg (%0)" ::"r" (addr) : "memory");

但是,这并不能**确保系统其余核心之间的一致性。您需要执行TLBSHOOTDOWN并手动执行IPI以同步TLB。对于其他架构,例如ARM64,这不是必需的。

第二选项

更改Linux内核并导出要使用的函数的符号。这是一种非常肮脏的方法,不应该在生产环境中使用,但如果只是为了测试,这应该足够了。

EXPORT_SYMBOL(flush_tlb_range)

第三种选择

使用kallsyms_lookup_name查找函数的地址。下面是一个示例内核模块。请注意,启用IBT和AFAIK时存在问题,目前没有解决此问题的方案stackoverflow-discussion

#include <asm/io.h>
#include <asm/tlbflush.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/ptrace.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
#define KPROBE_KALLSYMS_LOOKUP 1
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
kallsyms_lookup_name_t kallsyms_lookup_name_func;
#define kallsyms_lookup_name kallsyms_lookup_name_func

static struct kprobe kp = {
    .symbol_name = "kallsyms_lookup_name"
};
#endif

void (*flush_tlb_mm_range_func)(struct mm_struct *, unsigned long, unsigned long, unsigned int, bool);

/*
 * Init function of our module
 */
static int __init init_custom(void) {
    /*
     * Dirty workaround to get kallsyms_lookup_name address, since it is not exported anymore
    */ 
    register_kprobe(&kp);
    #ifdef CONFIG_X86_KERNEL_IBT
    printk("IBT IS ENABLED\n");
    return -ENXIO;
    #else
    printk("IBT IS DISABLED\n");
    kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
    #endif

    printk("kallsyms_lookup_name address: %px\n", kallsyms_lookup_name);

    unregister_kprobe(&kp);

    if(!unlikely(kallsyms_lookup_name)) {
      pr_alert("Could not retrieve kallsyms_lookup_name address\n");
      return -ENXIO;
    }

    // get flush_tlb_mm_range address
    flush_tlb_mm_range_func = (void *)kallsyms_lookup_name("flush_tlb_mm_range");
    printk("flush_tlb_mm_range address: %p\n", flush_tlb_mm_range_func);
    if (!flush_tlb_mm_range_func) {
        pr_alert("Could not retrieve flush_tlb_mm_range function\n");
        return -ENXIO;
    }
    return 0;
}

/*
 * Exit function of our module.
 */
static void __exit exit_custom(void) {
    printk(KERN_INFO "Hasta la vista, Kernel!\n");
}

MODULE_DESCRIPTION("WIP");
MODULE_LICENSE("GPL");

module_init(init_custom);
module_exit(exit_custom);

相关问题