vfs_caches_init_early()

VFS(Virtual File System)에서 사용하는 Dentry 캐시와 Inode 캐시에 대해 early  할당 받고 초기화한다.

vfs_caches_init_early-1

 

vfs_caches_init_early()

fs/dcache.c

void __init vfs_caches_init_early(void)
{
        dcache_init_early();
        inode_init_early();
}

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

 

dcache_init_early()

fs/dcache.c

static void __init dcache_init_early(void)
{
        unsigned int loop;

        /* 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,
                                        &d_hash_shift,
                                        &d_hash_mask,
                                        0,
                                        0);

        for (loop = 0; loop < (1U << d_hash_shift); loop++)
                INIT_HLIST_BL_HEAD(dentry_hashtable + loop);
}

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

inode_init_early()

fs/inode.c

/*
 * Initialize the waitqueues and inode hash table.
 */
void __init inode_init_early(void)
{
        unsigned int loop;

        /* 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,
                                        &i_hash_shift,
                                        &i_hash_mask,
                                        0,
                                        0);

        for (loop = 0; loop < (1U << i_hash_shift); loop++)
                INIT_HLIST_HEAD(&inode_hashtable[loop]);
}

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

 

구조체

pid 구조체

include/linux/pid.h

/*
 * What is struct pid?
 *
 * A struct pid is the kernel's internal notion of a process identifier.
 * It refers to individual tasks, process groups, and sessions.  While
 * there are processes attached to it the struct pid lives in a hash
 * table, so it and then the processes that it refers to can be found
 * quickly from the numeric pid value.  The attached processes may be
 * quickly accessed by following pointers from struct pid.
 *
 * Storing pid_t values in the kernel and referring to them later has a
 * problem.  The process originally with that pid may have exited and the
 * pid allocator wrapped, and another process could have come along
 * and been assigned that pid.
 *
 * Referring to user space processes by holding a reference to struct
 * task_struct has a problem.  When the user space process exits
 * the now useless task_struct is still kept.  A task_struct plus a
 * stack consumes around 10K of low kernel memory.  More precisely
 * this is THREAD_SIZE + sizeof(struct task_struct).  By comparison
 * a struct pid is about 64 bytes.
 *
 * Holding a reference to struct pid solves both of these problems.
 * It is small so holding a reference does not consume a lot of
 * resources, and since a new struct pid is allocated when the numeric pid
 * value is reused (when pids wrap around) we don't mistakenly refer to new
 * processes.
 */
struct pid
{
        atomic_t count;
        unsigned int level;
        /* lists of tasks that use this pid */
        struct hlist_head tasks[PIDTYPE_MAX];
        struct rcu_head rcu;
        struct upid numbers[1];
};

 

upid 구조체

include/linux/pid.h

/*
 * struct upid is used to get the id of the struct pid, as it is
 * seen in particular namespace. Later the struct pid is found with
 * find_pid_ns() using the int nr and struct pid_namespace *ns.
 */
struct upid {
        /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
        int nr;
        struct pid_namespace *ns;
        struct hlist_node pid_chain;
};

 

pid_link 구조체

struct pid_link
{
        struct hlist_node node;
        struct pid *pid;
};

 

pid_type

enum pid_type
{
        PIDTYPE_PID,
        PIDTYPE_PGID,
        PIDTYPE_SID,
        PIDTYPE_MAX
};

 

참고

setup_log_buf()

많은 수의 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;
        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_virt_alloc(new_log_buf_len, LOG_ALIGN);
        } else {
                new_log_buf = memblock_virt_alloc_nopanic(new_log_buf_len,
                                                          LOG_ALIGN);
        }

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

        raw_spin_lock_irqsave(&logbuf_lock, 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);
        raw_spin_unlock_irqrestore(&logbuf_lock, flags);

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

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

  • if (log_buf != __log_buf) return;
    • 이미 log_buf가 기본 버퍼를 사용하지 않고 재 설정된 경우 함수를 빠져나간다.
  • if (!early && !new_log_buf_len) log_buf_add_cpu();
    • early 호출이 아니면서 new_log_buf_len이 0으로 되어 있는 경우
    • 정규 호출은 start_kernel()에서 각 아키텍처의 setup_arch() 함수가 완료된 후에 호출한다.
    • 보다 빠르게 로그 버퍼가 필요한 시스템에서 early=1로 하여 호출한다.
      • 예)  x86 아키텍처의 setup_arch() 함수에서 호출한다.
  • if (!new_log_buf_len) return;
    • new_log_buf_len이 여전히 0인 경우 함수를 빠져나간다.
  • if (early) { new_log_buf = memblock_virt_alloc(new_log_buf_len, LOG_ALIGN);
    • early 호출인 경우 new_log_buf를 new_log_buf_len 만큼 할당한다.
    • early 호출 시 memblock 할당이 안되는 경우 panic()이 호출된다.
  • else { new_log_buf = memblock_virt_alloc_nopanic(new_log_buf_len, LOG_ALIGN); }
    • early 호출이 아닌 경우 new_log_buf를 new_log_buf_len 만큼 할당한다
    • memblock 할당이 안되는 경우에도 panic()이 호출되지 않는다.
  • if (unlikely(!new_log_buf)) {
    • 적은 확률로 new_log_buf가 설정되지 않으면 에러 메시지를 출력하고 함수를 빠져나간다.
  • log_buf_len = new_log_buf_len; log_buf = new_log_buf; new_log_buf_len = 0;
    • log 버퍼를 새롭게 할당 받은 버퍼로 설정한다.
  • free = __LOG_BUF_LEN – log_next_idx;
    • 원래 로그 버퍼에서 남은 공간 길이
  • memcpy(log_buf, __log_buf, __LOG_BUF_LEN);
    • 원래 로그 버퍼 전체를 새 로그 버퍼로 복사한다.

 

kernel/printk/printk.c

/* record buffer */
#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
#define LOG_ALIGN 4
#else
#define LOG_ALIGN __alignof__(struct printk_log)
#endif
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
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 설정된 로그 버퍼 기준으로 cpu 수가 적정 수(arm=17, x86=33)개를 초과하는 경우에만 추가 cpu 버퍼를 준비한다.

  • if (num_possible_cpus() == 1) return;
    • cpu가 1개일 경우 추가 로그 버퍼를 만들지 않고 리턴한다.
  • cpu_extra = (num_possible_cpus() – 1) * __LOG_CPU_MAX_BUF_LEN;
    • cpu 수 만큼의 로그 버퍼 길이
    • __LOG_CPU_MAX_BUF_LEN
      • default로 4K (2 ^ 12)
  • if (cpu_extra <= __LOG_BUF_LEN / 2) return;
    • __LOG_BUF_LEN
      • default로 128K (2 ^ 17)
    • arm에서 16개 이하의 cpu는 위의 조건으로 인하여 함수를 빠져나간다.
  •  log_buf_len_update(cpu_extra + __LOG_BUF_LEN);
    •  로그 버퍼를 기본 버퍼 + cpu_extra 버퍼 만큼 설정한다.
    • 예) cpu=128개
      • 508K + 128K = 636K 버퍼

 

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)
                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 한다.

  • 예) 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한 후 리턴한다.

 

jump_label_init()

커널과 모듈에서 조건문의 branch miss ratio를 낮추어 성능을 향상 시키기 위한 방법으로 static key를 사용한 jump label API를 사용하는데 커널에서 이러한 jump label 코드들을 모두 찾아 초기화한다.

  • 모듈에서 사용되는 jump label API도 사용하기 전에 초기화를 하여야 하는데 이의 처리를 위해서 로드된 모듈을 초기화하는 콜백함수를 등록해서 사용한다.
    • early_initcall(jump_label_init_module);
      • 등록되는 모든 initcall 함수들은 kernel_init 스레드의 do_initcalls() 함수에서 호출된다.

 

모듈에서 사용된 static key를 사용한 jump label API 초기화

jump_label_init-1

 

jump_label_init()

kernel/jump_label.c

void __init jump_label_init(void)
{
        struct jump_entry *iter_start = __start___jump_table;
        struct jump_entry *iter_stop = __stop___jump_table;
        struct static_key *key = NULL;
        struct jump_entry *iter;
                                             
        jump_label_lock();
        jump_label_sort_entries(iter_start, iter_stop);

        for (iter = iter_start; iter < iter_stop; iter++) {
                struct static_key *iterk;

                iterk = (struct static_key *)(unsigned long)iter->key;
                arch_jump_label_transform_static(iter, jump_label_type(iterk));
                if (iterk == key)
                        continue;

                key = iterk;
                /*
                 * Set key->entries to iter, but preserve JUMP_LABEL_TRUE_BRANCH.
                 */
                *((unsigned long *)&key->entries) += (unsigned long)iter;
#ifdef CONFIG_MODULES
                key->next = NULL;
#endif
        }
        static_key_initialized = true;
        jump_label_unlock();
}

__jump_table에 있는 모든 엔트리를 읽어와서 연관된 커널 코드(jump label 코드)의 1 word를 nop 또는 branch 명령으로 변경한다.

  • jump_label_sort_entries(iter_start, iter_stop);
    • __jump_table에 있는 엔트리를 key 주소로 heap sorting을 한다.
  • for (iter = iter_start; iter < iter_stop; iter++) {
    • __jump_table의 처음 부터 끝 까지 루프를 돈다.
  • iterk = (struct static_key *)(unsigned long)iter->key;
    • 엔트리가 가리키는 key
  • arch_jump_label_transform_static(iter, jump_label_type(iterk));
    • static key를 사용한 jump_label API 코드 주소에 1 word 코드(nop or branch)로 치환한다.
  • if (iterk == key) continue;
    • key 주소로 sorting 되어 있으므로 jump_label API가 동일한 key를 가리키는 경우 두 번 처리하지 않는다.
  • *((unsigned long *)&key->entries) += (unsigned long)iter;
    • key->entries에 static key 선언시의 default 값(0 or 1)이 들어 있는데 이 곳에 key 객체의 주소를 더한다.
  • key->next = NULL;
    • key->next에는 statick key를 사용하는 모듈이 연결된다. 따라서 null로 초기화 한다.
  • static_key_initialized = true;
    • static key가 모두 초기화 되었음을 알리는 전역 플래그 변수이다.

 

아래 그림은 커널에서 사용한 모든 static key를 사용한 jump label API 코드의 주소를 __jump_table에서 찾아 nop 또는 branch 명령을 결정하여 패치하는 모습을 보여준다. (self-modify instruction code)

jump_label_init-2

jump_label_sort_entries()

kernel/jump_label.c

static void
jump_label_sort_entries(struct jump_entry *start, struct jump_entry *stop)
{
        unsigned long size;

        size = (((unsigned long)stop - (unsigned long)start)
                                        / sizeof(struct jump_entry));
        sort(start, size, sizeof(struct jump_entry), jump_label_cmp, NULL);
}

__jump_table의 entry를 key 주소로 heap sorting 알고리즘을 수행한다.

 

jump_label_type()

kernel/jump_label.c

static enum jump_label_type jump_label_type(struct static_key *key)
{
        bool true_branch = jump_label_get_branch_default(key);
        bool state = static_key_enabled(key);

        if ((!true_branch && state) || (true_branch && !state))
                return JUMP_LABEL_ENABLE;

        return JUMP_LABEL_DISABLE;
}

branch 여부(jump label 타입)를 알아온다.

  • bool true_branch = jump_label_get_branch_default(key);
    • static key가 선언될 때 true key인지 false key인지 여부
      • STATIC_KEY_INIT_FALSE인 경우 false
      • STATIC_KEY_INIT_TRUE인 경우 true
  • bool state = static_key_enabled(key);
    • static key가 enable되었는지 여부를 알아온다.
  • if ((!true_branch && state) || (true_branch && !state)) return JUMP_LABEL_ENABLE;
    • branch 코드가 만들어져야 하는 case
  • return JUMP_LABEL_DISABLE;
    • nop 코드가 만들어져야 하는 case

 

아래 그림은 2가지 상태(초기 설정 상태와 enable 상태)에 따라 nop(JUMP_LABEL_DISABLE)과 branch(JUMP_LABEL_ENABLE) 동작 상태를 기존 API를 사용하여 보여준다.

  • STATIC_KEY_INIT_FALSE 또는 STATIC_KEY_INIT_TRUE로 초기화된 경우는 처음에 항상 nop를 수행하고 static_key_slow_inc() 또는 static_key_slow_dec() 함수에 의해 branch code가 동작하게된다.

jump_label_type-1a

아래 그림은 3가지 상태(초기 설정 상태, enable 상태 및 조건 API)에 따라 nop(JUMP_LABEL_DISABLE)과 branch(JUMP_LABEL_ENABLE) 동작 상태를 신규 API를 사용하여 보여준다.

  • DEFINE_STATIC_KEY_FALSE 또는 DEFINE_STATIC_KEY_TRUE로 초기화된 경우는 static_branch_unlikely() 및 static_branch_likely() 함수와 enable 상태의 조합에 따라 초기 값으로 nop/branch가 결정되고 이후 static_branch_enable() 및 static_branch_disable() API에 의해 조건이 반전된다.

jump_label_type-2a

 

jump_label_get_branch_default()

include/linux/jump_label.h

static inline bool jump_label_get_branch_default(struct static_key *key)
{
        if (((unsigned long)key->entries & JUMP_LABEL_TYPE_MASK) ==
            JUMP_LABEL_TYPE_TRUE_BRANCH)
                return true;
        return false;
}

key->entries의 lsb 1 bit를 사용하여 static key의 초기 설정 값을 알아온다.

  • 1인 경우 true이고 0인 경우 false이다.

include/linux/jump_label.h

#define JUMP_LABEL_TYPE_FALSE_BRANCH    0UL
#define JUMP_LABEL_TYPE_TRUE_BRANCH     1UL
#define JUMP_LABEL_TYPE_MASK            1UL

 

static_key_enabled()

include/linux/jump_label.h

static inline bool static_key_enabled(struct static_key *key)
{
        return static_key_count(key) > 0;
}

static key가 enable 되었는지 여부를 리턴한다.

  • key->enabled 여부를 리턴한다.

 

static_key_count()

include/linux/jump_label.h

static inline int static_key_count(struct static_key *key)
{
        return atomic_read(&key->enabled);
}

key->enabled 값을 atomic하게 읽어온다.

 

arch_jump_label_transform_static()

arch/arm/kernel/jump_label.c

void arch_jump_label_transform_static(struct jump_entry *entry,
                                      enum jump_label_type type)
{
        __arch_jump_label_transform(entry, type, true);
}

요청 타입(nop, branch)에 따라 변경할 1 워드 코드를 early patch 한다.

 

__arch_jump_label_transform()

arch/arm/kernel/jump_label.c

static void __arch_jump_label_transform(struct jump_entry *entry,
                                        enum jump_label_type type,
                                        bool is_static)
{
        void *addr = (void *)entry->code;
        unsigned int insn;

        if (type == JUMP_LABEL_ENABLE)
                insn = arm_gen_branch(entry->code, entry->target);
        else
                insn = arm_gen_nop();

        if (is_static)
                __patch_text_early(addr, insn);
        else
                patch_text(addr, insn);
}

요청 타입에 따라 변경할 1 워드 코드를 patch 하는데 다음의 2 가지 옵션이 있다.

  • 커널을 처음 초기화중이면 is_static이 true로 호출되어 곧장 패치한다.
    • 커널이 초기화중이므로 read/write 허용된 상태
  • 커널이 운영중이면 is_static이 false로 호출되어 모든 cpu의 스케쥴러를 정지시킨 후 커널 영역을 fixmap 매핑 영역에 잠시 매핑한 후 patch 한다
    • 커널이 read만 허용한 상태

 

  • if (type == JUMP_LABEL_ENABLE) insn = arm_gen_branch(entry->code, entry->target);
    • 타입이 enable이면 branch 코드를 만든다.
  • else insn = arm_gen_nop();
    • 타입이 disable이면 nop 코드를 만든다.
  • if (is_static) __patch_text_early(addr, insn);
    • early 요청이 있는 경우 early patch 코드를 수행한다.
  • else patch_text(addr, insn);
    • early 요청이 없는 경우 patch 코드를 수행한다.

 

arm_gen_branch()

arch/arm/include/asm/insn.h

static inline unsigned long
arm_gen_branch(unsigned long pc, unsigned long addr)
{
        return __arm_gen_branch(pc, addr, false);
}

ARM 또는 THUMB 브랜치 코드를 만들어온다.

 

__arm_gen_branch()

arch/arm/kernel/insn.c

unsigned long
__arm_gen_branch(unsigned long pc, unsigned long addr, bool link)
{
        if (IS_ENABLED(CONFIG_THUMB2_KERNEL))
                return __arm_gen_branch_thumb2(pc, addr, link);
        else
                return __arm_gen_branch_arm(pc, addr, link);
}

ARM 또는 THUMB 브랜치 코드를 만들어온다.

 

__arm_gen_branch_arm()

arch/arm/kernel/insn.c

static unsigned long
__arm_gen_branch_arm(unsigned long pc, unsigned long addr, bool link)
{
        unsigned long opcode = 0xea000000;
        long offset;

        if (link)
                opcode |= 1 << 24;

        offset = (long)addr - (long)(pc + 8);
        if (unlikely(offset < -33554432 || offset > 33554428)) {
                WARN_ON_ONCE(1);
                return 0;
        }

        offset = (offset >> 2) & 0x00ffffff;

        return opcode | offset;
}

ARM용 branch 코드를 만들어 word로 리턴한다.

 

arm_gen_nop()

arch/arm/include/asm/insn.h

static inline unsigned long
arm_gen_nop(void)
{
#ifdef CONFIG_THUMB2_KERNEL
        return 0xf3af8000; /* nop.w */
#else   
        return 0xe1a00000; /* mov r0, r0 */
#endif
}

ARM용 nop 코드로 mov r0, r0에 해당하는 word를 리턴한다.

 

__patch_text_early()

arch/arm/include/asm/patch.h

static inline void __patch_text_early(void *addr, unsigned int insn)
{
        __patch_text_real(addr, insn, false);
}

주어진 주소에 명령을 즉각 패치한다.

  • 커널 초기화 시에는 이 early 함수를 호출하여 사용한다.

patch_text()

arch/arm/kernel/patch.c

void __kprobes patch_text(void *addr, unsigned int insn)
{
        struct patch patch = {
                .addr = addr,
                .insn = insn,
        };

        stop_machine(patch_text_stop_machine, &patch, NULL);
}

cpu의 스케쥴링을 모두 정지 시킨 후 주어진 주소에 명령을 패치한다.

  • 커널이 운영중일 경우에는 커널 코드가 read만 허용하므로 fixmap 영역 중 한 페이지를 사용하여 잠시 매핑하여 수정하게 한다.
  • 참고로 커널 초기화 시에는 이 함수를 사용하지 않고 __patch_text_early() 함수를 사용한다.

 

patch_text_stop_machine()

arch/arm/kernel/patch.c

static int __kprobes patch_text_stop_machine(void *data)
{
        struct patch *patch = data;

        __patch_text(patch->addr, patch->insn);

        return 0;
}

주어진 주소 위치에 명령을 기록(패치)한다.

 

__patch_text()

arch/arm/include/asm/patch.h

static inline void __patch_text(void *addr, unsigned int insn)
{
        __patch_text_real(addr, insn, true);
}

주소에 주소 위치에 명령을 기록(패치)하고 리매핑한다.

__patch_text_real()

arch/arm/kernel/patch.c

void __kprobes __patch_text_real(void *addr, unsigned int insn, bool remap)
{
        bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);
        unsigned int uintaddr = (uintptr_t) addr;
        bool twopage = false;
        unsigned long flags;
        void *waddr = addr;
        int size;

        if (remap)
                waddr = patch_map(addr, FIX_TEXT_POKE0, &flags);
        else
                __acquire(&patch_lock);

        if (thumb2 && __opcode_is_thumb16(insn)) {
                *(u16 *)waddr = __opcode_to_mem_thumb16(insn);
                size = sizeof(u16);
        } else if (thumb2 && (uintaddr & 2)) {
                u16 first = __opcode_thumb32_first(insn);
                u16 second = __opcode_thumb32_second(insn);
                u16 *addrh0 = waddr;
                u16 *addrh1 = waddr + 2;

                twopage = (uintaddr & ~PAGE_MASK) == PAGE_SIZE - 2;
                if (twopage && remap)
                        addrh1 = patch_map(addr + 2, FIX_TEXT_POKE1, NULL);

                *addrh0 = __opcode_to_mem_thumb16(first);
                *addrh1 = __opcode_to_mem_thumb16(second);

                if (twopage && addrh1 != addr + 2) {
                        flush_kernel_vmap_range(addrh1, 2);
                        patch_unmap(FIX_TEXT_POKE1, NULL);
                }

                size = sizeof(u32);
        } else {
                if (thumb2)
                        insn = __opcode_to_mem_thumb32(insn);
                else
                        insn = __opcode_to_mem_arm(insn);

                *(u32 *)waddr = insn;
                size = sizeof(u32);
        }

        if (waddr != addr) {
                flush_kernel_vmap_range(waddr, twopage ? size / 2 : size);
                patch_unmap(FIX_TEXT_POKE0, &flags);
        } else
                __release(&patch_lock);

        flush_icache_range((uintptr_t)(addr),
                           (uintptr_t)(addr) + size);
}

ARM 코드에 대해 리매핑을 하지 않고 패치하는 경우는 간단하게 4바이트만 변경하고 해당 4바이트 주소의 i-cache를 flush한다. 리매핑이 필요한 경우에는 fix-map을 사용하여 매핑 후 명령 부분의 4바이트를 변경하고 cpu 아키텍처에 따라 해당 4바이트 영역의 d-cache에 대해 flush하고 fix-map을 언매핑한 후 같은 영역에 대해 i-cache를 flush한다.

 

patch_map()

arch/arm/kernel/patch.c

static void __kprobes *patch_map(void *addr, int fixmap, unsigned long *flags)
        __acquires(&patch_lock)
{
        unsigned int uintaddr = (uintptr_t) addr;
        bool module = !core_kernel_text(uintaddr);
        struct page *page;

        if (module && IS_ENABLED(CONFIG_DEBUG_SET_MODULE_RONX))
                page = vmalloc_to_page(addr);
        else if (!module && IS_ENABLED(CONFIG_DEBUG_RODATA))
                page = virt_to_page(addr);
        else
                return addr;

        if (flags)
                spin_lock_irqsave(&patch_lock, *flags);
        else
                __acquire(&patch_lock);

        set_fixmap(fixmap, page_to_phys(page));

        return (void *) (__fix_to_virt(fixmap) + (uintaddr & ~PAGE_MASK));
}

Fixmap을 사용하여 해당 페이지를 매핑한다.

 

flush_kernel_vmap_range()

arch/arm/include/asm/cacheflush.h

static inline void flush_kernel_vmap_range(void *addr, int size)
{
        if ((cache_is_vivt() || cache_is_vipt_aliasing()))
          __cpuc_flush_dcache_area(addr, (size_t)size);
}

캐시가 vivt 또는 vipt aliasing인 경우 d-cache의 해당 영역을 flush 한다.

 

patch_unmap()

arch/arm/kernel/patch.c

static void __kprobes patch_unmap(int fixmap, unsigned long *flags)
        __releases(&patch_lock)
{
        clear_fixmap(fixmap);

        if (flags)
                spin_unlock_irqrestore(&patch_lock, *flags);
        else
                __release(&patch_lock);
}

매핑된 Fixmap 인덱스를 해지한다.

 

모듈에서 사용된 static key를 사용한 jump label API

jump_label_module_notify-1

초기화 함수 등록

kernel/jump_label.c

early_initcall(jump_label_init_module);

.initcall 섹션에 jump_label_init_module() 함수를 등록한다.

  • 등록되는 모든 initcall 함수들은 kernel_init 스레드의 do_initcalls() 함수에서 호출된다.

 

early_initcall()

include/linux/init.h

/*      
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)              __define_initcall(fn, early)

 

__define_initcall()

include/linux/init.h

/* initcalls are now grouped by functionality into separate 
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *   
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */  
        
#define __define_initcall(fn, id) \
        static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" #id ".init"))) = fn; \
        LTO_REFERENCE_INITCALL(__initcall_##fn##id)

initcall 함수를 .initcallearly.init 섹션에 등록한다.

  • 예) __initcall_jump_label_init_moduleearly = jump_label_init_module;

 

jump_label_init_module()

kernel/jump_label.c

static __init int jump_label_init_module(void)
{
        return register_module_notifier(&jump_label_module_nb);
}

jump_label_module_nb 구조체를 module notifier 블록에 등록한다.

 

jump_label_module_nb 구조체

kernel/jump_label.c

struct notifier_block jump_label_module_nb = {
        .notifier_call = jump_label_module_notify,
        .priority = 1, /* higher than tracepoints */
};
  •  jump_label_module_notify 함수가 등록된다.
  • notifier block이 ascending으로 정렬되어 있고 priority는 1의 값이다.

 

초기화 함수 호출

jump_label_module_notify()

kernel/jump_label.c

static int 
jump_label_module_notify(struct notifier_block *self, unsigned long val,
                         void *data)
{
        struct module *mod = data;
        int ret = 0;

        switch (val) {
        case MODULE_STATE_COMING:
                jump_label_lock();
                ret = jump_label_add_module(mod);
                if (ret)
                        jump_label_del_module(mod);
                jump_label_unlock();
                break;
        case MODULE_STATE_GOING:
                jump_label_lock();
                jump_label_del_module(mod);
                jump_label_unlock();
                break;
        case MODULE_STATE_LIVE:
                jump_label_lock();
                jump_label_invalidate_module_init(mod);
                jump_label_unlock();
                break;
        }

        return notifier_from_errno(ret);
}

개개 모듈이 로드/언로드/초기화 되었을 때 각각 호출되며 3가지 메시지에 대해 아래의 메시지에 대해 구분하여 처리된다.

  • MODULE_STATE_COMING
    • 모듈이 로드 되었을 때 호출되는 이벤트로 static key에 static_key_module 객체를 할당하여 연결한다.
    • load_module() 함수에서 notifier_call_chain()을 호출한다.
  • MODULE_STATE_GOING
    • 모듈이 언로드 되었을 때 호출되는 이벤트로 static key에 연결된 static_key_module  객체들 중 해당 모듈이 있는 객체를 찾아 삭제한다.
    • delete_module() 함수에서 notifier_call_chain()을 호출한다.
  • MODULE_STATE_LIVE
    • 모듈이 초기화 되었을 떄 호출되는 이벤트로 해당 모듈 엔트리에 있는 code 주소가 모두 커널 init 영역인 경우 code 부분을 0으로 치환한다. 커널 init 영역은 나중에 없어지는 영역이므로 해당 주소가 무의미 해진다.
    • do_init_module() 함수에서 notifier_call_chain()을 호출한다.

 

jump_label_add_module()

kernel/jump_label.c

static int jump_label_add_module(struct module *mod)
{
        struct jump_entry *iter_start = mod->jump_entries;
        struct jump_entry *iter_stop = iter_start + mod->num_jump_entries;
        struct jump_entry *iter;
        struct static_key *key = NULL;
        struct static_key_mod *jlm;

        /* if the module doesn't have jump label entries, just return */
        if (iter_start == iter_stop)
                return 0;

        jump_label_sort_entries(iter_start, iter_stop);

        for (iter = iter_start; iter < iter_stop; iter++) {
                struct static_key *iterk;

                iterk = (struct static_key *)(unsigned long)iter->key;
                if (iterk == key)
                        continue;

                key = iterk;
                if (__module_address(iter->key) == mod) {
                        /*
                         * Set key->entries to iter, but preserve JUMP_LABEL_TRUE_BRANCH.
                         */
                        *((unsigned long *)&key->entries) += (unsigned long)iter;
                        key->next = NULL;
                        continue;
                }
                jlm = kzalloc(sizeof(struct static_key_mod), GFP_KERNEL);
                if (!jlm)
                        return -ENOMEM;
                jlm->mod = mod;
                jlm->entries = iter;
                jlm->next = key->next;
                key->next = jlm;

                if (jump_label_type(key) == JUMP_LABEL_ENABLE)
                        __jump_label_update(key, iter, iter_stop, JUMP_LABEL_ENABLE);
        }

        return 0;
}

모듈에서 사용하는 jump label 엔트리를 정렬(heap sort)하고 key 객체가 해당 모듈에 이미 포함되어 있는 경우 key->entries가 해당 엔트리를 가리키게 하고, 그렇지 않은 경우 static_key_mod  객체를 할당하고 이를 key에 연결한다.  그런 후 타입이 enable인 경우 동일한 키를 사용하는 모든 엔트리가 가리키는 code  주소의 instruction을 branch or nop 코드로 update한다.

  • if (iter_start == iter_stop) return 0;
    • 엔트리가 없으면 리턴한다.
  • jump_label_sort_entries(iter_start, iter_stop);
    • 모듈에 존재하는 모든 jump label 엔트리를 정렬(heap sort)한다.
  • for (iter = iter_start; iter < iter_stop; iter++) {
    • 모듈에 있는 모든 jump label 엔트리 수 만큼
  • iterk = (struct static_key *)(unsigned long)iter->key;
    • 엔트리가 가리키는 static key 주소
  • if (iterk == key) continue;
    • 정렬된 엔트리의 key 주소는 동일할 수 있다. 처음 발견된 key 주소가 아니면 skip 한다.
  • if (__module_address(iter->key) == mod) {
    • static key 객체가 현재의 모듈에 존재하는 경우
  • *((unsigned long *)&key->entries) += (unsigned long)iter;
    • key->entries가 이 모듈의 static key를 가리키게 한다.
      • 동일한 static key를 가리키는 엔트리들 중 첫 엔트리만 static key를 가리킨다.
  • key->next = NULL;
    • 이 static key에 next를 null로 초기화 한다.
    • 모듈내에 존재하는 static key에 연결되는 것은 아무것도 없다.
  • jlm = kzalloc(sizeof(struct static_key_mod), GFP_KERNEL);
    • static_key_mod 객체용 메모리 공간을 할당 받는다.
    • 이 객체는 모듈에서 사용한 외부 글로벌 키 수 만큼 만들어지며 각 static key의 선두에 추가된다.
  • jlm->mod = mod; jlm->entries = iter;
    • static_key_mod 객체의 mod 멤버는 사용한 module을 가리키고 entres 멤버는 jump label entry를 가리킨다.
  • jlm->next = key->next; key->next = jlm;
    • static_key_mod 객체를 key가 가리키는 다른 static_key_mod의 선두 부분에 추가한다.
  • if (jump_label_type(key) == JUMP_LABEL_ENABLE)
    • jump label 타입이 enable이면
  • __jump_label_update(key, iter, iter_stop, JUMP_LABEL_ENABLE);
    • 엔트리가 가리키는 kernel code 주소의 1개 instruction을 branch instruction으로 update 한다.

 

다음 그림은 module-B가 로드되어 static key를 사용한 jump label API  코드가 초기화되는 과정을 보여준다.

  • static_key_mod 객체가 추가되고 추가된 모듈은 module을 가리키고 entries 멤버는 해당 모듈의 해당 키를 사용한 첫 엔트리를 가리킨다.
  • 모듈내부의 각 static_key의 entries는 이 키를 사용하는 첫 jump entry를 가리키고 next에는 null을 가리키게 한다.

jump_label_add_module-1

 

jump_label_del_module()

kernel/jump_label.c

static void jump_label_del_module(struct module *mod)
{
        struct jump_entry *iter_start = mod->jump_entries;
        struct jump_entry *iter_stop = iter_start + mod->num_jump_entries;
        struct jump_entry *iter;
        struct static_key *key = NULL;
        struct static_key_mod *jlm, **prev;

        for (iter = iter_start; iter < iter_stop; iter++) {
                if (iter->key == (jump_label_t)(unsigned long)key)
                        continue;

                key = (struct static_key *)(unsigned long)iter->key;

                if (__module_address(iter->key) == mod)
                        continue;

                prev = &key->next;
                jlm = key->next;

                while (jlm && jlm->mod != mod) {
                        prev = &jlm->next;
                        jlm = jlm->next;
                }

                if (jlm) {
                        *prev = jlm->next;
                        kfree(jlm);
                }
        }
}

모듈에서 사용한 외부 글로벌 static key를 찾고 여기에 연결된 static_key_mod 객체를 찾아 연결을 끊고 메모리를 해지한다.

  • for (iter = iter_start; iter < iter_stop; iter++) {
    • 모듈에 있는 모든 jump label 엔트리 수 만큼
  • if (iter->key == (jump_label_t)(unsigned long)key) continue;
    • key 값이 두 번 이상 반복되는 경우 skip 한다.
    • 각 키 주소로 정렬되어 있는 상태이다.
  • if (__module_address(iter->key) == mod) continue;
    • jump label 엔트리가 가리키는 static key가 모듈 내에 정의된 경우는 skip 한다.
  •  prev = &key->next; jlm = key->next;
    • static_key_mod 객체를 찾기 위해 먼저 prev에 static_key->next 주소를 대입하고 jlm에는 static_key_mod 객체의 선두 주소를 대입한다.
  • while (jlm && jlm->mod != mod) { prev = &jlm->next; jlm = jlm->next; }
    • unload 할 모듈을 찾는다.
  • if (jlm) { *prev = jlm->next; kfree(jlm); }
    • 찾은 경우 연결을 끊고 할당된 메모리를 해지한다.

 

다음 그림은 module-B가 언로드될 때 모듈 연결 정보인 static_key_mod 객체를 소멸시키는 과정을 보여준다.

jump_label_del_module-1

 

jump_label_invalidate_module_init()

kernel/jump_label.c

static void jump_label_invalidate_module_init(struct module *mod)
{
        struct jump_entry *iter_start = mod->jump_entries;
        struct jump_entry *iter_stop = iter_start + mod->num_jump_entries;
        struct jump_entry *iter;

        for (iter = iter_start; iter < iter_stop; iter++) {
                if (within_module_init(iter->code, mod))
                        iter->code = 0;
        }
}

모듈이 초기화될 때 호출되는 함수로 static key를 사용하는 jump label 엔트리들을 모두 조사하여 이 엔트리들이 가리키는 code 주소가 module init  섹션에 포함된 경우 code에 0을 넣어 해당 코드 주소를 가리키지 않도록 한다.

  • module init 섹션은 초기화 된 후에는 사용되지 않는 코드가 되므로 엔트리가 이들을 가리키지 않도록 한다.

 

참고