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를 리턴한다.

 

참고

Bootmem with bitmap

특징

  • 부트업 프로세스(타임)에서 사용하는 물리 메모리 할당 및 조정자(A boot-time physical memory allocator and configurator)
  • Bootmem 은 간단하다. 커널 부트업 프로세스의 early 파트를 진행하는 동안 low-레벨 메모리 할당자가 커널에 의해 사용된다.
  • Bootmem은 초기에 MMU(페이징) 기능이 동작된 후부터 early boot process를 수행하는 동안 buddy 할당자가 동작하기 전까지 시스템에 필요한 메모리 할당을 담당하며 사용 후에는 buddy 할당으로 변환된다.
  • Bootmem은 메모리의 사용 유무를 bitmap으로 표현한다.
  • Bootmem은 각 아키텍처마다 메인 커널에서 점점 사용하지 않고 memblock만을 사용하여 운영하는 방법으로 전환 되어가고 있다.
  • x86 시스템의 메모리 할당 변천을 보면
    • 1) very very early allocator (early brk model) [x86] – BIOS “e820″을 사용
    • 2) very early allocator (early_res) -> (memblock) [some generic]
      • kernel 2.6.35에서 early_res를 memblock으로 대체
    • 3) early allocator (bootmem) [generic]
    • 4) full buddy allocator
  • ARM 커널 v3.14-rc1에서 arm_bootmem_init()이 삭제되고 CONFIG_NO_BOOTMEM 옵션을 추가하였다.

bootmem-1

아래 그림은 bootmem이 비트맵으로 표현되는 방법을 보여준다.

bootmem-2

구조체

struct bootmem_data

include/linux/bootmem.h

#ifndef CONFIG_NO_BOOTMEM
/*
 * node_bootmem_map is a map pointer - the bits represent all physical 
 * memory pages (including holes) on the node.
 */
typedef struct bootmem_data {
        unsigned long node_min_pfn;
        unsigned long node_low_pfn;
        void *node_bootmem_map;
        unsigned long last_end_off;
        unsigned long hint_idx;
        struct list_head list;
} bootmem_data_t;

extern bootmem_data_t bootmem_node_data[];
#endif

 

struct pglist_data

include/linux/mmzone.h

/*
 * The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM
 * (mostly NUMA machines?) to denote a higher-level memory zone than the
 * zone denotes.
 *
 * On NUMA machines, each NUMA node would have a pg_data_t to describe
 * it's memory layout.
 *
 * Memory statistics and page replacement data structures are maintained on a
 * per-zone basis.
 */
struct bootmem_data;
typedef struct pglist_data {
        struct zone node_zones[MAX_NR_ZONES];
        struct zonelist node_zonelists[MAX_ZONELISTS];
        int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
        struct page *node_mem_map;
#ifdef CONFIG_PAGE_EXTENSION
        struct page_ext *node_page_ext;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
        struct bootmem_data *bdata;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
        /*
         * Must be held any time you expect node_start_pfn, node_present_pages
         * or node_spanned_pages stay constant.  Holding this will also
         * guarantee that any pfn_valid() stays that way.
         *
         * pgdat_resize_lock() and pgdat_resize_unlock() are provided to
         * manipulate node_size_lock without checking for CONFIG_MEMORY_HOTPLUG.
         *
         * Nests above zone->lock and zone->span_seqlock
         */
        spinlock_t node_size_lock;
#endif
        unsigned long node_start_pfn;
        unsigned long node_present_pages; /* total number of physical pages */
        unsigned long node_spanned_pages; /* total size of physical page
                                             range, including holes */
        int node_id;
        wait_queue_head_t kswapd_wait;
        wait_queue_head_t pfmemalloc_wait;
        struct task_struct *kswapd;     /* Protected by
                                           mem_hotplug_begin/end() */
        int kswapd_max_order;
        enum zone_type classzone_idx;
#ifdef CONFIG_NUMA_BALANCING
        /* Lock serializing the migrate rate limiting window */
        spinlock_t numabalancing_migrate_lock;

        /* Rate limiting time interval */
        unsigned long numabalancing_migrate_next_window;

        /* Number of pages migrated during the rate limiting time interval */
        unsigned long numabalancing_migrate_nr_pages;
#endif
} pg_data_t;

 

contig_page_data 등

mm/bootmem.c

#ifndef CONFIG_NEED_MULTIPLE_NODES
struct pglist_data __refdata contig_page_data = { 
        .bdata = &bootmem_node_data[0]
};
EXPORT_SYMBOL(contig_page_data);
#endif

unsigned long max_low_pfn;
unsigned long min_low_pfn;
unsigned long max_pfn;

bootmem_data_t bootmem_node_data[MAX_NUMNODES] __initdata;

static struct list_head bdata_list __initdata = LIST_HEAD_INIT(bdata_list);

 

참고

Inline-Assembly

인라인 어셈블리의 문법은 다음과 같으며, ARM 아키텍처를 위주로 설명한다.

     asm asm-qualifiers ( AssemblerTemplate
                      : OutputOperands
                      [ : InputOperands
                      [ : Clobbers ] ])
     
     asm asm-qualifiers ( AssemblerTemplate
                           : OutputOperands
                           : InputOperands
                           : Clobbers
                           : GotoLabels)

 

Qualifiers

volatile

  • gcc는 성능 향상(optimization)을 목적으로  상황에 따라 명령을 무시하거나 명령 위치를 변경할 수 있는데 asm 명령 다음에 volatile을 사용하면 optimizer가 asm() 문장 전체를 재배치하지 못하도록 한다.

inline

  • inlining을 목적으로 최대한 사이즈를 작게 만든다.

goto

  • 어셈블리 코드안에서 C에서 사용하는 라벨로 점프할 수 있게 한다.

 

Parameters(파라메터)

AssemblerTemplate(코드)

  • 어셈블리 코드 문자열
    • input/output 오퍼랜드와 %l(goto) 파라메터와 조합하여 사용한다.
    • 예) ARM
      • “mov r0, r0”
      • “mov %0, #10”
      • “ldr %0, [%1]”
      • “bne %l[err1]”
  • 어셈블리 명령 끝 처리
    • “\n” 또는 “\n\t”을 사용하여 여러 개의 명령을 사용한다.
    • 세미콜론(;)을 사용하여 명령을 구분할 수도 있는데 컴파일러에 따라 다르므로  “\n\t” 등이 권장된다.
    • 예) ARM
      • “mov r0, r0\n\t”   “mov r1, r1”
      • “mov r0, r0;    mov r1, r1;”
  • “%%”
    • “%” Input/Output 오퍼랜드에서 사용하는 “%”와 혼동을 피하기 위하여 어셈블리 코드내에 x86 어셈블리와 같이 “%” 문자열을 사용해야 하는 경우 사용한다.
    • 예) x86
      • “movl %%eax, %0”
  • “%{“, “%|” “%}”
    • “%%”와 동일하게 AsmemblerTemplate 내에서 “{“, “|”, “}” 문자열을 사용하기 위함이다.
  • Input/Output Operands 사용
    • %n: n번째 인수에 매핑된 arm 32bit 레지스터를 지정한다.
    • %Qn: n번째 64비트 인수 중 하위 비트에 매핑된 ARM 32bit 레지스터를 지정한다.
    • %Rn: n번째 64비트 인수 중 상위 비트에 매핑된 ARM 32bit 레지스터를 지정한다.
    • %Hn: n번째 64비트 인수 중 매핑된 2 개의 ARM 32bit 레지스터 중 레지스터 번호가 높은 레지스터를 지정한다.
    • 예) ARM
      • “mov %0, #10”
      • “mov %Q0, %1, %2”
      • “mov %R0, %R0, #0”
      • “ldrd %H0, [%1]”
    • %[foo]
      • Input/Output Operands에서 %n과 같이 인덱스 번호를 직접 사용하지 않고 이름을 지정할 수 있다.

 

Input  Operands(입력 인수) & Output Operands(출력 인수)

형식: [ [asmSymbolicName] ]    constraint    (C-expression)

  • AssemblerTemplate(code)에 있는 명령에 의해 C 변수들에서(로) 입력/출력된다.
  • 빈 항목도 허용한다.
  • [ [asmSymbolicName] ]
    • 생략 가능하고 지정하는 경우 %0, %1, …과 같이 인수의 순번을 사용하는 대신 심볼명을 사용할 수도 있다.

 

예) %n 스타일

uint32_t c = 1;
uint32_t d;
uint32_t *e = &c;

asm ("mov %1, %0"
   : "=rm" (d)
   : "rm" (*e));

 

예) %[name] 스타일 – asmSymbolicName 사용

uint32_t c = 1;
uint32_t d;
uint32_t *e = &c;

asm ("mov %[e], %[d]"
   : [d] "=rm" (d)
   : [e] "rm" (*e));

 

  • constraint
    • 자주 사용하는 constraint 항목
      • “r”
        • C-expression을 범용 레지스터에 배정한다.
      • “I”~”P”
        • Immediate 위치로 C-expression이 상수이어야 한다.
        • 아키텍처마다 상수 표현 크기가 다르다.
        • ARM:
          • “I”: 0~255 값을 2의 차수(2^N) 단위로 만들 수 있는 상수 값
            • 예) 0x81(o), 0x8100_0000(o), 0x101
          • “J”: -4095~4095 범위의 상수
      • “m”
        • C-expression이 유효 메모리 주소이어야 한다.
      • “[digits]”
        • Input Operands에 사용되며 Output Operands의 순서와 똑같은 항목을 지칭한다. 이렇게 하면 컴파일러의 optimization이 Output Operands의 값이 수정된 것 처럼 속인다.
          • 예) __asm__ ( : =r(__ptr) : 0(ptr));
    • 메모리 access용 clobber를 지정할 수 있는 constraint 항목
      • “Q”, “Qo”
        • ARM clobber for memory
        • C-expression은 단일 레지스터에서 유효한 메모리 레퍼런스 주소이다.
        • gcc의 ARM용 clobber for memory로 input/output 오퍼랜드에서 메모리 access를 위해 사용한다.
        • ARM에서는 메모리 영역을 access 할 경우 clobber lists에서 “memory” 대신 “Q”를 사용한다.
        • “Qo”: optimization이 추가되어 코드가 일부 생략된다.
          • 보통 메모리 주소를 가리키는 레지스터 즉 “r”레지스터가 별도로 사용되면서 “r”과 “Q”에 대해 각각의 레퍼런스를 계산하기 위한 코드가 사용된다.  만일 “r”과 “Q”에서 사용되는 메모리 주소가 서로 같은 곳을 보는 경우 “Qo”를 사용하면 한 번의 계산을 생략할 수 있다.
          • 예) 아래와 같이 v->counter의 위치가 서로 같은 경우 “Qo”를 사용하여 코드를 절약할 수 있다.
            • asm (“ldrd %0, %H0, [%1]” : “=&r” (result) : “r” (&v->counter), “Qo” (v->counter);
    • constraint modifiers
      • “=”
        • OutputOperands에서 쓰기(write only)만 가능하다.
      • “+”
        • OutputOperands에서 읽고(read) 쓰기(write)가 가능하다.
      • “&”
        • early clobber modifier
        • OutputOperands에서 레지스터 할당 순서를 먼저 할 수 있도록 요청한다.
        • 보통 input operands에 레지스터를 할당하고 그 후 output operands의 레지스터를 사용하기 때문에 input operands에서 사용했던 레지스터를 output operands 레지스터로 배치하는 경우도 생기는데 그러면서 문제가 될 수 있는 곳에 “&”를 사용한다.
        • 보통 Output operands에  “&”를 사용하여 먼저 하나의 레지스터를 할당받아 사용하면서 다른 레지스터로 사용될 일을 막는다.
    • 사용 예)
      • “=r”
        • 쓰기만 하는 목적으로 해당 C-expression을 범용 레지스터에 배정한다.
      • “+rm”
        • 메모리 주소를 읽고 쓰는 목적으로 해당 C-expression을 범용 레지스터에 배정한다.
      • “Ir”
        • immediate 오퍼랜드 위치에서 사용하기 위하여 해당 C-expression을 범용 레지스터에 배정한다.
      • “=&r”
        • 쓰기만 하는 목적으로 해당 C-expression을 범용 레지스터에 먼저(early) 배정한다.
      • “+r”
        • 읽고 쓰는 목적으로 C-expression을 범용 레지스터에 배정한다.
      • “+Qo”
        • 읽고 쓰는 목적으로 C-expression을 메모리에 배정한다. (ARM clobber for memory)
  • (C-expression)
    • C 표현이 가능하다.
    • 예)
      • (var+10)
      • (*var)
      • (&var)

Clobbers

  • AssemblerTemplate 에 의해 변경되는 레지스터나 값들이다.
  • 즉 InputOperands 와 OutputOperands가 C로 부터 영향을 받거나 주는 경우를 지정하였지만 Clobbers는 어셈블리 코드에서 영향을 주는 것을 의미한다.
    • “cc”
      • 플래그 레지스터를 수정할 수 있다.
    • “memory”
      • 메모리 주소를 변경시킬 수 있다.
      • input/output operands에서 “Q” 또는 “Qo”를 사용하여 해당 항목에 사용할 수 있다. (최신 방법)
    • “r7”
      • AssemblerTemplate 내에서 r7 레지스터를 사용한다고 지정한다. 만일 asm() 사용 전에 r7 레지스터를 사용한 경우 컴파일러가 이의 사용을 하지 않도록 하여 사용자가 AssemblerTemplate 내에서 안전하게 r7 레지스터를 사용할 수 있다.
  • 예) “r9”, “cc”, “memory”
    • 어셈블리 코드로부터 r9 레지스터, 플래그 레지스터, 메모리가 수정되는 경우이다.

 

Goto Labels

  • 라벨로 점프를 하는 기능이며, 이  기능을 사용할 경우 OutputOperands를 사용할 수 없으므로 비워둬야 한다.

goto.c

#include <stdio.h>

int sub(int cnt)
{
        int x = 0;
        asm goto ( "mov %0, %1\n\t"
                   "cmp %0, #10\n\t"
                   "bhi %l[err2]\n\t"
                   "1: subs %0, #1\n\t"
                   "bne 1b"
                   :
                   :    "r" (x), "r" (cnt)
                   :    "cc"
                   :    err2);
        printf("cnt=%d\n", cnt);
        return x;
err2:
        printf("err: cnt=%d\n", cnt);
        return x;
}

int main()
{
        sub(5);
        sub(15);
}

$ ./goto
cnt=5
err: cnt=15

 

기타

 

Clobber for Memory (“Q”)

  • Q”는 “memory”를 대신하여 사용되는 오퍼랜드 항목의 clobber for memory 이다.

qo.c

#include <stdio.h>

int loop5(int * addr)
{
        int i;
        int tmp = 0;

        for (i = 0; i < 10; i++) {
                asm ("add %0, #2\n      str %0, [%2]"
                        : "=r" (tmp), "=Qo" (*addr)
                        : "r" (addr));
        }

        return tmp;
}

int loop6(int * addr)
{
        int i;
        int tmp = 0;

        for (i = 0; i < 10; i++) {
                asm volatile ("add %0, #2\n     str %0, [%2]"
                        : "=r" (tmp), "=Qo" (*addr)
                        : "r" (addr));
        }

        return tmp;
}

int main()
{
        int l5 = 1;
        int l6 = 1;

        loop5(&l5);
        loop6(&l6);
}
  • 위의 소스와 같이 “memory” clobber를 사용하지 않고 “Qo”를 사용하여 구현한 예이다.
  • 메모리에 읽거나 쓰는 데이터는 레지스터가 아닌 값을 의미하므로 addr가 아닌 *addr이 된다.
  • input operands에 사용된 addr은 읽기 용도로 변경되지 않으며 output operands에 사용된 *addr 값이 메모리에 기록되는 값이므로 “Qo” 앞에 기록 전용의 modifier인 “=”을 붙인다.

 

참고

Volatile

volatile

gcc compiler는 성능 향상(optimization)을 목적으로 경우에 따라 변수의 사용에 대해 무시하거나 사용 위치를 변경할 수 있는데 volatile을 사용하면 다음의  optimization을 하지 않고 코드를 만들어낸다.

  • Optimization case
    • 객체(변수)가 사용되지 않아도 된다고 판단할 때 무시한다.
    • 루프 문 내부에서 사용되는 객체(변수)가 input용도로만 사용되는 경우 루프문 바깥으로 이전한다.
  • 메모리, I/O 주소 등에 접근 시 생략될 가능성이 있거나 access 횟 수가 의도와 다르게 적게 호출될 가능성이 있는 경우 반드시 volatile을 사용하여 컴파일러로 하여금 관련 주소의 코드를 optimization 하지 않도록 해야 한다.

 

Using C

1) 무시되는 case

  • d1 값을 10번 읽어들일 때.

volatile-1.c

void discard1()
{
        int i;
        int d1 = 1;
        int sum = 0;

        for (i = 0; i < 10; i++)
                sum += d1;
}

void discard2()
{
        int i;
        volatile int d2 = 1;
        int sum = 0;

        for (i = 0; i < 10; i++)
                sum += d2;
}

int main()
{
        discard1();
        discard2();
}
  • 다음과 같이 disassemble 해보면 discard1() 함수의 경우 아무것도 하지 않음을 알 수 있다.
$ gcc -O2 volatile-1.c -o volatile-1
$ objdump -d volatile-1

(...생략...)
000083b4 <discard1>:
    83b4:       e12fff1e        bx      lr

000083b8 <discard2>:
    83b8:       e24dd008        sub     sp, sp, #8
    83bc:       e3a0300a        mov     r3, #10
    83c0:       e3a02001        mov     r2, #1
    83c4:       e58d2004        str     r2, [sp, #4]
    83c8:       e2533001        subs    r3, r3, #1
    83cc:       e59d2004        ldr     r2, [sp, #4]
    83d0:       1afffffc        bne     83c8 <discard2+0x10>
    83d4:       e28dd008        add     sp, sp, #8
    83d8:       e12fff1e        bx      lr
(...생략...)

 

2) 루프 밖으로 이동되는 케이스

 

#include <stdio.h>

int loop1(int * addr)
{
        int i;

        for (i = 0; i < 10; i++)
        {
                *addr += 1;
        }

        return i;
}

int loop2(volatile int * addr)
{
        int i;

        for (i = 0; i < 10; i++)
        {
                *addr += 1;
        }

        return i;
}

int main()
{
        int l1 = 0;
        volatile int l2 = 0;

        loop1(&l1);
        loop2(&l2);

        printf("l1=%d, l2=%d\n", l1, l2);
}

 

  • 다음과 같이 disassemble 해보면 loop1() 함수의 경우 10번 반복하지 않고 1번만 결과 값을 저장함을 알 수 있다.
$ gcc -O2 volatile-2.c -o volatile-2
$ objdump -d volatile-2

(...생략...)
00008408 <loop1>:
    8408:       e1a03000        mov     r3, r0
    840c:       e3a0000a        mov     r0, #10
    8410:       e5932000        ldr     r2, [r3]
    8414:       e0822000        add     r2, r2, r0
    8418:       e5832000        str     r2, [r3]
    841c:       e12fff1e        bx      lr

00008420 <loop2>:
    8420:       e3a0300a        mov     r3, #10
    8424:       e5902000        ldr     r2, [r0]
    8428:       e2533001        subs    r3, r3, #1
    842c:       e2822001        add     r2, r2, #1
    8430:       e5802000        str     r2, [r0]
    8434:       1afffffa        bne     8424 <loop2+0x4>
    8438:       e3a0000a        mov     r0, #10
    843c:       e12fff1e        bx      lr
(...생략...)
$ ./volatile-2
l1=10, l2=10

 

Using Inline Assembly

1) 무시되는 case

volatile-3.c

void discard3(int * input)
{
        int output;

        asm ("ldr %0, [%1]"
                        : "=r" (output)
                        : "r" (input)
                        : "cc");
}

void discard4(int * input)
{
        int output;

        asm volatile ("ldr %0, [%1]"
                        : "=r" (output)
                        : "r" (input));
}

int main()
{
        int d3 = 0;
        int d4 = 0;
        discard3(&d3);
        discard4(&d4);
}

 

  • 다음과 같이 disassemble 해보면 discard3() 함수의 경우 아무것도 하지 않음을 알 수 있다.
$ gcc -O2 volatile-3.c -o volatile-3
$ objdump -d volatile-3

(...생략...)
000083a4 <discard3>:
    83a4:       e12fff1e        bx      lr

000083ac <discard4>:
    83a8:       e5900000        ldr     r0, [r0]
    83ac:       e12fff1e        bx      lr
(...생략...)

 

2) 루프 밖으로 이동되는 케이스

volatile-4.c

int loop3(int * addr)
{
        int i;
        int tmp = 0;

        for (i = 0; i < 10; i++)
        {
                asm ("add %0, #1\n      str %0, [%1]"
                        : "=r" (tmp)
                        : "r" (addr)
                        : "memory");
        }
        return tmp;
}

int loop4(int * addr)
{
        int i;
        int tmp = 0;

        for (i = 0; i < 10; i++)
        {
                asm volatile ("add %0, #1\n     str %0, [%1]"
                        : "=r" (tmp)
                        : "r" (addr)
                        : "memory");
        }
        return tmp;
}

int main()
{
        int l3 = 1;
        int l4 = 1;

        loop3(&l3);
        loop4(&l4);
}
  • 1
$ gcc -O2 volatile-4.c -o volatile-4
$ objdump -d volatile-4

(...생략...)
000083cc <loop3>:
    83cc:       e3a0300a        mov     r3, #10
    83d0:       e2800001        add     r0, r0, #1
    83d4:       e5820000        str     r0, [r0]
    83d8:       e2533001        subs    r3, r3, #1
    83dc:       1afffffd        bne     83d8 <loop3+0xc>
    83e0:       e12fff1e        bx      lr

000083e4 <loop4>:
    83e4:       e3a0300a        mov     r3, #10
    83e8:       e2800001        add     r2, r2, #1
    83ec:       e5820000        str     r2, [r0]
    83f0:       e2533001        subs    r3, r3, #1
    83f4:       1afffffb        bne     83e8 <loop4+0x4>
    83f8:       e1a00002        mov     r0, r2
    83f8:       e12fff1e        bx      lr
(...생략...)

 

기타

Bit Operations

<kernel v5.10>

Bit 관련 매크로

BITS_PER_LONG

include/asm-generic/bitsperlong.h”

#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */

long 타입에서 사용될 수 있는 비트 수

 

BITS_PER_LONG_LONG

include/asm-generic/bitsperlong.h”

#ifndef BITS_PER_LONG_LONG
#define BITS_PER_LONG_LONG 64
#endif

long long 타입에서 사용될 수 있는 비트 수

 

BIT_ULL()

include/linux/bits.h

#define BIT_ULL(nr)             (ULL(1) << (nr))

nr 비트에 해당하는 unsigned long long 값을 반환한다.

  • nr=0~63 비트까지 지정할 수 있다.
  • 예)
    • nr=0 -> 1
    • nr=1 -> 2
    • ..
    • nr=63 -> 0x8000_0000_0000_0000

 

BIT_MASK()

include/linux/bits.h

#define BIT_MASK(nr)            (UL(1) << ((nr) % BITS_PER_LONG))

unsigned long 값(또는 배열)에서 nr 비트에 해당하는 비트를 추출할 목적의 비트 마스크를 만든다.

  • nr=0~
  • 예)
    • nr=0 -> 1
    • nr=1 -> 2
    • ..
    • nr=63 -> 0x8000_0000 (32 bit 기준), 0x8000_0000_0000_0000 (64 bit 기준)
    • nr=64 -> 1

 

 BIT_WORD()

include/linux/bits.h

#define BIT_WORD(nr)            ((nr) / BITS_PER_LONG)

@nr 비트가 속한 unsigned long 비트맵 배열의 인덱스 번호 (0부터 시작)

  • nr=0~
  • 예)
    • nr=0 -> 0
    • nr=31 -> 0
    • nr=32 -> 1 (32bit 기준), 0 (64bit 기준)
    • nr=63 -> 1 (32bit 기준), 0 (64bit 기준)
    • nr=64 -> 2 (32bit 기준), 1 (64bit 기준)
    • nr=95 -> 2 (32bit 기준), 1 (64bit 기준)
    • nr=96 -> 3 (32bit 기준), 1 (64bit 기준)
    • nr=127 -> 3 (32bit 기준), 1 (64bit 기준)
    • nr=128 -> 4 (32bit 기준), 2 (64bit 기준)
    • nr=159 -> 4 (32bit 기준), 2 (64bit 기준)
    • nr=160 -> 5 (32bit 기준), 2 (64bit 기준)
    • nr=191 -> 5 (32bit 기준), 2 (64bit 기준)

 

BIT_ULL_MASK()

include/linux/bits.h

#define BIT_ULL_MASK(nr)        (ULL(1) << ((nr) % BITS_PER_LONG_LONG))

unsigned long long 값(또는 배열)에서 nr 비트에 해당하는 비트를 추출할 목적의 비트 마스크를 만든다.

  • nr=0~
  • 예)
    • nr=0 -> 1
    • nr=1 -> 2
    • ..
    • nr=63 -> 0x8000_0000_0000_0000 (32 bit 및 64 bit 동일)
    • nr=64 -> 1

 

BIT_ULL_WORD()

include/linux/bits.h

#define BIT_ULL_WORD(nr)        ((nr) / BITS_PER_LONG_LONG)

@nr 비트가 속한 unsigned long long 비트맵 배열에 대한 인덱스 번호 (0부터 시작)

  • nr=0~
  • 예)
    • nr=0 -> 0
    • nr=63 -> 0
    • nr=64 -> 1
    • nr=127 -> 1
    • nr=128 -> 2
    • nr=191 -> 2

 

BITS_PER_BYTE

include/linux/bits.h

#define BITS_PER_BYTE           8

byte 타입에서 사용될 수 있는 비트 수

 


Bit Operations

API들

Search

  • fls()
  • __fls()
  • ffs()
  • __ffs()
  • ffz()
  • fls_long()

 

Iteration

  • for_each_set_bit(bit, addr, size)
    • size 비트 한도의 addr 값에서 셋(1) 비트 수 만큼 루프를 돈다.
    • 예) addr=0x3f0, size=8
      • 4번의 루프(bit=4, 5, 6, 7)
  • for_each_set_bit_from(bit, addr, size)
    • for_each_set_bit()와 유사하지만 시작 비트를 지정한 곳에서 출발한다.
    • 예) addr=0x3f0, size=8, bit=5
      • 3번의 루프(bit=5, 6, 7)
  • for_each_clear_bit(bit, addr, size)
    • size 비트 한도의 addr 값에서 클리어(0) 비트 수 만큼 루프를 돈다.
    • 예) addr=0xfff0, size=8
      • 4번의 루프(bit=0, 1, 2, 3)
  • for_each_clear_bit_from(bit, addr, size)
    • for_each_set_bit()와 유사하지만 시작 비트를 지정한 곳에서 출발한다.
    • 예) addr=0x3f0, size=8, bit=1
      • 3번의 루프(bit=1, 2, 3)

 

rotate

  • rol64()
  • ror64()
  • rol32()
  • ror32()
  • rol16()
  • ror16()
  • rol8()
  • ror8()

 

atomic

  • set_bit()
  • test_bit()
  • clear_bit()
  • change_bit()
  • test_and_set_bit()
  • test_and_clear_bit()
  • test_and_change_bit()
  • find_first_zero_bit()
  • find_next_zero_bit()
  • find_last_zero_bit()
  • find_first_bit()
  • find_next_bit()
  • find_last_bit()

 

lock

  • test_and_set_bit_lock()
  • clear_bit_unlock()
    • __clear_bit_unlock()
  • clear_bit_unlock_is_negative_byte()

 

le(little endian)

  • find_next_zero_bit_le()
  • find_next_bit_le()
  • find_first_zero_bit_le()
  • test_bit_le()
  • set_bit_le()
    • __set_bit_le()
  • clear_bit_le()
    • __clear_bit_le()
  • test_and_set_bit_le()
    • __test_and_set_bit_le()
  • test_and_clear_bit_le()
    • __test_and_clear_bit_le()

 

etc

  • get_bitmask_order()
  • hweight_long()
  • sign_extend32()
  • sign_extend64()
  • get_count_order()
  • get_count_order_long()
  • __ffs64()
  • assign_bit()
    • __assign_bit()
  • set_mask_bits()
  • bit_clear_unless()

 

bitops 아키텍처별 헤더 파일

Bit operation은 기본 구현 헤더, Generic 라이브러리, asm-generic 헤더 및 아키텍처별 헤더에 구현되어 있다.
  • 기본 구현 헤더
    • include/linux/bitops.h
    • include/asm-generic/bitops.h
    • include/asm-generic/bitops/*.h
  • Generic 라이브러리
    • lib/find_bit.c
    • lib/hweight.c
  • 아키텍처별 헤더
    • arch/arm/include/asm/bitops.h
    • arch/arm64/include/asm/bitops.h

 


비트 검색 operations

fls()

include/asm-generic/bitops/builtin-fls.h

/**
 * fls - find last (most-significant) bit set
 * @x: the word to search
 *
 * This is defined the same way as ffs.
 * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
 */
static __always_inline int fls(unsigned int x)
{
        return x ? sizeof(x) * 8 - __builtin_clz(x) : 0;
}

unsigned int 타입 @x 값에서 셋 되어 있는 마지막(가장 좌측) 비트의 위치(1~32)를 리턴한다. 설정된 비트가 없으면 0을 리턴한다.

  • 예)
    • 0 -> 0
    • 0x0000_0001 -> 1
    • 0x0000_0011 -> 2
    • 0x8000_0000 -> 32
    • 0x8800_0000 -> 32

bitops-1

 

__fls()

include/asm-generic/bitops/builtin-_ffs.h

/**
 * __fls - find last (most-significant) set bit in a long word
 * @word: the word to search
 *
 * Undefined if no set bit exists, so code should check against 0 first.
 */
static __always_inline unsigned long __fls(unsigned long word)
{
        return (sizeof(word) * 8) - 1 - __builtin_clzl(word);
}

unsigned long 타입 @word에서 가장 마지막(가장 좌측) bit가 1인 bit 번호를 리턴한다. (based 0)

  • 예)
    • 0 -> 오류 (0 값이 입력으로 전달되면 안된다)
    • 0x0000_0001 -> 0
    • 0x0000_0011 -> 1
    • 0x8000_0000 -> 31
    • 0x8800_0000 -> 31
    • 0x8800_0000_0000_0000 -> 63 (64bits only)

 

ffs()

include/asm-generic/bitops/builtin-ffs.h

/**
 * ffs - find first bit set
 * @x: the word to search
 *
 * This is defined the same way as
 * the libc and compiler builtin ffs routines, therefore
 * differs in spirit from the above ffz (man ffs).
 */
static __always_inline int ffs(int x)
{
        return __builtin_ffs(x);
}

int 타입 @x에서 셋 되어 있는 첫(가장 우측) 비트의 위치(1~32)를 리턴한다. x 값이 0 즉, 설정된 비트가 없으면 0을 리턴한다.

  • 예)
    • 0 -> 0
    • 1 -> 1
    • 0x0000_0011 -> 1
    • 0x8000_0000 -> 32
    • 0x8800_0000 -> 31

bitops-3

 

__ffs()

include/asm-generic/bitops/builtin-_ffs.h

/**
 * __ffs - find first bit in word.
 * @word: The word to search
 *
 * Undefined if no bit exists, so code should check against 0 first.
 */
static __always_inline unsigned long __ffs(unsigned long word)
{
        return __builtin_ctzl(word);
}

unsigned long 타입 @word에서 셋 되어 있는 첫(가장 우측) 비트의 위치(0~31)를 리턴한다. x 값이 0 즉, 설정된 비트가 없으면 -1을 리턴한다.

  • 예)
    • 0 -> -1
    • 0x0000_0001 -> 0
    • 0x0000_0011 -> 1
    • 0x8000_0000 -> 31
    • 0x8800_0000 -> 30
    • 0x8800_0000_0000_0000 -> 62 (64bits only)

 

ffz()

include/asm-generic/bitops/ffz.h

/*
 * ffz - find first zero in word.
 * @word: The word to search
 *
 * Undefined if no zero exists, so code should check against ~0UL first.
 */
#define ffz(x)  __ffs(~(x))

clear(0) 되어 있는 첫 비트의 위치(0~31)를 리턴한다.  x 값이 0xffff_ffff 즉,  zero bit가 없으면 -1을 리턴한다.

  • 예)
    • 0 -> 0
    • 0x0000_0001 -> 1
    • 0x0000_000f -> 4
    • 0x7fff_ffff -> 31
    • 0xffff_ffff -> -1

 

fls_long()

include/linux/bitops.h

static inline unsigned fls_long(unsigned long l) 
{
        if (sizeof(l) == 4)
                return fls(l);
        return fls64(l);
}

long형 타입 인수 @l에 대해 lsb 부터 msb 로의 비트 검색을 하여 마지막 set bit를 알아오고 못 찾은 경우 0을 리턴한다. (based=1)

  • 예)
    • 0 -> 0
    • 1 -> 1
    • 0x8000_0000 -> 32
    • 0x8000_0000_0000_0000 -> 64 (64bits only)

 


Atomic 비트 조작 operations

다음 API들을 알아본다.

  • set_bit()
  • clear_bit()
  • change_bit()
  • test_and_set_bit()
  • test_and_clear_bit()
  • test_and_change_bit()

 

Atomic 비트 조작 – Generic (ARM64 포함)

ARM64 아키텍처는 atomic api를 사용하여 처리한다.

 

set_bit() – Generic (ARM64 포함)

include/asm-generic/bitops/atomic.h

static inline void set_bit(unsigned int nr, volatile unsigned long *p)
{
        p += BIT_WORD(nr);
        atomic_long_or(BIT_MASK(nr), (atomic_long_t *)p);
}

unsigned long 타입 값(or 배열) @p에서 지정된 비트를 atomic 하게 1로 설정한다.

  • 예)
    • nr = 0 -> bit0 설정
    • nr = 1 -> bit1 설정
    • nr = 2 -> bit2 설정

 

clear_bit() – Generic (ARM64 포함)

include/asm-generic/bitops/atomic.h

static inline void clear_bit(unsigned int nr, volatile unsigned long *p)
{
        p += BIT_WORD(nr);
        atomic_long_andnot(BIT_MASK(nr), (atomic_long_t *)p);
}

unsigned long 타입 값(or 배열) @p에서 지정된 비트를 atomic 하게 0으로 클리어한다.

  • 예)
    • nr = 0 -> bit0 클리어
    • nr = 1 -> bit1 클리어
    • nr = 2 -> bit2 클리어

 

change_bit() – Generic (ARM64 포함)

include/asm-generic/bitops/atomic.h

static inline void change_bit(unsigned int nr, volatile unsigned long *p)
{
        p += BIT_WORD(nr);
        atomic_long_xor(BIT_MASK(nr), (atomic_long_t *)p);
}

unsigned long 타입 값(or 배열) @p에서 지정된 비트를 atomic 하게 뒤바꾼다(flip)

  • 예)
    • nr = 0 -> bit0 flip
    • nr = 1 -> bit1 flip
    • nr = 2 -> bit2 flip

 

test_and_set_bit() – Generic (ARM64 포함)

include/asm-generic/bitops/atomic.h

static inline int test_and_set_bit(unsigned int nr, volatile unsigned long *p)
{
        long old;
        unsigned long mask = BIT_MASK(nr);

        p += BIT_WORD(nr);
        if (READ_ONCE(*p) & mask)
                return 1;

        old = atomic_long_fetch_or(mask, (atomic_long_t *)p);
        return !!(old & mask);
}

unsigned long 타입 값(or 배열) @p에서 지정된 비트의 설정 여부를 0과 1로 알아오고, 결과와 상관 없이 해당 비트를 atomic 하게 1로 설정한다.

  • 예)
    • nr = 0 -> bit0 설정, 기존 읽은 old 값 반환
    • nr = 1 -> bit1 설정, 기존 읽은 old 값 반환
    • nr = 2 -> bit2 설정, 기존 읽은 old 값 반환

 

test_and_clear_bit() – Generic (ARM64 포함)

include/asm-generic/bitops/atomic.h

static inline int test_and_clear_bit(unsigned int nr, volatile unsigned long *p)
{
        long old;
        unsigned long mask = BIT_MASK(nr);

        p += BIT_WORD(nr);
        if (!(READ_ONCE(*p) & mask))
                return 0;

        old = atomic_long_fetch_andnot(mask, (atomic_long_t *)p);
        return !!(old & mask);
}

unsigned long 타입 값(or 배열) @p에서 지정된 비트의 설정 여부를 0과 1로 알아오고, 결과와 상관 없이 해당 비트를 atomic 하게 0으로 클리어한다.

  • 예)
    • nr = 0 -> bit0 클리어, 기존 읽은 old 값 반환
    • nr = 1 -> bit1 클리어, 기존 읽은 old 값 반환
    • nr = 2 -> bit2 클리어, 기존 읽은 old 값 반환

 

test_and_change_bit() – Generic (ARM64 포함)

include/asm-generic/bitops/atomic.h

static inline int test_and_change_bit(unsigned int nr, volatile unsigned long *p)
{
        long old;
        unsigned long mask = BIT_MASK(nr);

        p += BIT_WORD(nr);
        old = atomic_long_fetch_xor(mask, (atomic_long_t *)p);
        return !!(old & mask);
}

unsigned long 타입 값(or 배열) @p에서 지정된 비트의 설정 여부를 0과 1로 알아오고, 결과와 상관 없이 해당 비트를 atomic 하게 뒤바꾼다(flip)

  • 예)
    • nr = 0 -> bit0 flip, 기존 읽은 old 값 반환
    • nr = 1 -> bit1 flip, 기존 읽은 old 값 반환
    • nr = 2 -> bit2 flip, 기존 읽은 old 값 반환

 


Atomic 비트 조작 – ARM32

set_bit()

arch/arm/include/asm/bitops.h

#define set_bit(nr,p)                   ATOMIC_BITOP(set_bit,nr,p)

unsigned long 타입 값(or 배열) @p에서 지정된 비트를 atomic 하게 1로 설정한다.

 

clear_bit()

arch/arm/include/asm/bitops.h

#define clear_bit(nr,p)                 ATOMIC_BITOP(clear_bit,nr,p)

unsigned long 타입 값(or 배열) @p에서 지정된 비트를 atomic 하게 0으로 클리어한다.

 

change_bit()

arch/arm/include/asm/bitops.h

#define change_bit(nr,p)                ATOMIC_BITOP(change_bit,nr,p)

 

test_and_set_bit()

arch/arm/include/asm/bitops.h

#define test_and_set_bit(nr,p)          ATOMIC_BITOP(test_and_set_bit,nr,p)

unsigned long 타입 값(or 배열) @p에서 지정된 비트의 설정 여부를 0과 1로 알아오고, 결과와 상관 없이 해당 비트를 atomic 하게 1로 설정한다.

 

test_and_clear_bit()

arch/arm/include/asm/bitops.h

#define test_and_clear_bit(nr,p)        ATOMIC_BITOP(test_and_clear_bit,nr,p)

unsigned long 타입 값(or 배열) @p에서 지정된 비트의 설정 여부를 0과 1로 알아오고, 결과와 상관 없이 해당 비트를 atomic 하게 0으로 클리어한다.

 

test_and_change_bit()

arch/arm/include/asm/bitops.h

#define test_and_change_bit(nr,p)       ATOMIC_BITOP(test_and_change_bit,nr,p)

unsigned long 타입 값(or 배열) @p에서 지정된 비트의 설정 여부를 0과 1로 알아오고, 결과와 상관 없이 해당 비트를 atomic 하게 뒤바꾼다(flip)

 

ATOMIC_BITOP()
#ifndef CONFIG_SMP
/*
 * The __* form of bitops are non-atomic and may be reordered.
 */
#define ATOMIC_BITOP(name,nr,p)                 \
        (__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
#else
#define ATOMIC_BITOP(name,nr,p)         _##name(nr,p)
#endif

SMP 시스템에서 nr 값의 상수 여부에 따라 다음 두 함수를 호출한다.

  • 예) set_bit()
    • 상수인 경우 __atomic_set_bit() 함수 호출
    • 변수인 경우 _set_bit() 함수 호출

 

____atomic_set_bit()

arch/arm/include/asm/bitops.h

static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
{
        unsigned long flags;
        unsigned long mask = BIT_MASK(bit);

        p += BIT_WORD(bit);

        raw_local_irq_save(flags);
        *p |= mask;
        raw_local_irq_restore(flags);
}

 

____atomic_clear_bit()

arch/arm/include/asm/bitops.h

static inline void ____atomic_clear_bit(unsigned int bit, volatile unsigned long *p)
{
        unsigned long flags;
        unsigned long mask = BIT_MASK(bit);

        p += BIT_WORD(bit);

        raw_local_irq_save(flags);
        *p &= ~mask;
        raw_local_irq_restore(flags);
}

 

____atomic_change_bit()

arch/arm/include/asm/bitops.h

static inline void ____atomic_change_bit(unsigned int bit, volatile unsigned long *p)
{
        unsigned long flags;
        unsigned long mask = BIT_MASK(bit);

        p += BIT_WORD(bit);

        raw_local_irq_save(flags);
        *p ^= mask;
        raw_local_irq_restore(flags);
}

 

____atomic_test_and_set_bit()

arch/arm/include/asm/bitops.h

static inline int
____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)
{
        unsigned long flags;
        unsigned int res;
        unsigned long mask = BIT_MASK(bit);

        p += BIT_WORD(bit);

        raw_local_irq_save(flags);
        res = *p;
        *p = res | mask;
        raw_local_irq_restore(flags);

        return (res & mask) != 0;
}

 

____atomic_test_and_clear_bit()

arch/arm/include/asm/bitops.h

static inline int
____atomic_test_and_clear_bit(unsigned int bit, volatile unsigned long *p)
{
        unsigned long flags;
        unsigned int res;
        unsigned long mask = BIT_MASK(bit);

        p += BIT_WORD(bit);

        raw_local_irq_save(flags);
        res = *p;
        *p = res & ~mask;
        raw_local_irq_restore(flags);

        return (res & mask) != 0;
}

 

____atomic_test_and_change_bit()

arch/arm/include/asm/bitops.h

static inline int
____atomic_test_and_change_bit(unsigned int bit, volatile unsigned long *p)
{
        unsigned long flags;
        unsigned int res;
        unsigned long mask = BIT_MASK(bit);

        p += BIT_WORD(bit);

        raw_local_irq_save(flags);
        res = *p;
        *p = res ^ mask;
        raw_local_irq_restore(flags);

        return (res & mask) != 0;
}

 

_set_bit()

arch/arm/lib/setbit.S

bitop   _set_bit, orr

bitop 매크로를 사용하여 _set_bit에 대한 함수를 정의한다.

 

_clear_bit()

arch/arm/lib/clearbit.S

bitop   _clear_bit, bic

bitop 매크로를 사용하여 _clear_bit에 대한 함수를 정의한다.

 

_chage_bit()

arch/arm/lib/changebit.S

bitop   _change_bit, eor

bitop 매크로를 사용하여 _chage_bit에 대한 함수를 정의한다.

 

_test_and_set_bit()

arch/arm/lib/testsetbit.S

testop  _test_and_set_bit, orreq, streq

bitop 매크로를 사용하여 _test_and_set_bit에 대한 함수를 정의한다.

 

_test_and_clear_bit()

arch/arm/lib/testclearbit.S

testop  _test_and_clear_bit, bicne, strne

bitop 매크로를 사용하여 _test_and_clear_bit에 대한 함수를 정의한다.

 

_test_and_change_bit()

arch/arm/lib/testchangebit.S

testop  _test_and_change_bit, eor, str

bitop 매크로를 사용하여 _test_and_change_bit에 대한 함수를 정의한다.

 

bitop 매크로

arch/arm/lib/bitops.h

#if __LINUX_ARM_ARCH__ >= 6
        .macro  bitop, name, instr
ENTRY(  \name           )
UNWIND( .fnstart        )
        ands    ip, r1, #3
        strneb  r1, [ip]                @ assert word-aligned
        mov     r2, #1
        and     r3, r0, #31             @ Get bit offset
        mov     r0, r0, lsr #5
        add     r1, r1, r0, lsl #2      @ Get word offset
#if __LINUX_ARM_ARCH__ >= 7 && defined(CONFIG_SMP)
        .arch_extension mp
        ALT_SMP(W(pldw) [r1])
        ALT_UP(W(nop))
#endif
        mov     r3, r2, lsl r3
1:      ldrex   r2, [r1]
        \instr  r2, r2, r3
        strex   r0, r2, [r1]
        cmp     r0, #0
        bne     1b
        bx      lr
UNWIND( .fnend          )
ENDPROC(\name           )
        .endm

 


Atomic 비트 검색 operations

다음 함수들을 알아본다.

  • find_next_bit()
  • find_next_zero_bit()
  • find_next_and_bit()
  • find_first_bit()
  • find_first_zero_bit()
  • find_last_bit()

 

비트 검색 – Generic (ARM64 포함)

 

find_next_bit()

include/asm-generic/bitops/find.h

/**
 * find_next_bit - find the next set bit in a memory region
 * @addr: The address to base the search on
 * @offset: The bitnumber to start searching at
 * @size: The bitmap size in bits
 *
 * Returns the bit number for the next set bit
 * If no bits are set, returns @size.
 */

 

lib/find_bit.c

/*
 * Find the next set bit in a memory region.
 */
unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
                            unsigned long offset)
{
        return _find_next_bit(addr, NULL, size, offset, 0UL, 0);
}
EXPORT_SYMBOL(find_next_bit);

@addr 비트맵의 @size 제한 범위내에서 @offset 비트를 포함하여 비트가 1로 설정된 비트 위치를 찾아 알아온다. 발견하지 못한 경우는 size 값을 반환한다. (based 0)

  • 예)
    • addr=0xffff_8003, size=16, offset=-1 -> 16
    • addr=0xffff_8003, size=16, offset=0 -> 0
    • addr=0xffff_8003, size=16, offset=1 -> 1
    • addr=0xffff_8003, size=16, offset=2 -> 15
    • addr=0xffff_8003, size=16, offset=14 -> 15
    • addr=0xffff_8003, size=16, offset=15 -> 15
    • addr=0xffff_8003, size=16, offset=16 -> 16

 

다음 그림은 find_next_bit()에서 검색하는 비트들을 하늘색으로 표시하였다.

 

find_next_zero_bit()

include/asm-generic/bitops/find.h

/**
 * find_next_zero_bit - find the next cleared bit in a memory region
 * @addr: The address to base the search on
 * @offset: The bitnumber to start searching at
 * @size: The bitmap size in bits
 *
 * Returns the bit number of the next zero bit
 * If no bits are zero, returns @size.
 */

 

lib/find_bit.c

unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size,
                                 unsigned long offset)
{
        return _find_next_bit(addr, NULL, size, offset, ~0UL, 0);
}
EXPORT_SYMBOL(find_next_zero_bit);

@addr 비트맵의 @size 제한 범위내에서 @offset 비트를 포함하여 비트가 0으로 클리어된 비트 위치를 찾아 알아온다. 발견하지 못한 경우는 size 값을 반환한다.  (based 0)

  • 예)
    • addr=0xffff_800a, size=16, offset=-1 -> 16
    • addr=0xffff_800a, size=16, offset=0 -> 0
    • addr=0xffff_800a, size=16, offset=1 -> 2
    • addr=0xffff_800a, size=16, offset=2 -> 2
    • addr=0xffff_800a, size=16, offset=3 -> 4
    • addr=0xffff_800a, size=16, offset=14 -> 14
    • addr=0xffff_800a, size=16, offset=15 -> 16
    • addr=0xffff_800a, size=16, offset=16 -> 16

 

다음 그림은 find_next_zero_bit()에서 검색하는 비트들을 하늘색으로 표시하였다.

 

find_next_and_bit()

include/asm-generic/bitops/find.h

/**
 * find_next_and_bit - find the next set bit in both memory regions
 * @addr1: The first address to base the search on
 * @addr2: The second address to base the search on
 * @offset: The bitnumber to start searching at
 * @size: The bitmap size in bits
 *
 * Returns the bit number for the next set bit
 * If no bits are set, returns @size.
 */

 

lib/find_bit.c

unsigned long find_next_and_bit(const unsigned long *addr1,
                const unsigned long *addr2, unsigned long size,
                unsigned long offset)
{
        return _find_next_bit(addr1, addr2, size, offset, 0UL, 0);
}
EXPORT_SYMBOL(find_next_and_bit);

@addr과 @addr2 두 비트맵에서 @size 제한 범위내에서 @offset 비트를 포함하여 비트가 둘 다 1로 설정된 비트 위치를 찾아 알아온다. 발견하지 못한 경우는 size 값을 반환한다. (based 0)

  • 예)
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=-1 -> 16
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=0 -> 1
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=1 -> 1
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=2 -> 3
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=3 -> 3
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=4 -> 15
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=14 -> 15
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=15 -> 15
    • addr1=0xffff_800a, addr2=0xffff_800f, size=16, offset=16 -> 16

 

다음 그림은 find_next_and_bit()에서 검색하는 비트들을 하늘색으로 표시하였다.

 

_find_next_bit()

lib/find_bit.c

/*
 * This is a common helper function for find_next_bit, find_next_zero_bit, and
 * find_next_and_bit. The differences are:
 *  - The "invert" argument, which is XORed with each fetched word before
 *    searching it for one bits.
 *  - The optional "addr2", which is anded with "addr1" if present.
 */
static unsigned long _find_next_bit(const unsigned long *addr1,
                const unsigned long *addr2, unsigned long nbits,
                unsigned long start, unsigned long invert, unsigned long le)
{
        unsigned long tmp, mask;

        if (unlikely(start >= nbits))
                return nbits;

        tmp = addr1[start / BITS_PER_LONG];
        if (addr2)
                tmp &= addr2[start / BITS_PER_LONG];
        tmp ^= invert;

        /* Handle 1st word. */
        mask = BITMAP_FIRST_WORD_MASK(start);
        if (le)
                mask = swab(mask);

        tmp &= mask;

        start = round_down(start, BITS_PER_LONG);

        while (!tmp) {
                start += BITS_PER_LONG;
                if (start >= nbits)
                        return nbits;

                tmp = addr1[start / BITS_PER_LONG];
                if (addr2)
                        tmp &= addr2[start / BITS_PER_LONG];
                tmp ^= invert;
        }

        if (le)
                tmp = swab(tmp);

        return min(start + __ffs(tmp), nbits);
}

이 함수는 find_next_bit(), find_next_zero_bit() 및 find_next_and_bit() 함수에서 사용되는 헬퍼 함수이다.

  • 코드 라인 7~8에서 시작 위치 @start가 @nbits 이상인 경우 에러 의미를 갖는 @nbits를 반환한다.
  • 코드 라인 10~13에서 비트맵 @addr1에서 @start에 해당하는 unsigned long 만큼을 알아온다. 만일 비트맵 @addr2가 지정된 경우 동일하게 @start 에 해당하는 unsigned long 만큼을 알아온 후 먼저 알아온 값과 and 연산을 수행한다. 마지막으로 @invert 값과 exclusive or 연산을 수행하여 tmp에 대입한다.
  • 코드 라인 20~24에서 tmp 값에서 @start 비트 미만의 모든 비트를 모두 0으로 클리어하여 처리하지 않도록 한다.
    • 예) tmp=3, @start=1
      • tmp=2 (bit1 미만의 bit0만 클리어하였다.)
    • 만일 리틀 엔디안 @le가 설정된 경우라면 tmp 값을 swap 한다.
      • 예) tmp=0x1122_3344_5566_7788, le=1
        • tmp=0x8877_6655_4433_2211
  • 코드 라인 26에서 @start 값을 BITS_PER_LONG 단위로 절삭한다.
  • 코드 라인 28~37에서 만일 tmp 값이 0인 경우 다음 unsigned long 값을 알아온다. 물론 start가 @nbits 이상인 경우 에러 의미를 갖는 @nbits를 반환한다.
  • 코드 라인 39~40에서 만일 리틀 엔디안 @le가 설정된 경우라면 tmp 값을 원래 값을 갖기 위해 swap 한다.
  • 코드 라인 42에서 tmp에서 1로 설정된 가장 첫(가장 우측) 비트의 위치를 반환한다.

 

find_first_zero_bit() – Generic (ARM64 포함)

lib/find_bit.c

/*
 * Find the first cleared bit in a memory region.
 */
unsigned long find_first_zero_bit(const unsigned long *addr, unsigned long size)
{
        unsigned long idx;

        for (idx = 0; idx * BITS_PER_LONG < size; idx++) {
                if (addr[idx] != ~0UL)
                        return min(idx * BITS_PER_LONG + ffz(addr[idx]), size);
        }

        return size;
}
EXPORT_SYMBOL(find_first_zero_bit);

@addr 비트맵의 @size 제한 범위내에서 비트가 0으로 클리어된 가장 처음 비트 위치를 찾아 알아온다. 발견하지 못한 경우는 size 값을 반환한다. (based 0)

  • 예) addr=0xffff_800a, size=16 -> 0

 

다음 그림은 find_first_zero_bit()에서 검색한 첫 번째 비트를 하늘색으로 표시하였다.

 

find_first_bit() – Generic (ARM64 포함)

lib/find_bit.c

/*
 * Find the first set bit in a memory region.
 */
unsigned long find_first_bit(const unsigned long *addr, unsigned long size)
{
        unsigned long idx;

        for (idx = 0; idx * BITS_PER_LONG < size; idx++) {
                if (addr[idx])
                        return min(idx * BITS_PER_LONG + __ffs(addr[idx]), size);
        }

        return size;
}
EXPORT_SYMBOL(find_first_bit);

@addr 비트맵의 @size 제한 범위내에서 비트가 1로 설정된 가장 처음(가장 우측) 비트 위치를 찾아 알아온다. 발견하지 못한 경우는 size 값을 반환한다. (based 0)

  • 예) addr=0xffff_800a, size=16 -> 1

 

다음 그림은 find_first_bit()에서 검색한 첫 번째 비트를 하늘색으로 표시하였다.

 

find_last_bit() – Generic (ARM, ARM64 포함)

lib/find_bit.c

unsigned long find_last_bit(const unsigned long *addr, unsigned long size)
{
        if (size) {
                unsigned long val = BITMAP_LAST_WORD_MASK(size);
                unsigned long idx = (size-1) / BITS_PER_LONG;

                do {
                        val &= addr[idx];
                        if (val)
                                return idx * BITS_PER_LONG + __fls(val);

                        val = ~0ul;
                } while (idx--);
        }
        return size;
}
EXPORT_SYMBOL(find_last_bit);

@addr 비트맵의 @size 제한 범위내에서 비트가 1로 설정된 가장 마지막(가장 좌측) 비트 위치를 찾아 알아온다. 발견하지 못한 경우는 size 값을 반환한다.

  • 예) addr=0xffff_800a, size=16 -> 15

 

다음 그림은 find_last_bit()에서 검색한 첫 번째 비트를 하늘색으로 표시하였다.

 


비트 검색 – ARM32

find_first_zero_bit()

arch/arm/include/asm/bitops.h

#ifndef __ARMEB__
#define find_first_zero_bit(p,sz)       _find_first_zero_bit_le(p,sz)
#else
#define find_first_zero_bit(p,sz)       _find_first_zero_bit_be(p,sz)
#endif

비트맵의 size 제한 범위내에서 첫 번째 zero 비트 위치를 반환한다. 발견하지 못한 경우는 size 값을 반환한다. (based 0)

  • fls()가 find_first_zero_bit()와 세 가지 다른 점
    • 처리되는 데이터 타입이 다르다.
      • fls()는 unsigned int 타입에서 동작한다.
      • find_last_zero_bit()는 비트 제한이 없는 비트맵에서 동작한다.
    • 검색 비트 종류(set(1) bit / clear(0))
      • fls()는 set(1) bit를 검색한다.
      • find_first_zero_bit는 clear(0) bit를 검색한다.
    • 리턴되는 포지션 값
      • fls()는 bit0을 검색하면 1로 리턴한다. 검색되지 않으면 0을 리턴한다.
      • find_last_zero_bit는 bit0을 검색하면 0으로 리턴한다. 검색되지 않으면 max_bits 값을 리턴한다.
  • 이 매크로는 엔디안에 따라 구현 루틴이 다르다.

 

bits0, bit1, bit32들이 모두 1인 64개의 비트마스크를 예로알아본다.

  • 논리적 비트마스크 (first zero bit=2)
    • 0x0000_0001_0000_0003 = 0b00000000_00000000_00000000_00000001_00000000_00000000_00000000_00000011
  • 리틀 엔디안으로 저장 시:
    • {0x03, 0x00, 00, 00, 01, 00, 00, 00}
    • 32비트 레지스터에 2번에 나누어 읽어들이면
      • 0x0000_0003
      • 0x0000_0001
  • 빅 엔디안으로 저장 시:
    • {0x00, 00, 00, 01, 00, 00, 00, 03}
    • 32비트 레지스터에 2번에 나누어 읽어들이면
      • 0x0000_0001
      • 0x0000_0003

 

한 번 더 알아보면,

  • 논리적 비트마스크 (first zero bit=8)
    • 0xff12_3456_78ff = 0b11111111_00010010_00110100_01010110_01111000_11111111
  • 리틀 엔디안으로 저장 시:
    • { 0xff, 0x78, 0x56, 0x34, 0x12, 0xff }
    • 32비트 레지스터에 2번에 나누어 읽어들이면
      • 0x3456_78ff
      • 0x0000_ff12
  • 빅 엔디안으로 저장 시:
    • {0xff, 0x12, 0x34, 0x56, 0x78, 0xff }
    • 32비트 레지스터에 2번에 나누어 읽어들이면
      • 0xff12_3456
      • 0x78ff_0000

 

bitops-4a

 

_find_first_zero_bit_le()

arch/arm/lib/findbit.S

/*
 * Purpose  : Find a 'zero' bit
 * Prototype: int find_first_zero_bit(void *addr, unsigned int maxbit);
 */
ENTRY(_find_first_zero_bit_le)
                teq     r1, #0  
                beq     3f
                mov     r2, #0
1:
 ARM(           ldrb    r3, [r0, r2, lsr #3]    )
 THUMB(         lsr     r3, r2, #3              )
 THUMB(         ldrb    r3, [r0, r3]            ) 
                eors    r3, r3, #0xff           @ invert bits
                bne     .L_found                @ any now set - found zero bit
                add     r2, r2, #8              @ next bit pointer
2:              cmp     r2, r1                  @ any more?
                blo     1b
3:              mov     r0, r1                  @ no free bits
                ret     lr
ENDPROC(_find_first_zero_bit_le)
  • 레지스터 용도
    • r0(addr): 비트맵이 위치한 주소
    • r1(maxbit): maxbit로 처리할 최대 비트 수
    • r0(결과): clear(0) bit를 찾은 비트 번호를 리턴
      • 0~n: 첫 번째 비트가 clear bit.
      • maxbit: 비트맵 범위내에서 찾지 못함(not found).
    • r2(currbit): 현재 검색하고 있는 비트
      • 다음 바이트를 검색할 때마다 8비트씩 증가
    • r3(8bit 비트맵): 읽어들인 1바이트(8비트) 비트맵
  • 비트맵에서 1 바이트(8비트) 단위로 읽어와서 clear(0) 비트가 있는지 검사하여 있으면 .L_found 레이블로 이동한다.
  • teq     r1, #0
    • 처리할 비트가 없으며(maxbit가 0이면) r0←0(error)를 리턴
  • mov      r2, #0
    • currbit를 0으로 대입(처음 시작 비트)
  • 1: ldrb    r3, [r0, r2, lsr #3]
    • r3에 비트맵 주소 + currbit / 8을 하여 1바이트를 읽어온다.
  • eors    r3, r3, #0xff
    • 읽어들인 데이터를 반전(invert) 시킨다.
  • bne     .L_found
    • 즉, 읽어들인 8비트 비트맵에 clear(bit)가 존재하면 .L_found로 이동
  • add     r3, r3, #8
    • currbit += 8
      • 다음 바이트로 이동
  • cmp r2, r1
    • 아직 끝나지 않았으면(curbit < maxbit) 1b로 이동
  • #3: mov r0, r1
    • r0 ← r1(maxbit) 값을 리턴(not found)
/*
 * One or more bits in the LSB of r3 are assumed to be set.
 */
.L_found:
                rsb     r0, r3, #0
                and     r3, r3, r0
                clz     r3, r3
                rsb     r3, r3, #31
                add     r0, r2, r3

                cmp     r1, r0                  @ Clamp to maxbit
                movlo   r0, r1
                ret     lr
  • rsb     r0, r3, #0
    • r0 = -r3
  • and     r3, r3, r0
    • r3 &= r0
    • 위의 rsb, and 명령은 r3 &= -r3와 동일한 연산이고 이렇게 하면 set(bit)가 lsb 쪽에 하나만 남게된다.

bitops-5

  • clz     r3, r3
    • 시작 비트만 남겨놨으니 그 앞쪽에 있는 0 bit를 카운트한다.
    • counter leading zero로 bit31부터 시작해서 set(1) bit가 발견되기 전까지의 clear(0) bit 수를 카운트 한다.
      • clz(0x90) -> 24
      • clz(0x7777_7777) -> 1
  • rsb     r3, r3, #31
    • 31 – r3(0 비트 갯수)
  • add     r0, r2, r3
    • r0 ← r2(curbit) + r3(바이트 내에서 찾은 위치)
  • movlo   r0, r1
    • 범위를 벗어난 위치에서 비트가 발견되면r0 ← maxbit(not found)를 리턴
    • r1(maxbit> < r0(찾은 위치)

 

find_first_bit() – ARM

arch/arm/include/asm/bitops.h

#define find_first_bit(p,sz)            _find_first_bit_le(p,sz)

비트맵의 size 범위내에서 set(1) 비트가 처음 발견되는 비트 번호를 알아온다. 발견하지 못한 경우 size를 반환한다. (based 0)

  • 구현은 엔디안에 따라 두 개로 나뉘어 있다.
  • 예) *addr[]=0x8800_0000_0000_0000, size=64
    • -> 59
  • 예) *addr[]=0x0000_0088, size=32
    • -> 3

 

/*
 * Purpose  : Find a 'one' bit
 * Prototype: int find_first_bit(const unsigned long *addr, unsigned int maxbit);
 */
ENTRY(_find_first_bit_le)
                teq     r1, #0  
                beq     3f
                mov     r2, #0
1:
 ARM(           ldrb    r3, [r0, r2, lsr #3]    )
 THUMB(         lsr     r3, r2, #3              )
 THUMB(         ldrb    r3, [r0, r3]            )
                movs    r3, r3
                bne     .L_found                @ any now set - found zero bit
                add     r2, r2, #8              @ next bit pointer
2:              cmp     r2, r1                  @ any more?
                blo     1b
3:              mov     r0, r1                  @ no free bits
                ret     lr
ENDPROC(_find_first_bit_le)
  • 레지스터 용도
    • r0(addr): 비트맵이 위치한 주소
    • r1(maxbit): maxbit로 처리할 최대 비트 수
    • r0(결과): clear(0) bit를 찾은 비트 번호를 리턴
      • 0~n: set bit가 발견된 비트 위치.
      • maxbit: 비트맵 범위내에서 찾지 못함(not found).
    • r2(currbit): 현재 검색하고 있는 비트(0, 8, 16, 24, …)
      • 다음 바이트를 검색할 때마다 8비트씩 증가
    • r3(8bit 비트맵): 읽어들인 1바이트(8비트) 비트맵

비트맵에서 1 바이트(8비트) 단위로 읽어와서 clear(0) 비트가 있는지 검사하여 있으면 .L_found 레이블로 이동한다.

  • teq     r1, #0
    • 처리할 비트가 없으며(maxbit가 0이면) r0←0(error)를 리턴
  • mov      r2, #0
    • currbit를 0으로 대입(처음 시작 비트)
  • 1: ldrb    r3, [r0, r2, lsr #3]
    • r3에 비트맵 주소 + currbit / 8을 하여 1바이트를 읽어온다.
  • bne     .L_found
    • 즉, 읽어들인 8비트 비트맵에 set bit가 존재하면 .L_found로 이동
  • add     r3, r3, #8
    • currbit += 8
      • 다음 바이트로 이동
  • cmp r2, r1
    • 아직 끝나지 않았으면(curbit < maxbit) 1b로 이동
  • 3: mov r0, r1
    • r0 ← r1(maxbit) 값을 리턴(not found)
  • .L_found는 _find_first_zero_bit_le()에서 사용한 루틴과 동일하다.
  • 예) byte *addr[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88}, size=64
    • -> 59
  • 예) byte *addr[] = {0x11, 0x00, 0x00, 0x00}, size=32
    • -> 0

 


hweight operations

hweight_long()

include/linux/bitops.h

static inline unsigned long hweight_long(unsigned long w)
{
        return sizeof(w) == 4 ? hweight32(w) : hweight64(w);
}

long 타입 w에 대해 1로 설정되어 있는 bit 수를 리턴한다.

 

hweight**()

include/asm-generic/bitops/const_hweight.h

/*
 * Generic interface.
 */
#define hweight8(w) (__builtin_constant_p(w) ? __const_hweight8(w) : __arch_hweight8(w))
#define hweight16(w) (__builtin_constant_p(w) ? __const_hweight16(w) : __arch_hweight16(w))
#define hweight32(w) (__builtin_constant_p(w) ? __const_hweight32(w) : __arch_hweight32(w))
#define hweight64(w) (__builtin_constant_p(w) ? __const_hweight64(w) : __arch_hweight64(w))

1로 설정되어 있는 bit 수를 리턴한다.

 

__const_hweight**()

include/asm-generic/bitops/const_hweight.h

/*
 * Compile time versions of __arch_hweightN()
 */
#define __const_hweight8(w)             \
        ((unsigned int)                 \
         ((!!((w) & (1ULL << 0))) +     \
          (!!((w) & (1ULL << 1))) +     \
          (!!((w) & (1ULL << 2))) +     \
          (!!((w) & (1ULL << 3))) +     \
          (!!((w) & (1ULL << 4))) +     \
          (!!((w) & (1ULL << 5))) +     \
          (!!((w) & (1ULL << 6))) +     \
          (!!((w) & (1ULL << 7)))))
 
#define __const_hweight16(w) (__const_hweight8(w)  + __const_hweight8((w)  >> 8 ))
#define __const_hweight32(w) (__const_hweight16(w) + __const_hweight16((w) >> 16))
#define __const_hweight64(w) (__const_hweight32(w) + __const_hweight32((w) >> 32))

1로 설정되어 있는 bit 수를 리턴한다.

 

__arch_hweight**()

include/asm-generic/bitops/arch_hweight.h

static inline unsigned int __arch_hweight32(unsigned int w)
{
        return __sw_hweight32(w);
}

static inline unsigned int __arch_hweight16(unsigned int w)
{
        return __sw_hweight16(w);
}

static inline unsigned int __arch_hweight8(unsigned int w)
{
        return __sw_hweight8(w);
}

static inline unsigned long __arch_hweight64(__u64 w)
{
        return __sw_hweight64(w);
}

 

__sw_hweight32()

lib/hweight.c

/**
 * hweightN - returns the hamming weight of a N-bit word
 * @x: the word to weigh
 *
 * The Hamming Weight of a number is the total number of bits set in it.
 */
unsigned int __sw_hweight32(unsigned int w)
{       
#ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER
        w -= (w >> 1) & 0x55555555;
        w =  (w & 0x33333333) + ((w >> 2) & 0x33333333);
        w =  (w + (w >> 4)) & 0x0f0f0f0f;
        return (w * 0x01010101) >> 24;
#else
        unsigned int res = w - ((w >> 1) & 0x55555555);
        res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
        res = (res + (res >> 4)) & 0x0F0F0F0F;
        res = res + (res >> 8);
        return (res + (res >> 16)) & 0x000000FF;
#endif
}
EXPORT_SYMBOL(__sw_hweight32);

 

__sw_hweight16()

lib/hweight.c

unsigned int __sw_hweight16(unsigned int w)
{
        unsigned int res = w - ((w >> 1) & 0x5555);
        res = (res & 0x3333) + ((res >> 2) & 0x3333);
        res = (res + (res >> 4)) & 0x0F0F;
        return (res + (res >> 8)) & 0x00FF;
}
EXPORT_SYMBOL(__sw_hweight16);

 

__sw_hweight8()

lib/hweight.c

unsigned int __sw_hweight8(unsigned int w)
{
        unsigned int res = w - ((w >> 1) & 0x55);
        res = (res & 0x33) + ((res >> 2) & 0x33);
        return (res + (res >> 4)) & 0x0F;
}
EXPORT_SYMBOL(__sw_hweight8);

 

__sw_hweight64()

lib/hweight.c

unsigned long __sw_hweight64(__u64 w)
{
#if BITS_PER_LONG == 32
        return __sw_hweight32((unsigned int)(w >> 32)) +
               __sw_hweight32((unsigned int)w);
#elif BITS_PER_LONG == 64
#ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER
        w -= (w >> 1) & 0x5555555555555555ul;
        w =  (w & 0x3333333333333333ul) + ((w >> 2) & 0x3333333333333333ul);
        w =  (w + (w >> 4)) & 0x0f0f0f0f0f0f0f0ful;
        return (w * 0x0101010101010101ul) >> 56;
#else
        __u64 res = w - ((w >> 1) & 0x5555555555555555ul);
        res = (res & 0x3333333333333333ul) + ((res >> 2) & 0x3333333333333333ul);
        res = (res + (res >> 4)) & 0x0F0F0F0F0F0F0F0Ful;
        res = res + (res >> 8);
        res = res + (res >> 16);
        return (res + (res >> 32)) & 0x00000000000000FFul;
#endif
#endif
}
EXPORT_SYMBOL(__sw_hweight64);

 

참고