커널 패닉이란 말을 들어본 적이 있나요? 혹시 여러분이 임베디드 리눅스 개발자면 몇 번 쯤은 들어봤을 겁니다. 하지만, 리눅스를 자주 안 쓰거나 리눅스를 학습 용도로 쓰시는 분들에겐 단어가 낯설게 들릴수도 있죠. 
 
여러분들은 대부분 컴퓨터나 노트북을 윈도 운영체제로 쓰실 겁니다. 그런데 혹시 컴퓨터를 오래 쓰다가 블루 스크린을 본 적이 있나요? 밝은 파란색 배경 화면에 흰색으로 이상한 경고 문구가 떠있죠. 이 때 컴퓨터는 아무런 동작을 안합니다. 이런 화면을 처음 봤을 때 어땠나요? 좀 짜증나지 않았나요? 전 예전 처음 이 블루 스크린을 봤을 때 엄청 당황했었어요. 왜냐면 “전원을 다시 키면 부팅은 할까?”, “혹시 컴퓨터에 저장된 자료가 날아가는 건 아닌가”? 이런 걱정을 했죠. 정말 뭔가 컴퓨터에 큰 고장이 난 것 같았어요. 
 
이렇게 윈도 운영체제에서 뜨는 블루 스크린은 리눅스 운영 체제에서는 발생하지 않을까요? 다들 아시겠지만 윈도 운영체제에서 뜨는 블루 스크린이 리눅스에서도 있습니다. 대신 리눅스 운영체제에서는 검은색 배경에 흰색 문구를 띄웁니다. 이를 보통 커널 패닉이라고 합니다. 개발자들은 커널 크래시라고도 말하죠.
 
그럼 커널 크래시는 왜 발생할까요? 그 이유에 대해서 찬찬히 살펴볼게요.  혹시 소프트웨어 공학에서 ASSERT란 단어 들어 보신적 있나요? 이해를 돕기 위해서 간단한 코드를 소개 할게요. 아래 코드를 눈 여겨 보면 param이란 포인터가 NULL이 아니면 void 타입의 파라미터를 task란 지역 변수로 캐스팅합니다. 그런데 param이란 포인터가 NULL이면 ASSERT를 유발하죠.
void proc_func_ptr(void *param)
{
       struct task_struct *task;
if (!param) 
ASSERT(1);
    else
task = (struct task_struct*)param;
 
리눅스 커널에서는 ASSERT 대신 BUG() 혹은 panic() 란 함수를 씁니다. 보통 소프트웨어적으로 심각한 오류 상태라 더 이상 실행할 수 없다고 판단 할 때 호출 합니다. 그럼 만약 param 이란 포인터가 널일 때 아래 if 구문이 없으면 어떻게 동작할까요? 이후 구문에서 Data Abort로 익셉션이 발생 할 겁니다.
void proc_func_ptr(void *param)
{
        struct task_struct *task;
if (!param) 
BUG();
    else
task = (struct task_struct*)param;
 
그럼 비슷한 유형의 리눅스 커널 코드를 잠깐볼까요? 아래 함수를 보면 BUG_ON()이란 함수가 보이죠.
[drivers/clk/tegra/clk-tegra20.c]
1 static unsigned long tegra20_clk_measure_input_freq(void)
2 {
3 u32 osc_ctrl = readl_relaxed(clk_base + OSC_CTRL);
4 u32 auto_clk_control = osc_ctrl & OSC_CTRL_OSC_FREQ_MASK;
5 u32 pll_ref_div = osc_ctrl & OSC_CTRL_PLL_REF_DIV_MASK;
6 unsigned long input_freq;
7
8 switch (auto_clk_control) {
9 case OSC_CTRL_OSC_FREQ_12MHZ:
10 BUG_ON(pll_ref_div != OSC_CTRL_PLL_REF_DIV_1);
11 input_freq = 12000000;
12 break;
13 case OSC_CTRL_OSC_FREQ_13MHZ:
14 BUG_ON(pll_ref_div != OSC_CTRL_PLL_REF_DIV_1);
15 input_freq = 13000000;
16 break;
// .. 생략 ..
17 default:
18 pr_err("Unexpected clock autodetect value %d",
19        auto_clk_control);
20 BUG();
21 return 0;
22 }
 
위 코드는 nVidia Tegra SoC에서 클락을 제어하는 코드인데요. 
10번과 14번째 줄 코드를 보면 pll_ref_div != OSC_CTRL_PLL_REF_DIV_1, pll_ref_div != OSC_CTRL_PLL_REF_DIV_1 조건이 보이죠? 만약 pll_ref_div !=OSC_CTRL_PLL_REF_DIV_1 조건을 만족하면 결과는 1이기 때문에 BUG_ON(1)이 호출되고 결국 BUG()함수가 수행되어 커널 크래시를 발생합니다. 이 코드를 해석하면 이 조건이 시스템이 치명적인 결함이 생길 위험이 크다고 판단하는 것입니다.
 
또한 8번 줄의 auto_clk_control 값이 switch~case문에서 정의된 case에서 처리안되고 default가 수행되면 커널 패닉을 유발시킵니다. 이 조건도 시스템을 더 이상 구동시킬 수 없는 상태로 보고 있습니다.
 
자, 그럼 다음 코드를 같이 살펴볼까요?
[init/do_mounts.c]
1 void __init mount_block_root(char *name, int flags)
2 {
// .. 생략..
3 printk_all_partitions();
4 #ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT
5 printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify "
6        "explicit textual name for \"root=\" boot option.\n");
7 #endif
8 panic("VFS: Unable to mount root fs on %s", b);
 
8번 줄의 panic이란 함수가 보이죠? 부팅 도중 root 파일 시스템을 마운트를 못했을 때 panic() 함수를 호출해서 커널 패닉을 유발합니다. 그럼 왜 커널 크래시를 유발할까요? root 파일 시스템이 마운트 안되면 시스템 부팅 과정에서 계속 에러가 발생할 겁니다. root 파일 시스템이 마운트 안된 사실을 모르고 계속 시스템 에러를 잡는 삽질을 할 가능성이 높죠.  그래서 커널 크래시를 유발하고 우선 root 파일 시스템 마운트를 하라고 알려주는 겁니다. 참고로  root 파일 시스템을 통해 시스템 기본 설정에 연관된 init rc파일들을 읽습니다. 
 
 
이렇게 리눅스 커널 코드를 살펴보면 이런 panic과 BUG 코드가 지뢰 밭처럼 깔려 있는데요. 이런 코드를 추가한 이유는 뭘까요? 왜 강제로 커널 크래시를 유발해서 짜증나게 할까요? 이 이유에 대해서 조금 더 살펴볼께요.
 
"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
그리고 혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려드리겠습니다!"
 
#커널 크래시 디버깅 및 TroubleShooting
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
 

+ Recent posts