ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

201239
1625
172598


[리눅스커널] thread_info 구조체의 preempt_count - Soft IRQ 컨텍스트 실행 4. 프로세스(Process) 관리

Soft IRQ 컨텍스트의 시작 상태 저장

프로세스가 Soft IRQ 서비스를 처리 중이면 preempt_count 필드에 SOFTIRQ_OFFSET 매크로를 저장합니다. 다음 그림을 보면서 Soft IRQ 컨텍스트의 실행 시작을 설정하는 과정을 알아보겠습니다.

 
그림 4.18 Soft IRQ 컨텍스트 설정 시의 함수 흐름

그림 4.18에서 irq_exit() 함수가 보일 것입니다. 이 함수가 Soft IRQ 실행의 시작점입니다. 이처럼 Soft IRQ는 인터럽트 핸들링이 끝나면 실행을 시작합니다.

그렇다면 언제 Soft IRQ 컨텍스트를 설정할까요? 정답은 __do_softirq() 함수를 보면 알 수 있습니다. irq_exit() 함수에서 Soft IRQ 서비스 요청 여부를 체크한 다음 __do_softirq() 함수를 호출합니다. Soft IRQ 서비스를 실행하는 __do_softirq() 함수에서 프로세스 스택의 최상단 주소에 접근합니다. 

thread_info 구조체의 preempt_count 필드에 Soft IRQ 실행을 나타내는 SOFTIRQ_OFFSET(0x100) 매크로를 더합니다. __do_softirq() 함수의 코드를 보면서 이를 확인해 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c
01 asmlinkage __visible void __softirq_entry __do_softirq(void)
02 {
...
03 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
04 in_hardirq = lockdep_softirq_start();
05
06 restart:
07 /* Reset the pending bitmask before enabling irqs */
08 set_softirq_pending(0);
09
10 local_irq_enable();
11
12 h = softirq_vec;
13
14 while ((softirq_bit = ffs(pending))) {
15 unsigned int vec_nr;
16 int prev_count;
17
18 h += softirq_bit - 1;
19
20 vec_nr = h - softirq_vec;
21 prev_count = preempt_count();
22
23 kstat_incr_softirqs_this_cpu(vec_nr);
24
25 trace_softirq_entry(vec_nr);
26 h->action(h);
27 trace_softirq_exit(vec_nr);
...
28 }
...
29
30 lockdep_softirq_end(in_hardirq);
31 account_irq_exit_time(current);
32 __local_bh_enable(SOFTIRQ_OFFSET);

위 코드는 __do_softirq() 함수에서 중요한 부분을 요약한 것입니다. 먼저 03번째 줄을 봅시다. 

03 __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);

thread_info 구조체의 preempt_count 필드에 접근해 다음과 같이 +SOFTIRQ_OFFSET 플래그만큼 증가합니다. 

preempt_count += SOFTIRQ_OFFSET(0x100)

06~28번째 줄은 Soft IRQ 서비스를 실행하는 동작입니다. 

마지막 32번째 줄 코드를 보겠습니다. 

32 __local_bh_enable(SOFTIRQ_OFFSET); 

thread_info 구조체의 preempt_count 필드에 접근해 -SOFTIRQ_OFFSET 플래그만큼 감소시킵니다. 

preempt_count -= SOFTIRQ_OFFSET(0x100)

이번에는 Soft IRQ 컨텍스트를 설정하는 __local_bh_disable_ip() 함수를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c
01 void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
02 {
...
03 __preempt_count_add(cnt);

03번째 줄에서 cnt 인자로 __preempt_count_add() 함수를 호출합니다. 

__preempt_count_add() 함수는 전달된 인자를 thread_info 구조체의 preempt_count 필드에 더합니다. 여기서 cnt는 SOFTIRQ_OFFSET 매크로를 저장하고 있으니 thread_info 구조체의 preempt_count는 다음 수식과 같이 바뀝니다. 참고로 thread_info 구조체는 실행 중인 프로세스 스택의 최상단 주소에 있다는 점을 기억하세요. 

preempt_count  += SOFTIRQ_OFFSET;

Soft IRQ 서비스를 실행하는 서브 함수에서 in_softirq() 매크로를 if 문과 함께 쓰면 현재 실행 중인 코드가 Soft IRQ 컨텍스트인지 알 수 있습니다. in_softirq() 매크로 함수를 쓰는 예제 코드를 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/mm/memcontrol.c
bool mem_cgroup_charge_skmem(struct mem_cgroup *memcg, unsigned int nr_pages)
{
01 gfp_t gfp_mask = GFP_KERNEL;
...
02 /* Don't block in the packet receive path */
03 if (in_softirq())
04 gfp_mask = GFP_NOWAIT;

03~04번째 줄을 보면 in_softirq() 함수가 true를 반환하면 04번째 줄 코드를 실행합니다. 이 코드는 Soft IRQ 컨텍스트에서 gfp_mask를 GFP_NOWAIT로 설정한다는 의미입니다.


in_softirq() 함수는 thread_info 구조체의 preempt_count 필드에 대해 다음과 같은 AND 연산을 수행합니다.

preempt_count &  0xFF00 (SOFTIRQ_MASK)

__do_softirq() 함수에서 Soft IRQ 서비스를 실행하기 전 __local_bh_disable_ip() 함수를 호출하면 thread_info 구조체의 preempt_count는 0x100이 됩니다.

Soft IRQ 서비스를 실행하는 중에 in_softirq() 함수를 실행하면 thread_info 구조체의 preempt_count 필드가 0x2100이므로 true를 반환합니다.

0x100 &  0xFF00 (SOFTIRQ_MASK) // true


Soft IRQ 컨텍스트의 종료 상태를 저장

이번에는 Soft IRQ 서비스 함수의 호출을 마친 다음에 어떤 동작을 하는지 알아보겠습니다. 그림 4.19는 처리 과정을 나타낸 것입니다.

 
 그림 4.19 Soft IRQ 컨텍스트 설정 해제 시의 함수 흐름

'Soft IRQ 컨텍스트'가 종료됐다는 정보는 __do_softirq() 함수에서 Soft IRQ 서비스 핸들러 함수를 호출한 후 설정합니다. 처리 과정은 다음과 같습니다.

1. 프로세스 스택 최상단 주소에 접근한다.
2. thread_info 구조체의 preempt_count 필드에서 SOFTIRQ_OFFSET(0x100) 비트를 뺀다.

'Soft IRQ 컨텍스트'가 종료됐다고 업데이트한 후 오른쪽의 ① 구간으로 표시된 함수에서 in_softirq() 함수를 호출하면 false를 반환합니다. 이는 __do_softirq() 함수에서 Soft IRQ 서비스 실행을 마무리하면 __local_bh_enable() 함수를 호출해 thread_info 구조체의 preempt_count 필드에서 SOFTIRQ_OFFSET(0x100)를 빼는 연산을 수행하기 때문입니다.

이어서 Soft IRQ 서비스 실행을 마치면 호출하는 __local_bh_enable() 함수를 분석해 봅시다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/softirq.c
01 static void __local_bh_enable(unsigned int cnt)
02 {
...
03 __preempt_count_sub(cnt);
04 }

03번째 줄과 같이 __preempt_count_sub() 함수를 호출합니다. 이번에는 실행 중인 프로세스 스택의 최상단 주소에 접근한 후 thread_info 구조체의 preempt_count 필드에 -SOFTIRQ_OFFSET(0x100)만큼 감소시킵니다. 

preempt_count -= SOFTIRQ_OFFSET;

* 유튜브 강의 동영상도 있으니 같이 들으시면 좋습니다. 




#프로세스

프로세스 소개 
프로세스 확인하기  
프로세스는 어떻게 생성할까?  
유저 레벨 프로세스 실행 실습  
커널 스레드  
커널 내부 프로세스의 생성 과정   
프로세스의 종료 과정 분석  
태스크 디스크립터(task_struct 구조체)  
스레드 정보: thread_info 구조체  
프로세스의 태스크 디스크립터에 접근하는 매크로 함수  
프로세스 디버깅  
   * glibc의 fork() 함수를 gdb로 디버깅하기  


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

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


# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2







핑백

덧글

댓글 입력 영역