Home > Articles > Operating Systems, Server > Linux/UNIX/Open Source

  • Print
  • + Share This
This chapter is from the book

3.9 Level 1 CPU Cache Management

Because Linux manages the CPU cache in a very similar fashion to the TLB, this section covers how Linux uses and manages the CPU cache. CPU caches, like TLB caches, take advantage of the fact that programs tend to exhibit a locality of reference [Sea00] [CS98]. To avoid having to fetch data from main memory for each reference, the CPU will instead cache very small amounts of data in the CPU cache. Frequently, there are two levels called the Level 1 and Level 2 CPU caches. The Level 2 CPU caches are larger, but slower than the L1 cache, but Linux only concerns itself with the Level 1 or L1 cache.

CPU caches are organized into lines. Each line is typically quite small, usually 32 bytes, and each line is aligned to its boundary size. In other words, a cache line of 32 bytes will be aligned on a 32-byte address. With Linux, the size of the line is L1_CACHE_BYTES, which is defined by each architecture.

How addresses are mapped to cache lines vary between architectures, but the mappings come under three headings, direct mapping, associative mapping and set associative mapping. Direct mapping is the simplest approach where each block of memory maps to only one possible cache line. With associative mapping, any block of memory can map to any cache line. Set associative mapping is a hybrid approach where any block of memory can map to any line, but only within a subset of the available lines. Regardless of the mapping scheme, they each have one thing in common. Addresses that are close together and aligned to the cache size are likely to use different lines. Hence Linux employs simple tricks to try and maximize cache use:

  • Frequently accessed structure fields are at the start of the structure to increase the chance that only one line is needed to address the common fields.

  • Unrelated items in a structure should try to be at least cache-size bytes in part to avoid false sharing between CPUs.

  • Objects in the general caches, such as the mm_struct cache, are aligned to the L1 CPU cache to avoid false sharing.

If the CPU references an address that is not in the cache, a cache miss occurs, and the data is fetched from main memory. The cost of cache misses is quite high because a reference to a cache can typically be performed in less than 10ns where a reference to main memory typically will cost between 100ns and 200ns. The basic objective is then to have as many cache hits and as few cache misses as possible.

Just as some architectures do not automatically manage their TLBs, some do not automatically manage their CPU caches. The hooks are placed in locations where the virtual to physical mapping changes, such as during a page table update. The CPU cache flushes should always take place first because some CPUs require a virtual to physical mapping to exist when the virtual address is being flushed from the cache. The three operations that require proper ordering are important and are listed in Table 3.4.

Table 3.4. Cache and TLB Flush Ordering

Flushing Full MM

Flushing Range

Flushing Page

flush_cache_mm()

flush_cache_range()

flush_cache_page()

Change all page tables

Change page table range

Change single PTE

flush_tlb_mm()

flush_tlb_range()

flush_tlb_page()

The API used for flushing the caches is declared in <asm/pgtable.h> and is listed in Table 3.5. In many respects, it is very similar to the TLB flushing API.

Table 3.5. CPU Cache Flush API

void flush_cache_all(void)

This flushes the entire CPU cache system, which makes it the most severe flush operation to use. It is used when changes to the kernel page tables, which are global in nature, are to be performed.

void flush_cache_mm(struct mm_struct mm)

This flushes all entries related to the address space. On completion, no cache lines will be associated with mm.

void flush_cache_range(struct mm_struct *mm, unsigned long start, unsigned long end)

This flushes lines related to a range of addresses in the address space. Like its TLB equivalent, it is provided in case the architecture has an efficient way of flushing ranges instead of flushing each individual page.

void flush_cache_page(struct vm_area_struct *vma, unsigned long vmaddr)

This is for flushing a single-page-sized region. The VMA is supplied because the mm_struct is easily accessible through vmavm_mm. Additionally, by testing for the VM_EXEC flag, the architecture will know if the region is executable for caches that separate the instructions and data caches. VMAs are described further in Chapter 4.

It does not end there, though. A second set of interfaces is required to avoid virtual aliasing problems. The problem is that some CPUs select lines based on the virtual address, which means that one physical address can exist on multiple lines leading to cache coherency problems. Architectures with this problem may try and ensure that shared mappings will only use addresses as a stop-gap measure. However, a proper API to address this problem is also supplied, which is listed in Table 3.6.

Table 3.6. CPU D-Cache and I-Cache Flush API

void flush_page_to_ram(unsigned long address)

This is a deprecated API that should no longer be used and, in fact, will be removed totally for 2.6. It is covered here for completeness and because it is still used. The function is called when a new physical page is about to be placed in the address space of a process. It is required to avoid writes from kernel space being invisible to userspace after the mapping occurs.

void flush_dcache_page(struct page *page)

This function is called when the kernel writes to or copies from a page cache page because these are likely to be mapped by multiple processes.

void flush_icache_range(unsigned long address, unsigned long endaddr)

This is called when the kernel stores information in addresses that is likely to be executed, such as when a kernel module has been loaded.

void flush_icache_user_range(struct vm_area_struct *vma, struct page *page, unsigned long addr, int len)

This is similar to flush_icache_range() except it is called when a userspace range is affected. Currently, this is only used for ptrace() (used when debugging) when the address space is being accessed by access_process_vm().

void flush_icache_page(struct vm_area_struct *vma, struct page *page)

This is called when a page-cache page is about to be mapped. It is up to the architecture to use the VMA flags to determine whether the I-Cache or D-Cache should be flushed.

  • + Share This
  • 🔖 Save To Your Account