Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

19113
1478
166889


[리눅스커널][인터럽트] 인터럽트 컨택스트: in_interrupt() 함수 코드분석 5. 인터럽트

다음 질문을 시작으로 in_interrupt() 함수에 대해 알아보겠습니다. 

현재 실행 중인 코드가 인터럽트 컨텍스트 구간인지 어떻게 알 수 있을까요?

in_interrupt() 함수가 이 정보를 알려줍니다. in_interrupt() 함수가 true를 반환하면 인터럽트 컨텍스트이고, 반대로 false를 반환하면 프로세스 컨텍스트입니다. 

그렇다면 in_interrupt() 함수를 호출해 현재 실행 중인 코드가 인터럽트 컨텍스트인지 왜 알려고 할까요? 커널 코드를 잠시 보면 알겠지만 커널 코드 내에서는 수많은 함수를 호출합니다. 함수들은 복잡하게 호출되므로 함수 호출 흐름을 간단히 파악하기 어렵습니다. 그렇다 보니 커널 혹은 드라이버 코드에서 볼 수 있는 함수가 '인터럽트 컨텍스트'에서 실행 중인지 분간하기 어렵습니다. 

그런데 현재 실행되는 코드가 인터럽트 핸들러의 서브루틴으로 실행 중이면 되도록 더 빨리 동작해야 합니다. 인터럽트 서비스 루틴은 실행 중인 프로세스를 멈추고 동작하기 때문입니다. 그래서 디바이스 드라이버 코드에서 인터럽트 컨텍스트 조건에서만 신속하게 코드를 추가하는 것입니다.

in_interrupt() 함수를 써서 만든 패치 코드

in_interrupt() 함수를 써서 만든 다음 패치를 함께 살펴봅시다. 

diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
index df9320c..0eb54dc 100644
--- a/drivers/mmc/core/block.c
+++ b/drivers/mmc/core/block.c
@@ -361,7 +361,7 @@ static struct mmc_blk_ioc_data *mmc_blk_ioctl_copy_from_user(
01        struct mmc_blk_ioc_data *idata;
02        int err;
03
04 -       idata = kmalloc(sizeof(*idata), GFP_KERNEL);
05 +       idata = kmalloc(sizeof(*idata), in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
06        if (!idata) {
07                err = -ENOMEM;
08                goto out;


참고로 – 기호는 원래 코드이고 + 기호는 추가하는 코드입니다. 


위 패치는 인터럽트 컨텍스트 조건에서 다른 동작을 수행하는 코드입니다.

05 번째 줄을 봅시다. 

05 +       idata = kmalloc(sizeof(*idata), in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);

리눅스 커널에서 자주 볼 수 있는 삼항 연산자입니다.

in_interrupt() 함수가 true를 반환하면 GFP_ATOMIC 플래그로, 반대의 경우 GFP_KERNEL 플래그로 kmalloc() 함수를 호출합니다.

in_interrupt() 함수가 true를 반환하는 경우 인터럽트 컨텍스트이며 '인터럽트 처리 중'이라고 볼 수 있습니다. 이 조건에서 gfp_flag를 GFP_ATOMIC으로 설정하고 메모리를 할당합니다. 대신 GFP_ATOMIC 옵션으로 kmalloc() 함수를 호출하면 커널 내부에서 메모리를 할당할 때 스케줄링을 수행하지 않습니다.

반대로 in_interrupt() 함수가 false를 반환하면 현재 코드가 프로세스 컨텍스트에서 수행 중이니 GFP_KERNEL 옵션으로 메모리를 할당합니다. 참고로 GFP_ATOMIC 옵션으로 메모리를 할당하면 프로세스는 휴면하지 않고 메모리를 할당하고, GFP_KERNEL 옵션인 경우 메모리 할당 실패 시 휴면할 수 있습니다. 


보통 인터럽트 컨텍스트에서 GFP_ATOMIC 플래그로 kmalloc() 함수를 호출해 메모리를 할당합니다.


요약하면 인터럽트를 처리하는 도중에는 빨리 메모리를 할당하는 코드입니다.

in_interrupt() 함수 관련 리눅스 커널 패치 소개

이번에는 최근에 반영된 리눅스 커널 패치 코드를 소개합니다.

https://lore.kernel.org/patchwork/patch/835607/
diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c
index 7c38e850a8fc..685049a9048d 100644
--- a/mm/zsmalloc.c
+++ b/mm/zsmalloc.c
@@ -1349,7 +1349,7 @@  void *zs_map_object(struct zs_pool *pool, unsigned long handle,
  * pools/users, we can't allow mapping in interrupt context
  * because it can corrupt another users mappings.
  */
- WARN_ON_ONCE(in_interrupt());
+ BUG_ON(in_interrupt());
 
  /* From now on, migration cannot move the object */
  pin_tag(handle);

먼저 패치 코드의 내용을 살펴보겠습니다. zs_map_object() 함수가 인터럽트 컨텍스트에서 호출되면 zsmalloc() 함수로 할당한 버퍼가 오염되는 문제가 확인돼 커널 패닉을 유발하도록 코드를 수정한 것입니다. 

위 코드를 잠깐 보면 in_interrupt() 매크로가 TRUE를 반환하면 실행하는 매크로 함수를 BUG_ON() 함수로 변경했습니다. 그런데 BUG_ON() 매크로를 호출하면 어떻게 동작할까요? 커널 패닉을 유발합니다. zs_map_object() 함수가 인터럽트 컨텍스트에서 실행되면 커널 패닉을 유발시켜 시스템을 리셋시키겠다는 의도입니다. 리눅스 시스템이 리셋되면 유저 입장에서 불편할 텐데 왜 커널 패닉을 유발시킬까요? 여기엔 그럴 만한 이유가 있습니다. 바로 BUG_ON() 함수가 실행되는 조건이 시스템에 치명적인 오류라고 보는 것입니다. 시스템에 치명적인 오류가 감지되면 차라리 리셋시켜서 예상치 못한 오류를 방지하겠다는 의도입니다.


리눅스 커널에는 치명적인 시스템 오동작을 유발할 수 있는 에러 조건을 점검해 커널 패닉을 유발하는 코드가 많습니다. 리눅스 시스템이 다양한 오동작을 보이기 전에 문제 발생 시점에서 커널 패닉으로 코어 덤프를 받아 근본 원인을 해결하려는 의도입니다.


in_interrupt() 함수 코드 분석하기

현재 실행 중인 코드가 인터럽트 컨텍스트인지 알려주는 in_interrupt() 함수를 분석해 봅시다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/preempt.h
#define in_interrupt()  (irq_count())

in_interrupt() 함수 선언부를 보니 irq_count() 함수로 치환된다는 사실을 알 수 있습니다.
 
다음으로 irq_count() 함수 선언부를 보겠습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/preempt.h
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
      | NMI_MASK))  

preempt_count() 함수가 반환하는 값과 HARDIRQ_MASK | SOFTIRQ_MASK 비트 마스크에 대해 OR 연산을 수행합니다. 여기서 HARDIRQ_MASK, SOFTIRQ_MASK, NMI_MASK 플래그 값은 다음과 같습니다.

HARDIRQ_MASK: 0xf0000
SOFTIRQ_MASK:   0xff00
NMI_MASK:     0x100000

위 플래그 값을 OR 연산한 결과는 0x1fff00이므로 irq_count() 매크로 함수는 다음과 같이 변환할 수 있습니다.

preempt_count() & 0x1fff00

여기서 한 가지 의문이 듭니다. preempt_count() 함수의 정체는 무엇일까요?

preempt_count() 함수는 실행 중인 프로세스의 thread_info 구조체의 preempt_count 필드값을 반환합니다.

이어서 preempt_count() 함수를 분석하겠습니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/asm-generic/preempt.h
01 static __always_inline int preempt_count(void)
02 {
03 return READ_ONCE(current_thread_info()->preempt_count);
04 }

3번째 줄을 보면 current_thread_info()->preempt_count 값을 반환합니다.

이어서 current_thread_info() 함수를 살펴봅시다.

[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/include/asm/thread_info.h]
01 register unsigned long current_stack_pointer asm ("sp");  
02
03 static inline struct thread_info *current_thread_info(void)
04 {
05 return (struct thread_info *)
06 (current_stack_pointer & ~(THREAD_SIZE - 1));  
07 }

01 번째 줄을 보겠습니다. 스택 주소를 current_stack_pointer 변수로 읽는 동작입니다. current_stack_pointer를 register 타입으로 선언했는데 current_stack_pointer 변수를 ARM 레지스터를 써서 처리하라는 의미입니다.

참고로 asm(“sp”)는 인라인 어셈블리 코드로서 현재 스택 주소를 의미합니다. 이는 C 문법에 없는 코드인데, 리눅스 커널에서는 상당히 자주 호출되는 코드에는 인라인 어셈블리를 사용하는 경우가 있으므로 알아둘 필요가 있습니다.

---
보통 함수에 선언하는 지역변수와 register 타입으로 선언된 지역변수의 차이점은 무엇일까요? 보통 지역변수를 선언하면 프로세스 스택 공간에 지역변수를 저장합니다. 그런데 register 키워드로 지역변수를 선언하면 ARM 레지스터를 이용해 변수를 처리합니다. 따라서 더 빠른 연산 속도를 기대할 수 있습니다. 
---

다음으로 05~06 번째 줄을 보겠습니다.

05 return (struct thread_info *)
06 (current_stack_pointer & ~(THREAD_SIZE - 1));  

현재 실행 중인 프로세스의 스택 최상단 주소를 계산합니다. 이 주소에 프로세스 실행 정보가 담긴 thread_info 구조체가 있습니다.

preempt_count() 함수의 정체는 현재 실행 중인 함수에서 스택 주소를 얻어 스택 최상단 주소를 계산해 thread_info 구조체의 preempt_count 필드에서 얻어오는 값입니다.
 
[그림 5.7] in_interrupt() 함수 동작 원리

그림 5.7을 보면 스택 최상단 주소에 thread_info 구조체 필드가 있습니다. 커널 프로세스마다 스택 최상단 주소에 이 구조체 데이터를 저장합니다. 

정리하면 in_interrupt() 매크로 함수를 실행하면 현재 프로세스의 스택 최상단 주소에 저장된 thread_info 구조체의 preempt_count 필드값을 읽고 이 값을 다음 값과 AND 연산을 수행합니다.

0x1fff00 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)

이번에는 인터럽트 컨텍스트에서 실행 중인 프로세스의 thread_info 구조체의 preempt_count 값을 확인해 봅시다. 다음은 TRACE32프로그램으로 확인한 thread_info 구조체의 필드 값입니다. 

01 (struct thread_info*)(0xA359B908 & ~0x1fff) = 0xA359A000 -> (
02     flags = 0x2,
03     preempt_count = 0x00010002,  
04     addr_limit = 0xBF000000,
05     task = 0xA0B5EA40,   
06     exec_domain = 0xA1A1AF1C,
07     cpu = 0x0,
08     cpu_domain = 0x15,

03번째 줄을 보면 preempt_count가 0x00010002입니다. 그러면 이 값을 가지고 다음과 같이 비트 연산을 수행해 봅시다.

0x00010002
      0x1fff00
--------------- AND
      0x10000

연산 결과가 true이니 이 프로세스는 인터럽트 컨텍스트에서 실행 중이라고 볼 수 있습니다. 그런데 여기서 한 가지 의문이 생깁니다. preempt_count 값 중에서 '현재 프로세스가 인터럽트를 처리 중'임을 나타내는 비트는 무엇일까요?

다음과 같이 굵게 표시한 1이 인터럽트 컨텍스트임을 나타냅니다.

0x00010002

이 내용을 읽으니 자연히 궁금한 점이 더 생깁니다. 그렇다면 0x10000 비트는 어느 함수에서 설정할까요? __irq_enter() 함수에서 HARDIRQ_OFFSET 비트를 프로세스의 thread_info 구조체의 preempt_count에 저장합니다.

__irq_enter() 함수 분석

인터럽트 처리 시작을 설정하는 HARDIRQ_OFFSET 비트는 __irq_enter() 함수에서 설정합니다. 
        
[그림 5.8] 인터럽트 컨택스트를 설정하는 함수 흐름

__handle_domain_irq() 함수를 열어봅시다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/irq/irqdesc.c
1 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
2 bool lookup, struct pt_regs *regs)
3 {
4 struct pt_regs *old_regs = set_irq_regs(regs);
5 unsigned int irq = hwirq;
6 int ret = 0;
7
8 irq_enter();
9
...
10 if (unlikely(!irq || irq >= nr_irqs)) {
11 ack_bad_irq(irq);
12 ret = -EINVAL;
13 } else {
14 generic_handle_irq(irq);
15 }
16
17 irq_exit();
18 set_irq_regs(old_regs);
19 return ret;
20 }

먼저 8번째 줄을 보겠습니다. __handle_domain_irq() 함수에서 인터럽트 핸들러를 호출하기 전 irq_enter() 함수를 호출합니다. 함수 이름대로 “인터럽트 처리 시작”을 나타내는 표시입니다.

---
irq_enter() 함수의 이름을 irq_context_enter()로 바꾸면 코드를 분석하기 전 함수의 동작을 빨리 짐작할 수 있었을 것입니다. 
---

irq_enter() 함수는 __irq_enter() 함수를 호출합니다. 이어서 __irq_enter() 매크로 함수의 구현부를 봅시다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/hardirq.h
1 #define __irq_enter()     \
2  do {      \
3   account_irq_enter_time(current); \
4   preempt_count_add(HARDIRQ_OFFSET); \  
5   trace_hardirq_enter();   \
6  } while (0)

4번째 줄을 봅시다.

4   preempt_count_add(HARDIRQ_OFFSET); \  

preempt_count_add() 함수를 호출하면 current_thread_info()->preempt_count 필드에 HARDIRQ_OFFSET 비트를 더합니다. 이 동작은 지금 인터럽트를 처리 중이라는 의미입니다. 


current_thread_info() 매크로 함수는 프로세스의 스택 최상단 주소에 접근해 thread_info 구조체로 캐스팅하는 역할을 합니다.


__irq_exit() 함수 분석

이번에는 인터럽트 핸들링을 마무리한 후 호출되는 irq_exit() 함수를 분석하겠습니다. 다음과 같은 코드 흐름에서 __irq_exit() 함수를 봅시다.

  
[그림 5.9] 인터럽트 컨택스트 해제 함수 흐름

보다시피 __handle_domain_irq() 함수에서 인터럽트 핸들러 호출을 끝낸 후 irq_exit() 함수를 호출합니다. irq_exit() 함수에서 __irq_exit() 함수를 호출해 thread_info 구조체의 preempt_count 필드에서 HARDIRQ_OFFSET 비트를 빼는 동작을 수행합니다.

이어서 __irq_exit() 함수를 봅시다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/hardirq.h
01 #define __irq_exit()     \
02  do {      \
03   trace_hardirq_exit();   \
04   account_irq_exit_time(current);  \
05   preempt_count_sub(HARDIRQ_OFFSET); \
06  } while (0) 

05번째 줄을 실행하면 current_thread_info()->preempt_count 필드에서 HARDIRQ_OFFSET 비트를 뺍니다. 이 동작은 지금 인터럽트를 처리 중인 상태가 아니라는 것을 말해줍니다.  

여기까지 HARDIRQ_OFFSET 플래그 비트를 설정하고 해제하는 흐름을 알아봤습니다. 그렇다면 HARDIRQ_OFFSET 비트는 어떤 의미를 지닐까요? HARDIRQ_OFFSET 비트(0x10000)는 현재 코드가 인터럽트를 처리 중인지를 알려줍니다.

이 비트를 프로세스의 thread_info 구조체의 preempt_count 플래그에 설정하면 “현재는 인터럽트 처리 중”, 해제하면 “현재 인터럽트 처리 중이 아님”을 나타냅니다. 

이제 코드 분석으로 파악한 내용을 정리해 보겠습니다.

in_interrupt() 함수는 현재 실행 중인 프로세스 스택의 최상단 주소에 위치한 thread_info 구조체의 preempt_count 필드와 다음 비트 플래그와 AND 연산을 수행한 결과임

   0x1fff00 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK) 

인터럽트 핸들링을 하기 직전 __irq_enter() 함수에서 HARDIRQ_OFFSET를 더함
인터럽트 핸들링을 하고 난 후 __irq_exit() 함수에서 HARDIRQ_OFFSET를 뺌 

여기까지 리눅스 커널에서 정의된 in_interrupt() 함수를 분석하고 관련 코드를 분석했습니다. 다음 절에서는 라즈베리 파이에서 실습을 통해 '리눅스 시스템에서 in_interrupt() 함수가 실제로 어떤 값을 반환하는지' 확인해 보겠습니다.

라즈베리 파이에서 in_interrupt() 함수의 동작 방식 확인

이번에는 라즈베리 파이에서 in_interrupt() 함수가 어떤 값을 반환하는지 확인하는 실습을 해보겠습니다. 이를 위해 먼저 리눅스 커널의 소스코드를 수정해야 합니다. 먼저 패치 코드를 소개하겠습니다.

diff --git a/drivers/mmc/host/bcm2835-sdhost.c b/drivers/mmc/host/bcm2835-sdhost.c
index 273b1be05..5f57b3dab 100644
--- a/drivers/mmc/host/bcm2835-sdhost.c
+++ b/drivers/mmc/host/bcm2835-sdhost.c
@@ -1472,6 +1472,16 @@ static irqreturn_t bcm2835_sdhost_irq(int irq, void *dev_id)
01 struct bcm2835_host *host = dev_id;
02 u32 intmask;
03 
04 + void *stack;
05 + struct thread_info *current_thread;
06 +
07 + stack = current->stack;   
08 + current_thread = (struct thread_info*)stack;  
09 +
10 + printk("[+] in_interrupt: 0x%08x,preempt_count = 0x%08x, stack=0x%08lx \n", 
11 + (unsigned int)in_interrupt(), (unsignedint)current_thread->preempt_count, (long unsigned int)stack);  
12 +
13 +
14 spin_lock(&host->lock);
15 
16 intmask = bcm2835_sdhost_read(host, SDHSTS);

패치 코드를 설명하기에 앞서 패치 코드를 입력 방법을 소개합니다.

다음은 bcm2835_sdhost_irq() 함수의 원래 코드입니다.

https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/mmc/host/bcm2835-sdhost.c
static irqreturn_t bcm2835_sdhost_irq(int irq, void *dev_id)
{
irqreturn_t result = IRQ_NONE;
struct bcm2835_host *host = dev_id;
u32 intmask;

/* 패치 코드를 입력하세요. */
spin_lock(&host->lock);

다음 패치 코드를 /* 패치 코드를 입력하세요. */ 부분에 입력하면 됩니다.

04 + void *stack;
05 + struct thread_info *current_thread;
06 +
07 + stack = current->stack;   
08 + current_thread = (struct thread_info*)stack;  
09 +
10 + printk("[+] in_interrupt: 0x%08x,preempt_count = 0x%08x, stack=0x%08lx \n", 
11 + (unsigned int)in_interrupt(), (unsignedint)current_thread->preempt_count, (long unsigned int)stack);  
12 +

이제 패치 코드를 리뷰해 봅시다. 먼저 7 번째 줄을 보겠습니다.

07 + stack = current->stack;

현재 실행 중인 프로세스의 스택 최상단 주소를 읽어 stack에 저장합니다. 

그런데 curruent 코드의 의미는 무엇일까요? 프로세스의 태스크 디스크립터 주소를 얻기 위해 current 매크로를 사용하는 것입니다(이와 관련된 세부 동작 방식은 4장 ‘프로세스’에서 다뤘습니다). 즉, task_struct 구조체의 stack 필드에 저장된 스택 최상단 주소를 stack이라는 지역변수에 저장하는 코드입니다.

---
current는 리눅스 커널에서 자주 사용됩니다. 세부 동작 방식은 4장 ‘프로세스’에서 다루고 있으므로 참고합니다.
---

다음은 08 번째 줄입니다.

08 + current_thread = (struct thread_info*)stack;  

스택 최상단 주소를 thread_info 구조체로 캐스팅해 current_thread 지역변수에 할당합니다. thread_info 구조체는 프로세스 스택의 최상단 주소에 있다는 점을 기억해주세요.

이번에는 패치 코드에서 가장 중요한 부분입니다.

10 + printk("[+] in_interrupt: 0x%08x,preempt_count = 0x%08x, stack=0x%08lx \n", 
11 + (unsigned int)in_interrupt(), (unsignedint)current_thread->preempt_count, (long unsigned int)stack);  

이 패치 코드가 실행되면 다음 정보를 커널 로그로 출력합니다.

in_interrupt() 함수 반환값
thread_info 구조체의 preempt_count 필드값

이렇게 해서 패치 코드를 입력하는 방법과 패치 코드의 내용을 설명했습니다. 앞에서 소개한 패치 코드를 작성하고 컴파일하면 문제없이 커널이 빌드될 것입니다. 이후 커널 이미지를 라즈베리 파이에 설치한 후 리부팅해 봅시다.  

이번에는 앞의 패치 코드가 실행되면서 출력한 커널 로그를 소개합니다.

01 [0.911605] Indeed it is in host mode hprt0 = 00021501
02 [1.001692] mmc1: new high speed SDIO card at address 0001
03 [1.037804] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
04 [1.039271] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
05 [1.041839] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
06 [1.042911] mmc0: host does not support reading read-only switch, assuming write-enable
07 [1.044570] [+] in_interrupt: 0x00010000,preempt_count = 0x00010000, stack=0x80c00000 
08 [1.046503] mmc0: new high speed SDHC card at address aaaa
09 [1.046995] mmcblk0: mmc0:aaaa SB16G 14.8 GiB May 13 13:07:23 raspberrypi kernel:

03번째 줄에서 preempt_count 값은 0x00010000이고 in_interrupt 값은 0x00010000입니다. preempt_count는 프로세스 스택의 최상단 주소에 있는 thread_info 구조체의 preempt_count 필드에 저장된 값입니다. 이 preempt_count 필드와 다음 플래그를 AND 연산한 결과는 0x00010000입니다.

preempt_count & 0x1fff00 (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK)

결국 “in_interrupt: 0x00010000”이라는 로그는 현재 인터럽트를 처리 중이고 현재 코드가 인터럽트 컨텍스트라는 의미입니다. 

다음 절에서는 인터럽트 컨텍스트에서 실행 시간이 오래 걸리는 코드를 입력하면 어떤 일이 발생하는지 확인해 보겠습니다.


"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)

# Reference (인터럽트 처리)

인터럽트 소개  
   * 리눅스 커널에서의 인터럽트 처리 흐름    
인터럽트 컨텍스트  
인터럽트 핸들러는 언제 호출될까?  
인터럽트 핸들러는 어떻게 등록할까?  
인터럽트 디스크립터  
인터럽트 디버깅  

핑백

덧글

  • Daniel 2018/12/14 11:41 # 답글

    만약 어떤 값이 0x210200인데 이 값에 HARDIRQ_MASK(0x10000)와 AND 비트 연산을 하면 결괏값은 0x10000이 됩니다.
    -> 만약 어떤 값이 0x210200인데 이 값에 HARDIRQ_OFFSET(0x10000)와 AND 비트 연산을 하면 결괏값은 0x10000이 됩니다
    로 변경해야 맞지 않은지 문의드립니다.
  • Guillermo 2018/12/14 14:11 #

    좋은 지적 감사드리고, 참 꼼꼼히 글을 읽어주셔서 감사합니다.

    HARDIRQ_MASK는 0xf0000이고 HARDIRQ_OFFSET는 0x10000이며 각각 이진수는 다음과 같습니다.

    HARDIRQ_MASK: 11110000000000000000
    HARDIRQ_OFFSET: 00010000000000000000

    그런데 인터럽트 컨택스트 도중에 IPI(Inter Process Interrupt)가 발생할 수 있고 이 경우 preempt_count() 값은
    00110000000000000000 이 될 수 있습니다.

    따라서 다음과 같은 코드로 인터럽트 컨택스트를 식별하는 것으로 보입니다.
    #define irq_count()(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK
    | NMI_MASK))

    #define in_interrupt()(irq_count())

    알려주신데로, HARDIRQ_OFFSET와 AND 연산을 하면 인터럽트 컨택스트를 제대로 식별할 수 있습니다만,
    코드 구현 내용을 따라 보니 HARDIRQ_MASK으로 언급한 것입니다.
  • Daniel 2018/12/14 14:25 #

    저는 변수명과 값이 매칭되지 않은 것을 확인차 문의드리건데
    자세한 답변 덕분에 또 하나 배워갑니다.
    감사합니다.
  • Guillermo 2018/12/14 14:27 #

    헉~~ 좋은 지적 감사합니다.
    HARDIRQ_MASK(0xf0000) 이렇게 해야 겠네요. ^^
  • harang90 2020/03/02 21:39 # 삭제 답글

    감사합니다. 도움 많이 되었습니다.
  • AustinKim 2020/03/03 07:22 #

    도움이 됐다니 뿌듯합니다. 자주 오셔서 유용한 정보 얻어 가세요.

    Thanks,
    Austin Kim
댓글 입력 영역