본문 바로가기
임베디드 개발/리눅스 디바이스 드라이버

LDD ] printk를 활용한 디버깅

by eteo 2024. 10. 6.

 

 

 

1. printk

커널 공간에서는 C 표준 라이브러리를 사용할 수 없으므로 printf와 유사한 printk를 사용한다.

 

 

 

2. printk 사용법

사용법은 printf와 거의 동일하며 단순히 printf를 printk로 대체하면 된다.

단, %f, %e 등 부동소수점 형식의 포맷지정자는 지원하지 않으며, linux/Documentation/printk-formats.txt에서 사용 가능한 포맷지정자를 확인할 수 있다.

 

ex.

printk("num: %d, hex: %x\n", num, hex_num);

 

Type printk format specifier
int %d / %x
unsigned int %u / %x
long %ld / %lx
unsigned long %lu / %lx
long long %lld / %llx
unsigned long long %llu / %llx
size_t %zu / %zx
ssize_t %zd / %zx
s32 %d / %x
u32 %u / %x
s64 %lld / %llx
u64 %llu / %llx

 

 

 

 

 

3. 커널 링 버퍼와 dmesg

printk로 출력된 메시지는 커널의 링 버퍼에 저장되는데 이를 Kernel log라고 부른다. demsg 명령어를 사용하여 커널 링버퍼의 내용을 출력해 볼 수 있다.

 

ex.

# 최신 10개 메시지 확인
$ dmesg | tail
# 최신 5개 메시지 확인
$ dmesg | tail -5
# 실시간 메시지 확인
$ dmesg -w

 

 

 

4. printk와 커널 로그 레벨

커널 로그 레벨을 사용하여 printk 메시지의 우선순위를 제어할 수 있다. 총 8개의 로그 레벨이 존재하며 낮은 숫자일 수록 높은 우선순위를 나타낸다.

 

로그 레벨은 include/linux/kern_levels.h 파일에 정의된 C 매크로이며 다음과 같이 printk와 함께 사용한다. 로그 레벨과 메시지 사이에 콤마(,)는 들어가지 않으며 로그 레벨이 지정되지 않았을 때 printk의 디폴트 로그레벨은 일반적으로는 "4" KERN_WARNING이다.

 

printk(KERN_EMERG "Emergency: Immediate attention required!\n");	// 0
printk(KERN_ALERT "Alert: Critical error occurred!\n");				// 1
printk(KERN_CRIT "Critical: Severe error detected!\n");				// 2
printk(KERN_ERR "Error: A problem has occurred.\n");				// 3
printk(KERN_WARNING "Warning: Potential issue detected.\n");		// 4
printk(KERN_NOTICE "Notice: Noteworthy event detected.\n");			// 5
printk(KERN_INFO "Info: System is running normally.\n");			// 6
printk(KERN_DEBUG "Debug: Detailed debugging information.\n");		// 7

 

printk로 출력된 메시지가 콘솔에 출력될지 혹은 dmesg로 봐야할지 여부는 printk문에 지정된 로그 레벨과 현재 콘솔 로그 레벨을 비교하여 결정된다. printk 메시지의 커널 로그 레벨이 현재 콘솔 로그 레벨보다 작은 경우에만 해당 메시지가 콘솔에 출력된다.

 

 

 

- 커널 로그 레벨 확인법

$cat /proc/sys/kernel/printk
$4	4	1	7

 

  • 첫 번째 값 : Console Log Level, 콘솔에 출력되는 커널 메시지의 로그 레벨을 결정한다. 이 값보다 우선 순위가 높은 메시지(작은 숫자)가 콘솔에 출력된다. 즉, 이 값이 4이면 0, 1, 2, 3 커널 로그레벨을 가지는 메시지는 콘솔에 출력된다.
  • 두 번째 값 : Default Message Log Level, printk 호출시 명시적으로 로그 레벨이 지정되지 않은 메시지의 기본 로그 레벨이다. 지금은 첫 번째 값이 4고 두 번째 값도 4니까 로그 레벨 지정 없이 printk를 사용하면 콘솔에 출력되지 않는다.
  • 세 번째 값 : minimum_console_loglevel, 콘솔에 출력될 수 있는 최소한의 로그 레벨
  • 네 번째 값 : default_console_loglevel, 부팅 시 사용되는 기본 콘솔 로그 레벨

일반적으로 기본값은 4, 4, 1, 7로 설정되며, 필요에 따라 첫 번째 값을 조정하고 나머지 값은 4, 1, 7이 거의 고정으로 쓰이는 것 같다.

 

 

 

- 런타임에 커널 로그 레벨을 변경하는 방법

$sudo -s
$echo 7 4 1 7 > /proc/sys/kernel/printk

 

이 방법으로 변경한 경우 재부팅시 초기화된다.

 

 

- 재부팅 후에도 유지되도록 커널 로그 레벨을 변경하는 방법

$sudo vim /etc/sysctl.conf
kernel.printk = 7 4 1 7

 

 

 

- menuconfig에서 커널 로그레벨을 변경하는 방법

메뉴에서 Kernel hacking > printk and dmesg options > 설정 변경

 

 

 

 

5. printk의 Wrapper 함수

<kernel-source>/include/linux/printk.h 파일에는 printk에 로그레벨을 지정한 wrapper 매크로 함수가 정의되어 있어서 이걸 사용하면 코드를 더욱 간결하게 작성할 수 있다.

 

ex. printk(KERN_INFO "message") 대신 pr_info("message")를 사용.

pr_emerg("Emergency: Immediate attention required!\n");      // 0
pr_alert("Alert: Critical error occurred!\n");               // 1
pr_crit("Critical: Severe error detected!\n");               // 2
pr_err("Error: A problem has occurred.\n");                  // 3
pr_warn("Warning: Potential issue detected.\n");             // 4
pr_notice("Notice: Noteworthy event detected.\n");           // 5
pr_info("Info: System is running normally.\n");              // 6
pr_debug("Debug: Detailed debugging information.\n");        // 7

 

 

 

 

 

6. pr_fmt

pr_fmt는 커널 로그 메시지에 고유한 접두사를 붙이거나 형식을 지정하기 위해 사용된다.

 

 

사용 예시 및 출력 결과는 다음과 같다.

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
pr_info("Initializing driver\n");

 

[timestamp] my_driver: Initializing driver

 

#define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__
pr_err("An error occurred\n");

 

[timestamp] my_function:42: An error occurred