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로 이동
  • 문자열이 발견되면 일단 ck_cmdline에 suffix가 발견된 위치를 기억한다.
  • } 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
};

 

참고

 

 

답글 남기기

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