vmtouch実現原理解析

6741 ワード

vmtouchは、ファイルがメモリにキャッシュされているかどうかをクエリーしたり、ファイルをキャッシュにインポートしたり、キャッシュをロックしたりするのに便利なツールです.
ツールソース:https://github.com/hoytech/vmtouch
 
1.vmtouchの使用
まずこのツールの使い方を見てみましょう
 
2.vmtouch原理
まずvmtouchを見てみましょうcファイル、このツールはこのソースファイルだけで、どれだけ簡潔かがわかります.
main関数は簡単で、パラメータ解析を先に行い、for(i=0;i
複数のファイルを同時に分析できるからです.主な実装関数はvmtouch_crawlで.
vmtouchはディレクトリ全体のすべてのファイルを解析できるので、vmtouch_crawlではディレクトリ内のすべてのファイルを遍歴し、最後にvmtouch_file(path);の双曲線コサインを返します.
次にvmtouchをfile関数のいくつかのキーが表示されます.
void vmtouch_file(char *path) {
    fd = open(path, open_flags, 0);  
    mem = mmap(NULL, len_of_range, PROT_READ, MAP_SHARED, fd, offset);
    mincore(mem, len_of_range, (void*)mincore_array)
}

最も重要なのは、上記の3つのステップで、まずファイルを開き、mmapマッピングを行います.mmapマッピングは、ファイル内容をプロセスアドレス空間にマッピングするメモリです.次にmincore関数を呼び出します.この関数はvmtouchがファイルキャッシュを取得できる鍵です.
mincore関数の定義:
#include 
#include 

int mincore(void *addr, size_t length, unsigned char *vec);

パラメータは3つあります.
1.addr:アドレス
2.length:長さ
3.vec:vecは配列を指し、配列メンバーは(length+PAGE_SIZE-1)/PAGE_SIZE個、すなわち各ページに1バイト対応しており、そのバイトの最後のbitが1であれば、このページがキャッシュ中であることを示し、そうでなければキャッシュ中ではない.
説明:
mincoreは、ファイルの各ページを表し、プロセスの仮想アドレス空間に実際の物理メモリのキャッシュがあるかどうかを示す配列を返します.カーネルは、アドレスの開始ページから終了ページまでのメモリの存在を返します.アドレス(addr)は、ページ整列長(length)でなければなりません.ページ整列は必要ありませんが、返されるステータスはページ全体のステータスです.もちろん、ここで返される状態は瞬時の状態であり、メモリをロックしていない場合は、いつでも交換される可能性があります.
3.mincoreカーネル実装原理
mincoreはシステム呼び出しで、カーネルに入った後、実は現代コードは/kernel/mm/mincore.c
SYSCALL_DEFINE3(mincore, unsigned long, start, size_t, len,
		unsigned char __user *, vec)
{
    pages = len >> PAGE_SHIFT;
	pages += (offset_in_page(len)) != 0;
    ......
    while (pages) {
		/*
		 * Do at most PAGE_SIZE entries per iteration, due to
		 * the temporary buffer size.
		 */
		down_read(&current->mm->mmap_sem);
		retval = do_mincore(start, min(pages, PAGE_SIZE), tmp);
		up_read(&current->mm->mmap_sem);

		if (retval <= 0)
			break;
		if (copy_to_user(vec, tmp, retval)) {
			retval = -EFAULT;
			break;
		}
		pages -= retval;
		vec += retval;
		start += retval << PAGE_SHIFT;
		retval = 0;
	}
}

パラメータは、適用と同様に、開始アドレス、長さ、ページステータス記述の配列です.ここで、開始アドレスはポインタタイプからロング整数に変更されます.
まず、解析するページの数を長さに基づいて計算し、do_を呼び出します.mincoreで解析を完了します.
static long do_mincore(unsigned long addr, unsigned long pages, unsigned char *vec)
{
	struct vm_area_struct *vma;
	unsigned long end;
	int err;
	struct mm_walk mincore_walk = {
		.pmd_entry = mincore_pte_range,
		.pte_hole = mincore_unmapped_range,
		.hugetlb_entry = mincore_hugetlb,
		.private = vec,
	};

	vma = find_vma(current->mm, addr);
	if (!vma || addr < vma->vm_start)
		return -ENOMEM;
	mincore_walk.mm = vma->vm_mm;
	end = min(vma->vm_end, addr + (pages << PAGE_SHIFT));
	err = walk_page_range(addr, end, &mincore_walk);
	if (err < 0)
		return err;
	return (end - addr) >> PAGE_SHIFT;
}

do_mincoreのパラメータは、開始アドレス、ページ数、およびページステータス配列です.
まずmm_を定義しますwalk_にあるwalk構造体page_rangeはプロセスページツリーを遍歴する際にpmd,ptdコールバック処理関数を実行し,プライベートデータ処理も提供する.ここでprivateは、結果を格納するページステータス記述配列を指します.
find_vma関数は、プロセスアドレス空間のvmaを開始アドレスに基づいて検索します.ここのvmaはstruct vm_ですarea_struct、カーネルはプロセスアドレス空間を管理するために使用されます.1つのvmaは、仮想メモリ領域ごとに関連するstruct vm_によって1つの連続的な線形アドレス空間の抽象であり、それは独自の権限(読み取り可能、書き込み可能、実行可能など)を有する.area_struct構造について説明します.ここでは詳しくは言わないが,後続は単独で1編開く.
ファイルがプロセス空間にマッピングされているvma構造をクエリすると、ファイルデータがプロセス仮想アドレス空間に具体的にマッピングされていることがわかります.
次のプロセス:
do_mincore
|--->walk_page_range               VMA
|------->__walk_page_range
|------------>walk_pgd_range

static int walk_pgd_range(unsigned long addr, unsigned long end,
			  struct mm_walk *walk)
{
	pgd_t *pgd;
	unsigned long next;
	int err = 0;

	pgd = pgd_offset(walk->mm, addr);
	do {
		next = pgd_addr_end(addr, end);
		if (pgd_none_or_clear_bad(pgd)) {
			if (walk->pte_hole)
				err = walk->pte_hole(addr, next, walk);
			if (err)
				break;
			continue;
		}
		if (walk->pmd_entry || walk->pte_entry)
			err = walk_pud_range(pgd, addr, next, walk);
		if (err)
			break;
	} while (pgd++, addr = next, addr != end);

	return err;
}

このマッピングされたファイルは、プロセスの仮想アドレス空間でメモリにキャッシュされている場合、物理アドレスと仮想アドレスのマッピング関係がプロセスのページテーブルに記録されているに違いありません.ここでは、ファイルマッピングを管理するページテーブルを見つけます.(ここで疑問なのは、なぜmmapでファイルをマッピングした後、プロセスページテーブルがキャッシュ内のページを自動的に自分のページテーブルに更新するのかということです.)
pgdは1級ページテーブルで、linuxカーネルは2級マッピングを使用しています.したがって、1つのpgdエントリは、4 MBサイズのメモリ領域を表すことができる.ファイルがプロセスアドレス空間にマッピングされ、複数のVMAによって複数の連続したアドレス空間が管理されます.したがって、各vmaの各アドレス範囲内にあるpgdを探し、pgdでアドレス範囲内の次のページテーブルを見つけます.
まずwalk_を見てみましょうpgd_range関数、ここではまずプロセスのmm_に基づいてstruct構造は、プロセスpgdアドレスを取得する.次に、addrが存在するpgd項のオフセットをアドレスオフセットに基づいて計算する.pgd = pgd_offset(walk->mm, addr);
このVMAで我々が望むアドレスのpgdエントリを取得し,そのpgdでページテーブルエントリを検索し,マッピング関係の有無を判断すればよい.
ここはwalk_pud_rangeは次のレベルのページディレクトリ項目を解析しますが、実際には機能しません.カーネルは2レベルのページテーブルしか使用していませんので..
 
最後にstruct mm_を通過しますwalkのコールバック関数mincore_pte_rangeは、最後のレベルのページテーブルを処理します.
static int mincore_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
			struct mm_walk *walk)
{
	spinlock_t *ptl;
	struct vm_area_struct *vma = walk->vma;
	pte_t *ptep;
	unsigned char *vec = walk->private;
	int nr = (end - addr) >> PAGE_SHIFT;
	ptl = pmd_trans_huge_lock(pmd, vma);
	if (ptl) {
		memset(vec, 1, nr);
		spin_unlock(ptl);
		goto out;
	}
	if (pmd_trans_unstable(pmd)) {
		__mincore_unmapped_range(addr, end, vma, vec);
		goto out;
	}
	ptep = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
	for (; addr != end; ptep++, addr += PAGE_SIZE) {
		pte_t pte = *ptep;
		if (pte_none(pte))
			__mincore_unmapped_range(addr, addr + PAGE_SIZE,
						 vma, vec);
		else if (pte_present(pte))
			*vec = 1;
		else { /* pte is a swap entry */
			swp_entry_t entry = pte_to_swp_entry(pte);
			if (non_swap_entry(entry)) {
				/*
				 * migration or hwpoison entries are always
				 * uptodate
				 */
				*vec = 1;
			} else {
#ifdef CONFIG_SWAP
				*vec = mincore_page(swap_address_space(entry),
						    swp_offset(entry));
#else
				WARN_ON(1);
				*vec = 1;
#endif
			}
		}
		vec++;
	}
	pte_unmap_unlock(ptep - 1, ptl);
out:
	walk->private += nr;
	cond_resched();
	return 0;
}

パラメータは3つです:pmd(2級ページディレクトリ)、開始アドレス、終了アドレス、mm_walk構造
1つのforループ、1ページ1ページ処理します.先pte_noneは空かどうかを判断しpte_presentはあるか否かを判断し,最後に交換されたか否かを判断する.
そしてこのpgdをプレイすると判断し、次のpgdを行う.このVMAを判断したら、次のVMAに進みます.アドレス範囲が検索されるまで.そして、キャッシュ内のファイルの状況を得ました!
 
これで、mincoreのカーネルでの実装プロセスが完了します.
次にその前提の問題を見てみましょう.なぜmmapファイルの後、プロセスページテーブルがキャッシュ内のページに記録されるのですか?
 
未完待機・・・