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?
  1. 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.
  2. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. void tlb_migrate_finish(struct mm_struct *mm)
    Ten interfejs jest wywoływany po zakończeniu migracji procesu.
  7. 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).
  8. 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

  1. Źródła Linuksa w wersji 2.6.39
  2. http://lxr.linux.no/linux/Documentation/cachetlb.txt
  3. http://tldp.org/LDP/khg/HyperNews/get/memory/flush.html
  4. http://en.wikipedia.org/wiki/Translation_lookaside_buffer