<kernel v5.0>
많은 수의 CPU를 사용하는 SMP 시스템에서 로그 버퍼가 부족해지는 경우가 있어 그러한 시스템에서 더 커진 로그 버퍼를 할당한다.
setup_log_buf()
kernel/printk/printk.c
void __init setup_log_buf(int early) { unsigned long flags; char *new_log_buf; unsigned int free; if (log_buf != __log_buf) return; if (!early && !new_log_buf_len) log_buf_add_cpu(); if (!new_log_buf_len) return; if (early) { new_log_buf = memblock_alloc(new_log_buf_len, LOG_ALIGN); } else { new_log_buf = memblock_alloc_nopanic(new_log_buf_len, LOG_ALIGN); } if (unlikely(!new_log_buf)) { pr_err("log_buf_len: %lu bytes not available\n", new_log_buf_len); return; } logbuf_lock_irqsave(flags); log_buf_len = new_log_buf_len; log_buf = new_log_buf; new_log_buf_len = 0; free = __LOG_BUF_LEN - log_next_idx; memcpy(log_buf, __log_buf, __LOG_BUF_LEN); logbuf_unlock_irqrestore(flags); pr_info("log_buf_len: %u bytes\n", log_buf_len); pr_info("early log buf free: %u(%u%%)\n", free, (free * 100) / __LOG_BUF_LEN); }
cpu 수가 적정 수(arm=17개, x86=33)를 초과하는 시스템의 경우 그 cpu 수 만큼 더 커진 새 로그 버퍼를 할당한다.
- 코드 라인 7~8에서 이미 log_buf가 기본 버퍼를 사용하지 않고 재 설정된 경우 함수를 빠져나간다.
- 코드 라인 10~11에서 early 호출이 아니면서 new_log_buf_len이 0으로 되어 있는 경우 new_log_buf_len를 재조정한다.
- 보다 빠르게 로그 버퍼가 필요한 x86시스템에서만 setup_arch() 함수 내부에서 early=1로 하여 호출한다.
- 정규 호출은 start_kernel()에서 각 아키텍처의 setup_arch() 함수가 완료된 후에 호출한다.
- 코드 라인 13~14에서 로그 버퍼로 더 추가할 필요가 없는 경우 new_log_buf_len이 여전히 0이며 이 때 함수를 빠져나간다.
- 코드 라인 16~18에서 early 호출인 경우 new_log_buf를 new_log_buf_len 만큼 할당한다.
- early 호출 시 memblock 할당이 안되는 경우 panic()이 호출된다.
- 코드 라인 19~22에서 early 호출이 아닌 경우 new_log_buf를 new_log_buf_len 만큼 할당한다
- memblock 할당이 안되는 경우에도 panic()이 호출되지 않는다.
- 코드 라인 34~28에서 적은 확률로 new_log_buf가 설정되지 않으면 에러 메시지를 출력하고 함수를 빠져나간다.
- 코드 라인 31~33에서 log 버퍼를 새롭게 할당 받은 버퍼로 설정한다.
- 코드 라인 35에서 원래 로그 버퍼 전체를 새 로그 버퍼로 복사한다.
- 코드 라인 38~40에서 로그 버퍼 길이 정보를 출력한다.
kernel/printk/printk.c
/* record buffer */ #define LOG_ALIGN __alignof__(struct printk_log) #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) #define LOG_BUF_LEN_MAX (u32)(1 << 31) static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN); static char *log_buf = __log_buf; static u32 log_buf_len = __LOG_BUF_LEN;
kernel/printk/printk.c
/* requested log_buf_len from kernel cmdline */ static unsigned long __initdata new_log_buf_len;
log_buf_add_cpu()
kernel/printk/printk.c
static void __init log_buf_add_cpu(void) { unsigned int cpu_extra; /* * archs should set up cpu_possible_bits properly with * set_cpu_possible() after setup_arch() but just in * case lets ensure this is valid. */ if (num_possible_cpus() == 1) return; cpu_extra = (num_possible_cpus() - 1) * __LOG_CPU_MAX_BUF_LEN; /* by default this will only continue through for large > 64 CPUs */ if (cpu_extra <= __LOG_BUF_LEN / 2) return; pr_info("log_buf_len individual max cpu contribution: %d bytes\n", __LOG_CPU_MAX_BUF_LEN); pr_info("log_buf_len total cpu_extra contributions: %d bytes\n", cpu_extra); pr_info("log_buf_len min size: %d bytes\n", __LOG_BUF_LEN); log_buf_len_update(cpu_extra + __LOG_BUF_LEN); }
default 설정된 로그 버퍼 기준으로 possible cpu 수가 적정 수(arm=17, x86=33)를 초과하는 경우에만 추가 cpu 버퍼를 준비한다.
- 코드 라인 10~11에서 cpu가 1개일 경우 추가 로그 버퍼를 만들지 않고 함수를 빠져나간다.
- 코드 라인 13에서 possible cpu-1 만큼의 추가 로그 버퍼를 산출한다.
- __LOG_CPU_MAX_BUF_LEN
- default로 4K (2^12)
- powerpc 아키텍처는 8K(2^13)
- __LOG_CPU_MAX_BUF_LEN
- 코드 라인 16~17에서 추가 로그 버퍼가 기존 로그 버퍼의 절반 보다 작은 경우 추가 버퍼를 만들지 않고 함수를 빠져나간다.
- __LOG_BUF_LEN
- arm 아키텍처는 SoC 마다 8K(2^13) ~ 512K(2^19)를 사용한다.
- arm64 아키텍처는 128K(2^17)을 사용한다.
- x86 아키텍처는 256K(2^18)을 사용한다.
- __LOG_BUF_LEN
- 코드 라인 19~23에서 추가 버퍼 정보를 출력한다.
- 코드 라인 25에서 로그 버퍼를 기본 버퍼 + cpu_extra 버퍼 만큼 설정한다.
kernel/printk/printk.c
#define __LOG_CPU_MAX_BUF_LEN (1 << CONFIG_LOG_CPU_MAX_BUF_SHIFT)
- CONFIG_LOG_CPU_MAX_BUF_SHIFT
- default=12
kernel/printk/printk.c
#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
- CONFIG_LOG_BUF_SHIFT
- default=17
num_possible_cpus()
include/linux/cpumask.h
#define num_possible_cpus() cpumask_weight(cpu_possible_mask)
possible cpu 수를 알아온다.
cpumask_weight()
include/linux/cpumask.h
/** * cpumask_weight - Count of bits in *srcp * @srcp: the cpumask to count bits (< nr_cpu_ids) in. */ static inline unsigned int cpumask_weight(const struct cpumask *srcp) { return bitmap_weight(cpumask_bits(srcp), nr_cpumask_bits); }
cpu 관련 비트맵 srcp에서 nr_cpumask_bits 만큼의 비트에서 1로 설정된 비트 수를 알아온다.
nr_cpumask_bits
include/linux/cpumask.h
#ifdef CONFIG_CPUMASK_OFFSTACK /* Assuming NR_CPUS is huge, a runtime limit is more efficient. Also, * not all bits may be allocated. */ #define nr_cpumask_bits nr_cpu_ids #else #define nr_cpumask_bits NR_CPUS #endif
log_buf_len_update()
kernel/printk/printk.c
/* we practice scaling the ring buffer by powers of 2 */ static void __init log_buf_len_update(unsigned size) { if (size > (u64)LOG_BUF_LEN_MAX) { size = (u64)LOG_BUF_LEN_MAX; pr_err("log_buf over 2G is not supported.\n"); } if (size) size = roundup_pow_of_two(size); if (size > log_buf_len) new_log_buf_len = size; }
인수로 지정된 size 값을 가까운 2의 n승에 맞게 round up한 후 new_log_buf_len 보다 큰 경우에만 update 한다. 최대 2G로 제한된다.
- 예) size=636K
- 1M
/** * roundup_pow_of_two - round the given value up to nearest power of two * @n - parameter * * round the given value up to the nearest power of two * - the result is undefined when n == 0 * - this can be used to initialise global variables from constant data */ #define roundup_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ (n == 1) ? 1 : \ (1UL << (ilog2((n) - 1) + 1)) \ ) : \ __roundup_pow_of_two(n) \ )
n 값을 가까운 2의 n승에 맞게 round up한 후 리턴한다.
- 예) 100
- 128
/* * round up to nearest power of two */ static inline __attribute__((const)) unsigned long __roundup_pow_of_two(unsigned long n) { return 1UL << fls_long(n - 1); }
n 값을 가까운 2의 n승에 맞게 round up한 후 리턴한다.