커널 드라이버 구현시 고려사항

특정 루틴이 예상된 시간 내에 처리되지 않을 때?

  • 타임 아웃

특정 주기로 어떤 루틴을 반복해 실행시키고 싶다

  • 커널 타이머

fastpath

  • 최적의 시나리오에서 실행
  • 일반적으로 디바이스 드라이버를 개발할 때 구현 점검
  • 예시
    • 버스를 기다린 후 버스에 탑승

slowpath

  • 예외 사항
  • 복잡도가 있는 루틴
  • 시간이 오래 걸리는 루틴!
  • 예시
  • 버스를 타려고 했는데 버스에 승객이 가득차 있다?
  • 각종 예외 상황 (엘리베이터, 메일 기다림)

메모리 부족 시

  • 페이지 메모리를 확보하기 위한 알고리즘 동작
  • kswapd 프로세스
  • 보드의 온도가 뜨거워 졌을 때
  • CPU의 로드를 강제로 줄임

대부분 시스템 로드가 걸리지 않는 상황

  • 제대로 동작

여러 가지 로드가 걸린 상황에서 문제가 생김

  • 1000개 이상의 프로세스가 실행
  • 기존보다 더 많은 인터럽트가 트리거됨
  • 메모리가 부족한 상황

커널 코드 구성 방식

  • 기능을 구현
  • 실행 시간 흐름 제어

실행 시간의 흐름을 제어하는 루틴은 매우 중요

  • 시간을 제어하는 드라이버 코드에 대한 분석 능력을 더 키울 수 있음
  • 실행 흐름과 관련된 문제 해결 능력을 키울 수 있음

디바이스 드라이버에서 실행 시간 제어

  • 타임아웃
  • 예외처리

jiffies와 HZ의 주요 개념

리눅스 커널 드라이버에서 실행 시간의 흐름을 체크하려면?

  • jiffies

jiffies란

  • jiffies는 커널 드라이버에서 실행 시간을 체크하는 단위
  • jiffies는 1초에 HZ(250)만큼 증가하며 이를 기준으로 시간의 흐름을 관리

HZ란

  • HZ는 진동수라고 부르며, 1초에 지피스(jiffies)가 업데이트되는 횟수
  • 대부분의 Armv8 계열(Aarch64) 리눅스 커널에서는 HZ값이 250임
    (CONFIG_HZ=250) => .config 파일
  • Hz를 정하는 기준 : 시스템의 전반적인 구조를 설계하는 개발자가 변경하기도 함

jiffies가 1000이면 jiffies는 초당 다음과 같이 변경됨

  • 1초후: 1250
  • 2초후: 1500
  • 3초후: 1750

HZ가 크면 좋을까?

  • HZ가 너무 크면 시스템에 오버헤드가 걸림
  • HZ가 너무 작으면 시스템에 동적 타이머의 만료 시각을 처리하는 데 오차가 발생
  • 최적화된 값을 설정하는게 중요

jiffies와 jiffies_64(8바이트 단위) 변수의 관계

  • 두 변수는 주소가 같지만 크기가 다름
  • jiffies는 jiffies_64 시작 주소를 기준으로 4바이트 주소 공간에 있는 값을
    저장
  • 지피스 값의 차이가 중요하다.

jiffies가 증가될 때 stack trace (v5.15)

jiffies 값은 누가 언제 증가시킬까?

get_ftrace.sh-56521 [001] d.h1. 72.893553: irq_handler_entry: irq=11 name=arch_timer
get_ftrace.sh-56521 [001] d.h1. 72.893563: tick_do_update_jiffies64+0x4/0x150 <-tick_sched_do_timer+0xb0/0xb8
get_ftrace.sh-56521 [001] d.h1. 5972.893568: <stack trace>
=> tick_do_update_jiffies64+0x8/0x150 // 지피스 값이 업데이트된다
=> tick_sched_do_timer+0xb0/0xb8
=> tick_sched_timer+0x50/0xb8
=> __hrtimer_run_queues+0x114/0x350
=> hrtimer_interrupt+0xfc/0x258
=> arch_timer_handler_phys+0x38/0x48
=> handle_percpu_devid_irq+0xa8/0x240
=> generic_handle_domain_irq+0x34/0x50
=> gic_handle_irq+0xa0/0xd8
=> call_on_irq_stack+0x2c/0x54
=> do_interrupt_handler+0xe0/0xf8
=> el1_interrupt+0x38/0x70
=> el1h_64_irq_handler+0x18/0x28
=> el1h_64_irq+0x64/0x68
=> folio_add_lru+0xc4/0xf8
=> folio_add_lru_vma+0x30/0x48
=> lru_cache_add_inactive_or_unevictable+0x28/0x38
=> wp_page_copy+0x248/0x840

jiffies가 증가될 때 stack trace (v5.10)

jiffies 값은 누가 언제 증가시킬까?

<idle>-0 [002] d.h1 316.919529: irq_handler_entry: irq=12 name=arch_timer
<idle>-0 [002] d.h2 316.919533: do_timer+0x4/0x38 // <-지피스 업데이트tick_do_update_jiffies64.part.17+0x80/0x120
<idle>-0 [002] d.h2 316.919536: <stack trace>
=> do_timer+0x8/0x38
=> tick_do_update_jiffies64.part.17+0x80/0x120
=> tick_sched_do_timer+0x84/0x88
=> tick_sched_timer+0x50/0xb8
=> __hrtimer_run_queues+0x11c/0x470
=> hrtimer_interrupt+0xfc/0x258
=> arch_timer_handler_phys+0x38/0x48
=> handle_percpu_devid_irq+0xa8/0x2a0
=> generic_handle_irq+0x38/0x50
=> __handle_domain_irq+0x9c/0x110
=> gic_handle_irq+0xb0/0xf0
=> el1_irq+0xc8/0x180
=> arch_cpu_idle+0x18/0x28
=> default_idle_call+0x58/0x1d4
=> do_idle+0x25c/0x270
=> cpu_startup_entry+0x30/0x70
=> secondary_start_kernel+0x170/0x180

250ms 타임아웃 체크 루틴

jiffies를 사용하는 예제

https://elixir.bootlin.com/linux/v5.15/source/sound/isa/wss/wss_lib.c
void snd_wss_mce_down(struct snd_wss *chip)
{
unsigned long flags;
unsigned long end_time;
int timeout; // 실행시간의 흐름을 관리하는 변수
...
/* check condition up to 250 ms */
end_time = jiffies + msecs_to_jiffies(250); // 지피스 : 엔드타임 시점, 50코드 시점과 지피스값이 다를 수 있다. 250 밀리초를 지피스 단위로 바꾸는 커널 api
while (snd_wss_in(chip, CS4231_TEST_INIT) &
CS4231_CALIB_IN_PROGRESS) {
if (time_after(jiffies, end_time)) { // 위의 값을 엔드 타임으로 업데이트, 지피스 값이 엔드타임 이후인가? 
snd_printk(KERN_ERR "mce_down - "
"auto calibration time out (2)\n");
return;
}
msleep(1);
}
...
}

msecs_to_jiffies() 함수

jiffies에 대한 이해

  • Jiffies값을 통해 시간의 흐름을 제어할 때는 msecs_to_jiffies() 사용 권장

msecs_to_jiffies() 함수 소개

void kdt_driver_init(void)
{
unsigned long timeout = jiffies + 250;
...
while (SMC_GET_MMU_CMD(lp) & MC_BUSY) {
SMC_SET_INT_MASK(lp);
if (jiffies > timeout)
break;
}
void kdt_driver_init(void)
{
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
...
while (SMC_GET_MMU_CMD(lp) & MC_BUSY) {
SMC_SET_INT_MASK(lp);
if (jiffies > timeout) // 타임아웃보다 크면 벗어남
break;
}

jiffies에 대해서

  • 개발자가 이해하기 쉬운 시간 단위는 아님
  • jiffies는 CPU 아키텍처 마다 달리 설정됨
  • jiffies를 활용한 커널 API가 필요함

msecs_to_jiffies() 함수

  • 밀리초를 입력으로 받아 jiffies 단위 시각 정보를 반환
  • 리눅스 커널에서 실행 시간 기준으로 흐름을 제어할 때 많이 사용

msecs_to_jiffies() 함수 선언부

  • static __always_inline unsigned long msecs_to_jiffies(const unsigned int m);
  • 함수에 전달하는 const unsigned int m 인자는 밀리초 단위의 정수
  • unsigned long 타입의 jiffies를 반환
static __always_inline unsigned long msecs_to_jiffies(const unsigned int m)
{
if (__builtin_constant_p(m)) {
if ((int)m < 0)
return MAX_JIFFY_OFFSET;
return _msecs_to_jiffies(m);
} else {
return __msecs_to_jiffies(m);
}
}
  • 입력 인자 예외 처리
  • § 실제 jiffies 단위 시간 정보 계산

딜레이 워크 지연 시간 설정

static int rtsx_pci_probe(struct pci_dev *pcidev,
const struct pci_device_id *id)
{
struct rtsx_pcr *pcr;
struct pcr_handle *handle;
...
schedule_delayed_work(&pcr->idle_work, msecs_to_jiffies(200)); // 20미리초 이후의 워크를 딜레이해서 실행되도록 하는 아규먼트
...
}

시간 정보 설정

  • msecs_to_jiffies() 함수의 사용 예시2
static int smc_probe(struct net_device *dev, void __iomem *ioaddr,
unsigned long irq_flags) // 프로브 : 부팅할때 디바이스 드라이버를 초기화할때 생성된다.
{
struct smc_local *lp = netdev_priv(dev);
int retval;
...
dev->watchdog_timeo = msecs_to_jiffies(watchdog); // 밀리초 단위의 정보
dev->netdev_ops = &smc_netdev_ops;
dev->ethtool_ops = &smc_ethtool_ops;

time_after(), time_before()

time_after()와 time_before() 함수를 써서 실행 시간의 흐름을 제어

  • 함수나 함수 내 코드 블록의 실행 시간 체크
  • 실행 시간의 데드라인을 점검
  • 타임 아웃 설정

#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((b) - (a)) < 0))
#define time_before(a,b) time_after(b,a)

  • time_after(a,b) 함수
    • a가 b보다 크면 true를, 아니면 false를 반환
    • 실행 시간 흐름의 관점으로 “
      a가 담고 있는 시간 정보가 b의 시간 정보보다
      나중인지” 알려 줌
  • time_before() 함수
    • a가 b보다 크면 false를, 아니면 true를 반환
    • 실행 시간 흐름 관점으로 “
      a가 담고 있는 시간 정보가 b의 시간 정보보다 이
      전인지” 알려 줌

250ms 타임아웃 체크 루틴

time_after() 함수의 사용 예시1

https://elixir.bootlin.com/linux/v5.15/source/sound/isa/wss/wss_lib.c
void snd_wss_mce_down(struct snd_wss *chip)
{
unsigned long flags;
unsigned long end_time;
int timeout;
...
/* check condition up to 250 ms */
end_time = jiffies + msecs_to_jiffies(250);
while (snd_wss_in(chip, CS4231_TEST_INIT) &
CS4231_CALIB_IN_PROGRESS) {
if (time_after(jiffies, end_time)) {
snd_printk(KERN_ERR "mce_down - "
"auto calibration time out (2)\n");
return;
}
msleep(1);
}
...
}

‘ts+thread’ 기준 타임 아웃 체크

static void wq_watchdog_timer_fn(struct timer_list *unused)
{
unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ;
bool lockup_detected = false;
...
/* did we stall? */
if (time_after(now, ts + thresh)) {
lockup_detected = true;
pr_emerg("BUG: workqueue lockup - pool");
pr_cont_pool_info(pool);
pr_cont(" stuck for %us!\n",
jiffies_to_msecs(now - pool_ts) / 1000);
}
}
...
}

타임 아웃 체크

time_before() 함수의 사용 예시

https://elixir.bootlin.com/linux/v5.15.30/source/drivers/scsi/elx/libefc_sli/sli4.c
static bool
sli_wait_for_fw_ready(struct sli4 *sli4, u32 timeout_ms)
{
unsigned long end;
end = jiffies + msecs_to_jiffies(timeout_ms); // 밀리초 단위의 타임아웃 정보
do {
if (sli_fw_ready(sli4))
return true;
usleep_range(1000, 2000);
} while (time_before(jiffies, end)); // do while이 250밀리초 이상인 경우 빠져나옴 , 실행시간의 흐름을 체크하는 루틴 
return false;
}

sched_clock() 함수

커널에서 시간을 관리하는 추가 API

https://elixir.bootlin.com/linux/v5.15.30/source/sound/soc/mediatek/common/mtk-btcvsd.c
static int wait_for_bt_irq(struct mtk_btcvsd_snd *bt,
struct mtk_btcvsd_snd_stream *bt_stream)
{
unsigned long long t1, t2;
...
while (max_timeout_trial && !bt_stream->wait_flag) {
t1 = sched_clock(); // 시간 정보
if (bt_stream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = wait_event_interruptible_timeout(bt->tx_wait,
bt_stream->wait_flag,
nsecs_to_jiffies(timeout_limit));
...
}
t2 = sched_clock(); // 시간정보2
t2 = t2 - t1; /* in ns (10^9) */ // 시간정보를 뺀다
if (t2 > timeout_limit) { // 초과시 에러로그
dev_warn(bt->dev, "%s(), stream %d, timeout %llu, limit %llu, ret %d, flag %d\n",…
}

0개의 댓글