TLB Cache (API)

 

local_flush_tlb_kernel_page()

arch/arm/include/asm/tlbflush.h

static inline void local_flush_tlb_kernel_page(unsigned long kaddr)
{
        const unsigned int __tlb_flag = __cpu_tlb_flags;

        kaddr &= PAGE_MASK;

        if (tlb_flag(TLB_WB))
                dsb(nshst);

        __local_flush_tlb_kernel_page(kaddr);
        tlb_op(TLB_V7_UIS_PAGE, "c8, c7, 1", kaddr);

        if (tlb_flag(TLB_BARRIER)) {
                dsb(nsh);
                isb();
        }
}
  • __cpu_tlb_flags
    • cpu_tlb_fns 구조체로 만들어진 전역 cpu_tlb.tlb_flags로 연결된 define 문
    • rpi2:
      • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE |
        TLB_V7_UIS_ASID | TLB_V7_UIS_BP
  • kaddr &= PAGE_MASK;
    • kaddr에 대해 페이지 단위로 round down 한다.
  • if (tlb_flag(TLB_WB)) dsb(nshst);
    • 아키텍처가 TLB_WB 플래그를 지원하면 dsb(nshst) 명령을 수행한다.
      •  TLB_WB
        • (1 << 31)
    • dsb(option)
      • __asm__ __volatile__ (“dsb ” #option : : : “memory”)
      • 모든 cache operation이 끝날 때까지 기다린다.
  • __local_flush_tlb_kernel_page(kaddr);
    • 요청 페이지에 대한 TLB 캐시를 flush 한다.
  • tlb_op(TLB_V7_UIS_PAGE, “c8, c7, 1”, kaddr);
    • 아키텍처가 TLB_V7_UIS_PAGE를 지원하면 TLBIMVA(unified TLB Invalidate by MVA and ASID) 명령을 수행
    • TLB_V7_UIS_PAGE
      • (1 << 20)
    • rpi2:
      • TLBIMVA가 수행된다.
  • if (tlb_flag(TLB_BARRIER)) { dsb(nsh); isb(); }
    • 아키텍처가 TLB_BARRIER를 지원하면 dsb, isb를 수행한다.

 

tlb_flag()

arch/arm/include/asm/tlbflush.h

#define tlb_flag(f)     ((always_tlb_flags & (f)) || (__tlb_flag & possible_tlb_flags & (f)))
  • f(플래그)가 always_tlb_flags에 있거나 __tlb_flag가 possible_tlb_flags에 있는 경우 true

 

tlb_op()

arch/arm/include/asm/tlbflush.h

#define tlb_op(f, regs, arg)    __tlb_op(f, “p15, 0, %0, ” regs, arg)

  • f(플래그)가 always_tlb_flag에 있는 경우 TLB 레지스터에 대한 어셈블리 명령을 수행한다.
  • 그렇지 않은 경우 f가 possible_tlb_flags와 __tlb_flag 둘 다에 있는 경우 어셈블리 명령을 수행한다.

 

__tlb_op()

arch/arm/include/asm/tlbflush.h

#define __tlb_op(f, insnarg, arg)                                       \
        do {                                                            \
                if (always_tlb_flags & (f))                             \
                        asm("mcr " insnarg                              \
                            : : "r" (arg) : "cc");                      \
                else if (possible_tlb_flags & (f))                      \
                        asm("tst %1, %2\n\t"                            \
                            "mcrne " insnarg                            \
                            : : "r" (arg), "r" (__tlb_flag), "Ir" (f)   \
                            : "cc");                                    \
        } while (0)

instruction을 실행시키되 다음의 조건을 만족해야 한다.

  • arm의 모든 아키텍처가 지원하는 명령인지 비교하여 일치하는 경우
  • 현재 아키텍처가 지원하는 명령인지 비교하여 일치하는 경우에는 cpu가 지원하는 명령인지도 추가적으로 비교한다.
  • if (always_tlb_flags & (f)) asm(“mcr ” insnarg : : “r” (arg) : “cc”);
    • 요청 flag가 alway_tlb_flags에 있는 경우 insnarg 어셈블리 명령을 수행한다.
  • else if (possible_tlb_flags & (f)) asm(“tst %1, %2\n\t” “mcrne ” insnarg : : “r” (arg), “r” (__tlb_flag), “Ir” (f) : “cc”);
    • 그렇지 않고 요청 flag가 possible_tlb_flags에 있을 때
  • asm(“tst %1, %2\n\t” “mcrne ” insnarg : : “r” (arg), “r” (__tlb_flag), “Ir” (f) : “cc”);
    • flag가 __tlb_flag에 있는 경우 insarg 어셈블리 명령을 수행한다.

TLB 플래그들

always_tlb_flags
#define always_tlb_flags        (v4_always_flags & \
                                 v4wbi_always_flags & \
                                 fr_always_flags & \
                                 v4wb_always_flags & \
                                 fa_always_flags & \
                                 v6wbi_always_flags & \
                                 v7wbi_always_flags)

arm의 모든 아키텍처가 지원하는 명령어

  • TLB_WB | TLB_BARRIER

 

possible_tlb_flags
#define possible_tlb_flags      (v4_possible_flags | \
                                 v4wbi_possible_flags | \
                                 fr_possible_flags | \
                                 v4wb_possible_flags | \
                                 fa_possible_flags | \
                                 v6wbi_possible_flags | \
                                 v7wbi_possible_flags)

arm 아키텍처 중 현재 아키텍처가 지원하는 명령어

  • ARMv7
    • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE |
      TLB_V7_UIS_ASID | TLB_V7_UIS_BP | TLB_DCLEAN | TLB_V6_U_FULL | TLB_V6_U_PAGE | TLB_V6_U_ASID | TLB_V6_BP

 

__tlb_flag

현재 cpu가 지원하는 명령어 플래그

  • rpi2:
    • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE | TLB_V7_UIS_ASID | TLB_V7_UIS_BP

 

__local_flush_tlb_kernel_page()

arch/arm/include/asm/tlbflush.h

static inline void __local_flush_tlb_kernel_page(unsigned long kaddr)
{
        const int zero = 0;
        const unsigned int __tlb_flag = __cpu_tlb_flags;

        tlb_op(TLB_V4_U_PAGE, "c8, c7, 1", kaddr);
        tlb_op(TLB_V4_D_PAGE, "c8, c6, 1", kaddr);
        tlb_op(TLB_V4_I_PAGE, "c8, c5, 1", kaddr);
        if (!tlb_flag(TLB_V4_I_PAGE) && tlb_flag(TLB_V4_I_FULL))
                asm("mcr p15, 0, %0, c8, c5, 0" : : "r" (zero) : "cc");

        tlb_op(TLB_V6_U_PAGE, "c8, c7, 1", kaddr);
        tlb_op(TLB_V6_D_PAGE, "c8, c6, 1", kaddr);
        tlb_op(TLB_V6_I_PAGE, "c8, c5, 1", kaddr);
}
  • 아키텍처에 맞는 TLB operation 코드가 선택 수행된다.
    • TLBIMVA(unified TLB Invalidate by MVA and ASID)
    • DTLBIMVA(Data TLB Invalidate by MVA and ASID)
    • ITLBIMVA(Instruction TLB Invalidate by MVA and ASID)
    • ITLBIALL(Instruction TLB Invalidate all)
    • rpi2:
      • 해당되는 플래그가 없어서 아무것도 수행하지 않는다.

 

clean_pmd_entry()

arch/arm/include/asm/tlbflush.h

static inline void clean_pmd_entry(void *pmd)
{
        const unsigned int __tlb_flag = __cpu_tlb_flags;

        tlb_op(TLB_DCLEAN, "c7, c10, 1  @ flush_pmd", pmd);
        tlb_l2_op(TLB_L2CLEAN_FR, "c15, c9, 1  @ L2 flush_pmd", pmd);
}

TLB 엔트리 하나를 clean 한다.

  • tlb_op(TLB_DCLEAN, “c7, c10, 1  @ flush_pmd”, pmd);
    • 아키텍처가 TLB_DCLEAN을 지원하는 경우 tlb operation “mcr c7, c10, 1″을  실행한다.
      • Clean data or unified cache line by MVA to PoC
  • tlb_l2_op(TLB_L2CLEAN_FR, “c15, c9, 1  @ L2 flush_pmd”, pmd);
    • 아키텍처가 TLB_L2CLEAN_FR을 지원하는 경우 tlb operation “mcr c15, c9, 1″을  실행한다.

 

구조체

cpu_tlb_fns 구조체

struct cpu_tlb_fns {
        void (*flush_user_range)(unsigned long, unsigned long, struct vm_area_struct *);
        void (*flush_kern_range)(unsigned long, unsigned long);
        unsigned long tlb_flags;
};
  • flush_user_range
    • rpi2: v7wbi_flush_user_tlb_range() 함수를 가리킨다.
  • flush_kern_range
    • rpi2: v7wbi_flush_kern_tlb_range() 함수를 가리킨다.
  • tlb_flags
    • 현재 cpu가 지원하는 TLB 캐시 관련 플래그
    • rpi2:
      • TLB_WB | TLB_BARRIER | TLB_V7_UIS_FULL | TLB_V7_UIS_PAGE | TLB_V7_UIS_ASID | TLB_V7_UIS_BP

참고

vfs_caches_init_early()

<kernel v5.0>

VFS 캐시 초기화

vfs_caches_init_early()

fs/dcache.c

void __init vfs_caches_init_early(void)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(in_lookup_hashtable); i++)
                INIT_HLIST_BL_HEAD(&in_lookup_hashtable[i]);

        dcache_init_early();
        inode_init_early();
}

vfs(virtual file system)에서 사용되는 각종 해시 테이블들을 초기화한다.

 

dcache_init_early()

fs/dcache.c

static void __init dcache_init_early(void)
{
        /* If hashes are distributed across NUMA nodes, defer
         * hash allocation until vmalloc space is available.
         */
        if (hashdist)
                return;

        dentry_hashtable =
                alloc_large_system_hash("Dentry cache",
                                        sizeof(struct hlist_bl_head),
                                        dhash_entries,
                                        13,
                                        HASH_EARLY | HASH_ZERO,
                                        &d_hash_shift,
                                        NULL,
                                        0,
                                        0);
        d_hash_shift = 32 - d_hash_shift;
}

Dentry 캐시 해시 리스트를 memblock에 early 할당 받고 초기화한다.

 

inode_init_early()

fs/inode.c

/*
 * Initialize the waitqueues and inode hash table.
 */
void __init inode_init_early(void)
{
        /* If hashes are distributed across NUMA nodes, defer
         * hash allocation until vmalloc space is available.
         */
        if (hashdist)
                return;

        inode_hashtable =
                alloc_large_system_hash("Inode-cache",
                                        sizeof(struct hlist_head),
                                        ihash_entries,
                                        14,
                                        HASH_EARLY | HASH_ZERO,
                                        &i_hash_shift,
                                        &i_hash_mask,
                                        0,
                                        0);
}

Inode 캐시 해시 리스트를 memblock에 early 할당 받고 초기화한다.

 

참고

alloc_large_system_hash()

이 함수를 사용하는 해시명과 사용하는 구조체들은 다음과 같다.

  • “PID”, pid_hash
  • “TCP established”, inet_ehash_bucket
  • “TCP bind”, inet_bind_hashbucket
  • “UDP”, udp_hslot
  • “UDP-Lite”, udp_hslot
  • “futex”, futex_queues
  • “Inode-cache”, hlish_head
  • “Dentry cache” hlist_bl_head
  • “Mount-cache”, hlish_head
  • “Mountpoint-cache”, hlist_head

 

alloc_large_system_hash-1

 

alloc_large_system_hash()

mm/page_alloc.c

/*
 * allocate a large system hash table from bootmem
 * - it is assumed that the hash table must contain an exact power-of-2
 *   quantity of entries
 * - limit is the number of hash buckets, not the total allocation size
 */
void *__init alloc_large_system_hash(const char *tablename,
                                     unsigned long bucketsize,
                                     unsigned long numentries,
                                     int scale,
                                     int flags,
                                     unsigned int *_hash_shift,
                                     unsigned int *_hash_mask,
                                     unsigned long low_limit,
                                     unsigned long high_limit)
{
        unsigned long long max = high_limit;
        unsigned long log2qty, size;
        void *table = NULL;

        /* allow the kernel cmdline to have a say */
        if (!numentries) {
                /* round applicable memory size up to nearest megabyte */
                numentries = nr_kernel_pages;

                /* It isn't necessary when PAGE_SIZE >= 1MB */
                if (PAGE_SHIFT < 20)
                        numentries = round_up(numentries, (1<<20)/PAGE_SIZE);

                /* limit to 1 bucket per 2^scale bytes of low memory */
                if (scale > PAGE_SHIFT)
                        numentries >>= (scale - PAGE_SHIFT);
                else
                        numentries <<= (PAGE_SHIFT - scale);

                /* Make sure we've got at least a 0-order allocation.. */
                if (unlikely(flags & HASH_SMALL)) {
                        /* Makes no sense without HASH_EARLY */
                        WARN_ON(!(flags & HASH_EARLY));
                        if (!(numentries >> *_hash_shift)) {
                                numentries = 1UL << *_hash_shift;
                                BUG_ON(!numentries);
                        }
                } else if (unlikely((numentries * bucketsize) < PAGE_SIZE))
                        numentries = PAGE_SIZE / bucketsize;
        }
        numentries = roundup_pow_of_two(numentries);

인수로 해시 엔트리 수가 0이 입력된 경우 커널 free 페이지 수를 사용하여 최대 수로 해시 엔트리 수를 산출하고 scale 값으로 조정하여 해시 엔트리 수를 결정하되 low ~ high limit 범위로 제한한다.

  •  if (!numentries) {
    • 엔트리 수는 커널의 cmdline에서 커널 파라메터를 사용하여 설정될 수 있다.
      • 예) thash_entries=, uhash_entries=, ihash_entries=, dhash_entries=, mhash_entries=, mphash_entries=
  • numentries = nr_kernel_pages;
    • nr_kernel_pages
      • 커널이 사용할 수 있는 free lowmem page 수
      • setup_arch()->paging_init()->bootmem_init()->zone_sizes_init()->free_area_init_node()->free_area_init_core() 함수에서 설정된다.
  • if (PAGE_SHIFT < 20) numentries = round_up(numentries, (1<<20)/PAGE_SIZE);
    • 페이지 프레임이 1M가 안되는 경우 엔트리 수를 1M/PAGE_SIZE로 round up 한다.
  • if (scale > PAGE_SHIFT) numentries >>= (scale – PAGE_SHIFT);
    • scale이 PAGE_SHIFT보다 큰 경우 엔트리 수를 n(scale – PAGE_SHIFT)으로 나눈다.
      • 예) PID의 경우 scale=18 이므로 페이지 크기와 6 차이가 나므로 산출된 엔트리 수에서 2^6=64로 나눈다.
        • numentries >>= (18 – 12)
  • else numentries <<= (PAGE_SHIFT – scale);
    • 페이지 크기가 scale 크기보다 작은 경우 엔트리 수를 n(PAGE_SHIFT – scalke)으로 곱한다.
  • if (unlikely(flags & HASH_SMALL)) {
    • 작은 확률로 HASH_SMALL 플래그 옵션을 사용한 경우
    • 이 옵션을 사용하는 해시
      • PID 해시
      • futex 해시의 경우 해시 사이즈가 256보다 작은 경우
  • if (!(numentries >> *_hash_shift)) { numentries = 1UL << *_hash_shift; }
    • 엔트리 수가 최소한 _hash_shift 표현 비트보다 크도록 조정한다.
      • PID 해시 예) _hash_shift=4
        • 해시 엔트리 수가 16보다 작은 경우 최소 16으로 조정된다.
  • } else if (unlikely((numentries * bucketsize) < PAGE_SIZE)) numentries = PAGE_SIZE / bucketsize;
    • 작은 확률로 해시 엔트리 수 * 버킷사이즈가 페이지 보다 작은 경우 엔트리 수를 페이지당 버킷사이즈가 들어갈 수 있는 최대 수로 확대 조정한다.
      • 한 페이지도 채우지 못하는 해시 엔트리의 경우 한 페이지를 채우는 수 만큼 확대 조정
  • numentries = roundup_pow_of_two(numentries);
    • 2^n에 가까운 값으로 round up 한다.
      • 예) 4000
        • -> 4096

 

        /* limit allocation size to 1/16 total memory by default */
        if (max == 0) {
                max = ((unsigned long long)nr_all_pages << PAGE_SHIFT) >> 4;
                do_div(max, bucketsize);
        }
        max = min(max, 0x80000000ULL);

        if (numentries < low_limit)
                numentries = low_limit;
        if (numentries > max)
                numentries = max;

        log2qty = ilog2(numentries);

        do {
                size = bucketsize << log2qty;
                if (flags & HASH_EARLY)
                        table = memblock_virt_alloc_nopanic(size, 0);
                else if (hashdist)
                        table = __vmalloc(size, GFP_ATOMIC, PAGE_KERNEL);
                else {
                        /*
                         * If bucketsize is not a power-of-two, we may free
                         * some pages at the end of hash table which
                         * alloc_pages_exact() automatically does
                         */
                        if (get_order(size) < MAX_ORDER) {
                                table = alloc_pages_exact(size, GFP_ATOMIC);
                                kmemleak_alloc(table, size, 1, GFP_ATOMIC);
                        }
                }
        } while (!table && size > PAGE_SIZE && --log2qty);

        if (!table)
                panic("Failed to allocate %s hash table\n", tablename);

        printk(KERN_INFO "%s hash table entries: %ld (order: %d, %lu bytes)\n",
               tablename,
               (1UL << log2qty),
               ilog2(size) - PAGE_SHIFT,
               size);

        if (_hash_shift)
                *_hash_shift = log2qty;
        if (_hash_mask)
                *_hash_mask = (1 << log2qty) - 1;

        return table;
}
  • if (max == 0) { max = ((unsigned long long)nr_all_pages << PAGE_SHIFT) >> 4; do_div(max, bucketsize); }
    • 인수 high_limit이 지정되지 않은 경우 max(최대 할당 가능 바이트) 값을 산출하여 사용하는데 max를 최대 메모리의 1/16으로 설정 후 다시 max를 bucketsize로 나눈다.
    • do_div(n, base)
      • 64 bit n 값을 32 bit base로 나눈 몫을 n에 저장하고 나머지를 리턴한다.
      • 실제 동작과 달리 코드는 무척 난해하므로 생략한다.
        • 참고: arch/arm/include/asm/div64.h
    • nr_all_pages
      • 전체 메모리 페이지 수가 담긴 전역 변수이다.
      • setup_arch()->paging_init()->bootmem_init()->zone_sizes_init()->free_area_init_node()->free_area_init_core() 함수에서 설정된다.
  • max = min(max, 0x80000000ULL);
    • max 값이 2G를 초과하지 않도록 한다.
  • if (numentries < low_limit) numentries = low_limit;
    • 계산된 해시 엔트리 수가 인수 low_limit 이상이 되게 조정한다.
  • if (numentries > max) numentries = max;
    • 계산된 해시 엔트리 수가 max 이하가 되게 조정한다.
  • log2qty = ilog2(numentries);
    • log2를 계산한다.
    • 예) numentries=120
      • 7

계산된 log2qty bit 수 만큼 메모리 할당을 시도하고 실패하면 반복하여 log2qty bit를 1씩 줄여서 시도한다. 메모리 할당은 다음 3가지 유형을 선택 사용한다.

  • early 요청이 있는 경우 memblock에 할당한다.
  • early 요청이 아니면서 NUMA 및 64비트 시스템인 경우 vmalloc에 할당한다.
  • 그 외의 경우 alloc_pages_exact()를 사용하여 메모리를 할당한다.
    • 내부에서 get_free_pages() 사용

 

  • size = bucketsize << log2qty;
    • 할당할 사이즈는 버킷사이즈를 log2qty 만큼 좌측으로 쉬프트한다.
  • if (flags & HASH_EARLY) table = memblock_virt_alloc_nopanic(size, 0);
    • early 요청이 있는 경우 memblock에 할당한다.
  • else if (hashdist) table = __vmalloc(size, GFP_ATOMIC, PAGE_KERNEL);
    • early 요청이 아니면서 NUMA 및 64비트 시스템인 경우 vmalloc에 할당한다.
  • if (get_order(size) < MAX_ORDER) { table = alloc_pages_exact(size, GFP_ATOMIC); kmemleak_alloc(table, size, 1, GFP_ATOMIC); }
    • size 처리 비트가 MAX_ORDER 보다 작은 경우에 한해 size 만큼의 메모리를 alloc_pages_exact()함수를 사용하여 할당한다.
  • } while (!table && size > PAGE_SIZE && –log2qty);
    • 메모리 할당이 실패한 경우 log2qty를 1씩 줄여 할당 공간을 반으로 줄여가며 재 시도한다.
    • size가 PAGE_SIZE 이하가 되면 메모리 할당을 실패한 채로 루프를 빠져나온다.
  • if (!table) panic(“Failed to allocate %s hash table\n”, tablename);
    • 메모리 할당이 실패하면 panic() 코드를 수행한다.
  • 해시 테이블 정보를 출력한다.
    • rpi2 예)
      • PID hash table entries: 4096 (order: 2, 16384 bytes)
      • Dentry cache hash table entries: 131072 (order: 7, 524288 bytes)
      • Inode-cache hash table entries: 65536 (order: 6, 262144 bytes)
      • Mount-cache hash table entries: 2048 (order: 1, 8192 bytes)
      • Mountpoint-cache hash table entries: 2048 (order: 1, 8192 bytes)
      • TCP: Hash tables configured (established 8192 bind 8192)
      • UDP hash table entries: 512 (order: 3, 49152 bytes)
      • UDP-Lite hash table entries: 512 (order: 3, 49152 bytes)
      • futex hash table entries: 1024 (order: 4, 65536 bytes)
  • if (_hash_shift) *_hash_shift = log2qty;
    • _hash_shift가 지정된 경우 _hash_shift값을 업데이트 한다.
  • if (_hash_mask) *_hash_mask = (1 << log2qty) – 1;
    • _hash_mask가 지정된 경우 _hash_mask를 업데이트 한다.

 

참고

 

pidhash_init()

PID 테이블을 해쉬로 구현하여 빠르게 검색하고 처리할 수 있도록 하는데 이 함수를 사용하여 초기화 한다.

pidhash_init-1

pidhash_init()

kernel/pid.c

/*
 * The pid hash table is scaled according to the amount of memory in the
 * machine.  From a minimum of 16 slots up to 4096 slots at one gigabyte or
 * more.
 */
void __init pidhash_init(void)
{
        unsigned int i, pidhash_size;

        pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
                                           HASH_EARLY | HASH_SMALL,
                                           &pidhash_shift, NULL,
                                           0, 4096);
        pidhash_size = 1U << pidhash_shift;

        for (i = 0; i < pidhash_size; i++)
                INIT_HLIST_HEAD(&pid_hash[i]);
}

pid용 해쉬 테이블을 할당하고 초기화한다.

  • pid_hash = alloc_large_system_hash(“PID”, sizeof(*pid_hash), 0, 18, HASH_EARLY | HASH_SMALL, &pidhash_shift, NULL, 0, 4096);
    • 커널 초기화 중이므로 early를 사용하여 memblock에 pid용 해쉬테이블 공간을 할당받는다.
    • low_limit와 high_limit는 0과 4096개로 제한한다.
    • 세 번째 인수로 0이 주어진 경우 커널의 lowmem free 페이지 공간을 확인하여 최대 수의 해시 엔트리 수를 계산해낸다.
    •  HASH_EARLY
      • memblock에 할당 요청
    • HASH_SMALL
      • 최소 엔트리 수인 경우 확대하도록 한다.
      • 해시 엔트리가 최소 2^pidhash_shift 이상 그리고 PAGE_SIZE / pid_hash 사이즈 갯 수 보다 크도록 확대 한다.
    • pid_hash_shift
      • 초기 값은 4
    • rpi2 정보 출력 예)
      • PID hash table entries: 4096 (order: 2, 16384 bytes)
  • pidhash_size = 1U << pidhash_shift;
    • pidhash_size를 2^pidhash_shift로 재조정 한다
    • alloc_large_system_hash()를 수행하면서 결정한 해시 엔트리 수에 따라 전역 pidhash_shift 변수가 재조종된다.
      • rpi2 예)
        • 재조정되어 pidhash_shift=12
  • for (i = 0; i < pidhash_size; i++) INIT_HLIST_HEAD(&pid_hash[i]);
    • pidhash_size 만큼 pid_hash[]를 초기화한다.

 

kernel/pid.c

static struct hlist_head *pid_hash;

 

다음 그림은 pid_hash가 pid 구조체 내부에 있는 upid 구조체들과 연결되는 모습을 보여준다.

pidhash_init-2

 

참고

 

setup_log_buf()

<kernel v5.0>

많은 수의 CPU를 사용하는 SMP 시스템에서 로그 버퍼가 부족해지는 경우가 있어 그러한 시스템에서 더 커진 로그 버퍼를 할당한다.

setup_log_buf-1

 

setup_log_buf()

kernel/printk/printk.c

void __init setup_log_buf(int early)
{
        unsigned long flags;
        char *new_log_buf;
        unsigned int free;

        if (log_buf != __log_buf)
                return;

        if (!early && !new_log_buf_len)
                log_buf_add_cpu();

        if (!new_log_buf_len)
                return;

        if (early) {
                new_log_buf =
                        memblock_alloc(new_log_buf_len, LOG_ALIGN);
        } else {
                new_log_buf = memblock_alloc_nopanic(new_log_buf_len,
                                                          LOG_ALIGN);
        }

        if (unlikely(!new_log_buf)) {
                pr_err("log_buf_len: %lu bytes not available\n",
                        new_log_buf_len);
                return;
        }

        logbuf_lock_irqsave(flags);
        log_buf_len = new_log_buf_len;
        log_buf = new_log_buf;
        new_log_buf_len = 0;
        free = __LOG_BUF_LEN - log_next_idx;
        memcpy(log_buf, __log_buf, __LOG_BUF_LEN);
        logbuf_unlock_irqrestore(flags);

        pr_info("log_buf_len: %u bytes\n", log_buf_len);
        pr_info("early log buf free: %u(%u%%)\n",
                free, (free * 100) / __LOG_BUF_LEN);
}

cpu 수가 적정 수(arm=17개, x86=33)를 초과하는 시스템의 경우 그 cpu 수 만큼 더 커진 새 로그 버퍼를 할당한다.

  • 코드 라인 7~8에서 이미 log_buf가 기본 버퍼를 사용하지 않고 재 설정된 경우 함수를 빠져나간다.
  • 코드 라인 10~11에서 early 호출이 아니면서 new_log_buf_len이 0으로 되어 있는 경우 new_log_buf_len를 재조정한다.
    • 보다 빠르게 로그 버퍼가 필요한 x86시스템에서만 setup_arch() 함수 내부에서 early=1로 하여 호출한다.
    • 정규 호출은 start_kernel()에서 각 아키텍처의 setup_arch() 함수가 완료된 후에 호출한다.
  • 코드 라인 13~14에서 로그 버퍼로 더 추가할 필요가 없는 경우 new_log_buf_len이 여전히 0이며 이 때 함수를 빠져나간다.
  • 코드 라인 16~18에서 early 호출인 경우 new_log_buf를 new_log_buf_len 만큼 할당한다.
    • early 호출 시 memblock 할당이 안되는 경우 panic()이 호출된다.
  • 코드 라인 19~22에서 early 호출이 아닌 경우 new_log_buf를 new_log_buf_len 만큼 할당한다
    • memblock 할당이 안되는 경우에도 panic()이 호출되지 않는다.
  • 코드 라인 34~28에서 적은 확률로 new_log_buf가 설정되지 않으면 에러 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 31~33에서 log 버퍼를 새롭게 할당 받은 버퍼로 설정한다.
  • 코드 라인 35에서 원래 로그 버퍼 전체를 새 로그 버퍼로 복사한다.
  • 코드 라인 38~40에서 로그 버퍼 길이 정보를 출력한다.

 

kernel/printk/printk.c

/* record buffer */
#define LOG_ALIGN __alignof__(struct printk_log)
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
#define LOG_BUF_LEN_MAX (u32)(1 << 31)
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
static char *log_buf = __log_buf;
static u32 log_buf_len = __LOG_BUF_LEN;

 

kernel/printk/printk.c

/* requested log_buf_len from kernel cmdline */
static unsigned long __initdata new_log_buf_len;

 

log_buf_add_cpu()

kernel/printk/printk.c

static void __init log_buf_add_cpu(void)
{
        unsigned int cpu_extra;

        /*
         * archs should set up cpu_possible_bits properly with
         * set_cpu_possible() after setup_arch() but just in
         * case lets ensure this is valid.
         */
        if (num_possible_cpus() == 1)
                return;

        cpu_extra = (num_possible_cpus() - 1) * __LOG_CPU_MAX_BUF_LEN;

        /* by default this will only continue through for large > 64 CPUs */
        if (cpu_extra <= __LOG_BUF_LEN / 2)
                return;

        pr_info("log_buf_len individual max cpu contribution: %d bytes\n",
                __LOG_CPU_MAX_BUF_LEN);
        pr_info("log_buf_len total cpu_extra contributions: %d bytes\n",
                cpu_extra);
        pr_info("log_buf_len min size: %d bytes\n", __LOG_BUF_LEN);

        log_buf_len_update(cpu_extra + __LOG_BUF_LEN);
}

default 설정된 로그 버퍼 기준으로 possible cpu 수가 적정 수(arm=17, x86=33)를 초과하는 경우에만 추가 cpu 버퍼를 준비한다.

  • 코드 라인 10~11에서 cpu가 1개일 경우 추가 로그 버퍼를 만들지 않고 함수를 빠져나간다.
  • 코드 라인 13에서 possible cpu-1 만큼의 추가 로그 버퍼를 산출한다.
    • __LOG_CPU_MAX_BUF_LEN
      • default로 4K (2^12)
      • powerpc 아키텍처는 8K(2^13)
  • 코드 라인 16~17에서 추가 로그 버퍼가 기존 로그 버퍼의 절반 보다 작은 경우 추가 버퍼를 만들지 않고 함수를 빠져나간다.
    •  __LOG_BUF_LEN
      • arm 아키텍처는 SoC 마다 8K(2^13) ~ 512K(2^19)를 사용한다.
      • arm64 아키텍처는 128K(2^17)을 사용한다.
      • x86 아키텍처는 256K(2^18)을 사용한다.
  • 코드 라인 19~23에서 추가 버퍼 정보를 출력한다.
  • 코드 라인 25에서  로그 버퍼를 기본 버퍼 + cpu_extra 버퍼 만큼 설정한다.

 

kernel/printk/printk.c

#define __LOG_CPU_MAX_BUF_LEN (1 << CONFIG_LOG_CPU_MAX_BUF_SHIFT)
  • CONFIG_LOG_CPU_MAX_BUF_SHIFT
    • default=12

 

kernel/printk/printk.c

#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
  • CONFIG_LOG_BUF_SHIFT
    • default=17

 

num_possible_cpus()

include/linux/cpumask.h

#define num_possible_cpus()     cpumask_weight(cpu_possible_mask)

possible cpu 수를 알아온다.

 

cpumask_weight()

include/linux/cpumask.h

/**
 * cpumask_weight - Count of bits in *srcp
 * @srcp: the cpumask to count bits (< nr_cpu_ids) in.
 */
static inline unsigned int cpumask_weight(const struct cpumask *srcp)
{
        return bitmap_weight(cpumask_bits(srcp), nr_cpumask_bits);
}

cpu 관련 비트맵 srcp에서 nr_cpumask_bits 만큼의 비트에서 1로 설정된 비트 수를 알아온다.

 

nr_cpumask_bits

include/linux/cpumask.h

#ifdef CONFIG_CPUMASK_OFFSTACK
/* Assuming NR_CPUS is huge, a runtime limit is more efficient.  Also,
 * not all bits may be allocated. */
#define nr_cpumask_bits nr_cpu_ids
#else
#define nr_cpumask_bits NR_CPUS
#endif

 

log_buf_len_update()

kernel/printk/printk.c

/* we practice scaling the ring buffer by powers of 2 */
static void __init log_buf_len_update(unsigned size)
{
        if (size > (u64)LOG_BUF_LEN_MAX) {
                size = (u64)LOG_BUF_LEN_MAX;
                pr_err("log_buf over 2G is not supported.\n");
        }
        if (size)
                size = roundup_pow_of_two(size);
        if (size > log_buf_len)
                new_log_buf_len = size;
}

인수로 지정된 size 값을 가까운 2의 n승에 맞게 round up한 후 new_log_buf_len 보다 큰 경우에만 update 한다. 최대 2G로 제한된다.

  • 예) size=636K
    • 1M

 

/**
 * roundup_pow_of_two - round the given value up to nearest power of two
 * @n - parameter
 *
 * round the given value up to the nearest power of two
 * - the result is undefined when n == 0
 * - this can be used to initialise global variables from constant data
 */
#define roundup_pow_of_two(n)                   \
(                                               \
        __builtin_constant_p(n) ? (             \
                (n == 1) ? 1 :                  \
                (1UL << (ilog2((n) - 1) + 1))   \
                                   ) :          \
        __roundup_pow_of_two(n)                 \
 )

n 값을 가까운 2의 n승에 맞게 round up한 후 리턴한다.

  • 예) 100
    • 128

 

/*
 * round up to nearest power of two
 */
static inline __attribute__((const))
unsigned long __roundup_pow_of_two(unsigned long n)
{
        return 1UL << fls_long(n - 1);
}

n 값을 가까운 2의 n승에 맞게 round up한 후 리턴한다.