TLB & cache flushing w Linuksie
Rafał Rawicki
Po co czyścić cache i TLB?
Jeżeli procesor ma cache indeksowane adresami wirtualnymi, to po każdej zmianie w tablicy stron (np. przełączenie kontekstu)
w cache pozostawałyby niepoprawne wpisy. Na architekturach z cache indeksowanym adresami fizycznymi, lub indeksowanym adresami wirtualnymi
i tagowanym adresami fizycznymi (VIPT) — na przykład i386 — ten problem nie występuje i funkcje do czyszczenia cache nic nie robią.
source/include/asm-i386/pgtable.h:
27 /* Caches aren't brain-dead on the intel. */
28 #define flush_cache_all() do { } while (0)
29 #define flush_cache_mm(mm) do { } while (0)
30 #define flush_cache_range(mm, start, end) do { } while (0)
31 #define flush_cache_page(vma, vmaddr) do { } while (0)
32 #define flush_page_to_ram(page) do { } while (0)
33 #define flush_dcache_page(page) do { } while (0)
34 #define flush_icache_range(start, end) do { } while (0)
35 #define flush_icache_page(vma,pg) do { } while (0)
Problem aktualności wpisów w TLB dotyczy wszystkich architektur. Przy wykonywaniu operacji zmieniających
przestrzeń adresową procesu — na przykład mmap, munmap — lub po przełączeniu kontekstu, pozycje w TLB także powinny być uaktualnione.
Najprostszym i najczęściej stosowanym rozwiązaniem jest unieważnienie od razu całego TLB. Niektóre procesory pozwalają na bardziej
selektywne unieważnianie wpisów w TLB, co jest bardziej wydajne.
Na procesorach i386 wyczyszczenie TLB wykonuje się przez przeładowanie rejestru CR3.
source/include/asm-i386/pgtable.h:
37 #define __flush_tlb() \
38 do { \
39 unsigned int tmpreg; \
40 \
41 __asm__ __volatile__( \
42 "movl %%cr3, %0; # flush TLB \n" \
43 "movl %0, %%cr3; \n" \
44 : "=r" (tmpreg) \
45 :: "memory"); \
46 } while (0)
Dostępna jest też instrukcja INVLPG do czyszczenia pojedynczych wpisów.
Sposób użycia
flush_cache_XXX(...);
zmodyfikuj_przestrzeń_adresową();
flush_tlb_XXX(...);
Dlaczego?
- W niektórych architekturach fragment cache nie może istnieć bez mapowania w TLB do niego,
a więc wyczyszczenie cache musi nastąpić zanim zmiana przestrzeni adresowej zostanie wykonana.
- W pewnych architekturach procesor sam wykonuje przejście po tablicy stron.
Dlatego, wyczyszczenie TLB wykonuje się po zmianie tablic stron, tak aby po tej operacji
hardware mogło załadować aktualne informacje do TLB.
Interfejsy
void flush_cache_all(void)
void flush_tlb_all(void)
Są wykorzystywane wtedy, gdy zmieniły się tablice stron jądra,
a zatem dokonana zmiana dotyczy każdego procesu.
Po wywołaniu tego interfejsu wszystkie poprzednio dokonane
zmiany tablic stron stają się widoczne dla procesora.
void flush_cache_mm(struct mm_struct *mm)
void flush_tlb_mm(struct mm_struct *mm)
Te procedury są wywoływane, gdy cała przestrzeń adresowa opisywana przez
mm_struct
została zmieniona.
Interfejs zakłada, że zmiana przestrzeni adresowej nastąpiła wyłącznie w przestrzeni użytkownika.
Przykładowym zastosowaniem jest odświeżenie cache i TLB po wywołaniu fork lub exec.
void flush_cache_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end)
void flush_tlb_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end)
Służą do czyszczenia konkretnego przedziału adresów wirtualnych z TLB.
Po uruchomieniu tych procedur wszystkie modyfikacje przestrzeni adresowej
vma->vm_mm od start do end-1 będą widoczne dla procesora.
Przykładowym zastosowaniem jest implementacja mmunmap().
Ten interfejs pojawił się w nadziei, że implementacja dla konkretnej architektury
potrafi znaleźć bardziej wydajną metodę czyszczenia wpisów w TLB oraz cache rozciągających się na wiele stron
niż wywoływanie flush_tlb_page dla każdej strony.
void flush_cache_page(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn)
void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
Usuwa translacje dotyczące jednej strony.
Za pomocą vma ustalana jest przestrzeń adresowa procesu (vma->vm_mm),
oraz sprawdzane jest czy podany obszar jest wykonywalny (vma->vm_flags & VM_EXEC),
a więc czy wpisy wskazujące zmienianą stronę znajdują się w TLB dla instrukcji,
w architekturach z oddzielnymi TLB.
void update_mmu_cache(struct vm_area_struct *vma,
unsigned long address, pte_t *ptep)
Pod koniec obsługi każdego błędu braku strony, ta procedura jest
wywoływana, aby kod obsługujący konkretną architekturę mógł
zapewnić, że odpowiednia translacja istnieje pod adresem address,
dla przestrzeni adresowej vma->vm_mm, w tablicy stron.
void tlb_migrate_finish(struct mm_struct *mm)
Ten interfejs jest wywoływany po zakończeniu migracji procesu.
void flush_cache_kmaps(void)
Tą procedurę wykorzystuje się, gdy platforma wykorzystuje
highmem. Jest ona wywoływana przed unieważnieniem mapowań zrobionych za pomocą funkcji kmap().
Po jej wywołaniu w cache nie ma wpisów dla adresów wirtualnych jądra
od PKMAP_ADDR(0) do PKMAP_ADDR(LAST_PKMAP).
void flush_cache_vmap(unsigned long start, unsigned long end)
void flush_cache_vunmap(unsigned long start, unsigned long end)
Te dwa interfejsy służą do czyszczenia podanego przedziału
adresów wirtualnych (jądra) z cache.
Pierwszą z tych procedur wywołuje się po tym jak map_vm_area()
wprowadziło wpisy do tablicy stron. Druga jest wywoływana przed
usunięciem wpisów przez unmap_kernel_range().
SMP
Intrfejsy do czyszczenia cache i TLB nie zmieniają się przy wielu procesorach.
Jednak nie musi to oznaczać straty wydajności.
Można je zoptymalizować, na przykład sprawdzając, na jakich
procesorach był wykonywany kod z danej przestrzeni adresowej
(korzystając z vma->cpu_vm_mask), i nie czyścić cache i TLB na tych procesorach, na których nie jest to potrzebne.
source/arch/i386/kernel/smp.c:
374 struct mm_struct *mm = vma->vm_mm;
375 unsigned long cpu_mask = mm->cpu_vm_mask & ~(1 << smp_processor_id());
376
377 if (current->active_mm == mm) {
378 if(current->mm)
379 __flush_tlb_one(va);
380 else
381 leave_mm(smp_processor_id());
382 }
383
384 if (cpu_mask)
385 flush_tlb_others(cpu_mask, mm, va);
386 }
Źródła
- Źródła Linuksa w wersji 2.6.39
- http://lxr.linux.no/linux/Documentation/cachetlb.txt
- http://tldp.org/LDP/khg/HyperNews/get/memory/flush.html
- http://en.wikipedia.org/wiki/Translation_lookaside_buffer