Freeze (hibernation/suspend)

리눅스 시스템 차원의 서스펜드 또는 하이버네이션 기능으로 현재 동작중인 모든 유저 스레드와 몇 개의 커널 스레드들을 얼려 정지시킨다.

  • 유저 스레드들은 시그널 핸들링 코드에 의해 try_to_freeze() 호출이 되면서 freeze가 된다.
  • 커널 스레드들은 명확하게 적절한 위치에서 freeze 요청을 확인하고 호출해줘야 한다.
  • freeze 불가능한 작업큐에 있는 태스크들이 완료될 때 까지 대기하는데 딜레이 된 태스크들에 대해서는 전부 active 시킨 후 대기한다.

 

Freeze

freezing()

include/linux/freezer.h

/*
 * Check if there is a request to freeze a process
 */
static inline bool freezing(struct task_struct *p) 
{
        if (likely(!atomic_read(&system_freezing_cnt)))
                return false;
        return freezing_slow_path(p);
}

현재 태스크가 freeze 상태로 들어갈 수 있는지 여부를 확인하여 반환한다. (true=freeze 가능, false=freeze 불가능)

  • 코드 라인 06~07에서 높은 확률로 전역 system_freezing_cnt가 0인 경우 freeze 요청이 없으므로 false를 반환한다.
  • 코드 라인 08에서 현재 태스크가 freeze 상태로 들어갈 수 있는지 여부를 확인하여 반환한다.

 

freezing_slow_path()

kernel/freezer.c

/**
 * freezing_slow_path - slow path for testing whether a task needs to be frozen
 * @p: task to be tested
 *
 * This function is called by freezing() if system_freezing_cnt isn't zero
 * and tests whether @p needs to enter and stay in frozen state.  Can be
 * called under any context.  The freezers are responsible for ensuring the
 * target tasks see the updated state.
 */
bool freezing_slow_path(struct task_struct *p)
{
        if (p->flags & (PF_NOFREEZE | PF_SUSPEND_TASK))
                return false;

        if (test_thread_flag(TIF_MEMDIE))
                return false;

        if (pm_nosig_freezing || cgroup_freezing(p))
                return true;

        if (pm_freezing && !(p->flags & PF_KTHREAD))
                return true;

        return false;
}
EXPORT_SYMBOL(freezing_slow_path);

현재 태스크가 freeze 상태로 들어갈 수 있는지 여부를 확인하여 반환한다.

  • 코드 라인 12~13에서 현재 태스크에 PF_NOFREEZE 또는 PF_SUSPEND_TASK 플래그가 설정된 경우 freeze 하지 못하게 false를 반환한다.
  • 코드 라인 15~16에서 현재 태스크에 TIF_MEMDIE 플래그가 있는 경우 freeze 하지 못하게 false를 반환한다.
  • 코드 라인18~19에서 전역 pm_nosig_freezing이 true이거나 현재 태스크에 대해 cgroup에 freezing이 설정된 경우  freezing을 할 수 있게 true를 반환한다.
  • 코드 라인 21~22에서 전역 pm_freezing이 true이면서 현재 태스크가 커널 스레드가 아닌 경우 freezing을 할 수 있게 true를 반환한다.

 

try_to_freeze_tasks()

kernel/power/process.c

static int try_to_freeze_tasks(bool user_only)
{
        struct task_struct *g, *p;
        unsigned long end_time;
        unsigned int todo;
        bool wq_busy = false;
        struct timeval start, end;
        u64 elapsed_msecs64;
        unsigned int elapsed_msecs;
        bool wakeup = false;
        int sleep_usecs = USEC_PER_MSEC;

        do_gettimeofday(&start);

        end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs);

        if (!user_only)
                freeze_workqueues_begin();

        while (true) {
                todo = 0;
                read_lock(&tasklist_lock);
                for_each_process_thread(g, p) {
                        if (p == current || !freeze_task(p))
                                continue;

                        if (!freezer_should_skip(p))
                                todo++;
                }
                read_unlock(&tasklist_lock);

                if (!user_only) {
                        wq_busy = freeze_workqueues_busy();
                        todo += wq_busy;
                }

                if (!todo || time_after(jiffies, end_time))
                        break;

                if (pm_wakeup_pending()) {
                        wakeup = true;
                        break;
                }

                /*
                 * We need to retry, but first give the freezing tasks some
                 * time to enter the refrigerator.  Start with an initial
                 * 1 ms sleep followed by exponential backoff until 8 ms.
                 */
                usleep_range(sleep_usecs / 2, sleep_usecs);
                if (sleep_usecs < 8 * USEC_PER_MSEC)
                        sleep_usecs *= 2;
        }

        do_gettimeofday(&end);
        elapsed_msecs64 = timeval_to_ns(&end) - timeval_to_ns(&start);
        do_div(elapsed_msecs64, NSEC_PER_MSEC);
        elapsed_msecs = elapsed_msecs64;

        if (todo) {
                pr_cont("\n");
                pr_err("Freezing of tasks %s after %d.%03d seconds "
                       "(%d tasks refusing to freeze, wq_busy=%d):\n",
                       wakeup ? "aborted" : "failed",
                       elapsed_msecs / 1000, elapsed_msecs % 1000,
                       todo - wq_busy, wq_busy);

                if (!wakeup) {
                        read_lock(&tasklist_lock);
                        for_each_process_thread(g, p) {
                                if (p != current && !freezer_should_skip(p)
                                    && freezing(p) && !frozen(p))
                                        sched_show_task(p);
                        }
                        read_unlock(&tasklist_lock);
                }
        } else {
                pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000,
                        elapsed_msecs % 1000);
        }

        return todo ? -EBUSY : 0;
}

유저 스레드 및 커널 스레드들을 freeze 시도한다. 정상적으로 freeze한 경우 0을 반환하고 그렇지 않은 경우 -EBUSY를 반환한다. 인수로 user_only가 false인 경우 freeze 불가능한 작업큐에서 동작하는 태스크와 지연된 태스크들에 대해 모두 처리가 완료될 때까지 최대 20초를 기다린다.

 

  • 코드 라인 15에서 현재 시간으로 부터 20초를 타임아웃으로 설정한다.
    • freeze_timeout_msecs
      • 20 * MSEC_PER_SEC;
  • 코드 라인 17~18에서 user_only가 false인 경우 freeze 할 수 없는 워크 큐에 있는 지연된 태스크들을 모두 pool 워크큐로 옮기고 activate 시킨다.
  • 코드 라인 20~30에서 freeze되지 않고 아직 남아 있는 스레드 수를 todo로 산출한다.
    • 모든 스레드들에서 현재 태스크와 freeze되지 않은 태스크들에 대해 skip 한다.
    • freeze된 스레드들에서 freezer가 skip 해야 하는 경우가 아니었다면 todo를 증가시킨다.
  • 코드 라인 32~35에서 pool 워크큐에서 여전히 activate 된 수를 todo에 더한다.
    • user_only가 false인 경우 pool 워크큐가 여전히 active 된 태스크들이 존재하면 todo를 증가시킨다.
  • 코드 라인 37~38에서 처리할 항목이 없거나 타임 오버된 경우 루프를 탈출한다.
  • 코드 라인 40~43에서 suspend(freeze)가 포기된 경우 wakeup 준비한다.
  • 코드 라인 50~53에서 sleep_usecs의 절반 ~ sleep_usecs 범위에서 sleep 한 후 sleep_usces가 8ms 미만인 경우 sleep_usecs를 2배로 키운다.
    • 처음 sleep_usecs 값은 1000으로 1ms이다.
  • 코드 라인 55~58에서 소요 시간을 산출하여 elapsed_msecs에 저장한다.
  • 코드 라인 60~76에서 freeze 되지 않은 항목이 남아 있는 경우 이에 대한 에러 출력을 하고 해당 스레드에 대한 정보를 자세히 출력한다.

 

기타

다음은 freeze와 관련된 함수이다.

  • freeze_processes()
  • freeze_kernel_threads()
  • thaw_processes()

 

참고

 

per-cpu 동적 할당

 

Per-cpu 동적 할당

alloc_percpu-1

 

alloc_percpu()

include/linux/percpu.h

#define alloc_percpu(type)                                              \
        (typeof(type) __percpu *)__alloc_percpu(sizeof(type),           \
                                                __alignof__(type))

요청 타입의 per-cpu 메모리를  할당한다.

 

__alloc_percpu()

mm/percpu.c

/**
 * __alloc_percpu - allocate dynamic percpu area
 * @size: size of area to allocate in bytes
 * @align: alignment of area (max PAGE_SIZE)
 *
 * Equivalent to __alloc_percpu_gfp(size, align, %GFP_KERNEL).
 */
void __percpu *__alloc_percpu(size_t size, size_t align)
{
        return pcpu_alloc(size, align, false, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(__alloc_percpu);

요청 size 및 align 값으로 per-cpu 메모리를  할당한다.

 

alloc_percpu_gfp()

include/linux/percpu.h

#define alloc_percpu_gfp(type, gfp)                                     \
        (typeof(type) __percpu *)__alloc_percpu_gfp(sizeof(type),       \
                                                __alignof__(type), gfp)

요청 타입 및 gfp 플래그를 사용하여 per-cpu 메모리를  할당한다.

 

__alloc_percpu_gfp()

mm/percpu.c

/**
 * __alloc_percpu_gfp - allocate dynamic percpu area
 * @size: size of area to allocate in bytes
 * @align: alignment of area (max PAGE_SIZE)
 * @gfp: allocation flags
 *
 * Allocate zero-filled percpu area of @size bytes aligned at @align.  If
 * @gfp doesn't contain %GFP_KERNEL, the allocation doesn't block and can
 * be called from any context but is a lot more likely to fail.
 *
 * RETURNS:
 * Percpu pointer to the allocated area on success, NULL on failure.
 */
void __percpu *__alloc_percpu_gfp(size_t size, size_t align, gfp_t gfp)
{
        return pcpu_alloc(size, align, false, gfp);
}
EXPORT_SYMBOL_GPL(__alloc_percpu_gfp);

요청 size, align 및 gfp 플래그 값으로 per-cpu 메모리를  할당한다.

 

__alloc_reserved_percpu()

mm/percpu.c

/**
 * __alloc_reserved_percpu - allocate reserved percpu area
 * @size: size of area to allocate in bytes
 * @align: alignment of area (max PAGE_SIZE)
 *
 * Allocate zero-filled percpu area of @size bytes aligned at @align
 * from reserved percpu area if arch has set it up; otherwise,
 * allocation is served from the same dynamic area.  Might sleep.
 * Might trigger writeouts.
 *
 * CONTEXT:
 * Does GFP_KERNEL allocation.
 *
 * RETURNS:
 * Percpu pointer to the allocated area on success, NULL on failure.
 */
void __percpu *__alloc_reserved_percpu(size_t size, size_t align)
{
        return pcpu_alloc(size, align, true, GFP_KERNEL);
}

요청 size 및 align 값으로 reserved per-cpu 영역의 메모리를  할당한다.

 

pcpu 동적 할당  메인

pcpu_alloc-1

pcpu_alloc()

mm/percpu.c

/**
 * pcpu_alloc - the percpu allocator
 * @size: size of area to allocate in bytes
 * @align: alignment of area (max PAGE_SIZE)
 * @reserved: allocate from the reserved chunk if available
 * @gfp: allocation flags
 *
 * Allocate percpu area of @size bytes aligned at @align.  If @gfp doesn't
 * contain %GFP_KERNEL, the allocation is atomic.
 *
 * RETURNS:
 * Percpu pointer to the allocated area on success, NULL on failure.
 */
static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved,
                                 gfp_t gfp)
{
        static int warn_limit = 10;
        struct pcpu_chunk *chunk;
        const char *err;
        bool is_atomic = (gfp & GFP_KERNEL) != GFP_KERNEL;
        int occ_pages = 0;
        int slot, off, new_alloc, cpu, ret;
        unsigned long flags;
        void __percpu *ptr;

        /*
         * We want the lowest bit of offset available for in-use/free
         * indicator, so force >= 16bit alignment and make size even.
         */
        if (unlikely(align < 2)) 
                align = 2; 

        size = ALIGN(size, 2); 

        if (unlikely(!size || size > PCPU_MIN_UNIT_SIZE || align > PAGE_SIZE)) {
                WARN(true, "illegal size (%zu) or align (%zu) for "
                     "percpu allocation\n", size, align);
                return NULL;
        }

        spin_lock_irqsave(&pcpu_lock, flags);

요청 size와 align 값으로 per-cpu 메모리를 동적으로 할당한다. 모듈에서 호출하는 경우 reserved를 true로 호출하여 reserved per-cpu 영역에서 할당하게 한다. 할당 받을 공간이 부족한 경우 chunk를 새로 추가하는데, 만일 atomic 요청인 경우에는 확장하지 않고 실패 처리한다.

  • bool is_atomic = (gfp & GFP_KERNEL) != GFP_KERNEL;
    • GFP_KERNEL
      • define GFP_KERNEL      (__GFP_WAIT | __GFP_IO | __GFP_FS)
    • GFP_KERNEL 옵션을 사용하지 않는 경우 할당은 atomic이다.
    • alloc_percpu() 함수 등을 사용하여 호출하는 경우 항상 GFP_KERNEL 옵션을 사용하므로 atomic 조건을 사용하지 않는다.
    • 현재 커널에서 alloc_percpu_gfp() 함수를 사용하는 경우에는 gfp 옵션을 바꿀 수 있는데 실제 적용된 코드에는 아직까지 GFP_KERNEL 옵션 이외의 gfp 옵션을 사용한 사례가 없다. 향후 atomic 조건을 사용하려고 미리 준비해둔 함수이다.
    • atomic 조건으로 이 함수를 동작시키는 경우 populate된 페이지에서만 per-cpu 데이터를 할당할 수 있게 제한한다. atomic 조건이 아닌 경우는 chunk가 부족한 경우 chunk도 생성할 수 있고 unpopulate된 페이지들을 population 과정을 통해 사용할 수 있게한다.
  • if (unlikely(align < 2))
    • 낮은 확률로 align 인수가 2보다 작으면 최소 2로 설정한다. (최소 2 byte align)
  • if (unlikely(!size || …
    • 낮은 확률로 size가 0이거나 PCPU_MIN_UNIT_SIZE(32K)보다 크거나 또는 align이 PAGE_SIZE(4K) 보다 큰 경우 경고 출력 후 함수를 빠져나간다.
  • spin_lock_irqsave(&pcpu_lock, flags);
    • 할당 준비를 하는동안 interrupt를 막는다.
        /* serve reserved allocations from the reserved chunk if available */
        if (reserved && pcpu_reserved_chunk) {
                chunk = pcpu_reserved_chunk;

                if (size > chunk->contig_hint) {
                        err = "alloc from reserved chunk failed";
                        goto fail_unlock;
                }

                while ((new_alloc = pcpu_need_to_extend(chunk, is_atomic))) {
                        spin_unlock_irqrestore(&pcpu_lock, flags);
                        if (is_atomic ||
                            pcpu_extend_area_map(chunk, new_alloc) < 0) { 
                                err = "failed to extend area map of reserved chunk"; 
                                goto fail; 
                        } 
                        spin_lock_irqsave(&pcpu_lock, flags); 
                }

                off = pcpu_alloc_area(chunk, size, align, is_atomic, 
                                      &occ_pages); 
                if (off >= 0)
                        goto area_found;

                err = "alloc from reserved chunk failed";
                goto fail_unlock;
        }

module per-cpu 데이터를 위해 reserved allocation을 수행한다.

 

  • if (reserved && pcpu_reserved_chunk) {
    • 인수 reserved를 true로 호출하여 reserved 영역을 요청하면서 별도 옵션인 pcpu_reserved_chunk 구조체가 설정된 경우 reserved chunk에 할당을 한다.
    • 그렇지 않은 경우 normal chunk에 할당을 하기 위해 조건을 빠져나간다.
  • if (size > chunk->contig_hint) {
    • 요청 사이즈가 reserved chunk의 contig_hint 보다 큰 경우 할당이 불가능하여 실패하고 빠져나간다.
  • while ((new_alloc = pcpu_need_to_extend(chunk, is_atomic))) {
    • 현재 reserved chunk가 확장이 필요한 경우 루프를 수행한다.
    • pcpu_need_to_extend()
      • 현 chunk 영역 맵을 확장해야 하는지 결정하는데 맵 확장이 필요한 경우 필요 사이즈를 리턴한다.
        • 확장 시킬 맵 사이즈는 먼저 PCPU_DFL_MAP_ALLOC(16) 부터 시작하여 map_used와 margin을 비교하여 더 필요한 만큼 배수로 리턴한다.
          • first chunk를 만들 때에는 PERCPU_DYNAMIC_EARLY_SLOTS(128)개부터 시작하고 추가되는 dynamic chunk는 PCPU_DFL_MAP_ALLOC(16)개부터 시작한다.
    • pcpu_extend_area_map()
      • 요청 받은 new_alloc 사이즈만큼 맵을 확장하는데 PAGE_SIZE 보다 큰 경우 vzalloc()으로 할당하고 작은 경우 kzmalloc()으로 할당한다.
      • 맵 확장은 slub memory allocator가 동작할 경우에만 허용된다.
    • 맵 확장이 실패하면 함수를 빠져나간다.
  • pcpu_alloc_area()
    • chunk내에 영역을 할당한다.
    • 할당 후 chunk->contig_hint 에는 남은 공간 중 최대 할당 가능한 사이즈를 저장한다.
    • 이 함수 수행 후 chunk 멤버 변수인 map[], free_size, first_free, map_used 등이 영향을 받는다.
  • 영역 할당을 받은 경우 area_found: 레이블로 이동한다.

 

restart:
        /* search through normal chunks */
        for (slot = pcpu_size_to_slot(size); slot < pcpu_nr_slots; slot++) {
                list_for_each_entry(chunk, &pcpu_slot[slot], list) { 
                        if (size > chunk->contig_hint)
                                continue;

                        new_alloc = pcpu_need_to_extend(chunk, is_atomic);
                        if (new_alloc) {
                                if (is_atomic)
                                        continue;
                                spin_unlock_irqrestore(&pcpu_lock, flags);
                                if (pcpu_extend_area_map(chunk,
                                                         new_alloc) < 0) { 
                                        err = "failed to extend area map"; 
                                        goto fail; 
                                } 
                                spin_lock_irqsave(&pcpu_lock, flags); 
                                /* 
                                 * pcpu_lock has been dropped, need to 
                                 * restart cpu_slot list walking. 
                                 */ 
                                goto restart; 
                        } 

                        off = pcpu_alloc_area(chunk, size, align, is_atomic,
                                              &occ_pages); 
                        if (off >= 0)
                                goto area_found;
                }
        }

        spin_unlock_irqrestore(&pcpu_lock, flags);

reserved_chunk로 할당하지 않는 경우 dynamic 영역을 사용한다.

  •  for (slot = pcpu_size_to_slot(size); …
    • 요청 size가 있는 slot 부터 마지막 슬롯까지 루프를 돈다.
    • pcpu_size_to_slot()
      • 각 slot에는 size 범위가 있는데 해당되는 slot 번호를 리턴한다.
        • 예) 44K -> 13번 슬롯
    • 적합한 size를 찾을 때 free_size가 작은 슬롯부터 검색을 한다.
  •  list_for_each_entry(chunk, &pcpu_slot[slot], list) {
    • pcpu_slot[해당 slot].list 수 만큼 루프를 돈다.
  • if (size > chunk->contig_hint)
    • 요청 사이즈가 리스트에 담긴 chunk의 contig_hint(남은 최대 할당 가능 사이즈)보다 큰 경우 할당을 할 수 없으므로 다음 리스트 엔트리로 진행한다.
  • new_alloc = pcpu_need_to_extend()
    • size 할당 가능한 영역인 경우 이 루틴에 진입하여 pcpu_need_to_extend() 함수를 호출하여 이 chunk에서 맵을 확장할 필요가 있는지 확인 요청한다.
    • atomic하게 할당해야 하는 경우 map 배열에 최소 3개의 항목이 남아 있어야 한다.
      • 할당 시 align 및 size가 남아 추가한 맵 엔트리의 앞 뒤로 엔트리가 추가될 수 있다.
    • atomic하게 할당할 때 32개 이하의 엔트리가 남은 경우 맵 확장 함수를 스케쥴한다.
    • atomic하게 할당하는 경우가 아닌 경우 64개 이하의 맵 엔트리가 남은 경우 확장이 필요한 갯 수를 산출하여 반환하게 한다.
  • if (new_alloc) {
    • 맵을 확장해야 하는 경우 new_alloc가 0이 아닌 map size값을 담는다.
    • pcpu_extend_area_map() 함수를 사용하여 맵을 확장한다.
    • 실패한 경우 fail: 루틴으로 이동하고 성공한 경우 restart: 부터 다시 시작한다.
  •  off = pcpu_alloc_area()
    • 맵을 확장하지 않아도 되는 경우에 이 루틴에 진입하며 해당 chunk에 할당을 한다.
    • 할당 후 chunk->contig_hint 에는 남은 공간 중 최대 할당 가능한 사이즈를 저장한다.
    • 이 함수 수행 후 chunk 멤버 변수인 map[], free_size, first_free, map_used 등이 영향을 받는다.
  • if (off >= 0)
    • 영역 할당이 정상(-1이 아닌 값) 완료되면 area_found: 레이블로 이동한다.

 

        /*
         * No space left.  Create a new chunk.  We don't want multiple
         * tasks to create chunks simultaneously.  Serialize and create iff
         * there's still no empty chunk after grabbing the mutex.
         */
        if (is_atomic)
                goto fail;

        mutex_lock(&pcpu_alloc_mutex);

        if (list_empty(&pcpu_slot[pcpu_nr_slots - 1])) {
                chunk = pcpu_create_chunk();
                if (!chunk) {
                        mutex_unlock(&pcpu_alloc_mutex);
                        err = "failed to allocate new chunk";
                        goto fail;
                }

                spin_lock_irqsave(&pcpu_lock, flags);
                pcpu_chunk_relocate(chunk, -1);
        } else {
                spin_lock_irqsave(&pcpu_lock, flags);
        }

        mutex_unlock(&pcpu_alloc_mutex);
        goto restart;

할당을 못하고 이 루틴에 진입하게 되면 여기서 새로운 chunk를 만들어야 한다.

  • if (is_atomic)
    • atomic 할당을 요청한 경우라면 새로운 chunk를 만들 수 없어 fail: 레이블로 이동한다.
  •  mutex_lock(&pcpu_alloc_mutex);
    • chunk를 새로 할당 받는 동안 mutex로 보호받는다.
  • if (list_empty(&pcpu_slot[pcpu_nr_slots – 1])) {
    • 시간이 지났으므로 정확한 동기화를 위해  다시 현재 시점에서 pcpu_slot[]의 마지막 슬롯이 비어있는지 체크한다.
    • 역시 비어 있는 경우 chunk를 만들 계획이며 그렇지 않은 경우 spin_lock_irqsave() 함수를 호출하고 다시 restart: 레이블로 이동한다.
  • chunk = pcpu_create_chunk();
    • chunk를 새로 만들고 성공 시 chunk에는 0이 아닌 값이 들어간다.
    • 만들어진 새로운 chunk는 관리 구조체만 새로 할당 받아 초기화하고 실제 vmalloc 공간에 배치하지만 물리 페이지의 할당 및 매핑은 하지 않는다.
    • 당연히 populated 비트맵은 0으로 초기화된다.
  • if (!chunk) {
    • 실패 시 fail: 레이블로 이동한다.
  • pcpu_chunk_relocate(chunk, -1);
    • 새로 chunk가 만들어졌으므로 슬롯을 재 정비한 후 restart:로 이동하여 영역 할당을 다시 시도한다.

 

area_found:
        spin_unlock_irqrestore(&pcpu_lock, flags);

        /* populate if not all pages are already there */
        if (!is_atomic) {
                int page_start, page_end, rs, re;

                mutex_lock(&pcpu_alloc_mutex);

                page_start = PFN_DOWN(off);
                page_end = PFN_UP(off + size);

                pcpu_for_each_unpop_region(chunk, rs, re, page_start, page_end) {
                        WARN_ON(chunk->immutable);

                        ret = pcpu_populate_chunk(chunk, rs, re);

                        spin_lock_irqsave(&pcpu_lock, flags);
                        if (ret) {
                                mutex_unlock(&pcpu_alloc_mutex);
                                pcpu_free_area(chunk, off, &occ_pages);
                                err = "failed to populate";
                                goto fail_unlock;
                        }
                        pcpu_chunk_populated(chunk, rs, re);
                        spin_unlock_irqrestore(&pcpu_lock, flags);
                }

                mutex_unlock(&pcpu_alloc_mutex);
        }

        if (chunk != pcpu_reserved_chunk)
                pcpu_nr_empty_pop_pages -= occ_pages;

        if (pcpu_nr_empty_pop_pages < PCPU_EMPTY_POP_PAGES_LOW)
                pcpu_schedule_balance_work(); 

        /* clear the areas and return address relative to base address */
        for_each_possible_cpu(cpu) 
                memset((void *)pcpu_chunk_addr(chunk, cpu, 0) + off, 0, size); 

        ptr = __addr_to_pcpu_ptr(chunk->base_addr + off);
        kmemleak_alloc_percpu(ptr, size);
        return ptr;

atomic 할당 요청 중이 아니고 사용하려는 공간의 페이지가 unpopulate된 페이지의 경우 면 populate 처리를 진행한다.

  • 요청된 size의 할당을 위해 필요한 size 만큼의 페이지를 order-0 단위의 물리 페이지들을 할당 받아 vmalloc 공간에 매핑하게 한다.

 

  • if (!is_atomic) {
    • atomic 할당 요청 중이 아니면
  • page_start
    • 할당 받은 페이지의 시작 pfn
  • page_end
    • 할당 받은 페이지의 끝 pfn
  • pcpu_for_each_unpop_region()
    • chunk내 기준 시작 페이지부터 끝 페이지까지 un-populated 영역의 시작(rs)주소와 끝(re) 주소를 알아온다.
    • 페이지 번호들은 모두 물리 페이지 기준의 PFN이 아니고 chunk의 첫 유닛을 기준으로한다.
  • pcpu_populate_chunk()
    • 해당 chunk의 지정된 영역을 populate 시킨다.
    • 실제 물리 페이지를 할당받아 지정된 vmalloc 공간에 매핑한다.
  • pcpu_chunk_populated()
    • chunk에 해당 영역이 populate되었다는 정보를 기록한다.
      • chunk의 멤버변수 populated의 비트맵을 설정한다.
      • chunk의 멤버변수 nr_populated 및 pcpu_nr_empty_pop_pages 전역변수에 populated된 페이지 수를 추가한다.
  •  if (chunk != pcpu_reserved_chunk)
    • chunk가 reserved chunk가 아니면 pcpu_nr_empty_pop_pages 에서 occ_pages(영역이 점유된 페이지 수) 만큼 뺀다.
  • if (pcpu_nr_empty_pop_pages < PCPU_EMPTY_POP_PAGES_LOW)
    • 빈 populated 페이지 수가 2개 미만이면
  • pcpu_schedule_balance_work()
    • populate를 하기 위해 밸런스 워크를 수행한다.
    • pcpu_balance_workfn()
      • 이 함수에서는 하나의 빈 chunk에 atomic allocation이 가능하도록  populated free pages를 PCPU_EMPTY_POP_PAGES_LOW(2) ~ HIGH(4)까지 확보한다.
      • atomic operation을 위해 미리 populate된 페이지를 확보해 둔다.
  • memset(…)
    • 할당된 영역의 cpu 수만큼 offset를 벌려서 깨끗이 청소한다.
  • __addr_to_pcpu_ptr()
    • 주어진 주소로 per-cpu 포인터 주소로의 변환을 위해 사용되는 매크로이다.
    • per-cpu 포인터 주소는 unit 0에 해당하는 실제 데이터의 주소가 아니라 그 주소에서 delta를 뺀 주소를 가리킨다. 실제 사용시에는 이 값에  해당 cpu가 저장하고 있는 TPIDRPRW 값을 더해 사용한다.
      • TPIRDRPRW에는 first chunk를 처음 설정 시 cpu에 해당하는 유닛 offset + delta 값이 보관되어 있으며 이 값은 향후 바뀌지 않는다.
      • delta 값은 first chunk를 처음 설정 시 산출된 가장 낮은 노드(그룹)의 base offset 에서 per-cpu 섹션의 시작 주소를 뺀 가상의 주소이다.
        • 실제 static 변수의 경우 per-cpu 섹션에 컴파일 타임에 만들어진 주소를 가지며, dynamic 할당에서는 그 delta 값을 고려하여 미리 감소 시킨 값을 가리킨다. 따라서 어떠한 경우에도 per-cpu 포인터 값을 access하면 안된다.
        • static per-cpu 데이터이든 dynamic 하게 할당되어 사용하는 per-cpu 데이터이든 동일한 this_cpu_ptr()등의 API 함수를 사용하게 하기 위해 고려되었다.
  • kmemleak_alloc_percpu()
    • 메모리 leak 감시를 위해 오브젝트를 등록한다.
  • 성공하였으므로 함수를 빠져나간다.
fail_unlock:
        spin_unlock_irqrestore(&pcpu_lock, flags);
fail:
        if (!is_atomic && warn_limit) {
                pr_warning("PERCPU: allocation failed, size=%zu align=%zu atomic=%d, %s\n",
                           size, align, is_atomic, err);
                dump_stack();
                if (!--warn_limit)
                        pr_info("PERCPU: limit reached, disable warning\n");
        }
        if (is_atomic) {
                /* see the flag handling in pcpu_blance_workfn() */
                pcpu_atomic_alloc_failed = true;
                pcpu_schedule_balance_work();
        }
        return NULL;
}

할당에 실패한 경우 이 루틴으로 진입한다.

  • atomic 할당 요청을 받은 경우가 아니면 경고를 출력하고 return 한다.
  • atomic 할당 요청을 받은 경우 pcpu_schedule_balance_work() 루틴을 호출하여 별도로 스케쥴 할당을 받아 populated된 free 페이지의 할당을 준비한다.

 

pcpu_need_to_extend()

/**
 * pcpu_need_to_extend - determine whether chunk area map needs to be extended
 * @chunk: chunk of interest
 * @is_atomic: the allocation context
 *
 * Determine whether area map of @chunk needs to be extended.  If
 * @is_atomic, only the amount necessary for a new allocation is
 * considered; however, async extension is scheduled if the left amount is
 * low.  If !@is_atomic, it aims for more empty space.  Combined, this
 * ensures that the map is likely to have enough available space to
 * accomodate atomic allocations which can't extend maps directly.
 *
 * CONTEXT:
 * pcpu_lock.
 *
 * RETURNS:
 * New target map allocation length if extension is necessary, 0
 * otherwise.
 */
static int pcpu_need_to_extend(struct pcpu_chunk *chunk, bool is_atomic)
{
        int margin, new_alloc;

        if (is_atomic) {
                margin = 3; 

                if (chunk->map_alloc <
                    chunk->map_used + PCPU_ATOMIC_MAP_MARGIN_LOW &&
                    pcpu_async_enabled)
                        schedule_work(&chunk->map_extend_work);
        } else {
                margin = PCPU_ATOMIC_MAP_MARGIN_HIGH;
        }

        if (chunk->map_alloc >= chunk->map_used + margin)
                return 0;

        new_alloc = PCPU_DFL_MAP_ALLOC;
        while (new_alloc < chunk->map_used + margin)
                new_alloc *= 2;

        return new_alloc;
}

맵 배열이 부족한지 확인하고 필요한 배열 수를 리턴한다.

  • PCPU_ATOMIC_MAP_MARGIN_LOW, PCPU_ATOMIC_MAP_MARGIN_HIGH
    • 32, 64
  • if (is_atomic) {
    • is_atomic으로 요청 받은 경우 margin에 3을 대입한다. 할당 시 최소 3개의 엔트리는 있어야 한다.
      • 1개는 마지막 주소가 담아있고, 2개는 맵 엔트리에 size를 추가 시 align 만큼 공간이 남는 경우 그 영역에 대한 맵 엔트리와 할당할 size 영역을 제외한 남는 공간에 대한 엔트리로 사용될 수 있다.
    • pcpu_async_enabled 플래그가 동작하면서 맵이 사용된 수 + PCPU_ATOMIC_MAP_MARGIN_LOW(32)가 현재 맵 사용된 수보다 큰 경우
  • schedule_work(&chunk->map_extend_work);
    • schedule_work() 함수를 호출하여 스케쥴러를 통해 pcpu_map_extend_workfn() 함수를 동작시킨다.
    • 결국 32개 이하의 엔트리가 남는 경우 atomic 요청이 있는 경우는 미리 백그라운드로 맵 확장 함수를 스케쥴하여 둔다.
  • margin = PCPU_ATOMIC_MAP_MARGIN_HIGH;
    • is_atomic으로 요청 받은 경우가 아니면 더 큰 배열 수를 확보하기 위해 64를 margin으로 대입한다.
  • if (chunk->map_alloc >= chunk->map_used + margin)
    • 맵이 사용된 수 + margin 보다 맵 관리 갯수(map_alloc)가 더 커서 여유가 있으면 함수를 빠져나간다.
    • 겱국 atomic  요청이 아닌 경우 64개 이하의 엔트리가 남는 경우 확장해야 할 갯수를 구하게 한다.
  • new_alloc = PCPU_DFL_MAP_ALLOC;
    • 맵 배열 초기 갯수로 16을 new_alloc에 우선 대입한다.
  • while (new_alloc < chunk->map_used + margin)
    • 맵이 사용된 수 + margin 보다 맵 관리 갯수(map_alloc)가 작은 동안
  • new_alloc *= 2;
    • 할당할 사이즈를 두 배로 키운다.
      • 16, 32, 64, 128, …

 

pcpu_extend_area_map()

/**
 * pcpu_extend_area_map - extend area map of a chunk
 * @chunk: chunk of interest
 * @new_alloc: new target allocation length of the area map
 *
 * Extend area map of @chunk to have @new_alloc entries.
 *
 * CONTEXT:
 * Does GFP_KERNEL allocation.  Grabs and releases pcpu_lock.
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
static int pcpu_extend_area_map(struct pcpu_chunk *chunk, int new_alloc)
{
        int *old = NULL, *new = NULL;
        size_t old_size = 0, new_size = new_alloc * sizeof(new[0]);
        unsigned long flags;

        new = pcpu_mem_zalloc(new_size);
        if (!new)
                return -ENOMEM;

        /* acquire pcpu_lock and switch to new area map */
        spin_lock_irqsave(&pcpu_lock, flags);

        if (new_alloc <= chunk->map_alloc)
                goto out_unlock;

        old_size = chunk->map_alloc * sizeof(chunk->map[0]);
        old = chunk->map;

        memcpy(new, old, old_size);

        chunk->map_alloc = new_alloc;
        chunk->map = new;
        new = NULL;

out_unlock:
        spin_unlock_irqrestore(&pcpu_lock, flags);

        /*
         * pcpu_mem_free() might end up calling vfree() which uses
         * IRQ-unsafe lock and thus can't be called under pcpu_lock.
         */
        pcpu_mem_free(old, old_size);
        pcpu_mem_free(new, new_size);

        return 0;
}

chunk 내의 맵을 확장하고 기존 맵을 복사한 후 기존 맵은 해제한다.

 

pcpu_map_extend_workfn()

static void pcpu_map_extend_workfn(struct work_struct *work)
{
        struct pcpu_chunk *chunk = container_of(work, struct pcpu_chunk,
                                                map_extend_work);
        int new_alloc;

        spin_lock_irq(&pcpu_lock);
        new_alloc = pcpu_need_to_extend(chunk, false);
        spin_unlock_irq(&pcpu_lock);

        if (new_alloc)
                pcpu_extend_area_map(chunk, new_alloc);
}
  • chunk 스케쥴러를 통해 호출된다.
  • pcpu_need_to_extend() 함수를 통해 맵이 부족한가 파악하여 맵이 부족한 경우 pcpu_extend_area_map() 함수를 호출하여 맵을 확장한다.

 

pcpu_alloc_area()

mm/percpu.c

/**
 * pcpu_alloc_area - allocate area from a pcpu_chunk
 * @chunk: chunk of interest
 * @size: wanted size in bytes
 * @align: wanted align
 * @pop_only: allocate only from the populated area
 * @occ_pages_p: out param for the number of pages the area occupies
 *
 * Try to allocate @size bytes area aligned at @align from @chunk.
 * Note that this function only allocates the offset.  It doesn't
 * populate or map the area.
 *
 * @chunk->map must have at least two free slots.
 *
 * CONTEXT:
 * pcpu_lock.
 *
 * RETURNS:
 * Allocated offset in @chunk on success, -1 if no matching area is
 * found.
 */
static int pcpu_alloc_area(struct pcpu_chunk *chunk, int size, int align,
                           bool pop_only, int *occ_pages_p)
{
        int oslot = pcpu_chunk_slot(chunk);
        int max_contig = 0;
        int i, off;
        bool seen_free = false;
        int *p;

        for (i = chunk->first_free, p = chunk->map + i; i < chunk->map_used; i++, p++) {
                int head, tail;
                int this_size;

                off = *p;
                if (off & 1)
                        continue;

                this_size = (p[1] & ~1) - off;

                head = pcpu_fit_in_area(chunk, off, this_size, size, align,
                                        pop_only);
                if (head < 0) {
                        if (!seen_free) {
                                chunk->first_free = i;
                                seen_free = true;
                        }
                        max_contig = max(this_size, max_contig);
                        continue;
                }

                /*
                 * If head is small or the previous block is free,
                 * merge'em.  Note that 'small' is defined as smaller
                 * than sizeof(int), which is very small but isn't too
                 * uncommon for percpu allocations.
                 */
                if (head && (head < sizeof(int) || !(p[-1] & 1))) {
                        *p = off += head;
                        if (p[-1] & 1)
                                chunk->free_size -= head;
                        else
                                max_contig = max(*p - p[-1], max_contig);
                        this_size -= head;
                        head = 0;
                }

                /* if tail is small, just keep it around */
                tail = this_size - head - size;
                if (tail < sizeof(int)) {
                        tail = 0;
                        size = this_size - head;
                }

                /* split if warranted */
                if (head || tail) {
                        int nr_extra = !!head + !!tail;

                        /* insert new subblocks */
                        memmove(p + nr_extra + 1, p + 1,
                                sizeof(chunk->map[0]) * (chunk->map_used - i));
                        chunk->map_used += nr_extra;

                        if (head) {
                                if (!seen_free) {
                                        chunk->first_free = i;
                                        seen_free = true;
                                }
                                *++p = off += head;
                                ++i;
                                max_contig = max(head, max_contig);
                        }
                        if (tail) {
                                p[1] = off + size;
                                max_contig = max(tail, max_contig);
                        }
                }

                if (!seen_free)
                        chunk->first_free = i + 1;

                /* update hint and mark allocated */
                if (i + 1 == chunk->map_used)
                        chunk->contig_hint = max_contig; /* fully scanned */
                else
                        chunk->contig_hint = max(chunk->contig_hint,
                                                 max_contig);

                chunk->free_size -= size;
                *p |= 1;

                *occ_pages_p = pcpu_count_occupied_pages(chunk, i);
                pcpu_chunk_relocate(chunk, oslot);
                return off;
        }

        chunk->contig_hint = max_contig;        /* fully scanned */
        pcpu_chunk_relocate(chunk, oslot);

        /* tell the upper layer that this chunk has no matching area */
        return -1;
}

per-cpu chunk로부터 영역을 할당해온다.

  • int oslot = pcpu_chunk_slot(chunk);
    • 요청 chunk가 있는 슬롯을 가져온다.
  • for (i = chunk->first_free, p = chunk->map + i; i < chunk->map_used; i++, p++) {
    • chunk의 맵에서 첫 free 엔트리부터 마지막 맵 엔트리까지 루프를 돈다.
  • head = pcpu_fit_in_area(chunk, off, this_size, size, align, pop_only);
    • 맵에서 사용 가능한 공간을 찾는다. 결과 값이 -1인 경우 다음 맵을 찾는다. (head=정렬로 인해 할당할 공간 앞에 생긴 자투리 영역)
  • if (head && (head < sizeof(int) || !(p[-1] & 1))) {
    • 할당할 영역이 정렬을 이유로 앞에 head 만큼의 공간이 발생하였고 이 공간이 정수형 하나 들어갈 수 없이 작거나 이전 엔트리가 사용 중인 경우 현재 엔트리에 head 만큼의 공간을 더한다. 이렇게 자투리 공간은 무시하고 사용하지 못하게 한다.
  • tail = this_size – head – size;   if (tail < sizeof(int)) {
    • 할당할 영역의 뒷 부분의 공간 역시 정수형 하나 들어갈 수 없이 작으면 그 영역을 제거한다.
  • if (head || tail) {
    • 찾은 map 공간에 align으로 인해 발생한 head 영역과 배치하고 남은 tail 영역에 대해 map 엔트리를 최대 2개만큼 추가한다.
    • 각각의 head와 tail 영역이 sizeof(int)보다 적을 경우에는 해당 엔트리를 만들지 않도록 한다
  • if (i + 1 == chunk->map_used) chunk->contig_hint = max_contig; else chunk->contig_hint = max(chunk->contig_hint, max_contig);
    • contig_hint를 갱신한다.
  • chunk->free_size -= size; *p |= 1;
    • free_size를 줄이고, 해당 맵을 사용중으로 표기한다.
  • *occ_pages_p = pcpu_count_occupied_pages(chunk, i);
    • 현재의 map 영역에서 온전한 페이지 수를 알아온다. 다만 이전 map 영역이나 이후 map 영역이 1페이지 이상의 free 영역인 경우 partial 영역도 포함시킨다)
  • pcpu_chunk_relocate(chunk, oslot);
    • 할당 후 free_size가 변했으므로 이에 따라 적절한 slot을 찾아 이동한다

 

pcpu_fit_in_area()

mm/percpu.c

/**
 * pcpu_fit_in_area - try to fit the requested allocation in a candidate area
 * @chunk: chunk the candidate area belongs to
 * @off: the offset to the start of the candidate area
 * @this_size: the size of the candidate area
 * @size: the size of the target allocation
 * @align: the alignment of the target allocation
 * @pop_only: only allocate from already populated region
 *
 * We're trying to allocate @size bytes aligned at @align.  @chunk's area
 * at @off sized @this_size is a candidate.  This function determines
 * whether the target allocation fits in the candidate area and returns the
 * number of bytes to pad after @off.  If the target area doesn't fit, -1
 * is returned.
 *
 * If @pop_only is %true, this function only considers the already
 * populated part of the candidate area.
 */
static int pcpu_fit_in_area(struct pcpu_chunk *chunk, int off, int this_size,
                            int size, int align, bool pop_only)
{
        int cand_off = off;

        while (true) {
                int head = ALIGN(cand_off, align) - off;
                int page_start, page_end, rs, re;

                if (this_size < head + size)
                        return -1;

                if (!pop_only)
                        return head;

                /*
                 * If the first unpopulated page is beyond the end of the
                 * allocation, the whole allocation is populated;
                 * otherwise, retry from the end of the unpopulated area.
                 */
                page_start = PFN_DOWN(head + off);
                page_end = PFN_UP(head + off + size);

                rs = page_start;
                pcpu_next_unpop(chunk, &rs, &re, PFN_UP(off + this_size));
                if (rs >= page_end)
                        return head;
                cand_off = re * PAGE_SIZE;
        }
}

찾은 빈 공간의 위치를 바로 사용하는 것이 아니라 align 시켜 재조정된 위치를 찾는다. 만일 pop_only가 true인 경우 활성화된 페이지내에서만 공간을 잡는다. -1은 적당한 공간이 없음. 0은 찾은 공간이 fit 됨. 양수 값은 찾은 공간 앞에 정렬로 인해 해당 양수 값 만큼의 자투리 공간이 발생함.

  • 참고로 현재 버전의 커널 코드에서는 pop_only가 항상 false로 진입되는 상태이다.

 

할당 페이지 범위 활성화

pcpu_populate_chunk()

mm/percpu-vm.c

/**
 * pcpu_populate_chunk - populate and map an area of a pcpu_chunk
 * @chunk: chunk of interest
 * @page_start: the start page
 * @page_end: the end page
 *
 * For each cpu, populate and map pages [@page_start,@page_end) into
 * @chunk.
 *
 * CONTEXT:
 * pcpu_alloc_mutex, does GFP_KERNEL allocation.
 */
static int pcpu_populate_chunk(struct pcpu_chunk *chunk, 
                               int page_start, int page_end)
{
        struct page **pages;

        pages = pcpu_get_pages(chunk);
        if (!pages)
                return -ENOMEM;

        if (pcpu_alloc_pages(chunk, pages, page_start, page_end))
                return -ENOMEM;

        if (pcpu_map_pages(chunk, pages, page_start, page_end)) {
                pcpu_free_pages(chunk, pages, page_start, page_end);
                return -ENOMEM;
        }
        pcpu_post_map_flush(chunk, page_start, page_end);

        return 0;
}

chunk의 요청 페이지 범위에 대해 활성화(population)한다.

  • pages = pcpu_get_pages(chunk); if (!pages) return -ENOMEM;
    • 필요 page descriptor 만큼 할당을 받는다. 할당 실패시 -ENOMEM으로 반환한다.
  • if (pcpu_alloc_pages(chunk, pages, page_start, page_end)) return -ENOMEM;
    • 필요 페이지 범위를 cpu 수 만큼 할당 받는다. 할당 실패시 -ENOMEM으로 반환한다.
  • if (pcpu_map_pages(chunk, pages, page_start, page_end)) { pcpu_free_pages(chunk, pages, page_start, page_end); return -ENOMEM; }
    • 할당받은 영역 페이지들을 vmalloc 공간에 매핑시킨다.
  • pcpu_post_map_flush(chunk, page_start, page_end);
    • 매핑이 완료되면 TLB 캐시를 flush 한다.

 

pcpu_get_pages()

mm/percpu-vm.c

/**
 * pcpu_get_pages - get temp pages array
 * @chunk: chunk of interest
 *
 * Returns pointer to array of pointers to struct page which can be indexed
 * with pcpu_page_idx().  Note that there is only one array and accesses
 * should be serialized by pcpu_alloc_mutex.
 *
 * RETURNS:
 * Pointer to temp pages array on success.
 */
static struct page **pcpu_get_pages(struct pcpu_chunk *chunk_alloc)
{
        static struct page **pages;
        size_t pages_size = pcpu_nr_units * pcpu_unit_pages * sizeof(pages[0]);

        lockdep_assert_held(&pcpu_alloc_mutex);

        if (!pages)
                pages = pcpu_mem_zalloc(pages_size);
        return pages;
}

chunk 할당을 위해 전체 per-cpu 유닛에 필요한 page descriptor 사이즈 만큼 메모리 할당을 받아온다.

 

pcpu_map_pages()

mm/percpu-vm.c

/**
 * pcpu_map_pages - map pages into a pcpu_chunk
 * @chunk: chunk of interest
 * @pages: pages array containing pages to be mapped
 * @page_start: page index of the first page to map
 * @page_end: page index of the last page to map + 1
 *
 * For each cpu, map pages [@page_start,@page_end) into @chunk.  The
 * caller is responsible for calling pcpu_post_map_flush() after all
 * mappings are complete.
 *
 * This function is responsible for setting up whatever is necessary for
 * reverse lookup (addr -> chunk).
 */
static int pcpu_map_pages(struct pcpu_chunk *chunk,
                          struct page **pages, int page_start, int page_end)
{
        unsigned int cpu, tcpu;
        int i, err;

        for_each_possible_cpu(cpu) {
                err = __pcpu_map_pages(pcpu_chunk_addr(chunk, cpu, page_start),
                                       &pages[pcpu_page_idx(cpu, page_start)],
                                       page_end - page_start);
                if (err < 0)
                        goto err;

                for (i = page_start; i < page_end; i++)
                        pcpu_set_page_chunk(pages[pcpu_page_idx(cpu, i)],
                                            chunk);
        }
        return 0;
err:
        for_each_possible_cpu(tcpu) {
                if (tcpu == cpu)
                        break;
                __pcpu_unmap_pages(pcpu_chunk_addr(chunk, tcpu, page_start),
                                   page_end - page_start);
        }
        pcpu_post_unmap_tlb_flush(chunk, page_start, page_end);
        return err;
}

할당받은 영역 페이지들을 vmalloc 공간에 매핑시킨다

  • for_each_possible_cpu(cpu) { err = __pcpu_map_pages(pcpu_chunk_addr(chunk, cpu, page_start), &pages[pcpu_page_idx(cpu, page_start)], page_end – page_start);
    • possible cpu 수 만큼 루프를 돌며 per-cpu chunk를 vmalloc 공간에 매핑한다.
  • for (i = page_start; i < page_end; i++) pcpu_set_page_chunk(pages[pcpu_page_idx(cpu, i)], chunk);
    • 각 페이지(page->index)들이 pcpu_chunk를 가리키도록 설정한다.

 

__pcpu_map_pages()

mm/percpu-vm.c

static int __pcpu_map_pages(unsigned long addr, struct page **pages,
                            int nr_pages)
{
        return map_kernel_range_noflush(addr, nr_pages << PAGE_SHIFT,
                                        PAGE_KERNEL, pages);
}

할당받은 영역 페이지들을 요청 vmalloc 가상 주소 공간에 매핑시킨다

 

map_kernel_range_noflush()

mm/vmalloc.c

/**
 * map_kernel_range_noflush - map kernel VM area with the specified pages
 * @addr: start of the VM area to map
 * @size: size of the VM area to map
 * @prot: page protection flags to use
 * @pages: pages to map
 *
 * Map PFN_UP(@size) pages at @addr.  The VM area @addr and @size
 * specify should have been allocated using get_vm_area() and its
 * friends.
 *                          
 * NOTE:                                
 * This function does NOT do any cache flushing.  The caller is
 * responsible for calling flush_cache_vmap() on to-be-mapped areas
 * before calling this function.
 *
 * RETURNS:
 * The number of pages mapped on success, -errno on failure.
 */
int map_kernel_range_noflush(unsigned long addr, unsigned long size,
                             pgprot_t prot, struct page **pages)
{
        return vmap_page_range_noflush(addr, addr + size, prot, pages);
}

할당받은 영역 페이지들을 요청 vmalloc 가상 주소 공간에 vmap 매핑시킨다

 

참고

kmalloc vs vmalloc

kmalloc vs vmalloc 비교

 

커널에서 메모리를 할당할 때 요구 메모리 사이즈와 성능 사이에서 고려할 API가 다음 두 개가 있고 특징에 따라 구분하여 사용하여야 한다.

  • 할당 크기 등은 slab, slub 및 slob의 단위가 모두 다르며 최근에 가장 많이 사용하는 slub을 기준으로 나타낸다.

 

kmalloc의 특징

연속된 물리 메모리 공간을 할당 받으며 sleep하지 않으므로 인터럽트 핸들러에서도 사용할 수 있다.

  • 요구 메모리가 kmalloc 캐시 최대 크기 이하인 경우 kmalloc 캐시에서 slub object를 할당받는다.
    • 예) 4K 페이지: kmalloc 캐시(slub) 최대 크기=8K (2개 페이지)
  • 요구 메모리가 kmalloc 최대 크기보다 큰 경우 버디 시스템을 사용하여 할당한다.
    • 예) arm, arm64-4K 페이지: kmalloc 최대 크기=8M
  • 연속된 물리 메모리 사용하는 DMA 디바이스에서 사용한다.
  • 이미 사전에 1:1 매핑된 lowmem(ZONE_DMA 및 ZONE_NORMAL) 공간을 사용하므로 vmalloc()에 비해 성능이 빠르다.
  • 단점으로는 물리적으로 연속적인 공간을 차지하기 때문에 페이지 관리 측면에서 버디 시스템이 페이지의 파편화 관리에 어려움이 발생한다

 

vmalloc의 특징

연속된 가상 메모리 공간을 할당 받으며 sleep 가능하므로 인터럽트 핸들러에서는 사용할 수 없다.

  • kmalloc()과 다르게 여러 개의 조각난 연속된 물리 메모리를 모아서 관리하고 이들을 별도의 공간(커널은 vmalloc address space, userland는 user space address)에 매핑하여 사용하므로 관리 요소가 증가되고 각 cpu의 tlb 플러쉬가 필요하므로 cpu core가 많은 경우 사용에 어려움이 있다.
    • 커널은 VMALLOC address space를 통해 연속된 가상 주소 메모리를 제공하는데 이 공간은 아키텍처에 따라 다르다.
      • arm64: kernel 매핑 공간의 약 절반 (커널 빌드 옵션마다 위치가 달라진다.)
      • arm: 240M (0xf000_0000 ~ 0xff00_0000)
    • 매핑 시 vmalloc address space 공간을 사용하는 vmap() 매핑 함수를 사용한다.
  • 대용량의 커널 메모리가 필요하고 lowmem 부족으로 인한 압박이 예상되면서 속도는 약간 느려도 무방한 경우 페이지의 파편화를 줄여주기 위해 vmalloc()을 사용하는 것이 좋다.
  • 연속되지 않은 물리 주소 블록들을 연속된 가상 주소로 매핑하기 위해 기존 커널은 리스트에 각각의 vma_area(한 블럭의 매핑 장보)를 추가하여 관리했었다. 비록 커널이 많은 수의 vmap_area를 사용하지 않았지만 커다란 application에서 수백 또는 수천 개의 vmp_area를 검색하여 성능이 저하되는 것을 막기 위해 커널 2.6에서 레드 블랙 트리를 사용하여 관리하도록 바뀌었다.

 

위와 같은 이유로 대부분의 커널 코드에서는 kmalloc()을 더 많이 사용하고 다음과 같은 사용 목적으로 vmalloc()을 사용한다.

  • swap area용 자료구조
  • 모듈 공간 할당
  • 일부 디바이스 드라이버 등

 

kzalloc() vs vzalloc()

  • kzalloc() 함수는 kmalloc() 함수로 할당 받은 메모리를 0으로 초기화한다.
  • vzalloc() 함수는 vmalloc() 함수로 할당 받은 메모리를 0으로 초기화한다.

 

참고

GFP 플래그

밑줄 3개 ___GFP 플래그

직접 사용 금지 (밑줄 3개)

  • include/linux/gfp.h 내부에서만 사용되고 다른 소스에서는 직접 사용되지 않는다.
  • 커널 v4.4까지 새롭게 추가된 항목
    • ___GFP_ATOMIC
    • ___GFP_ACCOUNT
    • ___GFP_DIRECT_RECLAIM
    • ___GFP_KSWAPD_RECLAIM
  • 커널 v4.4까지 삭제된 항목
    • ___GFP_WAIT
    • ___GFP_NOACCOUNT
    • ___GFP_NOKSWAPD
/* Plain integer GFP bitmasks. Do not use this directly. */
#define ___GFP_DMA              0x01u
#define ___GFP_HIGHMEM          0x02u
#define ___GFP_DMA32            0x04u
#define ___GFP_MOVABLE          0x08u
#define ___GFP_WAIT             0x10u
#define ___GFP_HIGH             0x20u
#define ___GFP_IO               0x40u
#define ___GFP_FS               0x80u
#define ___GFP_COLD             0x100u
#define ___GFP_NOWARN           0x200u
#define ___GFP_REPEAT           0x400u
#define ___GFP_NOFAIL           0x800u
#define ___GFP_NORETRY          0x1000u
#define ___GFP_MEMALLOC         0x2000u
#define ___GFP_COMP             0x4000u
#define ___GFP_ZERO             0x8000u
#define ___GFP_NOMEMALLOC       0x10000u
#define ___GFP_HARDWALL         0x20000u
#define ___GFP_THISNODE         0x40000u
#define ___GFP_RECLAIMABLE      0x80000u
#define ___GFP_NOACCOUNT        0x100000u
#define ___GFP_NOTRACK          0x200000u
#define ___GFP_NO_KSWAPD        0x400000u
#define ___GFP_OTHER_NODE       0x800000u
#define ___GFP_WRITE            0x1000000u
  • ___GFP_DMA
    • ZONE_DMA 영역에 할당 요청한다.
  • ___GFP_HIGHMEM
    • ZONE_HIGHMEM 영역에 할당 요청한다.
  • ___GFP_DMA32
    • ZONE_DMA32 영역에 할당 요청한다.
  • ___GFP_MOVABLE
    • 두 가지 용도로 사용
      • ZONE_MOVABLE이 가용할 때 이 영역에 할당 요청한다.
      • 페이지 이주가 가능하도록 할당 요청한다.
  • ___GFP_RECLAIMABLE
    • 회수 가능한 페이지로 할당 요청한다.
  • ___GFP_WAIT
    • 메모리 할당을 하는 동안 sleep을 허용하도록 요청한다.
  • ___GFP_HIGH
    • 높은 우선 순위에서 처리되도록 요청한다.
  • ___GFP_IO
    • 메모리 할당을 하는 동안 어떠한 I/O 처리도 가능하도록 요청한다.
  • ___GFP_FS
    • 메모리 할당을 하는 동안 File System calls 가능하도록 요청한다.
  • ___GFP_COLD
    • 메모리 파편화에 영향을 덜 주도록 warm(hot) 페이지 대신 cold 페이지에서 관리하도록 요청한다
  • ___GFP_NOWARN
    • 메모리 할당이 실패할 때 어떠한 경고도 처리하지 않도록 요청한다.
  • ___GFP_REPEAT
    • 메모리 할당이 처음 실패하는 경우 한 번 더 시도하도록 요청한다.
  • ___GFP_NOFAIL
    • 실패를 허용하지 않고, 메모리 할당 요청에 대해 성공할 때까지 처리하도록 요청한다.
  • ___GFP_NORETRY
    • 메모리 할당 요청에 대해 실패 시 재시도 하지 않도록 요청한다.
  • ___GFP_MEMALLOC
    • 메모리 할당에 비상 영역을 사용할 수 있도록 요청한다.
  • ___GFP_COMP
    • 메타 데이터 또는 연속된 복합 페이지를 구성하도록 요청한다.
  • ___GFP_ZERO
    • 할당된 영역을 0으로 초기화 하도록 요청한다.
  • ___GFP_NOMEMALLOC
    • 메모리 할당에 비상 영역을 사용하지 않도록 요청한다.
  • ___GFP_HARDWALL
    • 현재 태스크에 지정된 cpuset 메모리 할당 정책을 사용하게 요청한다.
  • ___GFP_THISNODE
    • 지정된 노드에서만 할당을 허용한다.
  • ___GFP_NOACCOUNT
    • kmemcg(메모리 Control Group)의 사용량 통제를 받지 않도록 요청한다.
  • ___GFP_NOTRACK
    • kmemcheck를 사용한 디버그 트래킹을 허용하지 않도록 요청한다.
  •  ___GFP_NO_KSWAPD
    • 할당된 페이지가 Swap 파일로 이동할 수 없게 요청한다.
  • ___GFP_OTHERNODE
    • 리모트 노드에서 할당을 하도록 요청한다.
  • ___GFP_WRITE
    • dirty(쓰기용 파일 캐시)  페이지 할당을 요청한다.

 

밑줄 2개 __GFP 플래그

ZONE 관련 (밑줄 2개)

  • 하위 4개의 비트를 사용한다.
/*
 * Physical address zone modifiers (see linux/mmzone.h - low four bits)
 *
 * Do not put any conditional on these. If necessary modify the definitions
 * without the underscores and use them consistently. The definitions here may
 * be used in bit comparisons.
 */
#define __GFP_DMA       ((__force gfp_t)___GFP_DMA)
#define __GFP_HIGHMEM   ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32     ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE   ((__force gfp_t)___GFP_MOVABLE)  /* Page is movable */
#define GFP_ZONEMASK    (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
  • __GFP_DMA
    • ZONE_DMA 영역에 할당 요청
  • __GFP_HIGHMEM
    • ZONE_HIGHMEM 영역에 할당 요청
  • __GFP_DMA32
    • ZONE_DMA32 영역에 할당 요청
  • __GFP_MOVABLE
    • ZONE_MOVABLE이 허락되는 경우 이 영역에 할당 요청
  • GFP_ZONEMASK
    • 위의 4개 zone을 포함
    • GFP 플래그를 사용하지 하지 않을 때 일반적으로 ZONE_NORMAL을 의미한다.

 

Page Mobility 와 장소 hint 관련 (밑줄 2개)

/*
 * Page mobility and placement hints
 *
 * These flags provide hints about how mobile the page is. Pages with similar
 * mobility are placed within the same pageblocks to minimise problems due
 * to external fragmentation.
 *
 * __GFP_MOVABLE (also a zone modifier) indicates that the page can be
 *   moved by page migration during memory compaction or can be reclaimed.
 *
 * __GFP_RECLAIMABLE is used for slab allocations that specify
 *   SLAB_RECLAIM_ACCOUNT and whose pages can be freed via shrinkers.
 *
 * __GFP_WRITE indicates the caller intends to dirty the page. Where possible,
 *   these pages will be spread between local zones to avoid all the dirty
 *   pages being in one zone (fair zone allocation policy).
 *
 * __GFP_HARDWALL enforces the cpuset memory allocation policy.
 *
 * __GFP_THISNODE forces the allocation to be satisified from the requested
 *   node with no fallbacks or placement policy enforcements.
 *
 * __GFP_ACCOUNT causes the allocation to be accounted to kmemcg (only relevant
 *   to kmem allocations).
 */
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)
#define __GFP_WRITE     ((__force gfp_t)___GFP_WRITE)
#define __GFP_HARDWALL   ((__force gfp_t)___GFP_HARDWALL)
#define __GFP_THISNODE  ((__force gfp_t)___GFP_THISNODE)
#define __GFP_ACCOUNT   ((__force gfp_t)___GFP_ACCOUNT)
  • __GFP_RECLAIMABLE
    • 회수 가능한 페이지로 할당한다.
  • __GFP_WRITE
    • dirty(쓰기용 파일 캐시)  페이지 할당을 요청한다.
  • __GFP_HARDWALL
    • 현재 태스크에 지정된 cpuset 메모리 할당 정책을 사용하게 요청한다.
  • __GFP_THISNODE
    • 지정된 노드에서만 할당을 허용한다.
  • __GFP_ACCOUNT
    • kmemcg(메모리 Control Group)의 사용량 통제를 받지 않도록 요청한다.

 

워터마크 관련 (밑줄 2개)

/*
 * Watermark modifiers -- controls access to emergency reserves
 *
 * __GFP_HIGH indicates that the caller is high-priority and that granting
 *   the request is necessary before the system can make forward progress.
 *   For example, creating an IO context to clean pages.
 *
 * __GFP_ATOMIC indicates that the caller cannot reclaim or sleep and is
 *   high priority. Users are typically interrupt handlers. This may be
 *   used in conjunction with __GFP_HIGH
 *
 * __GFP_MEMALLOC allows access to all memory. This should only be used when
 *   the caller guarantees the allocation will allow more memory to be freed
 *   very shortly e.g. process exiting or swapping. Users either should
 *   be the MM or co-ordinating closely with the VM (e.g. swap over NFS).
 *
 * __GFP_NOMEMALLOC is used to explicitly forbid access to emergency reserves.
 *   This takes precedence over the __GFP_MEMALLOC flag if both are set.
 */
#define __GFP_ATOMIC    ((__force gfp_t)___GFP_ATOMIC)
#define __GFP_HIGH      ((__force gfp_t)___GFP_HIGH)
#define __GFP_MEMALLOC  ((__force gfp_t)___GFP_MEMALLOC)
#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)
  • __GFP_ATOMIC
    • 페이지 회수나 슬립이 허용되지 않고 높은 우선 순위로 처리되도록 요청한다.
    • 인터럽트 핸들러들에서 보통 사용되며 __GFP_HIGH와 붙여서 사용될 수도 있다.
  • __GFP_HIGH
    • 높은 우선 순위로 처리되도록 요청한다. \
  • __GFP_MEMALLOC
    • 모든 메모리로의 접근을 허가하도록 요청한다.
    • 프로세스 종료나 스와핑의 사용 예와 같이 매우 짧은 시간내 메모리 할당이 요구될 때 필요하다.
  • __GFP_NOMEMALLOC
    • 비상용 reserves 영역을 이용하지 못하게 엄격히 금지하도록 요청한다.

 

페이지 회수관련 (밑줄 2개)

/*
 * Reclaim modifiers
 *
 * __GFP_IO can start physical IO.
 *
 * __GFP_FS can call down to the low-level FS. Clearing the flag avoids the
 *   allocator recursing into the filesystem which might already be holding
 *   locks.
 *
 * __GFP_DIRECT_RECLAIM indicates that the caller may enter direct reclaim.
 *   This flag can be cleared to avoid unnecessary delays when a fallback
 *   option is available.
 *
 * __GFP_KSWAPD_RECLAIM indicates that the caller wants to wake kswapd when
 *   the low watermark is reached and have it reclaim pages until the high
 *   watermark is reached. A caller may wish to clear this flag when fallback
 *   options are available and the reclaim is likely to disrupt the system. The
 *   canonical example is THP allocation where a fallback is cheap but
 *   reclaim/compaction may cause indirect stalls.
 *
 * __GFP_RECLAIM is shorthand to allow/forbid both direct and kswapd reclaim.
 *
 * __GFP_REPEAT: Try hard to allocate the memory, but the allocation attempt
 *   _might_ fail.  This depends upon the particular VM implementation.
 *
 * __GFP_NOFAIL: The VM implementation _must_ retry infinitely: the caller
 *   cannot handle allocation failures. New users should be evaluated carefully
 *   (and the flag should be used only when there is no reasonable failure
 *   policy) but it is definitely preferable to use the flag rather than
 *   opencode endless loop around allocator.
 *
 * __GFP_NORETRY: The VM implementation must not retry indefinitely and will
 *   return NULL when direct reclaim and memory compaction have failed to allow
 *   the allocation to succeed.  The OOM killer is not called with the current
 *   implementation.
 */
#define __GFP_IO        ((__force gfp_t)___GFP_IO)
#define __GFP_FS        ((__force gfp_t)___GFP_FS)
#define __GFP_DIRECT_RECLAIM    ((__force gfp_t)___GFP_DIRECT_RECLAIM) /* Caller can reclaim */
#define __GFP_KSWAPD_RECLAIM    ((__force gfp_t)___GFP_KSWAPD_RECLAIM) /* kswapd can wake */
#define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM))
#define __GFP_REPEAT    ((__force gfp_t)___GFP_REPEAT)
#define __GFP_NOFAIL    ((__force gfp_t)___GFP_NOFAIL)
#define __GFP_NORETRY   ((__force gfp_t)___GFP_NORETRY)
  • __GFP_IO
    • 메모리 할당을 하는 동안 어떠한 I/O 처리도 가능하도록 요청한다.
  • __GFP_FS
    • 메모리 할당을 하는 동안 File System calls 가능하도록 요청한다.
  • __GFP_DIRECT_RECLAIM
    • 페이지 할당 요청 시 free 페이지가 부족한 경우 direct reclaim(호출자가 직접 회수)을 들어갈 수 있도록 요청한다.
    • fallback이 유효한 경우 불필요한 지연을 없애기 위해 명확히 지정한다.
  • __GFP_KSWAPD_RECLAIM
    • low 워터마크에 접근하는 경우 kswapd를 깨워서 high 워터마크에 오를때까지 페이지를 회수하도록 요청한다.
  • __GFP_RECLAIM
    • 위 2가지 플래그 즉, direct reclaim과 kswapd를 사용한 회수를 동시에 요청한다.
  • __GFP_REPEAT
    • 메모리 할당이 처음 실패하는 경우 한 번 더 시도하도록 요청한다.
  • __GFP_NOFAIL
    • 실패를 허용하지 않고, 메모리 할당 요청에 대해 성공할 때까지 처리하도록 요청한다.
  • __GFP_NORETRY
    • 메모리 할당 요청에 대해 실패 시 재시도 하지 않도록 요청한다.

 

Action 관련 (밑줄 2개)

/*
 * Action modifiers
 *
 * __GFP_COLD indicates that the caller does not expect to be used in the near
 *   future. Where possible, a cache-cold page will be returned.
 *
 * __GFP_NOWARN suppresses allocation failure reports.
 *
 * __GFP_COMP address compound page metadata.
 *
 * __GFP_ZERO returns a zeroed page on success.
 *
 * __GFP_NOTRACK avoids tracking with kmemcheck.
 *
 * __GFP_NOTRACK_FALSE_POSITIVE is an alias of __GFP_NOTRACK. It's a means of
 *   distinguishing in the source between false positives and allocations that
 *   cannot be supported (e.g. page tables).
 *
 * __GFP_OTHER_NODE is for allocations that are on a remote node but that
 *   should not be accounted for as a remote allocation in vmstat. A
 *   typical user would be khugepaged collapsing a huge page on a remote
 *   node.
 */
#define __GFP_COLD      ((__force gfp_t)___GFP_COLD)
#define __GFP_NOWARN    ((__force gfp_t)___GFP_NOWARN)
#define __GFP_COMP      ((__force gfp_t)___GFP_COMP)
#define __GFP_ZERO      ((__force gfp_t)___GFP_ZERO)
#define __GFP_NOTRACK   ((__force gfp_t)___GFP_NOTRACK)
#define __GFP_NOTRACK_FALSE_POSITIVE (__GFP_NOTRACK)
#define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE)
  • __GFP_COLD
    • 메모리 파편화에 영향을 덜 주도록 warm(hot) 페이지 대신 cold 페이지에서 관리하도록 요청한다
  • __GFP_NOWARN
    • 메모리 할당이 실패할 때 어떠한 경고도 처리하지 않도록 요청한다.
  • __GFP_COMP
    • 메타 데이터 또는 연속된 복합 페이지를 구성하도록 요청한다.
  • __GFP_ZERO
    • 할당된 영역을 0으로 초기화 하도록 요청한다.
  • __GFP_NOTRACK
    • kmemcheck를 사용한 디버그 트래킹을 허용하지 않도록 요청한다.
  • __GFP_NOTRACK_FALSE_POSITIVE
    • kmemcheck를 사용한 false positive(가짜 긍정) 디버그 트래킹을 허용하지 않도록 요청한다.
  • __GFP_OTHERNODE
    • 리모트 노드에서 할당을 하도록 요청한다.

 

밑줄 없는 GFP 플래그

/*
 * Useful GFP flag combinations that are commonly used. It is recommended
 * that subsystems start with one of these combinations and then set/clear
 * __GFP_FOO flags as necessary.
 *
 * GFP_ATOMIC users can not sleep and need the allocation to succeed. A lower
 *   watermark is applied to allow access to "atomic reserves"
 *
 * GFP_KERNEL is typical for kernel-internal allocations. The caller requires
 *   ZONE_NORMAL or a lower zone for direct access but can direct reclaim.
 *
 * GFP_KERNEL_ACCOUNT is the same as GFP_KERNEL, except the allocation is
 *   accounted to kmemcg.
 *
 * GFP_NOWAIT is for kernel allocations that should not stall for direct
 *   reclaim, start physical IO or use any filesystem callback.
 *
 * GFP_NOIO will use direct reclaim to discard clean pages or slab pages
 *   that do not require the starting of any physical IO.
 *
 * GFP_NOFS will use direct reclaim but will not use any filesystem interfaces.
 *
 * GFP_USER is for userspace allocations that also need to be directly
 *   accessibly by the kernel or hardware. It is typically used by hardware
 *   for buffers that are mapped to userspace (e.g. graphics) that hardware
 *   still must DMA to. cpuset limits are enforced for these allocations.
 *
 * GFP_DMA exists for historical reasons and should be avoided where possible.
 *   The flags indicates that the caller requires that the lowest zone be
 *   used (ZONE_DMA or 16M on x86-64). Ideally, this would be removed but
 *   it would require careful auditing as some users really require it and
 *   others use the flag to avoid lowmem reserves in ZONE_DMA and treat the
 *   lowest zone as a type of emergency reserve.
 *
 * GFP_DMA32 is similar to GFP_DMA except that the caller requires a 32-bit
 *   address.
 *
 * GFP_DMA32 is similar to GFP_DMA except that the caller requires a 32-bit
 *   address.
 *
 * GFP_HIGHUSER is for userspace allocations that may be mapped to userspace,
 *   do not need to be directly accessible by the kernel but that cannot
 *   move once in use. An example may be a hardware allocation that maps
 *   data directly into userspace but has no addressing limitations.
 *
 * GFP_HIGHUSER_MOVABLE is for userspace allocations that the kernel does not
 *   need direct access to but can use kmap() when access is required. They
 *   are expected to be movable via page reclaim or page migration. Typically,
 *   pages on the LRU would also be allocated with GFP_HIGHUSER_MOVABLE.
 *
 * GFP_TRANSHUGE is used for THP allocations. They are compound allocations
 *   that will fail quickly if memory is not available and will not wake
 *   kswapd on failure.
 */
#define GFP_ATOMIC      (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL      (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT      (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO        (__GFP_RECLAIM)
#define GFP_NOFS        (__GFP_RECLAIM | __GFP_IO)
#define GFP_TEMPORARY   (__GFP_RECLAIM | __GFP_IO | __GFP_FS | \
                         __GFP_RECLAIMABLE)
#define GFP_USER        (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA         __GFP_DMA
#define GFP_DMA32       __GFP_DMA32
#define GFP_HIGHUSER    (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE    (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE   ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
                         __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN) & \
                         ~__GFP_RECLAIM)
  • GFP_ATOMIC
    • 슬립되지 않아야 하고 “atomic reserves”으로의 접근이 허락된  low 워터마크가 적용되게 요청한다.
  • GFP_KERNEL
    • 커널 내부 알고리즘이 이용하는 할당을 위해 사용되며, direct-reclaim이나 kswapd를 통한 reclaim이 가능하고 io 및 fs의 이용이 가능한 상태로 ZONE_NORMAL 또는 lower zone을 사용하도록 요청한다.
  • GFP_KERNEL_ACCOUNT
    • kmemcg(메모리 Control Group)의 사용량 통제를 받는것을 제외하고 GFP_KERNEL과 동일하다.
  • GFP_NOWAIT
    • 커널 할당을 위해 kswapd를 사용한 reclaim이 가능하도록 요청한다.
  • GFP_NOIO
    • direct reclaim을 이용 시 클린 페이지 또는 slab 페이지들을 버릴 수 없도록 한다.
  • GFP_NOFS
    • direct reclaim을 이용 시 io 처리는 가능하나 file system 인터페이스를 이용하지 못한다.
  • GFP_USER
    • userspace 할당을 위해 커널 및 하드웨어에 의해 직접 접근이 가능하도록 요청한다.
    • 현재 태스크에 지정된 cpuset 메모리 할당 정책을 사용하게 요청한다.
  • GFP_DMA
    • 가장 낮은 zone(ZONE_DMA)을 요청한다.
  • GFP_DMA32
    • ZONE_DMA를 요청한다.
  • GFP_HIGHUSER
    • userspace 할당을 위해 GFP_USER에 highmem 사용을 요청한다.
  • GFP_HIGHUSER_MOVABLE
    • userspace 할당을 위해 GFP_USER에 highmem 및 movable migrate 타입 사용을 요청한다.
  • GFP_TRANSHUGE
    • THP(Transparent Huge Page) 할당을 위해 사용되며 메모리 부족시 빠르게 실패하게 한다.
    • 실패한 경우에도 kswapd를 깨우지 않게 한다.