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 섹션은 초기화 된 후에는 사용되지 않는 코드가 되므로 엔트리가 이들을 가리키지 않도록 한다.

 

참고

 

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.