Kmap(Pkmap)

특징

  • HIGHMEM 영역을 lowmem 영역 중 kmap address space에 매핑하여 사용 하기 위한 Kernel Mapping 방법
    • 아키텍처마다 kmap address space 위치와 크기가 다름
    • ARM: PAGE_OFFSET – 2M ~ PAGE_OFFSET 까지 2M 크기
    • HIGHMEM 영역을 user에서 접근할 때에는 당연히 항상 매핑을 하여 사용한다.
    • HIGHMEM 영역은 kernel에서 접근할 때 NORMAL이나 DMA(DMA32) 영역과 달리 kernel에서 1:1 direct mapping된 채로 운영되지 않는다. 즉 HIGHMEM 영역을 kernel에서 접근하려면 별도의 매핑을 통해서만 접근이 가능하다.
      • HIGHMEM 영역을 kernel에서 접근하려면 kmap, vmap, 또는 fixmap 등의 방식으로 매핑하여 사용할 수 있다.
  • Pkmap(Persistence Kernel Map)으로 명명됨
  • x86 32비트 시스템에서는 896M 이상의 메모리의 경우 direct access를 할 수 없어서 그 이상의 메모리를 access하기 위해서는 간접 매핑을 하여 사용할 수 있다.
    • 2가지 간접 매핑
      • 첫 번째 매핑은 2~4G 영역까지 간접 매핑
      • 두 번째 매핑은 64G 영역까지 간접 매핑 (PAE)
        • 실제 리눅스는 관련 매핑 데이터에대한 overhead가 크고 반드시 매핑 데이터는 직접 access가 가능한 ZONE_NORMAL 영역에 있어야 하므로 64G까지 매핑을 해야 하는 경우 ZONE_NORMAL 영역이 너무 작아지므로 최대 매핑을 리눅스 스스로 16G 까지로 제한을 할 수 밖에 없었다.
        • 매핑 데이터는 mem_map, bitmap, pte, … 등등이 있다.
        • 참고로 16G 매핑 시 mem_map의 크기는 16G / 4K * 44byte = 176M가 소요되고, 작은 pte의 경우에도 case에 따라 다르지만 worst case에서 16M가 소요된다.
  • ARM 32비트 시스템에서도 일정 크기 이상의 물리 메모리의 access에는 간접 매핑이 필요하다.
    • 4G를 초과하는 영역에 대해서는 하드웨어적인 제약으로 인해 LPAE를 지원하여 매핑한다.
  • HIGHMEM을 필요한 시간 만큼 매핑하여 사용하게 되면 제한된 매핑 영역과 그 제어로 인해 성능이 떨어진다. 따라서 최대한 HIGHMEM 영역을 적게 사용하는 것이 메모리 매핑으로 인한 overhead를 피할 수 있으며 수 기가 메모리가 필요한 경우 virtual address space가 큰 64 비트 아키텍처를 사용하는 것이 훨씬 유리하다.
  • kmap()과 kunmap()은 한쌍으로 동작하고 highmem 페이지를 매우 잠시간만 lowmem 영역에 매핑하여 사용했다가 다시 해제한다.
  • kmap() 함수를 사용 시 페이지를 체크하여 이미 lowmem 영역에 매핑되어 있는 경우 그냥 사용한다.
  • kmap() 함수는 sleep될 수 있으므로 인터럽트 context에서는 kmap_atomic() 함수를 사용해야 한다.
    • kmap_atomic() 함수는fixmap 매핑 영역을 사용한다.

 

ZONE_HIGHMEM 매핑

물리 메모리는 ZONE 별로 매핑하는 방법이 다른데 다음과 같다.

  • ZONE_DMA
    • x86 등에서 초창기 디바이스 장치들이 ISA 버스를 사용하면서 access할 수 있는 1M 이하의 물리 주소 영역으로 제한되었었다.  그 후 16M까지 확장되어 사용해 왔는데 이를 호환시키기 위해 사용하는 영역이다.
    • ARM에서는 아키텍처 마다 다른 사이즈를 지원한다.
      • rpi2: ZONE_DMA를 사용하지 않는다.
  • ZONE_DMA32
    • x86_64에서 32비트 디바이스 장치들이 access할 수 있는 영역이다.
  •  ZONE_NORMAL
    • 아래 그림과 같이 커널의 lowmem 영역(PAGE_OFFSET 부터 시작)에 1:1 영구 매핑하여 사용한다.
  • ZONE_HIGHMEM
    • 1:1 매핑이 불가능한 메모리 영역을 ZONE_HIGHMEM이라 하는데 이 영역은 lowmem 영역 중 pkmap area 또는 fixmap area를 사용해 HIGHMEM 영역의 4K 단위의 페이지를 이 영역에 필요한 시간 만큼 매핑하여 사용한다.
    • 영역이 제한되어 있기 때문에 필요한 만큼 사용한 후에는 매핑을 해제해야 한다.
      • 아키텍처마다 pkmap area와 fixmap area의 위치가 다르며 영역 크기 또한 다르다.
        • ARM에서의 pkmap area:
          • PAGE_OFFSET 바로 아래에 위치하며 2M 크기를 사용한다.
          • 2M 영역을 사용하므로 PTE(L2) 페이지 하나를 할당 받아 512개의 엔트리(pte_t)를 교체하는 것으로 매핑한다.
    • rpi2에서는 기본 설정에서 ZONE_HIGHMEM을 사용하지 않는다.
      • rpi에서는 PAGE_OFFSET가 0xC000_0000(user space=3G, kernel space=1G) 이었는데 rpi2에서는 HIGHMEM을 사용하지 않으려 PAGE_OFFSET를 0x8000_0000(user space=2G, kernel space=2G)으로 하였다.
      • rpi 같은 임베디드 application이 큰 가상주소를 요구하는 경우가 많지 않아 메모리가 1G 이상의 시스템에서 PAGE_OFFSET를 0x8000_0000으로 사용하는 경우가 있다. 만일 rpi2의 경우도 rpi같이 최대 메모리가 512M 였으면 PAGE_OFFSET를 0xC000_0000으로 설정하였을 것이다.
  • ZONE_MOVEABLE
    • 이 영역은 NUMA 시스템에서 페이지 단편화의 효율을 위해 특별히 디자인된 영역이고 아울러 hotplug 메모리 용도로 사용할 수 있는 영역이다.

아래 그림과 같이 좌측 물리 메모리 주소가 우측 가상 메모리에 매핑된 사례 ZONE_NORMAL과 ZONE_HIGHMEM을 보여준다.

pkmap-2

아래 그림은 HIGHMEM 영역의 어느 한 페이지를 매핑한 경우의 연관된 흐름을 보여준다.

  • pkmap_page_table[]은 실제 매핑용 L2 페이지 테이블이다.
  • page_address_htable[]을 사용하여 128개의 해쉬 방법을 사용한다.
    • 해시 슬롯에는 리스트 구조로 페이지를 추가 삭제하여 관리한다.

kmap-1a

 

매핑과 해제

 

kmap()

arch/arm/mm/highmem.c

void *kmap(struct page *page)
{
        might_sleep();
        if (!PageHighMem(page))
                return page_address(page);
        return kmap_high(page);
}
EXPORT_SYMBOL(kmap);

page 주소를 가상 주소에 매핑하고 해당 가상 주소를 리턴한다. 이미 매핑된 경우는 해당 가상 주소를 리턴한다.

  • might_sleep();
    • Preemption Point가 요구되는 커널에서 필요 시 task를 양보한다.
  • if (!PageHighMem(page))
    • page 주소가 HIGHMEM 영역이 아니면
  • return page_address(page);
    • page에 해당하는 가상 주소를 리턴한다.
  • return kmap_high(page);
    • page 정보를 사용하여 HIGHMEM 영역의 물리주소를 kmap 매핑 주소 영역 중 하나의 페이지에 매핑한 후 그 가상 주소를 리턴한다.

 

 

PageHighMem()
#define PageHighMem(__p) is_highmem(page_zone(__p))

page가 highmem 영역에 있는지 여부를 알아낸다.

  • is_highmeme()
    • 해당 zone이 highmem 영역인지 여부를 알아낸다.

 

page_zone()
static inline struct zone *page_zone(const struct page *page)
{
        return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}

page에 해당하는 zone 구조체 포인터를 리턴한다.

  •  NODE_DATA()
    • #define NODE_DATA(nid) (&contig_page_data)
      • UMA 시스템에서는 1개의 노드를 사용하며 이 때에는 배열로 관리할 필요가 없어서 &contig_page_data 전역 구조체 변수를 리턴한다.
  • page_to_nid()
    • 페이지 번호로 node id를 알아온다.
  • page_zonenum()
    • page 멤버변수 flags에서 zone값을 알아온다.

 

page_zonenum()
static inline enum zone_type page_zonenum(const struct page *page)
{
        return (page->flags >> ZONES_PGSHIFT) & ZONES_MASK;
}

page 멤버변수 flags에서 zone 값을 알아온다.

  • #define ZONES_PGSHIFT (ZONES_PGOFF * (ZONES_WIDTH != 0))
    • #define ZONES_PGOFF             (NODES_PGOFF – ZONES_WIDTH)
      • #define NODES_PGOFF             (SECTIONS_PGOFF – NODES_WIDTH)
        •  #define SECTIONS_PGOFF          ((sizeof(unsigned long)*8) – SECTIONS_WIDTH)
          • SECTIONS_WIDTH는 sparse 메모리가 아닌 경우 항상 0
          • rpi2는 SPARSEMEM을 사용하지 않아 섹션 비트가 필요 없다 따라서 SECTIONS_PGOFF=32
        • NODES_WIDTH
          • 32비트 정수로 22개의 페이지용 플래그 + 섹션비트, 노드비트, 존비트를 표현할 수 있으면 CONFIG_NODES_SHIFT를 리턴
          • rpi2: 단일 노드이므로 0
        • rpi2는 단일 노드를 사용하므로 NODES_PGOFF=32
      • ZONES_WIDTH
        • 존 수에 따라 달라지는데 1개 존은 0, 2개 존은 1, 3~4개 존은 2, 그외는 에러
        • rpi2: 2개 존을 사용하므로 1
      • rpi2의 ZONES_PGOFF=31
    • rpi2의 ZONES_PGSHIFT=31
  • page->flags 값에서 zone 비트만 우측으로 쉬프트하고 마스크하여 추출한다.

page-1

 

is_highmem()
/**
 * is_highmem - helper function to quickly check if a struct zone is a 
 *              highmem zone or not.  This is an attempt to keep references
 *              to ZONE_{DMA/NORMAL/HIGHMEM/etc} in general code to a minimum.
 * @zone - pointer to struct zone variable
 */
static inline int is_highmem(struct zone *zone)
{
#ifdef CONFIG_HIGHMEM
        int zone_off = (char *)zone - (char *)zone->zone_pgdat->node_zones;
        return zone_off == ZONE_HIGHMEM * sizeof(*zone) ||
               (zone_off == ZONE_MOVABLE * sizeof(*zone) &&
                zone_movable_is_highmem());
#else
        return 0;
#endif
}

해당 zone이 highmem 영역인지 여부를 알아낸다.

  • zone_off
    • 현재 요청한 zone 구조체 주소에서 첫 zone 구조체 주소를 뺀 offset 주소이다.
  • zone_off 사이즈로 HIGHMEM 영역에 있는지 판단하여 리턴한다.

 

page_address()

mm/highmem.c

/**
 * page_address - get the mapped virtual address of a page
 * @page: &struct page to get the virtual address of
 *
 * Returns the page's virtual address.
 */
void *page_address(const struct page *page)
{
        unsigned long flags;
        void *ret;
        struct page_address_slot *pas;

        if (!PageHighMem(page))
                return lowmem_page_address(page);

        pas = page_slot(page);
        ret = NULL;
        spin_lock_irqsave(&pas->lock, flags);
        if (!list_empty(&pas->lh)) {
                struct page_address_map *pam;

                list_for_each_entry(pam, &pas->lh, list) {
                        if (pam->page == page) {
                                ret = pam->virtual;
                                goto done;
                        }
                }   
        }   
done:
        spin_unlock_irqrestore(&pas->lock, flags);
        return ret;
}

EXPORT_SYMBOL(page_address);

인수로 지정된 page 구조체 주소로 기존 해시 테이블을 검색하여 이미 매핑이 되어 있는 가상 주소 값을 찾아서 리턴한다.

  • return lowmem_page_address(page);
    • page에 인수로 조회하여 lowmem 영역에 대한 가상 주소를 리턴한다.
  • pas = page_slot(page);
    • 해시 테이블에서 해당 hash(slot)로 page_address_slot 구조체 포인터를 알아온다.
  • spin_lock_irqsave(&pas->lock, flags);
    • 리스트 엔트리를 검색하기 위해 spin lock을 한다.
  • if (!list_empty(&pas->lh)) {
    • 리스트가 비어있지 않으면
  • list_for_each_entry(pam, &pas->lh, list) {
    • 리스트 엔트리를 모두 조회하여 하나씩 pam(page_address_map 구조체 포인터)에 대입한다.
    • if (pam->page == page) {
      • 엔트리의 페이지와 인수로 요청한 페이지가 동일하면
    • ret = pam->virtual;
      • 해당 엔트리의 virtual 값을 리턴한다.
  • spin_unlock_irqrestore(&pas->lock, flags);
    • 리스트 엔트리의 조회가 모두 끝났으므로 spin unlock을 수행한다.

 

lowmem_page_address()

include/linux/mm.h

static __always_inline void *lowmem_page_address(const struct page *page)
{
        return __va(PFN_PHYS(page_to_pfn(page)));
}

lowmem 영역의 page 주소로 가상 주소값을 구한다.

  •  page_to_pfn()
    • page 주소로 pfn 값을 얻어온다.
    • 구현 루틴은 메모리 모델에 따라 다음 4가지 종류 중 하나를 사용한다.
      • CONFIG_FLATMEM, CONFIG_DISCONTIGMEM, CONFIG_SPARSEMEM_VMEMMAP, CONFIG_SPARSEMEM
      • CONFIG_FLATMEM을 사용하는 매크로 함수이다.
#define __page_to_pfn(page)     ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET
  • PFN_PHYS()
    • pfn  값으로 물리주소를 구한다.
#define PFN_PHYS(x)     ((phys_addr_t)(x) << PAGE_SHIFT)
  • __va()
    • lowmem영역의 물리주소를 가상주소 값으로 변환한다.
#define __va(x)                 ((void *)__phys_to_virt((phys_addr_t)(x)))

 

 

kmap_high()

mm/highmem.c

/**
 * kmap_high - map a highmem page into memory
 * @page: &struct page to map
 *
 * Returns the page's virtual memory address.
 *
 * We cannot call this from interrupts, as it may block.
 */
void *kmap_high(struct page *page)
{
        unsigned long vaddr;

        /*
         * For highmem pages, we can't trust "virtual" until
         * after we have the lock.
         */
        lock_kmap();
        vaddr = (unsigned long)page_address(page);
        if (!vaddr)
                vaddr = map_new_virtual(page);
        pkmap_count[PKMAP_NR(vaddr)]++;
        BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
        unlock_kmap();
        return (void*) vaddr;
}

EXPORT_SYMBOL(kmap_high);

page 구조체 정보를 사용하여 해당 페이지 프레임(4K)을 가상 주소에 매핑하고 그 주소를 리턴한다.

  • lock_kmap()
    • 전역 kmap_lock에 대해 spin lock을 사용하여 동시에 kmap() 및 kunmap() 함수를 이용하는 경우 순서대로 sirialization 한다.
  • vaddr = (unsigned long)page_address(page);
    • page 주소로 이미 매핑된 가상 주소를 알아온다.
  • if (!vaddr)
    • 매핑된 주소가 없는 새로운 페이지인 경우
  • vaddr = map_new_virtual(page);
    • 새로운 virtual 주소를 할당한다.
  • pkmap_count[PKMAP_NR(vaddr)]++
    • 해당 주소의 pkmap 엔트리에 대한 pkmap 카운터를 증가시킨다.
  • unlock_kmap();
    • 전역 kmap_lock에 대해 spin unlock을 수행한다.

 

map_new_virtual()

mm/highmem.c

static inline unsigned long map_new_virtual(struct page *page)
{
        unsigned long vaddr;
        int count;
        unsigned int last_pkmap_nr;
        unsigned int color = get_pkmap_color(page);

start:
        count = get_pkmap_entries_count(color);
        /* Find an empty entry */
        for (;;) {
                last_pkmap_nr = get_next_pkmap_nr(color);
                if (no_more_pkmaps(last_pkmap_nr, color)) {
                        flush_all_zero_pkmaps();
                        count = get_pkmap_entries_count(color);
                }
                if (!pkmap_count[last_pkmap_nr])
                        break;  /* Found a usable entry */
                if (--count)
                        continue;

                /*
                 * Sleep for somebody else to unmap their entries
                 */
                {
                        DECLARE_WAITQUEUE(wait, current);
                        wait_queue_head_t *pkmap_map_wait =
                                get_pkmap_wait_queue_head(color);

                        __set_current_state(TASK_UNINTERRUPTIBLE);
                        add_wait_queue(pkmap_map_wait, &wait);
                        unlock_kmap();
                        schedule();
                        remove_wait_queue(pkmap_map_wait, &wait);
                        lock_kmap();

                        /* Somebody else might have mapped it while we slept */
                        if (page_address(page))
                                return (unsigned long)page_address(page);

                        /* Re-start */
                        goto start;
                }
        }
        vaddr = PKMAP_ADDR(last_pkmap_nr);
        set_pte_at(&init_mm, vaddr,
                   &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));

        pkmap_count[last_pkmap_nr] = 1;
        set_page_address(page, (void *)vaddr);

        return vaddr;
}
  • unsigned int color = get_pkmap_color(page);
    • highmem 영역의 메모리에 data cache aliasing이 필요할 때 리턴되는 color 값인데 특정 아키텍처(mips & xtensa)에서만 사용하고 ARM에서는 아직 사용하지 않아 0으로 리턴한다.
    • 참고: mm/highmem: make kmap cache coloring aware
  • count = get_pkmap_entries_count(color);
    • ARM에서는 highmem을 위한 최대 매핑 엔트리 수를 리턴한다.
      • LAST_PKMAP(512)
  • last_pkmap_nr = get_next_pkmap_nr(color);
    • 마지막에 사용된 엔트리 번호에 1을 더한 번호를 리턴한다.
      • 0~511 까지를 반복한다.
  • if (no_more_pkmaps(last_pkmap_nr, color)) {
    • last_pkmap_nr = 0이면?
  • flush_all_zero_pkmaps();
    • 사용하지 않는 pkmap 엔트리를 해제한다.
  • count = get_pkmap_entries_count(color);
    • ARM에서는 항상 LAST_PKMAP(512)을 리턴한다.
  • if (!pkmap_count[last_pkmap_nr])
    • 빈 엔트리를 찾은 경우(엔트리 카운터가 0)
  • DECLARE_WAITQUEUE(wait, current);
    • 현재 태스크로 wait 항목을 만든다.
  • wait_queue_head_t *pkmap_map_wait = get_pkmap_wait_queue_head(color);
    • wait_queue를 알아온다.
  • __set_current_state(TASK_UNINTERRUPTIBLE);
    • 현재 태스크를 uninterruptible로 바꾼다.
  • add_wait_queue(pkmap_map_wait, &wait);
    • pkmap_map_wait 큐에 wait 엔트리를 추가한다.
  • unlock_kmap();
    • sleep() 하기 위해 spin unlock 한다.
  • schedule();
    • 리스케쥴 한다.
  • remove_wait_queue(pkmap_map_wait, &wait);
    • pkmap_map_wait 큐에서 현재 wait 항목을 제거한다.
  • lock_kmap();
    • 다시 spin lock 한다.
  • if (page_address(page))
    • 누군가(다른 태스크) 매핑을 한 경우 빠져나간다.
  • goto start;
    • 다시 처음 부터 시도한다.
  • vaddr = PKMAP_ADDR(last_pkmap_nr);
    • 마지막 사용한 엔트리 번호로 가상 주소를 알아온다.
#define PKMAP_ADDR(nr)          (PKMAP_BASE + ((nr) << PAGE_SHIFT))

 

  • set_pte_at(&init_mm, vaddr, &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
    • pte 엔트리를 설정한다.
  • pkmap_count[last_pkmap_nr] = 1;
    • 사용 카운터를 1로 만든다.
  • set_page_address(page, (void *)vaddr);
    • page의 가상 주소를 대입한다.

 

flush_all_zero_pkmaps()

mm/highmem.c

static void flush_all_zero_pkmaps(void)
{
        int i;
        int need_flush = 0;

        flush_cache_kmaps();

        for (i = 0; i < LAST_PKMAP; i++) {
                struct page *page;

                /*
                 * zero means we don't have anything to do,
                 * >1 means that it is still in use. Only
                 * a count of 1 means that it is free but
                 * needs to be unmapped
                 */
                if (pkmap_count[i] != 1)
                        continue;
                pkmap_count[i] = 0;

                /* sanity check */
                BUG_ON(pte_none(pkmap_page_table[i]));

                /*
                 * Don't need an atomic fetch-and-clear op here;
                 * no-one has the page mapped, and cannot get at
                 * its virtual address (and hence PTE) without first
                 * getting the kmap_lock (which is held here).
                 * So no dangers, even with speculative execution.
                 */
                page = pte_page(pkmap_page_table[i]);
                pte_clear(&init_mm, PKMAP_ADDR(i), &pkmap_page_table[i]);

                set_page_address(page, NULL);
                need_flush = 1;
        }
        if (need_flush)
                flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
}
  • flush_cache_maps()
#define flush_cache_kmaps() \
        do { \
                if (cache_is_vivt()) \
                        flush_cache_all(); \
        } while (0)
  • for (i = 0; i < LAST_PKMAP; i++) {
    • 전체 PKMAP 엔트리(512) 만큼 루프
  • if (pkmap_count[i] != 1)
    • 사용 카운터가 1을 초과한 경우는 이미 사용중이라는 것을 의미한다.
    • 1인 경우는 free 한 상태인 경우이다.
  • pkmap_count[i] = 0;
    • 일단 카운터를 0으로 설정한다.
  • page = pte_page(pkmap_page_table[i])
    • 해당 루프 카운터에 매치된 pte 엔트리 값으로 page를 알아온다.
  • pte_clear(&init_mm, PKMAP_ADDR(i), &pkmap_page_table[i]);
    • pte 엔트리 클리어
  • set_page_address(page, NULL);
    • PKMAP 엔트리에 매핑된 항목의 virtual 값에 null을 설정
  • need_flush = 1;
    • 한 번이라도 pte 엔트리가 바뀌면 1로 설정한다.
  • flush_tlb_kernel_range(PKMAP_ADDR(0), PKMAP_ADDR(LAST_PKMAP));
    • PKMAP 2M 영역에 대해 tlb flush를 수행한다.

 

set_page_address()

mm/highmem.c

/**
 * set_page_address - set a page's virtual address
 * @page: &struct page to set
 * @virtual: virtual address to use
 */
void set_page_address(struct page *page, void *virtual)
{
        unsigned long flags;
        struct page_address_slot *pas;
        struct page_address_map *pam;

        BUG_ON(!PageHighMem(page));

        pas = page_slot(page);
        if (virtual) {          /* Add */
                pam = &page_address_maps[PKMAP_NR((unsigned long)virtual)];
                pam->page = page;
                pam->virtual = virtual;

                spin_lock_irqsave(&pas->lock, flags);
                list_add_tail(&pam->list, &pas->lh);
                spin_unlock_irqrestore(&pas->lock, flags);
        } else {                /* Remove */
                spin_lock_irqsave(&pas->lock, flags);
                list_for_each_entry(pam, &pas->lh, list) {
                        if (pam->page == page) {
                                list_del(&pam->list);
                                spin_unlock_irqrestore(&pas->lock, flags);
                                goto done;
                        }
                }
                spin_unlock_irqrestore(&pas->lock, flags);
        }
done:
        return;
}

PKMAP 매핑 hash slot에서 page를 검색하여 발견되면 page->virtual 값을 설정한다.

  • pas = page_slot(page);
    • page 주소로 hash slot 정보를 알아온다.
  • if (virtual) {          /* Add */
    • 매핑 추가 명령이 요청된 경우
  • pam = &page_address_maps[PKMAP_NR((unsigned long)virtual)];
    • PKMAP 매핑 엔트리 번호로 pam을 알아온다.
  • pam->page = page;
    • page 구조체 주소를 기록한다.
  • pam->virtual = virtual;
    • 가상 주소를 기록한다.
  • list_add_tail(&pam->list, &pas->lh);
    • pas->lh에 pam->list를 추가한다.
    • 즉 해당 hash slot 관리 배열의 리스트에 page 매핑 구조체를 등록한다.
  • } else { /* Remove */
    • 매핑 삭제 명령이 요청된 경우
  • list_for_each_entry(pam, &pas->lh, list) {
    • 해당 hash slot 관리 배열의 리스트를 모두 조회한다.
  • if (pam->page == page) {
    • 해당 페이지가 발견되면
  • list_del(&pam->list);
    • 해당 매핑을 삭제한다.

 

kunmap()

kmap 영역에 매핑된 highmem page 주소를 매핑 해제한다. highmem page 주소가 아닌 경우는 그냥 리턴한다.

arch/arm/mm/highmem.c

void kunmap(struct page *page)
{
        BUG_ON(in_interrupt());
        if (!PageHighMem(page))
                return;
        kunmap_high(page);
}
EXPORT_SYMBOL(kunmap);
  • if (!PageHighMem(page))
    • page 주소가 HIGHMEM 영역이 아니면 그냥 리턴한다.
  • kunmap_high(page);
    • 해당 page를 kmap 매핑 영역에서 제거한다.

 

kunmap_high()

kmap 영역에 매핑된 highmem page 주소를 매핑 해제한다.

mm/highmem.c

/**
 * kunmap_high - unmap a highmem page into memory
 * @page: &struct page to unmap
 *
 * If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called
 * only from user context.
 */
void kunmap_high(struct page *page)
{
        unsigned long vaddr;
        unsigned long nr;
        unsigned long flags;
        int need_wakeup;
        unsigned int color = get_pkmap_color(page);
        wait_queue_head_t *pkmap_map_wait;

        lock_kmap_any(flags);
        vaddr = (unsigned long)page_address(page);
        BUG_ON(!vaddr);
        nr = PKMAP_NR(vaddr);

        /*
         * A count must never go down to zero
         * without a TLB flush!
         */
        need_wakeup = 0;
        switch (--pkmap_count[nr]) {
        case 0:
                BUG();
        case 1:
                /*
                 * Avoid an unnecessary wake_up() function call.
                 * The common case is pkmap_count[] == 1, but
                 * no waiters.
                 * The tasks queued in the wait-queue are guarded
                 * by both the lock in the wait-queue-head and by
                 * the kmap_lock.  As the kmap_lock is held here,
                 * no need for the wait-queue-head's lock.  Simply
                 * test if the queue is empty.
                 */
                pkmap_map_wait = get_pkmap_wait_queue_head(color);
                need_wakeup = waitqueue_active(pkmap_map_wait);
        }
        unlock_kmap_any(flags);

        /* do wake-up, if needed, race-free outside of the spin lock */
        if (need_wakeup)
                wake_up(pkmap_map_wait);
}

EXPORT_SYMBOL(kunmap_high);
  • unsigned int color = get_pkmap_color(page);
    • ARM에서는 아직 highmem 영역에 d-cache aliasing을 사용하지 않아 0으로 리턴한다.
  • lock_kmap_any(flags);
    • kmap 전역 spin lock을 사용하여 동시에 kmap() 및 kunmap() 함수를 이용하는 것을 순서대로 sirialization 한다.
  • vaddr = (unsigned long)page_address(page);
    • page 주소로 이미 매핑된 가상 주소를 알아온다.
  • nr = PKMAP_NR(vaddr);
    • 가상 주소에 해당하는 슬롯(0~127) 번호를 알아온다.
  • switch (–pkmap_count[nr]) {
    • 사용 카운터를 감소시킨다.
  • case 1:
    • 감소시킨 값이 1인 경우
  • pkmap_map_wait = get_pkmap_wait_queue_head(color);
    • wait_queue를 준비한다.
  • need_wakeup = waitqueue_active(pkmap_map_wait);
    • wait_queue에 태스크가 있으면 true
  • unlock_kmap_any(flags);
    • kmap 전역 spin unlock을 수행한다.
  • wake_up(pkmap_map_wait);
    • 대기 큐에 존재하는 하나의 태스크를 wake up 하게 한다.

 

 

get_pkmap_wait_queue_head()

mm/highmem.c

/*
 * Get head of a wait queue for PKMAP entries of the given color.
 * Wait queues for different mapping colors should be independent to avoid
 * unnecessary wakeups caused by freeing of slots of other colors.
 */
static inline wait_queue_head_t *get_pkmap_wait_queue_head(unsigned int color)
{
        static DECLARE_WAIT_QUEUE_HEAD(pkmap_map_wait);

        return &pkmap_map_wait;
}
  • ARM 아키텍처는 아직 HIGHMEM 영역에 대해 d-cache에 대한 color 값을 사용하지 않아 인수로 사용되는 color 값을 사용하지 않는다.
  • wait_queue를 static inline 할당하고 리턴한다.

 

 

waitqueue_active()

include/linux/wait.h

static inline int waitqueue_active(wait_queue_head_t *q)
{
        return !list_empty(&q->task_list);
}
  • wait_queue에 태스크가 있는 경우 true, 없으면 false를 리턴한다.

 

참고

댓글 남기기