setup_command_line()

<kernel v5.0>

setup_command_line()

init/main.c

/*
 * We need to store the untouched command line for future reference.
 * We also need to store the touched command line since the parameter
 * parsing is performed in place, and we should allow a component to
 * store reference of name/value for future reference.
 */
static void __init setup_command_line(char *command_line)
{
        saved_command_line =
                memblock_alloc(strlen(boot_command_line) + 1, SMP_CACHE_BYTES);
        initcall_command_line =
                memblock_alloc(strlen(boot_command_line) + 1, SMP_CACHE_BYTES);
        static_command_line = memblock_alloc(strlen(command_line) + 1,
                                             SMP_CACHE_BYTES);
        strcpy(saved_command_line, boot_command_line);
        strcpy(static_command_line, command_line);
}

다음 3 개의 cmdline용 메모리를 할당하여 그 주소를 전역 변수에 대입한다.

  • saved_command_line
    • boot_command_line을 복사하여 놓는다.
    • 이 문자열은 변경되지 않는다.
  • initcall_command_line
    • boot_command_line 크기만큼 일단 공간만 확보해 놓는다.
    • 이 문자열은 per-initcall 파라메터 파싱을 위해 사용된다.
  • static_command_line
    • 인수로 받은 command_line을 복사하여 놓는다.
    • 이 문자열은 파라메터 파싱용으로 사용되며 변경될 수 있다.

 

mm_init_cpumask()

<kernel v5.0>

mm_init_cpumask()

include/linux/mm_types.h

/* Pointer magic because the dynamic array size confuses some compilers. */
static inline void mm_init_cpumask(struct mm_struct *mm)
{
        unsigned long cpu_bitmap = (unsigned long)mm;

        cpu_bitmap += offsetof(struct mm_struct, cpu_bitmap);
        cpumask_clear((struct cpumask *)cpu_bitmap);
}

cpu mask 비트맵을 cpu 수만큼 비트 clear한다.

 

cpumask_clear()

include/linux/cpumask.h

/**
 * cpumask_clear - clear all cpus (< nr_cpu_ids) in a cpumask
 * @dstp: the cpumask pointer 
 */
static inline void cpumask_clear(struct cpumask *dstp)
{       
        bitmap_zero(cpumask_bits(dstp), nr_cpumask_bits);
}

nr_cpumask_bits 수 만큼 비트를 dstp->bits를 clear 한다.

 

CONFIG_CPUMASK_OFFSTACK

config CPUMASK_OFFSTACK
        bool "Force CPU masks off stack" if DEBUG_PER_CPU_MAPS
        help
          Use dynamic allocation for cpumask_var_t, instead of putting
          them on the stack.  This is a bit more expensive, but avoids
          stack overflow.
  • CONFIG_CPUMASK_OFFSTACK 커널 옵션을 사용하면 cpu 수가 많은 경우 cpu 수 만큼 비트맵으로 처리되는 cpumask의 크기가 커진다. 이의 처리를 스택을 통해 처리하지 않도록 별도의 메모리를 할당받아 사용하는 방법으로 stack overflow를 피할 수 있다.

 

참고

mdesc->init_early()

 

mdesc->init_early()

각 머신에 정의되어 있는 init_early() 함수를 호출한다.

 

아래는 rpi2에서 사용하는 머신 정의이며 init_early 멤버 변수가 bcm2709_init_early() 함수를 가리키는 것을 보여준다.

arch/arm/mach-bcm2709/bcm2709.c

MACHINE_START(BCM2709, "BCM2709")
    /* Maintainer: Broadcom Europe Ltd. */
#ifdef CONFIG_SMP
        .smp            = smp_ops(bcm2709_smp_ops),
#endif
        .map_io = bcm2709_map_io,
        .init_irq = bcm2709_init_irq,
        .init_time = bcm2709_timer_init,
        .init_machine = bcm2709_init,
        .init_early = bcm2709_init_early,
        .reserve = board_reserve,
        .restart        = bcm2709_restart,
        .dt_compat = bcm2709_compat,
MACHINE_END

 

bcm2709_init_early()

arch/arm/mach-bcm2709/bcm2709.c

void __init bcm2709_init_early(void)
{
        /*
         * Some devices allocate their coherent buffers from atomic
         * context. Increase size of atomic coherent pool to make sure such
         * the allocations won't fail.
         */
        init_dma_coherent_pool_size(SZ_4M);
}
  • coherent 버퍼에 대한 사이즈를 기본 256K에서 4M로 증가시킨다.

 

init_dma_coherent_pool_size()

arch/arm/mm/dma-mapping.c

/*      
 * This can be called during early boot to increase the size of the atomic
 * coherent DMA pool above the default value of 256KiB. It must be called
 * before postcore_initcall. 
 */
void __init init_dma_coherent_pool_size(unsigned long size)
{
        /*
         * Catch any attempt to set the pool size too late.
         */
        BUG_ON(atomic_pool);

        /*
         * Set architecture specific coherent pool size only if
         * it has not been changed by kernel command line parameter.
         */
        if (atomic_pool_size == DEFAULT_DMA_COHERENT_POOL_SIZE)
                atomic_pool_size = size;
}
  • coherent 버퍼에 대한 사이즈가 default 256KB 상태이면 요청 size로 변경한다.

 

static size_t atomic_pool_size = DEFAULT_DMA_COHERENT_POOL_SIZE;

 

#define DEFAULT_DMA_COHERENT_POOL_SIZE  SZ_256K

 

coherent_pool 커널 파라메터

early_coherent_pool()

arch/arm/mm/dma-mapping.c

static int __init early_coherent_pool(char *p)
{
        atomic_pool_size = memparse(p, &p);
        return 0;
}
early_param("coherent_pool", early_coherent_pool);

 

  • “coherent_pool=” 커널 파라메터에 의해 coherent 버퍼 사이즈를 변경할 수 있다.
    • 예) “coherent_pool=2M”

 

reserve_crashkernel()

커널 크래쉬(패닉)가 발생될 때 원인 분석을 위해 덤프를 출력할 수 있도록 별도의 Capture 커널을 로드해두어야 한다. 이에 필요한 영역을 reserve 한다.

reserve_crashkernel-1

 

다음 그림은 커널이 panic 되는 경우 Capture 커널로 부팅되고 화일로 덤프를 수행 후 다시 원래 커널로 부팅이 일어나는 과정을 보여준다.

reserve_crashkernel-2

 

crashkernel 커널 파라메터

        crashkernel=size[KMG][@offset[KMG]]
                        [KNL] Using kexec, Linux can switch to a 'crash kernel'
                        upon panic. This parameter reserves the physical
                        memory region [offset, offset + size] for that kernel
                        image. If '@offset' is omitted, then a suitable offset
                        is selected automatically. Check
                        Documentation/kdump/kdump.txt for further details.

        crashkernel=range1:size1[,range2:size2,...][@offset]
                        [KNL] Same as above, but depends on the memory
                        in the running system. The syntax of range is
                        start-[end] where start and end are both
                        a memory unit (amount[KMG]). See also
                        Documentation/kdump/kdump.txt for an example.

        crashkernel=size[KMG],high
                        [KNL, x86_64] range could be above 4G. Allow kernel
                        to allocate physical memory region from top, so could
                        be above 4G if system have more than 4G ram installed.
                        Otherwise memory region will be allocated below 4G, if
                        available.
                        It will be ignored if crashkernel=X is specified.
        crashkernel=size[KMG],low
                        [KNL, x86_64] range under 4G. When crashkernel=X,high
                        is passed, kernel could allocate physical memory region
                        above 4G, that cause second kernel crash on system
                        that require some amount of low memory, e.g. swiotlb
                        requires at least 64M+32K low memory.  Kernel would
                        try to allocate 72M below 4G automatically.
                        This one let user to specify own low range under 4G
                        for second kernel instead.
                        0: to disable low allocation.
                        It will be ignored when crashkernel=X,high is not used
                        or memory reserved is below 4G.
  • “,high” 또는 “,low” 옵션은 x86_64 아키텍처에서만 사용한다.

 

커널 파라메터

  • 아래 커널 파라메터 외에 CONFIG_SYSFS=y, CONFIG_DEBUG_INFO=Y

CONFIG_KEXEC

arch/arm/Kconfig

config KEXEC
        bool "Kexec system call (EXPERIMENTAL)"
        depends on (!SMP || PM_SLEEP_SMP)
        help
          kexec is a system call that implements the ability to shutdown your
          current kernel, and to start another kernel.  It is like a reboot
          but it is independent of the system firmware.   And like a reboot
          you can start any kernel with it, not just Linux.
        
          It is an ongoing process to be certain the hardware in a machine
          is properly shutdown, so do not be surprised if this code does not
          initially work for you.

 

CONFIG_CRASH_DUMP

config CRASH_DUMP
        bool "Build kdump crash kernel (EXPERIMENTAL)"
        help
          Generate crash dump after being started by kexec. This should
          be normally only set in special crash dump kernels which are
          loaded in the main kernel with kexec-tools into a specially
          reserved region and then later executed after a crash by
          kdump/kexec. The crash dump kernel must be compiled to a
          memory address not used by the main kernel

          For more details see Documentation/kdump/kdump.txt

 

CONFIG_PROC_VMCORE

fs/proc/Kconfig

config PROC_VMCORE
        bool "/proc/vmcore support"
        depends on PROC_FS && CRASH_DUMP
        default y
        help
        Exports the dump image of crashed kernel in ELF format.

 

reserve_crashkernel()

arch/arm/kernel/setup.c

/**
 * reserve_crashkernel() - reserves memory are for crash kernel
 *
 * This function reserves memory area given in "crashkernel=" kernel command
 * line parameter. The memory reserved is used by a dump capture kernel when
 * primary kernel is crashing.
 */
static void __init reserve_crashkernel(void)
{
        unsigned long long crash_size, crash_base;
        unsigned long long total_mem;
        int ret; 

        total_mem = get_total_mem();
        ret = parse_crashkernel(boot_command_line, total_mem,
                                &crash_size, &crash_base);
        if (ret)
                return;

        ret = memblock_reserve(crash_base, crash_size);
        if (ret < 0) { 
                pr_warn("crashkernel reservation failed - memory is in use (0x%lx)\n",
                        (unsigned long)crash_base);
                return;
        }

        pr_info("Reserving %ldMB of memory at %ldMB for crashkernel (System RAM: %ldMB)\n",
                (unsigned long)(crash_size >> 20), 
                (unsigned long)(crash_base >> 20), 
                (unsigned long)(total_mem >> 20));

        crashk_res.start = crash_base;
        crashk_res.end = crash_base + crash_size - 1; 
        insert_resource(&iomem_resource, &crashk_res);
}
  • total_mem = get_total_mem();
    • lowmem 사이즈를 알아온다.
  • ret = parse_crashkernel(boot_command_line, total_mem, &crash_size, &crash_base);
    • “crashkernel=” 커널 파라메터를 파싱하여 crash_size와 crash_base를 알아온다.
  • ret = memblock_reserve(crash_base, crash_size);
    • crash 영역을 memblock에 reserve 한다.
  • insert_resource(&iomem_resource, &crashk_res);
    • crashk_res 리소스를 전역 iomem_resource에 추가한다.

 

get_total_mem()

arch/arm/kernel/setup.c

static inline unsigned long long get_total_mem(void)
{
        unsigned long total;
        
        total = max_low_pfn - min_low_pfn;
        return total << PAGE_SHIFT;
}

lowmem 사이즈를 알아온다.

 

parse_crashkernel()

kernel/kexec.c

/*
 * That function is the entry point for command line parsing and should be
 * called from the arch-specific code.
 */
int __init parse_crashkernel(char *cmdline,
                             unsigned long long system_ram,
                             unsigned long long *crash_size,
                             unsigned long long *crash_base)
{
        return __parse_crashkernel(cmdline, system_ram, crash_size, crash_base,
                                        "crashkernel=", NULL);
}
  • cmdline 문자열을 파싱하여 crash_base와 crash_size 값을 알아온다.

 

__parse_crashkernel()

kernel/kexec.c

static int __init __parse_crashkernel(char *cmdline,
                             unsigned long long system_ram,
                             unsigned long long *crash_size,
                             unsigned long long *crash_base,
                             const char *name,
                             const char *suffix)
{
        char    *first_colon, *first_space;
        char    *ck_cmdline;

        BUG_ON(!crash_size || !crash_base);
        *crash_size = 0;
        *crash_base = 0;

        ck_cmdline = get_last_crashkernel(cmdline, name, suffix);

        if (!ck_cmdline)
                return -EINVAL;

        ck_cmdline += strlen(name);

        if (suffix)
                return parse_crashkernel_suffix(ck_cmdline, crash_size,
                                suffix);
        /*
         * if the commandline contains a ':', then that's the extended
         * syntax -- if not, it must be the classic syntax
         */
        first_colon = strchr(ck_cmdline, ':');
        first_space = strchr(ck_cmdline, ' ');
        if (first_colon && (!first_space || first_colon < first_space))
                return parse_crashkernel_mem(ck_cmdline, system_ram,
                                crash_size, crash_base);

        return parse_crashkernel_simple(ck_cmdline, crash_size, crash_base);
}

crash_size와 crash_base 값을 알아오는데 다음 3 가지 문법 형태에 따라 호출함수를 달리한다.

  • 1) suffix가 주어진 경우
    • “64M,high”
  • 2) ‘:’문자로 구분된 range가 주어진 경우
    • “xxx-yyy:64M@0”
  • 3) 단순히 사이즈(및 시작 주소)가 주어진 경우
    • “64M@0”

 

  • ck_cmdline = get_last_crashkernel(cmdline, name, suffix);
    • cmdline에서 name으로 검색된 마지막 위치에서 suffix가 일치하는 문자열을 알아온다.
      • name=”crashkernel=”
      • suffix=”,high”, “,low” 또는 null
      • 예) cmdline=”console=ttyS0 crashkernel=128M@0″, name=”crashkernel=”, suffix=null
        • ck_cmdline=”crashkernel=128M@0″
  • ck_cmdline += strlen(name);
    • ck_cmdline이 “crashkernel=” 문자열 다음을 가리키게 한다.
  • if (suffix) return parse_crashkernel_suffix(ck_cmdline, crash_size, suffix);
    • suffix가 주어진 경우
  • first_colon = strchr(ck_cmdline, ‘:’);
    • 처음 발견되는 “:” 문자열을 찾아본다.
  • first_space = strchr(ck_cmdline, ‘ ‘);
    • 처음 발견되는 space 문자열을 찾아본다.
  • if (first_colon && (!first_space || first_colon < first_space)) return parse_crashkernel_mem(ck_cmdline, system_ram, crash_size, crash_base);
    • “:” 문자열이 space 문자열 이전에 있는 경우 cmdline=”ramsize-range:size[,…][@offset]” 형태로 문자열이 전달되게 되는데 ‘,’로 구분한 마지막 range에서 size를 파싱하여 출력 인수 crash_size에 담고, @offset를 파싱하여 crash_base에 담은 후 성공리에 0을 리턴한다. 만일 range가 system_ram을 초과하는 경우에는 에러를 리턴한다.
  • return parse_crashkernel_simple(ck_cmdline, crash_size, crash_base);
    • 단순하게 cmdline=”size[@offset]” 형태로 문자열이 전달되는 경우 size를 파싱하여 crash_size에 담고, @offset를 파싱하여 crash_base에 담는다.

 

get_last_crashkernel()

kernel/kexec.c

static __init char *get_last_crashkernel(char *cmdline,
                             const char *name,
                             const char *suffix)
{
        char *p = cmdline, *ck_cmdline = NULL;

        /* find crashkernel and use the last one if there are more */
        p = strstr(p, name);
        while (p) {
                char *end_p = strchr(p, ' ');
                char *q;

                if (!end_p)
                        end_p = p + strlen(p);

                if (!suffix) {
                        int i;

                        /* skip the one with any known suffix */
                        for (i = 0; suffix_tbl[i]; i++) {
                                q = end_p - strlen(suffix_tbl[i]);
                                if (!strncmp(q, suffix_tbl[i],
                                             strlen(suffix_tbl[i])))
                                        goto next;
                        }
                        ck_cmdline = p;
                } else {
                        q = end_p - strlen(suffix);
                        if (!strncmp(q, suffix, strlen(suffix)))
                                ck_cmdline = p;
                }
next:
                p = strstr(p+1, name);
        }

        if (!ck_cmdline)
                return NULL;

        return ck_cmdline;
}

cmdline에서 마지막 “crashkernel=” 문자열을 찾고 인수로 지정한 suffix 문자열을 찾아 온다. 만일 suffix 인수가 지정되지 않았으면 suffix에 “,high” 또는 “,low” 문자열을 사용하여 검색한다.

  • p = strstr(p, name);
    • cmdline으로 받은 문자열 중 name(“crashkernel=”) 문자열이 있는지 찾아본다.
  • while (p) { char *end_p = strchr(p, ‘ ‘);
    • 문자열이 검색된 경우 그 위치부터 space 문자열 또는 문자열의 끝까지 루프를 돈다.
  • for (i = 0; suffix_tbl[i]; i++)  {
    • suffix 인수가 지정되지 않은 경우 suffix_tbl[] 배열 수 만큼 루프를 돈다.
  • if (!strncmp(q, suffix_tbl[i], strlen(suffix_tbl[i]))) goto next;
    • 문자열의 끝에 suffix_tbl에 있는 문자열이 발견된 경우 next로 이동
  • } else { q = end_p – strlen(suffix);
    • 인수 suffix가 지정된 경우
  • if (!strncmp(q, suffix, strlen(suffix))) ck_cmdline = p;
    • 인수로 지정한 suffix 문자열이 발견되는 경우 일단 ck_cmdline에 발견된 위치를 기억한다.
  • p = strstr(p+1, name);
    • 발견된 위치+1에서 다시 name 문자열을 찾아보고 발견되지 않으면 루프 조건에 의해 루프를 빠져나온다.

 

kernel/kexec.c

static __initdata char *suffix_tbl[] = {
        [SUFFIX_HIGH] = ",high",
        [SUFFIX_LOW]  = ",low",
        [SUFFIX_NULL] = NULL,
};

 

parse_crashkernel_suffix()

kernel/kexec.c

/*
 * That function parses "suffix"  crashkernel command lines like
 *
 *      crashkernel=size,[high|low]
 *
 * It returns 0 on success and -EINVAL on failure.
 */
static int __init parse_crashkernel_suffix(char *cmdline,
                                           unsigned long long   *crash_size,
                                           const char *suffix)
{
        char *cur = cmdline;

        *crash_size = memparse(cmdline, &cur);
        if (cmdline == cur) {
                pr_warn("crashkernel: memory value expected\n");
                return -EINVAL;
        }

        /* check with suffix */
        if (strncmp(cur, suffix, strlen(suffix))) {
                pr_warn("crashkernel: unrecognized char\n");
                return -EINVAL;
        }
        cur += strlen(suffix);
        if (*cur != ' ' && *cur != '\0') {
                pr_warn("crashkernel: unrecognized char\n");
                return -EINVAL;
        }

        return 0;
}

suffix 문자열로 끝나는 cmdline 문자열을 파싱하여 crashsize를 리턴한다.

  • *crash_size = memparse(cmdline, &cur);
    • 사이즈를 알아오고 cur에는 파싱 문자열의 끝+1의 위치를 담는다.
      • 예) cmdline=”64K,high”
        • crash_size=65536, cur=”,high”
  • if (strncmp(cur, suffix, strlen(suffix))) {
    • cur 문자열에서 인수로 지정한 suffix 문자열이 발견되지 않으면 경고 메시지를 출력하고 에러로 리턴한다.
  • if (*cur != ‘ ‘ && *cur != ‘\0’) {
    • suffix 문자열 다음 문자가 space 또는 null 문자가 아닌 경우 경고 메시지를 출력하고 에러로 리턴한다.

 

memparse()

lib/cmdline.c

/**
 *      memparse - parse a string with mem suffixes into a number
 *      @ptr: Where parse begins
 *      @retptr: (output) Optional pointer to next char after parse completes
 *
 *      Parses a string into a number.  The number stored at @ptr is
 *      potentially suffixed with K, M, G, T, P, E.
 */

unsigned long long memparse(const char *ptr, char **retptr)
{
        char *endptr;   /* local pointer to end of parsed string */

        unsigned long long ret = simple_strtoull(ptr, &endptr, 0);

        switch (*endptr) {
        case 'E':
        case 'e':
                ret <<= 10;
        case 'P':
        case 'p':
                ret <<= 10;
        case 'T':
        case 't':
                ret <<= 10;
        case 'G':
        case 'g':
                ret <<= 10;
        case 'M':
        case 'm':
                ret <<= 10;
        case 'K':
        case 'k':
                ret <<= 10;
                endptr++; 
        default:
                break;
        }

        if (retptr)
                *retptr = endptr;

        return ret;
}
EXPORT_SYMBOL(memparse);

단위를 포함한 숫자를 파싱하여 값을 리턴하고 retptr에는 파싱 완료한 다음 문자열을 가리킨다.

  • unsigned long long ret = simple_strtoull(ptr, &endptr, 0);
    • ptr 문자열을 unsigned long long으로 변환하여 리턴한다. retptr에는 변환에 필요한 문자열의 끝+1이 담긴다.
      • 예) ptr=”10G”
        • ret=10ULL, retptr=”G”
  • switch (*endptr) {
    • 단위를 나타내는 문자열을 비교하여 일치하는 경우 해당 case 문부터 break 될 때까지를 수행한다.

 

parse_crashkernel_mem()

kernel/kexec.c

/*
 * parsing the "crashkernel" commandline
 *
 * this code is intended to be called from architecture specific code
 */


/*
 * This function parses command lines in the format
 *
 *   crashkernel=ramsize-range:size[,...][@offset]
 *
 * The function returns 0 on success and -EINVAL on failure.
 */
static int __init parse_crashkernel_mem(char *cmdline,
                                        unsigned long long system_ram,
                                        unsigned long long *crash_size,
                                        unsigned long long *crash_base)
{
        char *cur = cmdline, *tmp;

        /* for each entry of the comma-separated list */
        do {
                unsigned long long start, end = ULLONG_MAX, size;

                /* get the start of the range */
                start = memparse(cur, &tmp);
                if (cur == tmp) {
                        pr_warn("crashkernel: Memory value expected\n");
                        return -EINVAL;
                }
                cur = tmp;
                if (*cur != '-') {
                        pr_warn("crashkernel: '-' expected\n");
                        return -EINVAL;
                }
                cur++;

                /* if no ':' is here, than we read the end */
                if (*cur != ':') {
                        end = memparse(cur, &tmp);
                        if (cur == tmp) {
                                pr_warn("crashkernel: Memory value expected\n");
                                return -EINVAL;
                        }
                        cur = tmp;
                        if (end <= start) {
                                pr_warn("crashkernel: end <= start\n");
                                return -EINVAL;
                        }
                }

                if (*cur != ':') {
                        pr_warn("crashkernel: ':' expected\n");
                        return -EINVAL;
                }
                cur++;

                size = memparse(cur, &tmp);
                if (cur == tmp) {
                        pr_warn("Memory value expected\n");
                        return -EINVAL;
                }
                cur = tmp;
                if (size >= system_ram) {
                        pr_warn("crashkernel: invalid size\n");
                        return -EINVAL;
                }

                /* match ? */
                if (system_ram >= start && system_ram < end) {
                        *crash_size = size;
                        break;
                }
        } while (*cur++ == ',');

        if (*crash_size > 0) {
                while (*cur && *cur != ' ' && *cur != '@')
                        cur++;
                if (*cur == '@') {
                        cur++;
                        *crash_base = memparse(cur, &tmp);
                        if (cur == tmp) {
                                pr_warn("Memory value expected after '@'\n");
                                return -EINVAL;
                        }
                }
        }

        return 0;
}

cmdline=”ramsize-range:size[,…][@offset]” 형태로 문자열이 전달된 경우 ‘,’로 구분한 마지막 range에서 size를 파싱하여 출력 인수 crash_size에 담고, @offset를 파싱하여 crash_base에 담은 후 성공리에 0을 리턴한다. 만일 range가 system_ram을 초과하는 경우에는 에러를 리턴한다.

  • start = memparse(cur, &tmp);
    • cur 문자열을 파싱하여 start에 시작 주소를 알아오고 tmp는 다음 문자열을 가리킨다.
  • if (*cur != ‘-‘) {
    • 다음 문자열이 ‘-‘가 아니면 경고 메시지를 출력하고 에러를 리턴한다.
  • if (*cur != ‘:’) { end = memparse(cur, &tmp);
    • ‘:’ 문자열이 아니면 cur 문자열을 파싱하여 end에 끝 주소를 알아오고 tmp는 다음 문자열을 가리킨다.
  • if (*cur != ‘:’) {
    • 다음 문자열이 ‘:’가 아니면 경고 메시지를 출력하고 에러를 리턴한다.
  • size = memparse(cur, &tmp);
    • cur 문자열을 파싱하여 size에 사이즈를 알아오고 tmp는 다음 문자열을 가리킨다.
  • if (size >= system_ram) {
    • size가 system_ram 크기를 초과하면 경고 메시지를 출력하고 에러로 리턴한다.
  • if (system_ram >= start && system_ram < end) { *crash_size = size;
    • start와 end가 system_ram(전체 lowmem) 보다 작으면 crash_size를 size로 결정하고 루프를 빠져나간다.
  • } while (*cur++ == ‘,’);
    • cur 문자열이 ‘,’ 인 경우 루프를 계속한다.
  • if (*crash_size > 0) {
    • crash_size가 0 이상인 경우
  • while (*cur && *cur != ‘ ‘ && *cur != ‘@’) cur++
    • cur 문자열이 ‘ ‘ 또는 ‘@’가 나올 때 까지 루프를 돌며 cur를 증가시킨다.
  • if (*cur == ‘@’) { cur++;
    • ‘@’ 문자열을 발견하면 cur를 증가시킨다.
  • *crash_base = memparse(cur, &tmp);
    • cur 문자열에서 사이즈를 알아와서 crash_base에 대입한다.

 

parse_crashkernel_simple()

kernel/kexec.c

/*
 * That function parses "simple" (old) crashkernel command lines like
 *
 *      crashkernel=size[@offset]
 *
 * It returns 0 on success and -EINVAL on failure.
 */
static int __init parse_crashkernel_simple(char *cmdline,
                                           unsigned long long *crash_size,
                                           unsigned long long *crash_base)
{
        char *cur = cmdline;

        *crash_size = memparse(cmdline, &cur);
        if (cmdline == cur) {
                pr_warn("crashkernel: memory value expected\n");
                return -EINVAL;
        }

        if (*cur == '@')
                *crash_base = memparse(cur+1, &cur);
        else if (*cur != ' ' && *cur != '\0') {
                pr_warn("crashkernel: unrecognized char\n");
                return -EINVAL;
        }

        return 0;
}

cmdline에 “size[@offset]” 형태인 경우 이를 파싱하여 size 부분을 crash_size에 담고 crash_

  • *crash_size = memparse(cmdline, &cur);
    • cmdline에서 size를 파싱하여 crash_size에 담고 cur에는 파싱한 문자열의 끝+1의 위치를 담는다.
  • if (*cur == ‘@’) *crash_base = memparse(cur+1, &cur);
    • 다음 문자가 ‘@’인 경우 crash_base에 ‘@’ 문자 다음을 파싱하여 가져온다.
  •  else if (*cur != ‘ ‘ && *cur != ‘\0’) {
    • 다음 문자가 space 문자 또는 null이 아닌 다른 문자인 경우 경고 메시지를 출력하고 에러로 리턴한다.

 

구조체

crashk_res 구조체

kernel/kexec.c

/* Location of the reserved area for the crash kernel */
struct resource crashk_res = {
        .name  = "Crash kernel",
        .start = 0, 
        .end   = 0, 
        .flags = IORESOURCE_BUSY | IORESOURCE_MEM
};

 

참고

 

 

가상화 지원 (하이퍼 모드)

<kernel v5.4>

가상화 지원 (하이퍼 모드)

하이퍼바이저라는 용어는 VM(Virtual Machine)을 생성하고, 관리하고 스케줄링 하기 위해 사용되는 소프트웨어이다.

 

ARM 시스템에서 가상화를 지원하기 위해서는 AVE(Architecture Virtualization Extension)가 필요하다.

  • ARM32에서는 SoC 마다 탑재 여부가 다르다.
  • ARM64에서는 대부분 내장되어 있다.

 

ARM32와 ARM64 시스템의에서 다음과 같은 하이퍼 바이저 운영 모드들이 지원된다. 대부분의 설명은 ARM64를 디폴트로 한다.

  • ARM32
    • 하이퍼 바이저는 HYP 모드에서 운영
    • Guest OS는 SVC 모드에서 운영
  • ARM64
    • 하이퍼 바이저는 EL2에서 운영
    • Guest OS는 EL1 모드에서 운영

 

하이퍼 바이저를 운영하는 방법은 다음과 같이 두 가지가 있다.

  • Type 1 (Standalone)
    • EL2에서 Host OS 없이 전용 하이퍼바이저를 운영하고, Host OS 없이 EL1에서 Guest OS를 운영한다.
      • 예) Xen Server, VMWare ESXi, VMWare vSphere, Oracle VM Server, Hyper-V
  • Type 2 (Hypervisors and Hosted)
    • EL2에서 하이퍼바이저와 Host OS를 같이 운영하고, EL1에서 Guest OS를 운영한다.
      • 예) VMware worksataion, Virtual Box, QEMU/KVM

 

VHE(Virutalization Host Extension)

ARM 리눅스를 Type 2 하이퍼 바이저로 운영될 때 ARMv8 VHE 지원 여부에 따라 EL2 모드와 EL1 모드를 사용한다.

  • VHE 미지원시
    • EL2 스위칭 + EL1 Host & Guest OS
    • Host OS를 EL2로 부팅한 후 EL2에 관련 스위칭(irq 라우팅 포함) stub 코드만 남겨 놓고 EL1으로 전환하여 운영한다.
      • 예) Host용 리눅스 커널이 EL2에서 부팅되고, EL1으로 전환한다.
  • VHE 지원시
    • EL2 Host OS + EL1 Guest OS
    • Host OS를 EL2로 부팅 및 운영하는 것으로 EL2 및 EL1 간 많은 스위칭을 제거해 성능을 향상시켰다.
      • 예)  지원을 통해 Host용 리눅스 커널이 EL2에서 부팅 및 운영을 한다.
    • VHE 기능을 사용하는 동안 EL1 레지스터를 사용하는 리눅스 커널의 코드 변경없이 EL2 모드에서 사용할 수 있게 하였다.즉 코드에서 접근하는 *_EL1 레지스터들은 실제 *_EL2 레지스터와 동일한 동작을 한다.
      • HCR_EL2.E2H 비트를 설정할 때에 사용할 수 있다.
      • EL2 모드에서 원래 EL0 및 EL1 레지스터를 사용하기 위해서는 *_EL02 및 *_EL12 레지스터를 사용한다.
    • 참고:

 

다음 그림은 리눅스 커널등의 OS가 다양한 하이퍼바이저 운영 상황에서 동작하는 모습을 보여준다.

  • 좌측 그림은 Xen, VMware/ESX 같은 전용 하이퍼바이저가 동작하고, guest 운영 체제들은 EL1에서 동작한다.
  • 중앙 및 우측 그림은 VHE 지원여부에 따라 각각 EL2 및 EL1에서 호스트 운영체제가 동작하고, guest 운영 체제들은 EL1에서 동작한다.
    • SCR_EL2.E2H==1 (VHE 운영) 에서 Host OS가 운영되는 cpu는 SCR_EL2.TGE 값이 1이고, Guest OS가 운영되는 cpu는 0이다.

 

하이퍼모드 지원 유무 확인

하이퍼모드로 부팅된 경우인지 아닌지 메시지(dmesg)를 출력하여 안내한다.

ARM64 예)

  • EL2 부팅
    • CPU: All CPU(s) started at EL2
  • EL1 부팅
    • “CPU: All CPU(s) started at EL1”

 

ARM32 예)

  • HYP 모드 부팅
    • “CPU: All CPU(s) started in HYP mode.”
    • “CPU: Virtualization extensions available.”
  • SVC 모드 부팅
    • “CPU: All CPU(s) started in SVC mode.”

 

Secure EL2 support

  • ARMv8.4 아키텍처부터 Secure EL2를 지원하기 시작하였다.

 

Full-virtualization vs Para-virtualization

한글로는 전가상화 또는 반가상화라고 불리운다. 오늘 날 가상화를 위한 하드웨어 지원이 있는 cpu에서 Guest OS들은 Guest OS 코드 변경 없이 전가상화를 지원하도록 빠르게 동작하고 있다.

  • 현재 전가상화와 반가상화를 구분하여 사용하지 않는다.
  • Guest OS를 보다 효과적인 성능으로 사용하기 위해 대부분의 운영체제에 반가상화용 드라이버(virtio)들이 포함되어 있다.

 

Stage 2 변환

하이퍼 모드를 사용하는 경우 운영체제 별로 매핑된 Stage 2 테이블을 사용는 변환을 통해 물리주소(PA)를 얻어낸다.

  • 하이퍼 모드 사용하지 않을 때
    • VA —-> (Stage 1 Transalation MMU) —-> PA
  • 하이퍼 모드 사용할 때
    • VA —-> (Stage 1 Transalation MMU) —-> IPA —-> (Stage 2 Transalation MMU) —-> PA

 

ASID(Address Space IDentifier)

Stage 1 변환에서 VA에 대해 application을 식별하기 위해 ASID를 사용한다.

 

VMID(Virtual Machine IDentifier)

Stage 2 변환에서 IPA에 대해 운영체제를 식별하기 위해 VMID를 사용한다.

 

속성 병합(Combining)

Stage 1과 Stage 2의 매핑은 각각 메모리 타입과 속성을 가지고 있고, 이들은 각각의 제한을 가지고 있는데 병합(Combining)하여 더 많은 제한을 가진다.

  • 예) 디바이스 + 메모리 = 디바이스

 

속성 오버라이딩(Overinding)

Stage 1과 Stage 2의 매핑은 각각 메모리 타입과 속성에 대해 동작 방식을 결정할 수 있는 레지스터가 있다.

  • HCR_EL2.CD
    • Stage 1 속성을 모두 Non-cacheable로 운영한다.
  • HCR_EL2.DC
    • Stage 1 속성을 모두 Normal, Write-Back 캐시 속성으로 운영한다.
  • HCR_EL2.FWB
    • 속성 병합대신 Stage 1 속성으로 오버라이딩한다. (forARMv8.4)

 

Virtual CPU

vCPU

하이퍼 바이저를 운영하면 cpu core를 각각의 운영체제가 공유하여 사용할 수 있도록 가상 cpu 개념이 사용된다. 즉 1개의 cpu를 여러 개의 vcpu로 나누어 운용할 수 있다.

예) 4 개의 core에 리눅스 Guest OS를 vcpu0~3까지 4개의 코어에서 운용하고, 또 다른 리눅스 Guest OS를 vcpu0~1까지 2개의 코어에서 운용할 수 있다.

 

Virtual Exception

vIRQ, vFIQ 및 vSErrors

하이퍼 바이저를 운영하면 EL2에서 수신된 물리 인터럽트를 vCPU로 forwarding할 때 가상 Exception인 vIRQ, vFIQ 및 vSErrors가 사용된다.

 


하이퍼 모드 체크

hyp_mode_check() – ARM64

arch/arm64/kernel/setup.c

static void __init hyp_mode_check(void)
{
        if (is_hyp_mode_available())
                pr_info("CPU: All CPU(s) started at EL2\n");
        else if (is_hyp_mode_mismatched())
                WARN_TAINT(1, TAINT_CPU_OUT_OF_SPEC,
                           "CPU: CPUs started in inconsistent modes");
        else
                pr_info("CPU: All CPU(s) started at EL1\n");
}

커널이 EL2로 부팅되었는지 EL1으로 부팅되었는지 여부를 메시지로 출력한다.

 

hyp_mode_check() – ARM32

arch/arm/kernel/setup.c

#ifndef ZIMAGE
void __init hyp_mode_check(void)
{
#ifdef CONFIG_ARM_VIRT_EXT
        sync_boot_mode();

        if (is_hyp_mode_available()) {
                pr_info("CPU: All CPU(s) started in HYP mode.\n");
                pr_info("CPU: Virtualization extensions available.\n");
        } else if (is_hyp_mode_mismatched()) {
                pr_warn("CPU: WARNING: CPU(s) started in wrong/inconsistent modes (primary CPU mode 0x%x)\n",
                        __boot_cpu_mode & MODE_MASK);
                pr_warn("CPU: This may indicate a broken bootloader or firmware.\n");
        } else       
                pr_info("CPU: All CPU(s) started in SVC mode.\n");
#endif               
}
#endif

커널이 HYP 모드로 부팅되었는지 SVC 모드로 부팅되었는지 여부를 메시지로 출력한다.

 

하이퍼 모드 운영 여부

is_hyp_mode_available() – ARM64

arch/arm64/include/asm/virt.h

/* Reports the availability of HYP mode */
static inline bool is_hyp_mode_available(void)
{
        return (__boot_cpu_mode[0] == BOOT_CPU_MODE_EL2 &&
                __boot_cpu_mode[1] == BOOT_CPU_MODE_EL2);
}

boot cpu가 EL2로 부팅되었는지 여부를 알아온다.

  • __boot_cpu_mode[] 저장 루틴은 다음을 참고한다.

 

is_hyp_mode_available() – ARM32

arch/arm/include/asm/virt.h

/* Reports the availability of HYP mode */
static inline bool is_hyp_mode_available(void)
{
        return ((__boot_cpu_mode & MODE_MASK) == HYP_MODE &&
                !(__boot_cpu_mode & BOOT_CPU_MODE_MISMATCH));
}

boot cpu가 EL2로 부팅되었는지 여부를 알아온다.

 


기타

sync_boot_mode()

arch/arm/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode the primary CPU was booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * if it fails to do this, the flag BOOT_CPU_MODE_MISMATCH is set to indicate
 * that some CPU(s) were booted in a different mode.
 *
 * This allows the kernel to flag an error when the secondaries have come up.
 */
extern int __boot_cpu_mode;

static inline void sync_boot_mode(void)
{
        /*  
         * As secondaries write to __boot_cpu_mode with caches disabled, we
         * must flush the corresponding cache entries to ensure the visibility
         * of their writes.
         */
        sync_cache_r(&__boot_cpu_mode);
}
  • 전역 __boot_cpu_mode 변수 영역에 대해 inner & outer 캐시 flush를 수행한다.

 

sync_cache_r()

arch/arm/include/asm/cacheflush.h

#define sync_cache_r(ptr) __sync_cache_range_r(ptr, sizeof *(ptr))
  • long으로 선언된 전역 __boot_cpu_mode 변수 위치에 대해 inner & outer 캐시 flush를 수행한다.
    • long 값이므로 32bit 시스템에서는 4 byte , 64bit 시스템에서는 8 byte 영역만큼에 대해 flush를 수행하게 된다.

 

__sync_cache_range_r()

arch/arm/include/asm/cacheflush.h

/*
 * Ensure preceding writes to *p by other CPUs are visible to
 * subsequent reads by this CPU.  We must be careful not to
 * discard data simultaneously written by another CPU, hence the
 * usage of flush rather than invalidate operations.
 */
static inline void __sync_cache_range_r(volatile void *p, size_t size)
{
        char *_p = (char *)p;

#ifdef CONFIG_OUTER_CACHE
        if (outer_cache.flush_range) {
                /*
                 * Ensure dirty data migrated from other CPUs into our cache
                 * are cleaned out safely before the outer cache is cleaned:
                 */
                __cpuc_clean_dcache_area(_p, size);

                /* Clean and invalidate stale data for *p from outer ... */
                outer_flush_range(__pa(_p), __pa(_p + size));
        }
#endif

        /* ... and inner cache: */
        __cpuc_flush_dcache_area(_p, size);
}

지정된 range에 대해 inner & outer 캐시 flush를 수행한다.

  • CONFIG_OUTER_CACHE
    • outer 캐시가 사용되는 시스템에서 사용하는 커널 옵션
    • rpi2: outer 캐시를 사용하지 않는다.
  • if (outer_cache.flush_range) {
    • outer 캐시를 사용하는 시스템에서 flush_range에 연결된 함수가 존재하는 경우
  • __cpuc_clean_dcache_area(_p, size);
    • 지정 range의 outer 캐시를 flush하기 전에 먼저 cpu 캐시 즉 inner 캐시를 먼저 clean 작업을 해야한다.
    • ARMv7:
      • inner d-cache 영역에 대해 clean 오퍼레이션 대신 flush를 구현하였다.
  • outer_flush_range(__pa(_p), __pa(_p + size));
    • 지정 range의 outer cache에 대한 flush(clean & invalidate)를 수행한다.
  • __cpuc_flush_dcache_area(_p, size);
    • 지정 range의 inner d-cache에 대한 flush(clean & invalidate)를 수행한다.

 

arch/arm/include/asm/cacheflush.h

/*
 * There is no __cpuc_clean_dcache_area but we use it anyway for
 * code intent clarity, and alias it to __cpuc_flush_dcache_area.
 */
#define __cpuc_clean_dcache_area __cpuc_flush_dcache_area
  • ARMv7에서는 clean 구현을 따로 준비하지 않았고 따라서 flush 구현을 사용한다.

 

arch/arm/include/asm/cacheflush.h

#define __cpuc_flush_dcache_area        cpu_cache.flush_kern_dcache_area
  • ARMv7: v7_flush_kern_dcache_area()

 

outer_flush_range()

arch/arm/include/asm/outercache.h

/**     
 * outer_flush_range - clean and invalidate outer cache lines
 * @start: starting physical address, inclusive
 * @end: end physical address, exclusive
 */
static inline void outer_flush_range(phys_addr_t start, phys_addr_t end)
{
        if (outer_cache.flush_range)
                outer_cache.flush_range(start, end);
}
  • outer_cache가 구현된 머신에서 지정 range의 outer cache에 대한 flush(clean & invalidate)를 수행한다.

 

is_hyp_mode_mismatched()

arch/arm/include/asm/virt.h

/* Check if the bootloader has booted CPUs in different modes */
static inline bool is_hyp_mode_mismatched(void)
{
        return !!(__boot_cpu_mode & BOOT_CPU_MODE_MISMATCH);
}

 

arch/arm/include/asm/virt.h

/*
 * Flag indicating that the kernel was not entered in the same mode on every
 * CPU.  The zImage loader stashes this value in an SPSR, so we need an
 * architecturally defined flag bit here.
 */
#define BOOT_CPU_MODE_MISMATCH  PSR_N_BIT

 

arch/arm/include/uapi/asm/ptrace.h

/*
 * PSR bits
 * Note on V7M there is no mode contained in the PSR
 */
#define USR26_MODE      0x00000000
#define FIQ26_MODE      0x00000001
#define IRQ26_MODE      0x00000002
#define SVC26_MODE      0x00000003
#if defined(__KERNEL__) && defined(CONFIG_CPU_V7M)
/*
 * Use 0 here to get code right that creates a userspace
 * or kernel space thread.
 */
#define USR_MODE        0x00000000
#define SVC_MODE        0x00000000
#else
#define USR_MODE        0x00000010
#define SVC_MODE        0x00000013
#endif
#define FIQ_MODE        0x00000011
#define IRQ_MODE        0x00000012
#define ABT_MODE        0x00000017
#define HYP_MODE        0x0000001a
#define UND_MODE        0x0000001b
#define SYSTEM_MODE     0x0000001f
#define MODE32_BIT      0x00000010
#define MODE_MASK       0x0000001f

 

arch/arm/include/uapi/asm/ptrace.h

/*
 * Use 0 here to get code right that creates a userspace
 * or kernel space thread.
 */
#define USR_MODE        0x00000000
#define SVC_MODE        0x00000000
#else
#define USR_MODE        0x00000010
#define SVC_MODE        0x00000013
#endif
#define FIQ_MODE        0x00000011
#define IRQ_MODE        0x00000012
#define ABT_MODE        0x00000017 
#define HYP_MODE        0x0000001a
#define UND_MODE        0x0000001b
#define SYSTEM_MODE     0x0000001f
#define MODE32_BIT      0x00000010
#define MODE_MASK       0x0000001f

 


전역 변수

__boot_cpu_mode – ARM64

arch/arm64/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode CPUs were booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * In this case, both 32bit halves of __boot_cpu_mode will contain the
 * same value (either 0 if booted in EL1, BOOT_CPU_MODE_EL2 if booted in EL2).
 *
 * Should the bootloader fail to do this, the two values will be different.
 * This allows the kernel to flag an error when the secondaries have come up.
 */
extern u32 __boot_cpu_mode[2];

 

arch/arm64/kernel/hyp-stub.S

/*
 * We need to find out the CPU boot mode long after boot, so we need to
 * store it in a writable variable.
 *
 * This is not in .bss, because we set it sufficiently early that the boot-time
 * zeroing of .bss would clobber it.
 */
ENTRY(__boot_cpu_mode)
        .long   BOOT_CPU_MODE_EL2
        .long   BOOT_CPU_MODE_EL1

 

__boot_cpu_mode – ARM32

arch/arm/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode the primary CPU was booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * if it fails to do this, the flag BOOT_CPU_MODE_MISMATCH is set to indicate
 * that some CPU(s) were booted in a different mode.
 *
 * This allows the kernel to flag an error when the secondaries have come up.
 */
extern int __boot_cpu_mode;

arch/arm/kernel/hyp-stub.S

/*
 * For the kernel proper, we need to find out the CPU boot mode long after
 * boot, so we need to store it in a writable variable.
 *
 * This is not in .bss, because we set it sufficiently early that the boot-time
 * zeroing of .bss would clobber it.
 */
.data
ENTRY(__boot_cpu_mode)
        .long   0

 

참고