Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


【펌】【디버깅】 게임계의 레전드인 김포프(Pope Kim)님의 디버깅에 대한 명언 임베디드 에세이

꽃미남이자 게임계의 레전드 개발자이자 엔지니어인 김포프님의 '디버깅'에 대한 명언을 적고자 합니다.

저와 분야는 다르지만 김포프님은 정말 주옥같은 말씀만 하시는 것 같습니다.

(1:15) 프로그래머의 자질은 코딩을 해서 제품을 만들 수 있는 능력이다. 
 
(2:45) 프로그래머가 갖추어야 할 가장 중요한 능력은 디버깅 스킬이다. 

(3:22) 디버깅을 잘한다는 것은 남의 코드를 잘 읽고 그 코드 속의 로직을 따질 수 있고 그 로직을 단계별로 나눌 수 있는 것이다. 

(4:52) 디버깅을 잘하면 남의 코드를 보는 것이 두렵지 않고 이 과정으로 배우는 것이 정말 많다.  

(5:01) 디버깅을 잘하는 프로그래머를 보면 엄청나게 성장할 것이란 것을 안다. 

(7:35) 
정말 코딩을 잘하는 사람 중에 디버깅을 못하는 사람을 본 적이 없다. 
디버깅을 잘하는 사람 중에 코딩을 못하는 사람을 본 적이 없다.
디버깅을 못하는 사람 중에 코딩을 잘하는 사람은 (거의) 본 적이 없다.
디버깅을 못하는 사람 중에 설계를 잘하는 사람은 (거의) 본 적이 없다.

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

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


[후기] '6회 한국 리눅스 커널 모임' 참석(2번째 주제 발표) 후기 임베디드 에세이

   "블로그 잘 보고 있습니다. 책은 언제 출간 되나요?"

한국 리눅스 커널 모임에서 받았던 질문입니다.    
사실 11/07(목요일)에 6회 한국 리눅스 커널 모임에 참가해 두 번째 주제 '커널 크래시 디버깅 방법 소개'로 발표했습니다.

출처: 

'lightning talk' 세션이라 발표 시간이 15분 밖에 주어지지 않아 하고 싶은 말을 충분히 하지는 못했습니다.
하지만 발표를 하면서 참석하신 분들의 표정을 보니 다행히 졸고 계신 분은 없었습니다. 커널 크래시에 대해서 관심이 많아 보였습니다.

4번째 발표(매우 유용한 주제였습니다)를 듣고 나서 네트워킹 타임을 가졌는데, 발표자와 참석자들이 서로 질문을 하면서 지식을 교류하는 시간이었습니다.
모임을 주최하신 분이 피자를 20박스 정도 들고 오셨습니다. 피자가 정말 맛있더군요.

파자를 먹으면셔 많은 참석자들이 저에게 다가와 발표 주제인 '커널 크래시 디버깅 방법'보다는 다음과 같은 질문을 많이 주셨습니다.

      "블로그 잘 보고 있습니다. 책은 언제 출간 되나요?"

이 이야기를 듣고 제가 다시 질문을 드렸습니다.

    "혹시 리눅스를 개발하는 주위 동료 분들이 제 블로그에 오시나요?"   

이 질문을 드리니 모든 분들이 자주 제 블로그에 방문하신다고 하셨습니다.
참석자분들은 이미 제 블로그에 오셨는데 이 블로그에 글을 올리는 게 누굴까 궁금하셨던 것 같습니다.
제가 누군지는 몰라도 이 블로그에 대해서 이미 대부분 알고 있었습니다. 
그래서 참 뿌듯하기도 하고 신기했습니다.

   "이미 내 블로그를 많이 알고 있구나!"

블로그에 대한 이야기를 하면서 다양한 리눅스 개발 분야에 몸 담고 계신 개발자(3~4년차)와 학생 분들과 대화를 나누는 시간을 가졌습니다.
저는 원래 젊은 개발자분들과 대화를 나누는 것을 선호하는데 이런 기회를 갖게 되어 참 좋았습니다.

저에게 발표할 기회를 주신 '한국 리눅스 커널 모임'을 주최하신 분들께 감사드리고,
피자 정말 맛있게 먹었습니다.

이어서 내년 상반기에 열리는 7회 '한국 리눅스 커널' 모임에도 참석해 발표를 해야 겠습니다.

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

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


리눅스 커널 레시피(12월 출간 예정) 전체 목차 -----Table of Contents-----

리눅스의 전망과 소개


라즈베리파이 설정

2.1 라즈베리파이 소개
2.2 라즈베리파이 설정 방법
   2.2.1 라즈베리파이 실습을 위한 준비물 챙기기
   2.2.2 라즈베리파이 설치하기
   2.2.3 라즈베리파이 기본 세팅하기
2.3 라즈베리파이 커널 빌드하기
   2.3.1 라즈비안 커널 소스 코드 내려받기
   2.3.2 라즈비안 리눅스 커널 빌드하기
   2.3.3 라즈비안 리눅스 커널 설치하기
   2.3.4 전처리 코드 생성해보기
2.4 라즈베리파이에서 objdump 바이너리 유틸리티 써보기
2.5 정리

리눅스커널 디버깅


프로세스

프로세스 디버깅
   glibc fork 함수 gdb 디버깅

인터럽트 처리



인터럽트 후반부 처리









6.9 Soft IRQ 서비스는 누가 언제 처리하나?




6.13 Soft IRQ 디버깅
6.13.1 ftrace Soft IRQ 이벤트 분석 방법
6.13.2 /proc/softirqs로 Soft IRQ 서비스 실행 횟수 확인

워크큐

7.1 워크큐 소개
   7.1.1 워크큐 주요 개념 알아보기
   7.1.2 워크큐의 특징은 무엇인가
   7.1.3 워크큐를 다른 인터럽트 후반부 기법과 비교해보기
   7.1.4 워크큐를 잘 알아야 하는 이유
7.2 워크큐 종류 알아보기
   7.2.1 alloc_workqueue() 함수 분석하기  
   7.2.2 7가지 워크큐를 알아보기
7.3 워크란
   7.3.1 struct work_struct 구조체
   7.3.2 워크는 어떻게 초기화를 할까?
7.4 워크를 워크큐에 어떻게 큐잉할까?
   7.4.1 워크를 워크큐에 큐잉하는 예제 코드 살펴보기
   7.4.2 워크큐 전체 흐름도에서 워크를 워크큐에 큐잉하는 과정 소개
   7.4.3 워크를 워크큐에 큐잉하는 인터페이스 함수 분석하기
   7.4.4 __queue_work() 함수 분석하기
   7.4.5 __queue_work_on() 함수에서 호출하는 워크큐 내부 함수 분석하기
7.5 워크는 누가 언제 실행하나?
   7.5.1 워크 실행의 출발점인 worker_thread() 함수 분석 
   7.5.2 process_one_work() 함수 분석
7.6. 워커 스레드란
   7.6.1 워커와 워커 스레드란
   7.6.2 워커 자료구조인 struct worker 구조체 알아보기
   7.6.3 워커 스레드는 누가 언제 만들까
   7.6.4 워커 스레드를 만드는 create_worker() 함수 분석하기
   7.6.5 create_worker() 함수에서 호출한 워크큐 커널 함수 분석하기
   7.6.6 워커 스레드 핸들 worker_thread() 함수 분석하기 
7.7 워크큐 실습 및 디버깅
   7.7.1 ftrace 워크큐 이벤트 소개
   7.7.2 라즈베리파이에서 ftrace로 워크큐 동작 확인
   7.7.3 인터럽트 후반부로 워크큐 추가 실습 및 로그 분석
7.8 딜레이 워크 소개
   7.8.1 딜레이 워크란 무엇인가?
   7.8.2 딜레이 워크 전체 흐름도 소개
   7.8.3 딜레이 워크는 어떻게 초기화할까?
   7.8.4 딜레이 워크 실행의 시작점은 어디일까?
   7.8.5 딜레이 워크는 누가 언제 큐잉할까?
7.9 라즈베리파이 딜레이 워크 실습 및 로그 확인
   7.9.1 패치 코드 내용과 작성 방법 알아보기
   7.9.2 ftrace 로그 설정 방법 소개
   7.9.3 ftrace 로그 분석해보기
7.10 정리

커널 시간관리

커널 타이머 관리 주요 개념 소개
jiffies란
커널 타이머 제어
동적 타이머 초기화
동적 타이머 등록하기
동적 타이머는 누가 언제 실행하나?
라즈베리파이 커널 타이머 실습 및 로그 분석
   
커널 동기화

커널 동기화 기본 개념 소개
레이스 발생 동작 확인
커널 동기화 기법 소개
스핀락
뮤텍스란
커널 동기화 디버깅

프로세스 스케줄링

스케줄링 소개
프로세스 상태 관리
   어떤 함수가 프로세스 상태를 바꿀까?
스케줄러 클래스
런큐
CFS 스케줄러
   CFS 관련 세부 함수 분석  
선점 스케줄링(Preemptive Scheduling)   
프로세스는 어떻게 깨울까?
스케줄링 핵심 schedule() 함수 분석
컨택스트 스위칭
스케줄링 디버깅
   스케줄링 프로파일링
     CPU에 부하를 주는 테스트   
     CPU에 부하를 주지 않는 테스트 

시스템 콜

시스템 콜 주요 개념 소개
유저 공간에서 시스템 콜은 어떻게 발생할까
시스템 콜 핸들러는 어떤 동작을 할까? 
시스템 콜 실행 완료 후 무슨 일을 할까?
시스템 콜 관련 함수  
시스템 콜 디버깅  
   

시그널이란

시그널이란
시그널 설정은 어떻게 할까
시그널 생성 과정 함수 분석
프로세스는 언제 시그널을 받을까
시그널 전달과 처리는 어떻게 할까?
시그널 제어 suspend() 함수 분석 
시그널 ftrace 디버깅
 
  
가상 파일시스템 

가상 파일시스템 소개
파일 객체
파일 객체 함수 오퍼레이션 동작
프로세스는 파일객체 자료구조를 어떻게 관리할까?
슈퍼블록 객체
아이노드 객체
덴트리 객체
가상 파일시스템 디버깅

커널 메모리 

가상 주소를 물리 주소로 어떻게 변환할까?   
메모리 존(Zone)에 대해서   
커널 메모리 할당은 어떻게 할까   
슬랩 메모리 할당자와 kmalloc 슬랩 캐시 분석   
커널 메모리 디버깅


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

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







[리눅스][디바이스드라이버] 모듈로 설치된 드라이버를 확인해보기: modules 변수 Linux Device Driver

리눅스 드라이버는 커널 이미지 내에 포함된 '빌트 인' 드라이버와 모듈로 설치된 드라이버로 구성돼 있습니다.
이번 시간에는 시스템에 설치된 모듈 드라이버를 확인하는 방법을 소개합니다.

이 내용을 알아보려고 하니 한 가지 의문이 생깁니다.

   커널 어디선가 모듈 드라이버들의 정보를 저장한 자료구조가 있지 않을까?

이 의문을 풀기 위해서는 다음 코드와 같이 modules 란 전역 변수를 분석할 필요가 있습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/module.c
static LIST_HEAD(modules);

modules 의 선언부를 보니 링크드 리스트란 사실을 알 수 있습니다.
그러면 modules 변수는 어떻게 활용될까요?

이를 위해서는 find_module_all() 함수를 열어 볼 필요가 있습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/module.c
static struct module *find_module_all(const char *name, size_t len,
      bool even_unformed)
{
struct module *mod;

module_assert_mutex_or_preempt();

list_for_each_entry_rcu(mod, &modules, list) {
if (!even_unformed && mod->state == MODULE_STATE_UNFORMED)
continue;
if (strlen(mod->name) == len && !memcmp(mod->name, name, len))
return mod;
}
return NULL;
}

위 코드에서 'list_for_each_entry_rcu(mod, &modules, list)' 구문을 눈여겨 볼 필요가 있습니다.
이 코드를 어떻게 해석할 수 있을까요?

   modules 링크드 리스트는 struct module 구조체의 list 필드 주소의 위치를 가르킨다.

다음 module 구조체의 선언부를 보면  2번째 필드로 list가 보입니다. 

https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/module.h
   
struct module {
enum module_state state;

/* Member of list of modules */
struct list_head list;

/* Unique handle for this module */
char name[MODULE_NAME_LEN];

/* Sysfs stuff. */
struct module_kobject mkobj;

이렇게  modules 링크드 리스트는 module 구조체의 list 필드 주소를 가르키는 것입니다.
  
커널에서는 이런 방식으로 큰 사이즈의 구조체를 관리합니다.

이제 modules 변수를 확인해보겠습니다.
이를 위해 크래시 유틸리티 프로그램으로 'p modules' 명령어를 입력해보겠습니다.

crash> p modules
modules = $1 = {
  next = 0xffffff92753c61c8,
  prev = 0xffffff927526e4c8
}

결과를 보니 next와 prev란 필드로 구성된 링크드 리스트의 정보를 확인할 수 있습니다.

이번에는 크래시 유틸리티에서 제공하는 list 명령어를 사용해 modules 변수를 확인볼까요? 
'list modules' 명령어를 입력해보겠습니다.

crash> list modules
ffffff9339f08dc8
ffffff92753c61c8
ffffff9275f05188
ffffff927588cd88
ffffff9275836e48
ffffff92758118c8
ffffff9275801108
ffffff92757f4808
ffffff92757d7248
ffffff92757c1e48
ffffff92757ab208
ffffff92757990c8
ffffff9275782988
ffffff9275766448
ffffff92757502c8
ffffff9275739fc8
ffffff9275727388
ffffff9275716008
ffffff9275703248
ffffff92756fa308
ffffff92756f1848
ffffff92756e5608
ffffff92756db288
ffffff92756d2108
ffffff92756c8b88
ffffff92756ad908
ffffff9275578688
ffffff92753596c8
ffffff92752ce2c8
ffffff92752949c8
ffffff92752881c8
ffffff927527f988
ffffff9275277108
ffffff927526e4c8

이번에는 module 구조체 내에 선언된 필드들의 오프셋을 확인하겠습니다.
'struct -o  module' 명령어를 입력해봅시다.
   
crash> struct -o  module
struct module {
    [0] enum module_state state;
    [8] struct list_head list;
   [24] char name[56];
   [80] struct module_kobject mkobj;
  [176] struct module_attribute *modinfo_attrs;
  [184] const char *version;

확인 결과 module 구조체의 시작 주소 기준으로 +8 바이트에 list 필드가 위치합니다.

이 정보로 'list modules' 명령어를 입력한 결과에서 -8를 뺀 주소가 module 구조체의 시작 주소임을 알 수 있습니다.

crash> list modules
ffffff9339f08dc8
ffffff92753c61c8

ffffff9339f08dc8 주소에서 -8를 뺀 주소가 ffffff9339f08dc0 이니 이 정보를 참고해서 module 구조체를 확인해보겠습니다.
출력 결과는 다음과 같습니다.

crash> struct  module -p ffffff9339f08dc0
struct module {
  state = MODULE_STATE_LIVE,
  list = {
    next = 0xffffff9275f05188,
    prev = 0xffffff9339f08dc8
  },
  name = "rpi_tty\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
  mkobj = {
    kobj = {
      name = 0xfffffffaf3a40d00 "rpi_tty",
      entry = {
        next = 0xfffffffb35e87780,
        prev = 0xffffff9275f051d8
      },
      parent = 0xfffffffb35e87798,
      kset = 0xfffffffb35e87780,

이처럼 크래시 유틸리티 명령어를 활용하면 모듈 타입으로 설치된 드라이버의 세부 정보를 확인할 수 있습니다.



[리눅스] 워크큐: 배리어 워크(Barrier Work)에 대해서 8. Workqueue

배리어 워크(Barrier Work)는 flush_work() 함수를 호출할 때 워크큐에 큐잉되며 워크 간의 실행 순서나 동기를 맞춰주는 역할을 수행합니다.

사실 이 블로그에서 한 개의 워크가 초기화된 후 워크큐에 큐잉하는 과정을 설명합니다.
하지만 라즈베리 파이에서 워크큐 관련 ftrace 이벤트를 키고 ftrace 메시지를 확인하면 여러 워크들이 동시다발적으로 실행된다는 사실을 알 수 있습니다. 
이처럼 실행되는 워크들 사이에는 의존성 혹은 연관성이 있을 수 있습니다.

만약 A란 워크의 워크 핸들러가 실행하는 동안 A란 워크와 연관된 B와 C란 워크가 워크큐에 큐잉됐는데 동기를 맞춰 B와 C 워크를 실행하고 싶을 때가 있습니다.
이 때 flush_work() 함수를 호출해야 하는데 이 과정에서 insert_wq_barrier() 함수가 실행돼 워크큐에 큐잉되는 워크가 배리어 워크(Barrier Work)입니다.

배리어 워크에 대한 세부 ftrace 로그 분석은 다음 포스팅을 참고하세요.

【LinuxKernel】 Signature of Soft IRQ context 6. Bottom-Half

>crash64_kaslr> task 6349
>PID: 6349   TASK: fffffffac14e4340  CPU: 0   COMMAND: "irq/12-90b6400."
>struct task_struct {
>  thread_info = {
>    flags = 0x2a,
>    padding = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
>    addr_limit = 0x7fffffffff,
>    preempt_count = 0x10102

The thread_info field contains 0x10102 where 0x00100 signature shows that this process is running under Soft IRQ context.

>  },
>
>

Below is relevant callstacks from kernel log.
>
>[  620.403963] <c2> __do_softirq+0x10c/0x480

From this function, we can deduce that Soft IRQ service is executing.

>[  620.409903] <c2> irq_exit+0xd0/0xd8
>[  620.415326] <c2> __handle_domain_irq+0xa8/0xf8
>[  620.421695] <c2> gic_handle_irq+0x154/0x1d4
>[  620.427821] <c2> el1_irq+0xb4/0x130

The 'el1_irq' is interrupt exception vector.

>[  620.433240] <c2> _raw_spin_unlock_irqrestore+0x30/0x70
>[  620.440349] <c2> free_debug_processing+0x2f8/0x3e0
>[  620.447095] <c2> kfree+0x2f8/0x738
>[  620.452422] <c2> rpi_invalidate+0x58/0xa8
>[  620.458451] <c2> rpi_bus_commit_data+0x7f4/0xa80
>[  620.465030] <c2> update_client_paths+0x1f8/0x270
>[  620.471593] <c2> update_request_adhoc+0x17c/0x488
>[  620.478242] <c2> rpi_bus_scale_client_update_request+0x34/0x60
>[  620.486058] <c2> devbw_target+0xe8/0x150
>[  620.491917] <c2> update_devfreq+0xfc/0x298
>[  620.497944] <c2> update_bw_hwmon+0x6c/0xe8
>[  620.503976] <c2> bwmon_intr_thread+0x1c/0x30
>[  620.510178] <c2> irq_thread_fn+0x2c/0x70
>[  620.516021] <c2> irq_thread+0x164/0x218
[  620.521784] <c2> kthread+0x128/0x138
[  620.527283] <c2> ret_from_fork+0x10/0x18

리눅스 커널 기여(Contribution) II (3/3) - 코드 리뷰 과정과 업스트림(병합) 확인하기 Linux Kernel Contribution



이렇게 메일로 패치 코드를 전달한 후 memory management/vmalloc 서브 시스템 메인테이너인 'Andrew Morton'로부터 답장이 왔습니다.
보낸사람: Andrew Morton <akpm@linux-foundation.org>
받는사람: Austin Kim <austindh.kim@gmail.com> 
참조:    linux-mm@kvack.org,
    linux-kernel@vger.kernel.org
제목: Re: [PATCH] mm/vmalloc: move 'area->pages' after if statement

Fair enough.  But we can/should also do this?

--- a/mm/vmalloc.c~mm-vmalloc-move-area-pages-after-if-statement-fix
+++ a/mm/vmalloc.c
@@ -2409,7 +2409,6 @@ static void *__vmalloc_area_node(struct
        nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
        array_size = (nr_pages * sizeof(struct page *));

-       area->nr_pages = nr_pages;
        /* Please note that the recursion is strictly bounded. */
        if (array_size > PAGE_SIZE) {
                pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
@@ -2425,6 +2424,7 @@ static void *__vmalloc_area_node(struct
        }

        area->pages = pages;
+       area->nr_pages = nr_pages;

        for (i = 0; i < area->nr_pages; i++) {
                struct page *page;

답장의 내용은 다음과 같습니다.
   
    Fair enough.  But we can/should also do this?    
    좋아. 그런데 다음과 같이 수정하는 것은 어떨까?
   
필자가 수정한 패치 코드에서 뭔가 추가하면 좋은 내용이 보여 메인테이너인 Andrew Morton이 패치 코드를 알려준 것이었습니다. 필자는 '고맙다, 동의한다'라고 답장을 보냈습니다.


필자가 이메일을 통해 Maintainer와 이메일로 의견을 교환한 내역은 다음 사이트에서 확인할 수 있습니다.
https://lkml.org/lkml/2019/8/30/952
https://patchwork.kernel.org/patch/11122851/

https://lkml.org와 https://patchwork.kernel.org 사이트는 리눅스 커널 메일링 리스트를 통해 커널 개발자가 나눈 이메일 내용을 보관하는 사이트입니다.


하루 후 다음과 같은 메일이 전달됐습니다. 패치가 리뷰되면서 'Signed-off-by', 'Acked-by'와 'Reviewed-by'와 같이 커널 개발자로 받은 도장이 보입니다. 필자가 처음에 'Signed-off-by'로 자신의 이메일 주소를 추가한 다음 메인테이너가 'Signed-off-by'를 추가한 것입니다.
제목: + mm-vmalloc-move-area-pages-after-if-statement.patch added to -mm tree

The patch titled
     Subject: mm/vmalloc.c: move 'area->pages' after if statement
has been added to the -mm tree.  Its filename is
     mm-vmalloc-move-area-pages-after-if-statement.patch

This patch should soon appear at
    http://ozlabs.org/~akpm/mmots/broken-out/mm-vmalloc-move-area-pages-after-if-statement.patch
and later at
    http://ozlabs.org/~akpm/mmotm/broken-out/mm-vmalloc-move-area-pages-after-if-statement.patch

Before you just go and hit "reply", please:
   a) Consider who else should be cc'ed
   b) Prefer to cc a suitable mailing list as well
   c) Ideally: find the original patch on the mailing list and do a
      reply-to-all to that, adding suitable additional cc's

*** Remember to use Documentation/process/submit-checklist.rst when testing your code ***

The -mm tree is included into linux-next and is updated
there every 3-4 working days

------------------------------------------------------
From: Austin Kim <austindh.kim@gmail.com>
Subject: mm/vmalloc.c: move 'area->pages' after if statement

If !area->pages statement is true where memory allocation fails, area is
freed.

In this case 'area->pages = pages' should not executed.  So move
'area->pages = pages' after if statement.

Link: http://lkml.kernel.org/r/20190830035716.GA190684@LGEARND20B15
Signed-off-by: Austin Kim <austindh.kim@gmail.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Reviewed-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Uladzislau Rezki (Sony) <urezki@gmail.com>
Cc: Roman Gushchin <guro@fb.com>
Cc: Roman Penyaev <rpenyaev@suse.de>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Mike Rapoport <rppt@linux.ibm.com>
Cc: Andrey Ryabinin <aryabinin@virtuozzo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---

 mm/vmalloc.c |    6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

--- a/mm/vmalloc.c~mm-vmalloc-move-area-pages-after-if-statement
+++ a/mm/vmalloc.c
@@ -2417,13 +2417,15 @@ static void *__vmalloc_area_node(struct
        } else {
                pages = kmalloc_node(array_size, nested_gfp, node);
        }
-       area->pages = pages;
-       if (!area->pages) {
+
+       if (!pages) {
                remove_vm_area(area->addr);
                kfree(area);
                return NULL;
        }

+       area->pages = pages;
+
        for (i = 0; i < area->nr_pages; i++) {
                struct page *page;

_

Patches currently in -mm which might be from austindh.kim@gmail.com are

mm-vmalloc-move-area-pages-after-if-statement.patch

이 이메일을 받고 난 후 23일 후에 리눅스 커널 메인 브랜치에 병합(Merge)된 것을 확인할 수 있었습니다.
author Austin Kim <austindh.kim@gmail.com> 2019-09-23 15:36:42 -0700
committer Linus Torvalds <torvalds@linux-foundation.org> 2019-09-24 15:54:10 -0700
commit 7ea362427c170061b8822dd41bafaa72b3bcb9ad (patch)
tree 3b31fc56753b3fcfd467ae6b19b714dd4776ee26
parent 688fcbfc06e4fdfbb7e1d5a942a1460fe6379d2d (diff)
download linux-next-7ea362427c170061b8822dd41bafaa72b3bcb9ad.tar.gz
mm/vmalloc.c: move 'area->pages' after if statement
If !area->pages statement is true where memory allocation fails, area is
freed.

In this case 'area->pages = pages' should not executed.  So move
'area->pages = pages' after if statement.
...
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index f095843..fcadd3e 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -2409,7 +2409,6 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
  nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
  array_size = (nr_pages * sizeof(struct page *));
 
- area->nr_pages = nr_pages;
  /* Please note that the recursion is strictly bounded. */
  if (array_size > PAGE_SIZE) {
  pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
@@ -2417,13 +2416,16 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
  } else {
  pages = kmalloc_node(array_size, nested_gfp, node);
  }
- area->pages = pages;
- if (!area->pages) {
+
+ if (!pages) {
  remove_vm_area(area->addr);
  kfree(area);
  return NULL;
  }
 
+ area->pages = pages;
+ area->nr_pages = nr_pages;
+
  for (i = 0; i < area->nr_pages; i++) {
  struct page *page;



출처는 다음 사이트입니다.
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=7ea362427c170061b8822dd41bafaa72b3bcb9ad


최신 리눅스 커널 코드를 검색해보니 필자와 메인테이너가 제안한 코드가 반영이 됐음을 확인할 수 있습니다.  
[https://elixir.bootlin.com/linux/v5.4-rc1/source/mm/vmalloc.c]
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
 pgprot_t prot, int node)
{
...

/* Please note that the recursion is strictly bounded. */
if (array_size > PAGE_SIZE) {
pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
PAGE_KERNEL, node, area->caller);
} else {
pages = kmalloc_node(array_size, nested_gfp, node);
}

if (!pages) {
remove_vm_area(area->addr);
kfree(area);
return NULL;
}

area->pages = pages;
area->nr_pages = nr_pages;


리눅스 커널 기여(Contribution) II (2/3) - 패치 보내기 Linux Kernel Contribution



패치 코딩 룰 체크하기

패치 코드를 생성했으면 리눅스 커널에 맞는 ‘코딩 룰’을 체크해야 합니다. 이를 위해 다음 스크립트 파일을 실행할 필요가 있습니다.
./scripts/checkpatch.pl [패치 코드 이름]

root@raspberrypi:/home/pi/kernel_src/linux-next# ./scripts/checkpatch.pl 0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch
total: 0 errors, 0 warnings, 17 lines checked

0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch has no obvious style problems and is ready for submission.

만약 ‘코딩 룰’에 맞지 않게 패치를 작성해서 메인테이너 에게 메일을 보내면 ‘코딩 룰’에 맞게 다시 패치를 보내란 답장을 받을 확률이 높습니다. 혹은 아예 반응이 없는 경우도 있습니다. 그래서 ‘./scripts/checkpatch.pl’ 스크립트를 실행한 후 경고나 에러 메시지가 출력되면 후속 초치를 취해야 합니다. 
패치 코드를 수정하고 ./scripts/checkpatch.pl’ 스크립트를 실행합니다.
‘./scripts/checkpatch.pl’ 경고 메시지가 없을 때 까지 패치 코드를 수정해야 합니다. 

패치를 보낼 메일 수신자 확인하기

이제 패치 작성이 완료됐으니 패치를 받을 수신자를 확인해야 합니다. 이를 위해 다음 스크립트를 실행할 필요가 있습니다.
./scripts/get_maintainer.pl [패치파일 이름]

다음은 0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch 패치 파일을 리뷰할 이메일 주소를 확인하는 과정을 보여 줍니다. 
root@raspberrypi:/home/pi/kernel_src/linux-next# ./scripts/get_maintainer.pl 0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch
Andrew Morton <akpm@linux-foundation.org> (commit_signer:44/38=100%)
"Uladzislau Rezki (Sony)" <urezki@gmail.com> (commit_signer:15/38=39%,authored:12/38=32%,added_lines:994/1402=71%,removed_lines:298/590=51%)
Roman Gushchin <guro@fb.com> (commit_signer:6/38=16%)
Roman Penyaev <rpenyaev@suse.de> (commit_signer:4/38=11%,authored:4/38=11%,removed_lines:31/590=5%)
Michal Hocko <mhocko@kernel.org> (commit_signer:3/38=8%)
Rick Edgecombe <rick.p.edgecombe@intel.com> (authored:3/38=8%,added_lines:102/1402=7%)
Mike Rapoport <rppt@linux.ibm.com> (authored:2/38=5%,added_lines:219/1402=16%,removed_lines:195/590=33%)
Andrey Ryabinin <aryabinin@virtuozzo.com> (authored:2/38=5%)
linux-mm@kvack.org (open list:MEMORY MANAGEMENT)
linux-kernel@vger.kernel.org (open list) 

메일 이름을 보면 아래는 개발자 이메일 주소로 보이니 'To:'로 지정하면 됩니다.
akpm@linux-foundation.org,
urezki@gmail.com,
guro@fb.com,
rpenyaev@suse.de,
mhocko@suse.com,
rick.p.edgecombe@intel.com,
rppt@linux.ibm.com,
aryabinin@virtuozzo.com

아래는 리눅스 커널 메일링 리스트 주소로 보이니 'Cc:'로 기입하겠습니다.
linux-mm@kvack.org
linux-kernel@vger.kernel.org 

패치의 코딩룰을 체크했고 메일을 받을 이메일 주소도 확인했으니 이제 패치 코드를 보낼 차례입니다.

mutt 프로그램으로 패치 전송하기

이제 패치 파일을 mutt 프로그램으로 전송할 일만 남았습니다. 이를 위해 다음 포멧으로 명령어를 입력해봅시다.
mutt -H [패치 코드 이름]

먼저 다음 명령어를 입력해볼까요?
root@raspberrypi:/home/pi/kernel_src/linux-next# mutt -H 0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch

mutt 프로그램이 실행되면서 다음 창이 출력 될 것입니다.
To:

‘To:’ 오른쪽에 다음 이메일을 수신자로 지정합니다.
akpm@linux-foundation.org,
urezki@gmail.com,
guro@fb.com,
rpenyaev@suse.de,
mhocko@suse.com,
rick.p.edgecombe@intel.com,
rppt@linux.ibm.com,
aryabinin@virtuozzo.com

아래는 메일 수신자를 기입한 후 화면입니다. 
To: akpm@linux-foundation.org, urezki@gmail.com, guro@fb.com, rpenyaev@suse.de, mhocko@suse.com, rick.p.edgecombe@intel.com, rppt@linux.ibm.com, aryabinin@virtuozzo.com

메일 수신자를 모두 입력한 다음 ‘엔터’를 입력합니다.
이어서 다음이 보일 것입니다. 패치 제목과 패치 내용을 볼 수 있습니다. 
Subject: [PATCH] mm/vmalloc.c: move 'area->pages' after if statement

  1 If !area->pages statement is true where memory allocation fails, area is
  2 freed.
  3
  4 In this case 'area->pages = pages' should not executed.  So move
  5 'area->pages = pages' after if statement.
  6
  7 Signed-off-by: Austin Kim <austindh.kim@gmail.com>
  8 ---
  9  mm/vmalloc.c | 6 ++++--
 10  1 file changed, 4 insertions(+), 2 deletions(-)
 11
 12 diff --git a/mm/vmalloc.c b/mm/vmalloc.c
 13 index e66e7ff..0471c78 100644
 14 --- a/mm/vmalloc.c
 15 +++ b/mm/vmalloc.c
 16 @@ -2416,13 +2416,15 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
 17     } else {
 18         pages = kmalloc_node(array_size, nested_gfp, node);
 19     }
 20 -   area->pages = pages;
 21 -   if (!area->pages) {
 22 +
 23 +   if (!pages) {
 24         remove_vm_area(area->addr);
 25         kfree(area);
 26         return NULL;
 27     }
 28
 29 +   area->pages = pages;
 30 +
 31     for (i = 0; i < area->nr_pages; i++) {
 32         struct page *page;
 33
 34 --
 35 2.6.2
 36

 /tmp/mutt-ATEARND20B15-1035-97268-32752046138858321                                                                  1,1            All
:wq

패치 코드와 제목에 문제가 없으니 ‘wq’ 를 입력해 다음 화면으로 이동합니다.
y:Send  q:Abort  t:To  c:CC  s:Subj  a:Attach file  d:Descrip  ?:Help
    From: Austin Kim <austindh.kim@gmail.com>
      To: akpm@linux-foundation.org, urezki@gmail.com, guro@fb.com, rpenyaev@suse.de, mhocko@suse.com, rick.p.edgecombe@intel.com, rppt@linux.ibm.com, aryabinin@virtuozzo.com
      Cc:
     Bcc:
 Subject: [PATCH] mm/vmalloc.c: move 'area->pages' after if statement
Reply-To:
     Fcc:
     Mix: <no chain defined>
Security: None

위 화면에서 ‘C’를 입력하면 Cc 메일 주소 입력 창이 출력됩니다. 
‘Cc:’


‘Cc:’ 리스트에는 이전 소절에서 아래 명령어로 확인한 메일링 리스트 주소를 입력하면 됩니다.
'./scripts/get_maintainer.pl [패치파일 이름]'
linux-mm@kvack.org
linux-kernel@vger.kernel.org,


‘Cc:’ 오른쪽 부분에 메일링 리스트 주소와 필자의 이메일 주소를 기입합니다.
Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org, austindh.kim@gmail.com

‘Cc:’ 메일 주소 입력을 마무리한 후 ‘엔터’키를 입력합니다. 그러면 다음 화면이 보일 것입니다.
y:Send  q:Abort  t:To  c:CC  s:Subj  a:Attach file  d:Descrip  ?:Help
    From: Austin Kim <austindh.kim@gmail.com>
      To: akpm@linux-foundation.org, urezki@gmail.com, guro@fb.com, rpenyaev@suse.de, mhocko@suse.com, rick.p.edgecombe@intel.com, rppt@linux.ibm.com, aryabinin@virtuozzo.com
      Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org, austindh.kim@gmail.com
     Bcc:
 Subject: [PATCH] mm/vmalloc.c: move 'area->pages' after if statement
Reply-To:
     Fcc:
     Mix: <no chain defined>
Security: None

메일 주소와 수신자를 다시 한 번 확인한 후 오탈자가 없으면 ‘y’를 입력해 패치 코드를 송신합니다.

[LinuxKernel] Why race condition occur? 9. Synchronize(spinlock/mutex)

In the previous section, we introduced the concepts of race conditions and critical areas. Reading this naturally raises the following questions: Why does race condition and concurrency occur? This time, let's see why.

Symmetric multiprocessing (SMP)

The first reason race conditions occur is because Linux systems use symmetric multiprocessing (SMP). SMP is a computer system architecture where multiple CPUs write a single memory on a system.

Reading the definition of these SMPs doesn't make much sense why race conditions occur. In terms of software, however, the SMP system does the following:

   Processes run in parallel on multiple CPUs

If an SMP system has four CPUs on a Linux system, then different processes can run in parallel on the four CPUs. This can lead to processes running on different CPUs accessing the same code or function.

This phenomenon is called concurrency. In other words, SMP systems are subject to concurrency.

Most Linux systems currently run in an SMP environment. Most Linux, including your smartphone, runs on SMP systems. Raspberry Pi also has a quad-core CPU (four CPUs).

Preemption scheduling

The second reason for a race is because the Linux kernel supports preemptive scheduling. Most Linux kernels now run in a preemptive scheduling environment, which can be interpreted as "preemptive scheduling while the code block we entered is running."

After reading this, the following questions naturally arise: Does preemption scheduling cause race conditions?

Preemptive scheduling can cause various races. To help you understand, let's take an example. Suppose your code is running in the rpi_set_synchronize () function. What happens if process B calls rpi_set_synchronize () again after process A is preemptively scheduled while executing rpi_set_synchronize ()? Races can occur if process B reruns sections of code that deal with critical data structures.

Preemptive scheduling can occur and cause other problems without requiring another process to call the rpi_set_synchronize () function. Suppose you are running code that gives an algorithmic operation or correct delay within the rpi_set_synchronize () function. In this case, if the code executing by preemptive scheduling is stopped and scheduled by another process, it may not be able to give a delay and cause a malfunction.

One reason for this concurrency is preemption.

Interrupt occurrence

Interrupts can occur at any time on any CPU. If an interrupt occurs while your code is running, you can stop it and call the interrupt handler. What happens if an interrupt occurs while executing the rpi_set_synchronize () function and enters the rpi_set_synchronize () function again? Race conditions can also occur in this situation.

Thread handling in user processes

You can create threads in your application so that multiple threads can access a file. This can cause concurrency or race conditions.

Concurrency Cases

If you are new to embedded Linux, you may not be able to understand why concurrency problems are occurring. To help you understand, here is an example of concurrency issues in an SMP environment, taken from the Linux kernel community.

First, let me introduce the first case.

[https://lore.kernel.org/patchwork/patch/901442/]
01         CPU0                    CPU1
02         ----                    ----
03    lock(&mm->mmap_sem);
04                                 lock(bpf_event_mutex);
05                                 lock(&mm->mmap_sem);
06    lock(bpf_event_mutex);
07
08   *** DEADLOCK ***

In line 03, we lock on CPU0 as & mm-> mmap_sem. In the lock section, as in line 05, CPU1 re-locks to & mm-> mmap_sem in the same code section.

This time, let's take a look at another case.

[https://lore.kernel.org/patchwork/patch/880120/]
01 CPU0                                    CPU1
02 mmap syscall                           ioctl syscall
03 -> mmap_sem (acquired)                 -> ashmem_ioctl
04 -> ashmem_mmap                            -> ashmem_mutex (acquired)
05    -> ashmem_mutex (try to acquire)       -> copy_from_user
06                                               -> mmap_sem (try to acquire)



In line 03, CPU0 accesses the mmap_sem () function and acquires the lock. By the way, at the far right, we are accessing the mmap_sem () function almost like CPU06, as in line 06.

The two cases have something in common: That is, processes running on more than one CPU run the same block of code at the same time.

So far we have seen cases where processes running on two CPUs simultaneously access the same block of code. However, readers who read this section may complain about:

   Can you explain in more detail the cause and solution of the problem?

This time, the focus is on the fact that two CPUs can execute the same block of code. Subsequently, we will learn more about race by analyzing race-related patches discussed in the Linux community.

[ARM] ARM 프로세스를 배우기 어려운 이유 - 어셈블리 명령어 [Linux][ARM] Core Analysis

많은 분들이 ARM 프로세스를 익히기 어렵다고 말합니다. 물론 저도 마찬가지였고요.
그렇다면 ARM 프로세스가 어려운 이유가 뭘까요? 

ARM 프로세스 자체가 어렵기보다는 ARM 프로세스를 익히는 방법에 문제가 있기 때문입니다.
달리 말하면 ARM 프로세스 내용은 그리 어렵다는 이야기입니다.

자, 그럼 ARM 프로세스의 핵심 개념이 무엇인지 살펴볼까요? ARM 프로세스 범위는 넓게 보면 MMU부터 캐시까지 다양하나 핵심 내용은 다음과 같이 좁힐 수 있습니다.
- 어셈블리 명령어
- 익셉션
- ARM 모드(ARMv7/ARMv8)
- 함수 호출 규약(Calling Convention)

이 밖에도 MMU, Cache(L1/L2) 등등 깊게 살펴볼 주제가 있습니다. 위에서 언급한 내용만 제대로 파악하면 임베디드 개발자로써 프로그래밍이나 디버깅하는데 문제가 없습니다.

제가 ARM 프로세스는 그 내용 자체가 어렵다기 보다는 ARM 프로세스를 익히는 방법이 효율적이기 못하다고 말씀드렸습니다. 그러면 위에서 언급한 4가지 핵심 주제로 토대로 조금 더 설명을 드리겠습니다.

어셈블리 명령어

'어셈블리 명령어'란 말만 들어도 머리가 멍해지는 분 있나요? 한번 손 들어보세요! 
제가 예전에 ARM 프로세스의 어셈블리 명령어를 공부할 때 정말 낯설었습니다. 아무리 어셈블리 명령어를 읽어도 머리 속에 남지 않고 바로 튕겨 나가는 듯한 느낌이었어요. 그 이유는 무엇일까요?

   "어셈블리 명령어를 문법 위주로 익혔기 때문입니다." 

어셈블리 명령어를 읽으면 일단 무슨 소리인지 이해는 갑니다. 그런데 어셈블리 명령어를 알면 뭐합니까? 

   "실제 프로그래밍을 할 때 써 먹는게 중요하지오!"

그럼 어셈블리 명령어 한 개를 예를 들어볼까요?

ldr r2 [r3]

위 명령어는 r3 레지스터가 담고 있는 값을 r2 레지스터에 저장하라는 의미입니다. 
다음과 같이 r3가 0xD000C000이고 0xD000C000에 0x2가 있다고 가정하겠습니다.

주소              값
---------------------
0xD000C000   0x2
0xD000C004   0x4

이 조건에서 'ldr r2 [r3]' 명령어를 실행하면 r2는 어떻게 업데이트될까요?

   "0x2가 됩니다."

자, 우리는 'ldr' 명령어를 알게 됐습니다. '난 이제 새로운 ARM 어셈블리 명령어를 알게 됐어'라고 생각하면 자신을 칭찬할 수 있습니다. 하지만 이처럼 공부하는 것은 정말 비효율적입니다.

    "그 이유는 이 방식으로 공부하는 까 먹을 확률이 99.9% 이기 때문입니다."

'ldr' 명령어만 배우는 것은 사실 공학적으로 아무런 의미가 없습니다. 그러면 ldr 명령어를 어떻게 배워야 머리 속에 오랫 동안 남을까요?

   "C 프로그램으로 작성한 코드가 ldr 명령어로 어떻게 변환되는지 알아야 합니다."

다시 말하면 우리가 작성한 C 코드가 ldr 명령어에 어떻게 대응하는지 알아야 한다는 것입니다.

다음 시간에는 실제 C 코드가 ARM 어셈블리 코드로 어떻게 변환되는지 알아보겠습니다.

[LinuxKernel] What is critical section and race condition? 9. Synchronize(spinlock/mutex)

Kernel synchronization is a technique for designing or maintaining code that behaves as follows:

1. Only one process is accessed when executing a function or a specific code segment.
2. Run the code in the specified order

By the way, to properly understand kernel synchronization, you need to know two concepts:
Critical Area
Race condition

This section introduces the main concepts of configuring kernel synchronization. First, find out what the critical areas and race conditions are, then find out why the race conditions occur and analyze the Linux kernel patches associated with the race conditions.

Critical Areas and Race Conditions

A critical section is a block of code that can cause concurrent access problems if two or more processes run simultaneously. That is, the beginning and end of a specific block of code that only one process should execute.

If process A is executing some code segment, assume process B executes the same block of code. This is a situation where process A and process B are simultaneously accessing the critical area. A situation in which two or more processes simultaneously perform some code operation in a critical area is called race or race condition. This is sometimes called a race condition.

Many older operating system books use toilets as an example to help you understand critical areas and race conditions. In this book, we will use the toilet as an example.

Figure 9.1 shows a situation where people A and B enter a public toilet where only one person can enter. Locking the lock on the only bathroom door and seeing business. The numbers in the illustration indicate the actions A and B do before entering the bathroom.

Consider this situation in parallel with the Linux kernel's synchronization process.

First, we look at the bathroom doors and check if they are locked before entering the bathroom to see if someone is already inside. If no one is in the toilet, enter the toilet and lock the toilet door.


Just as there are many types of toilet locks, the Linux kernel has different methods of locking depending on spinlock and mutex techniques.


Second, you can only see one person in the bathroom. Two people can not enter the bathroom at the same time. The only situation where one person sees business is called the critical area of ​​the operating system.

Third, if someone in the bathroom is watching you, wait outside. But the way you wait in the bathroom varies from person to person. A person in a hurry doesn't do anything else and keeps waiting with his feet rolling outside. Some wait leisurely while looking at the magazine outside the bathroom.


In the case of spinlocks in the Linux kernel, if someone has already acquired a spinlock, it will continue to wait without doing anything outside the bathroom. This is called Busy-wait. Instead, the mutex adds itself to the queue and falls asleep outside the bathroom.


Sometimes a process needs to execute a specific section of code, just as only one person needs to go into the toilet to see it. This is called the critical area. The interval indicated by the arrow in Figure 9.1 is the critical area, which can be explained as follows:

Only one person in the bathroom should enter and watch the business. Only one process should run a particular code segment.

You can clean up the critical area as above. If two people (processes) approach a critical zone at the same time, unexpected problems can occur.


This time, let's learn about Race. In Figure 9.1, A did not properly lock the lock on the toilet door before seeing business. After B, the toilet door is not locked, so there is no person inside the toilet. Eventually, two people enter the bathroom together. A race is a situation where two processes are approaching a critical area at the same time as two people enter the bathroom together. This condition is called a race condition.

"There are two people in the bathroom at the same time."
"Two processes run in a specific code segment."

Then in this condition, the race condition can be said as follows.

"A did not lock the toilet door properly."
"The process didn't lock on a particular code segment."

How can you prevent two people from entering the bathroom? Lock the toilet locks well. The same is true in the kernel. Only one process should run in the critical section to lock properly to avoid race conditions.
 

[IT] 실력있는 개발자가 인정 받기 어려운 이유 - 사내정치 임베디드 에세이

   "열심히 하면 성공할 수 있어."
   "개발자는 무엇보다 실력을 키우는게 중요하다."

여러분 이런 이야기 많이 들어보셨나요? 정말 그럴까요? 개발자들이 실력만 있으면 높은 연봉과 인센티브를 받으며 승승장구할 수 있을까요?

제가 본 바에 따르면 현실은 많이 다릅니다.

   "실력있는 개발자가 대우를 받지 못해 울분을 토하는 상황" 
   "폐암 말기 환자가피를 토하듯 갱스터 랩을 하는 장면"

즉 실력있는 개발자가 대우받지 못하는 이유는 무엇일까요?

실력있는 개발자가 인정받고 대우 받으려면 다음 3가지 조건을 충족해야 하기 때문입니다.
- 상식이 통하는 조직 책임자
- 건전한 생각을 가진 동료들
- 어느 정도 도전적인 과제가 있는 프로젝트

상식이 통하는 조직 책임자

위에서 언급한 3가지 조건 중 먼저 조직 책임자에 대해 이야기해 볼까요. 여기 실력 있는 개발자가 있습니다.

물론 주위 동료들도 인정합니다.
하지만 조직 책임자가 그 개발자의 실력을 인정하지 않으면 끝입니다. 제 말이 말이 안되는 헛소리 같다고요. 사실 제가 하는 말이 틀렸으면 좋겠습니다

하지만 냉혹한 현실은 이렇습니다

   "개발자의 조직 책임자가 개발자를 인정하지 않으면 그걸로 끝입니다."

그러면 주위 동료들도 그 개발자를 인정하는데 조직 책임자가능력을 인정하지 않을 수 있을까요?

네 그럴 수도 있습니다. 전 그런 경우를 자주 봤습니다.

건전한 생각을 가진 동료들

두 번째 조건은 건전한 생각이 있는 동료입니다 실력있는 개발자가 있으면 주위 동료는 그의 실력을 인정해 줄 것이라 생각합니다 하지만 현실은 약간 다릅니다.

심지어 자신이 겪고 있는 문제를 고수 개발자가 도와 줘도 그의 능력을 폄하하는 말을 하기도 합니다.

실력있는 개발자가 어려운 문제를 도와주고 분석을 해 줬습니다 결국 어려운 문제가 해결을 했는데 B개발자는 A 개발자에게 많은 도움을 받았습니다. 근데에서 조금은 충격적인 이야기를 들었습니다. B 개발자가했던 말을 들었습니다.

   "A란 놈, 괜히 나서서 잘난 척을 하고 나서기 좋아하네!"
   
도움을 받은 B란 개발자가 A의 개발자의 능력을 깎아 내리는 것이었습니다.

사실 전 좀 충격을 받았습니다. 어찌 인간이 이런 생각을 할 수가 있을까 사람 자체가 싫어졌습니다.

이렇게 제가 예를 들어 본 바와 같이 아무리 실력이 아무리 좋아도 비상식적인 주위 동료가 있다면 그 실력을 인정 받기 어렵습니다.

어느 정도 도전적인 과제가 있는 프로젝트

이번에 3가지 세 번째 예시를 들어 볼까요? 어느 정도 도전적인 프로젝트입니다.

프로젝트의 종료와 범위는 한 문장으로 정의 내리기 매우 어렵습니다. 

   "하지만 행정적인 혹은 단순한 시간을 투입하면 되는 부류의 개발을 맡고 있습니다. "
   
이런 조건에서 실력 있는 개발자는 그 능력을 발휘하기 어렵습니다.

투수가 시속 155킬로 직구를 겸비하고 있다고 보겠습니다. 
프로야구 155킬로의 구속만 있으면 10승은 무난할 것입니다. 

그런데 이 투수가 사회인 야구 해서 공을 던지면 어떻게 될까요? 문제는 사회인 야구에서 시속 120킬로 던져도 사회인 야구 리그를 지배하는 투수가 될 수 있습니다. 물론 155킬로의 구속을 던지는 투수도 사회인 야구 리그에서 그 리그를 지배하 것입니다. 하지만 그 구속만큼 인정받기는 어려울 것입니다.

비유가 적절할지 모르겠습니다.
이밖에도 다양한 조건이 머릿속에 떠오르지만 그래도 일반적인 경우는 위 세가지인 것 같습니다.

무엇보다도 개발자는 사내정치의 굉장히 취약하다고 봅니다. 세 가지 조건 중 하나만 충족되지 않아도 개발자는 그 능력을 인정받기 어렵기 때문입니다.

제 이야기를 읽으면 어떤 분들은 불쾌감을 느낄 것입니다. 

   "왜 문제만 얘기하고 그 해결책은 이야기 하지 않느냐?"
   "세상을 참 부정적으로 본다."

물론 제 생각이 틀릴 수도 있습니다. 오히려 재생 각이 틀렸으면 좋겠습니다. 하지만 어쩌겠습니까? 현실이 그런 걸
위 세가지 조건을 충족하지 않는데 그 실력을 인정받는 개발자가 있었으면 좋겠습니다.

다시 한번 반복하지만 듣기 개발자는 사내정치에 취약한 것은 분명한 것 같습니다.

이제 는사내정치를 깨 버릴 수 있는 방법을 이야기해 보려고 합니다 위 3가지 조건을 전부 만족하지 않아도 실력있는 개발자로 인정받는 방법입니다.

   "어떡하면 그럴 수 있을까?"

의문이 생기지 않나요 그렇다면 제 얘기를 좀 더 들어보실까요?

[리눅스커널] 질문: user application은 내가 open한 파일이 어느 파일시스템에서 관리하는지 어떻게 알까? 13. Virtual Filesystem

제 블로그에 감사하게도 '어떤 분이' 댓글로 질문을 주셨습니다. 
'각 파일시스템마다 ioctol()을 다르게 구현할 텐데, user application은 내가 open한 파일이 어느 파일시스템에 있는 파일인지 모르지 않습니까?'

답신이 길어져 새롭게 포스팅을 하고자 합니다.
 
ioctl() 함수를 사용하기 전에 open() 이나 create() 함수를 써서 fd 즉, 파일 디스크립터를 가져오게 됩니다.
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <fcntl.h>

int main()
{
int fd;
char buf[256];

fd = open("/dev/hdd_info", O_RDWR);
if (fd < 0)
{
printf("Device open error.\n");
return -1;
}

ioctl(fd, 0, buf);

printf("buf : %s\n", buf);

close(fd);

return 0;

다음 코드에서 fd를 얻어 온 후 fd를 첫 번째 인자로 ioctl() 함수를 호출하게 됩니다.
fd = open("/dev/hdd_info", O_RDWR);
...
ioctl(fd, 0, buf);

유저 공간에서 open() 시스템 콜을 호출하면 아래 함수가 호출됩니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/fs/open.c]
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;

return do_sys_open(AT_FDCWD, filename, flags, mode);
}

이후 아래 path로 VFS 함수들이 호출됩니다.
   * do_sys_open
   * do_filp_open
   * path_openat
   * link_path_walk
   * do_last 
   * vfs_open
   * do_dentry_open

이 때 다음 동작을 수행합니다. 

   * open하려는 파일이 위치한 디렉토리가 어떤 파일 시스템으로 관리하는지 체크
   
관련 핵심 코드는 다음과 같습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/fs/open.c]
static int do_dentry_open(struct file *f,
  struct inode *inode,
  int (*open)(struct inode *, struct file *))
{
static const struct file_operations empty_fops = {};
int error;

path_get(&f->f_path);
f->f_inode = inode;
f->f_mapping = inode->i_mapping;   

정리하면 파일을 오픈하는 open 시스템을 호출할 때 커널에서 해당 파일이 어떤 파일시스템에서 관리하는지 알게 됩니다.

[Linux-Kernel] LKML: cw1200: Fix a signedness bug in cw1200_load_firmware() Linux Kernel Contribution

[Linux-Kernel] LKML: cw1200: Fix a signedness bug in cw1200_load_firmware()

출처
https://git.kernel.org/pub/scm/linux/kernel/git/kvalo/wireless-drivers-next.git/commit/

흥미로운 패치 코드다.

> cw1200: Fix a signedness bug in cw1200_load_firmware() 
> The "priv->hw_type" is an enum and in this context GCC will treat it
> as an unsigned int so the error handling will never trigger.
> Fixes: a910e4a94f69 ("cw1200: add driver for the ST-E CW1100 & CW1200 WLAN chipsets")
> Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
> Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
> Diffstat
> -rw-r--r-- drivers/net/wireless/st/cw1200/fwio.c 6
> 1 files changed, 3 insertions, 3 deletions
> diff --git a/drivers/net/wireless/st/cw1200/fwio.c b/drivers/net/wireless/st/cw1200/fwio.c
> index 6574e78..2a03dc5 100644
> --- a/drivers/net/wireless/st/cw1200/fwio.c
> +++ b/drivers/net/wireless/st/cw1200/fwio.c
> @@ -320,12 +320,12 @@ int cw1200_load_firmware(struct cw1200_common *priv)
goto out;
}
>  
> - priv->hw_type = cw1200_get_hw_type(val32, &major_revision);
> - if (priv->hw_type < 0) {
> + ret = cw1200_get_hw_type(val32, &major_revision);
> + if (ret < 0) {
pr_err("Can't deduce hardware type.\n");
> - ret = -ENOTSUPP;
goto out;
}

cw1200_get_hw_type() 함수를 보면 다음 값을 반환한다.
HIF_8601_VERSATILE
HIF_8601_SILICON
-1

static int cw1200_get_hw_type(u32 config_reg_val, int *major_revision)
{
int hw_type = -1;
u32 silicon_type = (config_reg_val >> 24) & 0x7;
u32 silicon_vers = (config_reg_val >> 31) & 0x1;

switch (silicon_type) {
case 0x00:
*major_revision = 1;
hw_type = HIF_9000_SILICON_VERSATILE;
break;
case 0x01:
case 0x02: /* CW1x00 */
case 0x04: /* CW1x60 */
*major_revision = silicon_type;
if (silicon_vers)
hw_type = HIF_8601_VERSATILE;
else
hw_type = HIF_8601_SILICON;
break;
default:
break;
}

return hw_type;
}

-1를 반환할 때 예외 처리 코드가 없다.

> + priv->hw_type = ret;
>  
/* Set DPLL Reg value, and read back to confirm writes work */
ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,> 
>                                                                 > 
>                                                                 > 

리눅스 커널 기여(Contribution) II (1/3) -패치 작성 하기 Linux Kernel Contribution



패치 코드 작성 전 커널 코드 분석하기

이 포스팅을 올리는 주인공인 'Austin'은 리눅스 커널의 vmalloc 서브 시스템 내 __vmalloc_area_node() 함수 코드를 분석했습니다.

자, 그럼 소스 코드를 같이 볼까요? 특히 07~13번째 코드를 눈여겨봅시다. 
01 static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
02                 pgprot_t prot, int node)
{
03    struct page **pages;
04    unsigned int nr_pages, array_size, i;
...
05    area->nr_pages = nr_pages;
06    /* Please note that the recursion is strictly bounded. */
07    if (array_size > PAGE_SIZE) {
08        pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
09               PAGE_KERNEL, node, area->caller);
10    } else {
11        pages = kmalloc_node(array_size, nested_gfp, node);
12    }
13    area->pages = pages;
14    if (!area->pages) {
15        remove_vm_area(area->addr);
16        kfree(area);
17        return NULL;
18   }
19    for (i = 0; i < area->nr_pages; i++) {
20        struct page *page;
...
21    }

소스 코드를 보고 다음과 같은 반문을 할 수 있습니다.

   *뭐가 문제지?

저도 커널 소스 코드를 분석할 때 이해하는데 급급할 때가 많습니다. 
하지만 커널 소스 코드를 조금 '비판적'으로 보면 가끔 논리적인 오류가 발견될 때가 있습니다.

자, 그러면 위 소스 코드의 문제점에 대해서 이야기를 해보려 합니다.
07    if (array_size > PAGE_SIZE) {
08        pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
09               PAGE_KERNEL, node, area->caller);
10    } else {
11        pages = kmalloc_node(array_size, nested_gfp, node);
12    }

08번째와 11번째 줄 코드는 __vmalloc_node() 함수와 kmalloc_node() 함수를 호출해 페이지를 할당 받습니다. 그런데 메모리 부족으로 페이지가 부족할 때는 이 함수들은 NULL을 반환합니다. 

이번에 13~17번째 줄 코드를 보겠습니다.
13    area->pages = pages;
14    if (!area->pages) {
15        remove_vm_area(area->addr);
16        kfree(area);
17        return NULL;
18   }

다음 13번째 줄 코드를 보겠습니다.
페이지를 할당 받은 pages를 'area->pages' 필드에 저장합니다. page가 NULL일지도 모르는 상황인데도 'area->pages' 필드에 page를 저장합니다.

이번에는 14번째 줄 코드입니다.  'area->pages' 가 NULL인지 체크하는 동작입니다. 'area->pages' 가 NULL이면 '!area->pages' 구문은 TRUE이니 15번째 줄 코드를 실행해 'area->addr'와  'area' 주소를 해제(Free) 합니다. 

그래서 다음과 같은 아이디어가 생겼습니다.

   * 08번째와 11번째 줄 코드와 같이 __vmalloc_node() 함수와 kmalloc_node() 함수를 호출해 페이지를 할당 
    받은 다음 바로 page 포인터에 대한 NULL 체크를 하자.

  * 이 14~18번째 줄 예외 처리 코드 다음에 'area->pages = pages' 코드를 실행하게 하자.  

위에서 설명한 기준에 따라 소스 코드를 수정했습니다. 12~20번째 줄 코드가 수정한 코드입니다.
01 static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
02                 pgprot_t prot, int node)
{
03    struct page **pages;
04    unsigned int nr_pages, array_size, i;
...
05    /* Please note that the recursion is strictly bounded. */
06    if (array_size > PAGE_SIZE) {
07        pages = __vmalloc_node(array_size, 1, nested_gfp|highmem_mask,
08                PAGE_KERNEL, node, area->caller);
09    } else {
10        pages = kmalloc_node(array_size, nested_gfp, node);
11   }
12
13    if (!pages) {
14        remove_vm_area(area->addr);
15        kfree(area);
16        return NULL;
17    }
18
19    area->pages = pages;
20
21    for (i = 0; i < area->nr_pages; i++) {
22       struct page *page;
...
23   }    

이번에는 패치 형태로 소스 수정 내역을 확인해보겠습니다.   
root@raspberrypi:/home/pi/kernel_src/linux-next# git diff mm/vmalloc.c
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index e66e7ff..0471c78 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -2416,13 +2416,15 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
        } else {
                pages = kmalloc_node(array_size, nested_gfp, node);
        }
-       area->pages = pages;
-       if (!area->pages) {
+
+       if (!pages) {
                remove_vm_area(area->addr);
                kfree(area);
                return NULL;
        }

+       area->pages = pages;
+
        for (i = 0; i < area->nr_pages; i++) {
                struct page *page;

커밋과 커밋 메시지 작성하기

코드를 수정했으니 이제 패치를 커밋으로 생성해볼까요?

‘git add 파일_이름’ 명령어를 입력해 패치를 인덱스에 등록 합니다.   
root@raspberrypi:/home/pi/kernel_src/linux-next# git add mm/vmalloc.c 

다음 명령어를 입력해 커밋 메시지와 함께 커밋 인덱스를 추가합니다. 
root@raspberrypi:/home/pi/kernel_src/linux-next# git commit -m "mm/vmalloc.c: move 'area->pages' after if statement"
 [master 09c14a5] mm/vmalloc.c: move 'area->pages' after if statement
 1 file changed, 4 insertions(+), 2 deletions(-)

위 명령어에서 “커밋 메시지”는 다음과 같습니다.
"mm/vmalloc.c: move 'area->pages' after if statement"

이번에는 다음 명령어를 입력해 커밋 메시지에 ‘Signed-off-by: 이메일 주소’와 추가 커밋 메시지를 추가합니다. 
root@raspberrypi:/home/pi/kernel_src/linux-next# git commit --amend -s 

mm/vmalloc.c: move 'area->pages' after if statement

Signed-off-by: Austin Kim <austindh.kim@gmail.com>

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
...
#
# Changes to be committed:
#   modified:   mm/vmalloc.c
#

'Signed-off-by'는 오픈 소스 라이센스 정책을 이해했으며 정책을 따르겠다는 의도로 추가하는 '도장'입니다. 만약 'Signed-off-by' 없이 패치 코드를 Maintainer에게 전달하면 'Signed-off-by'와 함께 다시 패치를 보내란 답장을 받을 확률이 높습니다.

이어서 'mm/vmalloc.c: move 'area->pages' after if statement' 제목 아랫 부분에 패치를 설명하는 메시지를 추가합니다.

01 mm/vmalloc.c: move 'area->pages' after if statement
02 
03 If !area->pages statement is true where memory allocation fails, area is
04 freed.
05 
06 In this case 'area->pages = pages' should not executed.  So move
07 'area->pages = pages' after if statement.
08
09 Signed-off-by: Austin Kim <austindh.kim@gmail.com>
10
 
커밋 메시지를 작성할 때는 패치의 내용을 명확하게 설명할 수 있는 문장을 써야 합니다. 여기서 주의해야 할 점은 패치의 내용을 너무 과장해 설명하면 안됩니다. 패치를 설명할 때 만약 ‘Fix Bug…", “Use-after-Free Fix” 등등의 표현을 쓰면 패치를 리뷰할 때 Maintainer나 다른 커널 개발자로부터 다음과 같은 질문을 받을 가능성이 높습니다.

   * 테스트 케이스는 무엇이냐?
   * 문제가 발생할 때 커널 로그를 알려줘라.

패치 리뷰를 할 때 이런 질문에 일일이 대답을 해줘야 하며 제대로 답변을 못하면 패치는 거절될 가능성이 높습니다.

이제 다음 명령어로 패치를 하나 생성해보겠습니다.
root@raspberrypi:/home/pi/kernel_src/linux-next# git format-patch -1
0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch

‘git format-patch -1’는 최신 커밋 기준으로 1개 패치를 생성시키는 명령어입니다. 결과 다음 파일이 생성됐습니다. 
0001-mm-vmalloc.c-move-area-pages-after-if-statement.patch

이번에 위 파일을 열어 보겠습니다.
  1 From a7c761e9946132891664b2817984bf2466552130 Mon Sep 17 00:00:00 2001
  2 From: Austin Kim <austindh.kim@gmail.com>
  3 Date: Fri, 30 Aug 2019 12:57:16 2019 +0900 
  4 Subject: [PATCH] mm/vmalloc.c: move 'area->pages' after if statement
  5
  6 If !area->pages statement is true where memory allocation fails, area is
  7 freed.
  8
  9 In this case 'area->pages = pages' should not executed.  So move
 10 'area->pages = pages' after if statement.
 11
 12 Signed-off-by: Austin Kim <austindh.kim@gmail.com>
 13 ---
 14  mm/vmalloc.c | 6 ++++--
 15  1 file changed, 4 insertions(+), 2 deletions(-)
 16
 17 diff --git a/mm/vmalloc.c b/mm/vmalloc.c
 18 index e66e7ff..0471c78 100644
 19 --- a/mm/vmalloc.c
 20 +++ b/mm/vmalloc.c
 21 @@ -2416,13 +2416,15 @@ static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
 22     } else {
 23         pages = kmalloc_node(array_size, nested_gfp, node);
 24     }
 25 -   area->pages = pages;
 26 -   if (!area->pages) {
 27 +
 28 +   if (!pages) {
 29         remove_vm_area(area->addr);
 30         kfree(area);
 31         return NULL;
 32     }
 33
 34 +   area->pages = pages;
 35 +
 36     for (i = 0; i < area->nr_pages; i++) {
 37         struct page *page;
 38
 39 --
 40 2.6.2
 41

[임베디드] 리눅스 개발자면 반드시 오픈 소스 프로젝트에 참여해야 하는 이유 임베디드 에세이

이번 시간에 임베디드 리눅스 개발자들이 꼭 오픈 소스 프로젝트에 참여해야 하는 이유에 대해 이야기해보려고 합니다.

   * 회사 일도 바뻐 죽겠는데 개인 시간을 할애해 오픈 소스 프로젝트에 참여할 필요는 있나?

저는 반드시 참여해야 한다고 생각합니다. 그 이유는 생각보다 많은 것을 얻을 수 있기 때문입니다.

GIT 

리눅스 오픈 소스 프로젝트를 참여하면서 자연스럽게 GIT을 익히게 됩니다.
GIT는 리눅스 진영에서 소개된 소프트웨어 형상 관리 프로그램입니다. 그런데 지금은 다른 소프트웨어 개발 분야에서도 GIT를 많이 쓰고 있습니다. 

어떤 소프트웨어 회사이건 반드시 소프트웨어 버전 관리를 하기 마련인데, 
개발자들은 이를 잘 다뤄야지 용이하게 개발할 수 있습니다.

GIT의 기본 기능 명령어는 다음과 같습니다.

git add source_file_to_update.c 
git commit -m "commit message"
git commit --amend
 
특히 신입 개발자들이 어려워하는 게 git merge, git conflict를 잡는 건데
이 부분도 잘 익혀야 효율적으로 개발할 수 있습니다. 

코드 리뷰 

정상급 리눅스 개발자로부터 상세한 코드 리뷰를 받을 수 있습니다.
코드 리뷰 내용은 다음과 같습니다.

   * 코딩 룰 
   * 주석문의 내용 
   * 코드의 Side-Effect 
   
최근에는 Youtube를 통해 공부하는 시대입니다.
이에 발 맞춰 전 세계 어느 개발자와도 오픈 소스 프로젝트로 서로 소통할 수 있습니다.

Written-by: Austin Kim, 10/02/2019   


[리눅스커널] 워크큐: 워커 스레드 핸들 worker_thread() 함수 분석하기 (2/2) 8. Workqueue

2단계: “전처리” 단계

워커 스레드의 “전처리” 단계 코드를 분석할 차례입니다. 다음 24번째 줄 코드를 보겠습니다. 
24 recheck:
25 /* no more worker necessary? */
26 if (!need_more_worker(pool))
27 goto sleep;

need_more_worker() 함수는 다음 동작을 수행합니다.
struct worker_pool 구조체 worklist 필드에 접근해 이미 큐잉한 워크가 있는지 체크
struct worker_pool 구조체 nr_running 필드에 저장된 실행 중인 워커 스레드 갯수를 점검

워크를 워크큐에 큐잉한 적이 없다면 워커 스레드를 실행할 필요가 없습니다. 따라서 goto sleep; 구문을 실행해 다시 휴면에 진입합니다.


스케줄링으로 워커 스레드가 실행했을때 예외 처리 코드입니다.  


다음 35번째 줄 코드를 분석하겠습니다.
35 worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);

worker->flags 필드에서 (WORKER_PREP | WORKER_REBOUND) 연산 결과를 플래그 Clear시킵니다.  

    이제 워커 상태가 WORKER_PREP와 WORKER_REBOUND가 아니라는 의미입니다. 

WORKER_PREP 플래그는 워커 스레드 처리 흐름에서 전처리 단계를 의미합니다.

여기까지 워커 스레드 예외처리나 상태를 변경하는 루틴입니다. 

3단계: “실행” 단계
이번에는 worker_thread() 함수에서 가장 중요한 37~53번째 줄 코드를 분석할 차례입니다. 
37 do {
38 struct work_struct *work =
39 list_first_entry(&pool->worklist,
40  struct work_struct, entry);
41
42 pool->watchdog_ts = jiffies;
43
44 if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
45 /* optimization path, not strictly necessary */
46 process_one_work(worker, work);
47 if (unlikely(!list_empty(&worker->scheduled)))
48 process_scheduled_works(worker);
49 } else {
50 move_linked_works(work, &worker->scheduled, NULL);
51 process_scheduled_works(worker);
52 }
53 } while (keep_working(pool));


코드 분석에 들어가기 앞서 do~while 문이 실행할 조건을 결정하는 keep_working() 함수를 알아볼 필요가 있습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 static bool keep_working(struct worker_pool *pool)
2 {
3 return !list_empty(&pool->worklist) &&
4 atomic_read(&pool->nr_running) <= 1;
5}

3번째 줄 코드는 워커풀에 큐잉된 워크가 있는지 점검합니다. 

3~4번째 줄 코드와 같이 워커 풀에 큐잉된 워크가 있는 지와 실행 중인 워커 스레드 갯수를 AND 비트 연산한 결과를 반환합니다. keep_working() 함수가 포함된 do~while 문은 워커 풀에 큐잉된 워크를 모두 처리할 때까지 do~while 루프 내 코드를 실행한다는 의미입니다.


다시 코드 분석을 시작합니다. 먼저 38번째 줄 코드를 봅시다. 
38 struct work_struct *work =
39 list_first_entry(&pool->worklist,
40  struct work_struct, entry);

sturct worker_pool 구조체 worklist 필드 주소에 접근해 struct work_struct 구조체 주소를 work에 저장합니다. 

&pool->worklist 필드로 struct work_list 구조체 entry 필드 주소를 읽는 동작입니다.

38~40번째 줄 코드 실행 원리를 단계별로 조금 더 짚어 보겠습니다
[1] struct work_struct.entry 필드 오프셋을 계산합니다.
[2] &pool->worklist 주소에서 struct work_struct 구조체 entry 필드 오프셋을 빼서 struct work_struct *work에 저장합니다.

[1] 단계 코드 동작을 확인하겠습니다. 
list_first_entry() 매크로 함수 두 번째와 세 번째 인자는 각각 struct work_struct 구조체 필드인 entry입니다.

이는 struct work_struct 구조체에서 entry 필드가 위치한 오프셋 주소를 의미합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/workqueue.h]
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
}

오프셋 계산에 대한 이해를 돕기 위해 0xb62d3604 주소에 있는 struct work_struct 필드를 보겠습니다. 
1  (struct work_struct *) [-] (struct work_struct*)0xb62d3604 = 0xB62D3604 -> (
2    (atomic_long_t) [D:0xB62D3604] data = ((int) [D:0xB62D3604] counter = 0),
3    (struct list_head) [D:0xB62D3608] entry = ((struct list_head *)  
4    (work_func_t) [D:0xB62D3610] func = 0x804FDCA8 = flush_to_ldisc)

3번째 줄 디버깅 정보를 보면 entry는 0xB62D3608 주소에 있습니다. struct work_struct 구조체 주소가 0xB62D3604 이니 struct work_struct 구조체에서 entry 필드가 위치한 오프셋 주소는 0x4(0xB62D3608 - 0xB62D3604)입니다.

[2]번 실행 단계에서  &pool->worklist 주소에서 0x4를 빼서 struct work_struct *work 이란 지역 변수에 저장합니다.

이 동작은 다음 그림으로 설명할 수 있습니다.
 
[그림 7.14] 큐잉한 워크에 접근하는 동작 

위 그림 가장 오른쪽 아랫 부분을 눈으로 따라가 볼까요? worklist에서 struct work_struct 구조체 박스로 향하는 화살표를 눈여겨봅시다. 워크를 워크큐에 큐잉하면 워커풀인 struct worker_pool 구조체 worklist에 워크의 struct work_struct 구조체 entry 주소를 등록합니다.

다음 코드는 위 그림 오른쪽 하단에 entry에서 (struct work_struct) 으로 향하는 화살표와 같습니다.
38 struct work_struct *work =
39 list_first_entry(&pool->worklist,
40  struct work_struct, entry);

struct work_struct 구조체 필드 entry 오프셋 주소를 알고 있으니 struct work_struct 구조체에서 entry 필드가 위치한 오프셋을 빼서 struct work_struct 주소를 계산하는 것입니다.

다음 42번째 줄 코드를 보겠습니다.
42 pool->watchdog_ts = jiffies;

pool->watchdog_ts 필드에 현재 시각 정보를 표현하는 jiffies를 저장합니다. 이 값으로 워커 스레드 와치독 정보를 갱신합니다. 


참고로 임베디드 시스템에서 와치독(Watchdog)은 어떤 소프트웨어가 정해진 시간 내에 실행하는지를 확인려는 목적의 타이머입니다. 와치독(Watchdog)은 임베디드 리눅스 개발 도중 아주 많이 쓰이는 용어이니 잘 알아 둡시다.


이번에 44번째 줄 코드를 분석합니다.
44 if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
45 /* optimization path, not strictly necessary */
46 process_one_work(worker, work);

38~40번째 줄 코드에서 얻어온 struct work_struct 구조체 주소는 work이란 포인터형 지역 변수에 저장돼 있습니다. 이 변수로 struct work_struct 구조체 data 필드에 접근해 WORK_STRUCT_LINKED 플래그와 AND 비트 연산을 수행합니다. struct work_struct 구조체 data 필드가 WORK_STRUCT_LINKED이면 배리어 워크이니 50번째 줄 코드인 else 문을 실행합니다. 

일반적으로 워크를 실행할 때는 46번째 코드를 실행합니다. 나머지 else문은 배리어 워크를 처리하는 동작입니다. 

    46번째 줄에서는 process_one_work() 함수를 호출해 워크를 실행합니다. 

워커 스레드의 핵심 동작입니다. 이렇게 do~while 문에서 워커 풀에 큐잉된 워크를 모두 처리한 후 실행하는 55번째 줄 코드를 보겠습니다.
55 worker_set_flags(worker, WORKER_PREP);

struct worker 구조체 flags 필드에 WORKER_PREP 플래그를 저장합니다. 워커 스레드를 전처리 상태로 변경하는 것입니다.
  
4단계: “슬립” 단계
다음 56번째 줄 코드를 보겠습니다. 워커 스레드의 “슬립” 단계입니다.
56 sleep:
57 worker_enter_idle(worker);
58 __set_current_state(TASK_IDLE);
59 spin_unlock_irq(&pool->lock);
60 schedule();
61 goto woke_up;
62 }

56번째 줄 코드는 sleep이란 레이블입니다. 
57~59번째 줄 코드에서 워커 스레드가 휴면에 들어간 준비를 하고 60번째 줄 코드를 실행해 휴면에 들어갑니다. 

각 라인별로 코드를 분석하겠습니다.

57번째 코드를 보겠습니다.
57 worker_enter_idle(worker);

worker_enter_idle() 함수를 호출해서 워커 스레드 필드인 struct worker 구조체 flags를 WORKER_IDLE로 설정합니다. 워커 스레드가 워크를 처리하고 난 후 변경하는 상태입니다.

다음 58번째 줄 코드입니다.
58 __set_current_state(TASK_IDLE);

__set_current_state() 함수를 호출해서 워커 스레드 태스크 디스크립터 state 필드를 TASK_IDLE 플래그로 바꿉니다.  

    태스크 디스크립터 state 필드를 TASK_IDLE로 바꿔서 커널 스케줄러가 다른 조건으로 
   워커 스레드를 처리하게 합니다.

59번째 줄 코드에서 스핀락을 풀고 60번째 줄 코드와 같이 schedule() 함수 호출로 휴면에 들어갑니다.
59 spin_unlock_irq(&pool->lock);
60 schedule();
61 goto woke_up;

이렇게 60번째 줄 코드를 실행하면 워커 스레드는 휴면에 진입합니다. 그러면 워커 스레드가 다시 깨어나 실행을 시작할 때 어느 코드부터 실행할까요? 61번째 줄 코드를 실행해 woke_up 레이블로 이동합니다.


워커 스레드는 어떻게 깨울까요? 워크를 워크큐에 큐잉하고 난 후 wake_up_woker() 함수를 호출해 워커 스레드를 깨웁니다.


여기까지 워커 스레드 실행 흐름을 상세히 알아봤습니다. 
코드 분석으로 워커 스레드 핸들인 worker_thread() 함수에서 다음과 같은 동작을 한다는 사실을 알게 됐습니다. 
워커 스레드 종료
워크 실행 조건 점검
워크 실행

다음 절에는 그 동안 분석한 코드가 라즈베리파이에서는 어떻게 동작하는지 ftrace 로그로 확인하는 실습 시간을 갖겠습니다. 

[리눅스커널] ssize_t와 size_t의 실체 Linux Kernel - Core Analysis

ssize_t와 size_t 타입의 실체를 확인해보자.

아래 코드를 전처리 코드로 확인해볼까?
static ssize_t default_read_file(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
  return 0;
}

size_t 타입의 정체

먼저 size_t의 정체를 확인해보자.

 280 # 55 "/root/src/kernel_src/linux/include/linux/types.h"
  281 typedef __kernel_size_t size_t;

size_t 는 __kernel_size_t로 캐스팅 된다.  

 201 # 68 "/root/src/kernel_src/linux/include/uapi/asm-generic/posix_types.h"
  202 typedef unsigned int __kernel_size_t;

__kernel_size_t 타입은 'unsigned int'로 타입으로 캐스팅된다. 

정리하자.
size_t -> unsigned int

ssize_t 타입의 정체

이번에는 ssize_t 타입을 확인하자.
 280 # 55 "/root/src/rachman_kernel_src/linux/include/linux/types.h"
...
  282
  283
  284
  285
  286 typedef __kernel_ssize_t ssize_t;

ssize_t은 __kernel_ssize_t 타입으로 캐스팅된다.  

 201 # 68 "/root/src/rachman_kernel_src/linux/include/uapi/asm-generic/posix_types.h"
  202 typedef unsigned int __kernel_size_t;
  203 typedef int __kernel_ssize_t;

__kernel_ssize_t은  int 타입으로 캐스팅된다.

정리하면;
 ssize_t -> int

마무리 

이제 default_read_file() 함수를 볼 때, 
static ssize_t default_read_file(struct file *file, char *buf,
     size_t count, loff_t *ppos)
{
 return 0;
}

다음과 같이 해석해보자.
static int default_read_file(struct file *file, char *buf,
     unsigned int count, loff_t *ppos)
{
 return 0;
}

[Crash-Utility] Radix Tree 디버깅: 'tree -t radix -N (struct radix_tree_node *) 구조체 주소' [Debugging] Tips

이번 시간에는 크래시 유틸리티로 라덱스 트리를 디버깅하는 방법을 소개합니다.

라딕스 트리(Radix Tree)를 보기 위한 명령어


크래시 유틸리티로 라덱스 트리 노드를 보기 위한 명령어 포멧은 다음과 같습니다.

tree -t radix -N (struct radix_tree_node *) 구조체 주소


예제 명령어 및 결과 ( struct radix_tree_node 구조체 주소가 0xFFFFFFFF3A806E79 인 경우)

crash> tree -t radix -N 0xFFFFFFFF3A806E79
ffffffff3f53c180
ffffffff3f53c4c0
ffffffff3f555180
ffffffff3f5554c0
ffffffff3f56e180
ffffffff3f56e4c0
ffffffff3f587180
ffffffff3f5874c0
ffffffff3f5a0180
ffffffff3f5a04c0
ffffffff3f5b9180
ffffffff3f5b94c0
ffffffff3f5d2180
ffffffff3f5d24c0
ffffffff3f5eb180
ffffffff3f5eb4c0
fffffffe4683d480
ffffffff3657a480


Case Study: worker_pool_idr 전역변수로 라덱스 트리 디버깅해보기 


워커풀은 라덱스 트리로 구성돼 있습니다.

#define for_each_pool(pool, pi)                     \
    idr_for_each_entry(&worker_pool_idr, pool, pi)          \
        if (({ assert_rcu_or_pool_mutex(); false; })) { }   \
        else


worker_pool_idr 전역변수 타입은 struct idr worker_pool_idr입니다.

crash64> whatis worker_pool_idr
struct idr worker_pool_idr;
crash64>


struct idr 구조체 idr_rt 필드 타입은 struct radix_tree_root입니다.

crash64> struct idr
struct idr {
    struct radix_tree_root idr_rt;
    unsigned int idr_base;
    unsigned int idr_next;
}


worker_pool_idr.idr_rt 필드의 라덱스 트리 노드 주소는 0xffffffff3a806e79입니다.

crash64> p worker_pool_idr.idr_rt.rnode
$2 = (struct radix_tree_node *) 0xffffffff3a806e79


다음 명령어를 입력하면 라덱스 트리 모든 노드를 볼 수 있습니다.

crash64_kaslr> tree -t radix -N 0xFFFFFFFF3A806E79
ffffffff3f53c180
ffffffff3f53c4c0
ffffffff3f555180
ffffffff3f5554c0
ffffffff3f56e180
ffffffff3f56e4c0
ffffffff3f587180
ffffffff3f5874c0
ffffffff3f5a0180
ffffffff3f5a04c0
ffffffff3f5b9180
ffffffff3f5b94c0
ffffffff3f5d2180
ffffffff3f5d24c0
ffffffff3f5eb180
ffffffff3f5eb4c0
fffffffe4683d480
ffffffff3657a480


위  주소 리스트는 struct worker_pool 구조체이며 다음과 같이 확인할 수 있습니다.

1: ffffffff3f53c4c0 struct worker_pool 디버깅
[0]: watchdog_ts = 4295299595 // 워커 터치 시간
[1]: 해당 워커풀에 큐잉한 워크
[2]: 실행 중인 워커: 0xFFFFFFFEE11F5B80

  (struct worker_pool *) (struct worker_pool*)0xffffffff3f53c180 = 0xFFFFFFFF3F53C180 = end+0x68B4978180 -> (
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((atomic_t) val = ((int) counter = 0), (u8) locked = 0, (u8)
    (int) cpu = 0,
    (int) node = 0,
    (int) id = 0,
    (unsigned int) flags = 0,
    (long unsigned int) watchdog_ts = 4295299595, "//<<--[0]"
    (struct list_head) worklist = (
      (struct list_head *) next = 0xFFFFFF9689B2D098 = binder_deferred_work.entry -> (  "//<<--[1]"
        (struct list_head *) next = 0xFFFFFF9689A052C8 = delayed_fput_work.work.entry, "//<<--[1]"
        (struct list_head *) prev = 0xFFFFFFFF3F53C1A0 = end+0x68B49781A0),
      (struct list_head *) prev = 0xFFFFFF9689B65710 = nd_tbl.gc_work.work.entry), "//<<--[1]"
    (int) nr_workers = 3,
    (int) nr_idle = 2,
    (struct list_head) idle_list = ((struct list_head *) next = 0xFFFFFFFEE4BF0680 = end+0x685A02C680, (struct list_head *) prev = 0xFFFFFFFEE07B
    (struct timer_list) idle_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0xDEAD000000000200 
    (struct timer_list) mayday_timer = ((struct hlist_node) entry = ((struct hlist_node *) next = 0xDEAD000000000200 
    (struct hlist_head [64]) busy_hash = (
      [0] = ((struct hlist_node *) first = 0x0 = ),
...
      [45] = ((struct hlist_node *) first = 0x0 = ),
      [46] = ((struct hlist_node *) first = 0xFFFFFFFEE11F5B80 = end+0x6856631B80), "//<<--[2]"
      [47] = ((struct hlist_node *) first = 0x0 = ),
 ...
      [63] = ((struct hlist_node *) first = 0x0 = )),
    (struct worker *) manager = 0x0 = ,
    (struct list_head) workers = ((struct list_head *) next = 0xFFFFFFFEE07B9BC8 = end+0x6855BF5BC8, (struct list_head *) prev = 0xFFFFFFFEE11F5B
    (struct completion *) detach_completion = 0x0 = ,
    (struct ida) worker_ida = ((struct radix_tree_root) ida_rt = ((spinlock_t) xa_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lo
    (struct workqueue_attrs *) attrs = 0xFFFFFFFE46881C00 = end+0x67BBCBDC00,
    (struct hlist_node) hash_node = ((struct hlist_node *) next = 0x0 = , (struct hlist_node * *) pprev = 0x0 = ),
    (int) refcnt = 1,
    (atomic_t) nr_running = ((int) counter = 1),
    (struct callback_head) rcu = ((struct callback_head *) next = 0x0 = , (void (*)()) func = 0x0 = ))

리눅스 커널 디버깅 중 라딕스 트리를 만났을 때 이 명령어를 잘 활용합시다.

> Written-by: Austin Kim, 09/29/2019
> Posted-by: Austin Kim, 09/30/2019
>


[Linux-Kernel] LKML: qla2xxx: fix a potential NULL pointer dereference Linux Kernel Contribution

출처
https://patchwork.kernel.org/patch/11150763/
https://lkml.org/lkml/2019/9/18/796

//
// 흥미로운 패치다.
//

diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c
index 98e60a3..31714c9 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -3232,6 +3232,10 @@  static void qla2x00_iocb_work_fn(struct work_struct *work)
      req->req_q_in, req->req_q_out, rsp->rsp_q_in, rsp->rsp_q_out);
 
  ha->wq = alloc_workqueue("qla2xxx_wq", 0, 0);
+ if (unlikely(!ha->wq)) {
+ ret = -ENOMEM;
+ goto probe_failed;
+ }

//
// alloc_workqueue() 함수를 호출해 워크큐를 생성하지 못하면 에러를 반환한다.
// alloc_workqueue() 함수에서 NULL를 반환하는 조건이 있나?  
//
  if (ha->isp_ops->initialize_adapter(base_vha)) {
  ql_log(ql_log_fatal, base_vha, 0x00d6,

if (ret) {
kfree(rescuer);
return ret;
}

wq->rescuer = rescuer;
kthread_bind_mask(rescuer->task, cpu_possible_mask);
wake_up_process(rescuer->task);

return 0;
}

__printf(1, 4)
struct workqueue_struct *alloc_workqueue(const char *fmt,
 unsigned int flags,
 int max_active, ...)
{
size_t tbl_size = 0;
va_list args;
struct workqueue_struct *wq;
struct pool_workqueue *pwq;

if ((flags & WQ_UNBOUND) && max_active == 1)
flags |= __WQ_ORDERED;

/* see the comment above the definition of WQ_POWER_EFFICIENT */
if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
flags |= WQ_UNBOUND;

/* allocate wq and format name */
if (flags & WQ_UNBOUND)
tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);

wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
if (!wq)
return NULL; //<<--

//  메모리를 할당 받지 못하면 NULL을 반환한다.
if (flags & WQ_UNBOUND) {
wq->unbound_attrs = alloc_workqueue_attrs();
if (!wq->unbound_attrs)
goto err_free_wq;
}
...

if (alloc_and_link_pwqs(wq) < 0)
goto err_unreg_lockdep; //<<--
if (wq_online && init_rescuer(wq) < 0)
goto err_destroy; //<<--
if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
goto err_destroy;
// 그런데! 워크큐 생성 도중에 이런 예외 처리 코드도 있네?

...
mutex_unlock(&wq_pool_mutex);

return wq;

err_unreg_lockdep:
wq_unregister_lockdep(wq);
wq_free_lockdep(wq);
err_free_wq:
free_workqueue_attrs(wq->unbound_attrs);
kfree(wq);
return NULL;
// NULL을 반환한다.
err_destroy:
destroy_workqueue(wq);
return NULL;
// NULL을 반환한다.
}
EXPORT_SYMBOL_GPL(alloc_workqueue);

//
// alloc_workqueue() 함수는 kzalloc fail로 NULL를 반환하지만 다른 조건으로 NULL을 반환한다.
//
+ if (unlikely(!ha->wq)) {
+ ret = -ENOMEM;
+ goto probe_failed;
+ }
//
// 그렇다면 위 패치 코드는 valid 할까? 조금 더 생각해보자.
//
//
// 그런데 정말로 다른 코드를 보니 예외 처리를 하네?
//
https://elixir.bootlin.com/linux/v5.3.1/source/drivers/acpi/ec.c#L2039
static inline int acpi_ec_query_init(void)
{
if (!ec_query_wq) {
ec_query_wq = alloc_workqueue("kec_query", 0,
      ec_max_queries);
if (!ec_query_wq)
return -ENODEV;
}
return 0;
}

https://elixir.bootlin.com/linux/v5.3.1/source/drivers/acpi/thermal.c#L1249
acpi_thermal_pm_queue = alloc_workqueue("acpi_thermal_pm",
WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);
if (!acpi_thermal_pm_queue)
return -ENODEV;

https://elixir.bootlin.com/linux/v5.3.1/source/drivers/ata/libata-sff.c#L3270
int __init ata_sff_init(void)
{
ata_sff_wq = alloc_workqueue("ata_sff", WQ_MEM_RECLAIM, WQ_MAX_ACTIVE);
if (!ata_sff_wq)
return -ENOMEM;

return 0;

https://elixir.bootlin.com/linux/v5.3.1/source/drivers/block/nbd.c#L2264
recv_workqueue = alloc_workqueue("knbd-recv",
 WQ_MEM_RECLAIM | WQ_HIGHPRI |
 WQ_UNBOUND, 0);
if (!recv_workqueue)
return -ENOMEM;

 
최근 리눅스 커널 소스 코드를 찾아 봤더니, alloc_workqueue() 함수에 대한 예외 처리 루틴이 없는 부분이 있네?
다음과 같이 패치를 작성하면 어떨까? 

There is no exceptional routine to check whether creation for "libertas_sdio" workqueue fails.
Add error handling path upon workqueue creation failure. 

diff --git a/drivers/net/wireless/marvell/libertas/if_sdio.c b/drivers/net/wireless/marvell/libertas/if_sdio.c
index 242d884..a699252 100644
--- a/drivers/net/wireless/marvell/libertas/if_sdio.c
+++ b/drivers/net/wireless/marvell/libertas/if_sdio.c
@@ -1179,6 +1179,10 @@ static int if_sdio_probe(struct sdio_func *func,

        spin_lock_init(&card->lock);
        card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0);
+       if (unlikely(!card->workqueue)) {
+               ret = -ENOMEM;
+               goto err_work_queue;
+       }
        INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
        init_waitqueue_head(&card->pwron_waitq);

@@ -1230,6 +1234,7 @@ static int if_sdio_probe(struct sdio_func *func,
        lbs_remove_card(priv);
 free:
        destroy_workqueue(card->workqueue);
+err_work_queue:
        while (card->packets) {
                packet = card->packets;
                card->packets = card->packets->next;

[리눅스커널] 워크큐: 워커 스레드 핸들 worker_thread() 함수 분석하기 (1/2) 8. Workqueue

커널 스레드를 처음 분석하려고 할 때 다음과 같은 의문이 생길 때가 있습니다.

    새로운 커널 스레드 코드 분석을 시작할 때 어느 코드부터 분석해야 할까?

먼저 커널 스레드 핸들 함수를 열어봐야 합니다. 그 이유는 커널 스레드 세부 동작은 커널 스레드 핸들 함수에 구현됐기 때문입니다. 마찬가지로 워커 스레드 세부 동작을 알려면 먼저 스레드 핸들 함수인 worker_thread() 함수를 봐야 합니다.

이번 시간에는 worker_thread() 함수를 분석하면서 세부 동작 원리를 배워보겠습니다. 먼저 worker_thread() 함수의 주요 동작은 다음과 같습니다. 
워크 실행
필요 시 워커 스레드 생성 요청
워커 스레드 종료

위 항목 중 핵심 동작은 워크를 실행하는 것이며 나머지는 워커를 관리하는 제어 코드입니다.

워커 스레드 핸들 worker_thread() 함수를 등록하는 코드 알아보기

worker_thread() 함수를 살펴보기 전 이 스레드 핸들 함수를 등록하는 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c#L2190]
1 static struct worker *create_worker(struct worker_pool *pool)
2 {
3 struct worker *worker = NULL;
...
4 worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
5       "kworker/%s", id_buf);


kthread_create_on_node() 함수는 커널 스레드를 생성할 때 호출합니다.
중요한 인자만 살펴보면, 첫 번째 인자로 스레드 핸들, 두 번째 인자로 스레드 핸들 매개 변수,
네 번째 인자로 스레드 이름을 지정합니다.


4번째 줄 코드를 보면 worker_thread() 함수를 스레드 핸들, 두 번째 인자로 스레드 핸들 매개 변수, 그리고 네 번째 인자로 워커 스레드 이름을 지정합니다.

worker_thread() 함수 전체 동작 과정 파악하기

worker_thread() 함수 처리 과정은 다음 그림과 같이 4 단계로 나눌 수 있습니다.
 
[그림 7.13] 워커 스레드 핸들 함수 실행 흐름도

각 단계별로 워커 스레드가 어떤 동작을 하는지 살펴보겠습니다.

1단계: 깨어남
워크를 워크큐에 큐잉하면 wake_up_worker() 함수를 호출합니다.  

    워커 스레드를 깨우는 동작입니다.

스케줄러 정책과 우선 순위에 따라 워커 스레드가 실행할 상황이면 스케줄러는 워커 스레드를 실행합니다. 이 때 워커 스레드는 깨어나 실행을 시작합니다.

2단계: 전처리
워크를 실행하기 전 전처리를 수행하는 단계입니다. need_more_worker() 함수를 호출해 워커 스레드를 실행한 조건인지 점검합니다. 실제 워크를 워크큐에 큐잉하지 않았는데 워커 스레드를 깨울 수 있기 때문입니다. 이 조건을 만족하면 바로 슬립 단계에 진입합니다. 이후 워커 플래그에서 WORKER_PREP와 WORKER_REBOUND를 플래그를 해제합니다.

3단계: 워크 실행
워커풀 struct worker_pool 구조체 연결리스트인 worklist 필드에 접근해 워크 구조체를 로딩합니다. 

    process_one_work() 함수를 호출해 워크를 실행합니다. 

워크를 모두 실행한 다음 워커 플래그에서 WORKER_PREP 를 설정합니다.

4단계: 슬립
워커 상태를 아이들로 설정하고 슬립에 진입합니다. wake_up_worker() 함수가 호출되서 워커 스레드가 깨어날 때까지 슬립 상태를 유지합니다.

워커 스레드 전체 실행 흐름을 점검했으니 이어서 worker_thread() 함수 선언부와 인자를 확인합시다.
static int worker_thread(void *__worker);

먼저 인자를 확인하겠습니다.

void *__worker;
워커 스레드를 생성할 때 전달했던 struct worker 구조체 주소입니다.
워커를 처리하는 핸들 주소를 스레드 핸들인 worker_thread() 함수로 전달하는 것입니다.

이 자료구조로 각각 워커를 관리합니다. 

반환값
함수 선언부를 보면 int 키워드가 보입니다. 워커 해제 요청을 받아 worker_thread() 함수를 호출할 때만 0을 반환하고 이외에는 워커 스레드가 동작하는 동안 반환값을 전달하지 않고 스레드 핸들 함수 내에서 계속 실행합니다.
 
worker_thread() 함수 세부 코드 분석하기

이제 워커 스레드 핸들인 worker_thread() 함수를 분석합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 static int worker_thread(void *__worker)
2 {
3 struct worker *worker = __worker;
4 struct worker_pool *pool = worker->pool;
5
6 set_pf_worker(true); //worker->task->flags |= PF_WQ_WORKER;
7 woke_up:
8 spin_lock_irq(&pool->lock);
9
10 /* am I supposed to die? */
11 if (unlikely(worker->flags & WORKER_DIE)) {
12 spin_unlock_irq(&pool->lock);
13 WARN_ON_ONCE(!list_empty(&worker->entry));
14 set_pf_worker(false); // worker->task->flags &= ~PF_WQ_WORKER;
15
16 set_task_comm(worker->task, "kworker/dying");
17 ida_simple_remove(&pool->worker_ida, worker->id);
18 worker_detach_from_pool(worker, pool);
19 kfree(worker);
20 return 0;
21 }
22
23 worker_leave_idle(worker);
24 recheck:
25 /* no more worker necessary? */
26 if (!need_more_worker(pool))
27 goto sleep;
28
29 /* do we need to manage? */
30 if (unlikely(!may_start_working(pool)) && manage_workers(worker))
31 goto recheck;
32
33 WARN_ON_ONCE(!list_empty(&worker->scheduled));
34
35 worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);
36
37 do {
38 struct work_struct *work =
39 list_first_entry(&pool->worklist,
40  struct work_struct, entry);
41
42 pool->watchdog_ts = jiffies;
43
44 if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
45 /* optimization path, not strictly necessary */
46 process_one_work(worker, work);
47 if (unlikely(!list_empty(&worker->scheduled)))
48 process_scheduled_works(worker);
49 } else {
50 move_linked_works(work, &worker->scheduled, NULL);
51 process_scheduled_works(worker);
52 }
53 } while (keep_working(pool));
54
55 worker_set_flags(worker, WORKER_PREP);
56 sleep:
57 worker_enter_idle(worker);
58 __set_current_state(TASK_IDLE);
59 spin_unlock_irq(&pool->lock);
60 schedule();
61 goto woke_up;
62 }

6번째 줄 코드부터 보겠습니다.
6 set_pf_worker(true);

set_pf_worker(true); 함수를 호출해 프로세스 태스크 디스크립터 flags에 PF_WQ_WORKER 플래그를 저장합니다. set_pf_worker() 함수 구현부는 다음과 같습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
01 static void set_pf_worker(bool val)
02 {
03 mutex_lock(&wq_pool_attach_mutex);
04 if (val)
05 current->flags |= PF_WQ_WORKER;
06 else
07 current->flags &= ~PF_WQ_WORKER;
08 mutex_unlock(&wq_pool_attach_mutex);
09 }

current는 현재 실행 중인 프로세스의 struct task_struct 타입 태스크 디스립터 주소를 의미합니다. set_pf_worker() 함수 val이 true이면 struct task_struct 구조체 flags 필드에 PF_WQ_WORKER 플래그를 OR 연산으로 저장합니다. 위 코드를 실행해 현재 프로세스가 워커 스레드라고 설정하는 것입니다. 

1단계: “깨어남” 단계

7번째 줄 코드를 보겠습니다. 워커 스레드 실행 흐름 중 “깨어남” 단계입니다.
7 woke_up:
8 spin_lock_irq(&pool->lock);
9
10 /* am I supposed to die? */
11 if (unlikely(worker->flags & WORKER_DIE)) {
12 spin_unlock_irq(&pool->lock);
13 WARN_ON_ONCE(!list_empty(&worker->entry));
14 worker->task->flags &= ~PF_WQ_WORKER;
15
16 set_task_comm(worker->task, "kworker/dying");
17 ida_simple_remove(&pool->worker_ida, worker->id);
18 worker_detach_from_pool(worker, pool);
19 kfree(worker);
20 return 0;
21 }

워커 스레드가 깨어나면 실행하는 레이블입니다. 

woke_up 이란 레이블은 언제 실행할까요? 
56 sleep:
57 worker_enter_idle(worker);
58 __set_current_state(TASK_IDLE);
59 spin_unlock_irq(&pool->lock);
60 schedule();
61 goto woke_up;

위 코드 60번째 줄 코드와 같이 워커 스레드가 휴면에 들어간 다음 프로세스 스케줄링으로 깨어나면 61번째 줄 코드를 실행합니다. goto 문을 실행하면 woke_up; 레이블로 이동하는 것입니다.

work_up 레이블을 실행하면 다음과 같이 worker->flags 필드와 WORKER_DIE 매크로를 AND 연산해서 결과가 1이면 12~20번째 줄 코드를 실행해서 워커 스레드를 종료합니다.
11 if (unlikely(worker->flags & WORKER_DIE)) {


worker->flags와 WORKER_DIE 매크로와 AND 연산하기 전 어느 코드에서 worker->flags에 WORKER_DIE 매크로를 설정했을까요?

다음 destory_worker() 함수 3번째 줄 코드입니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 static void destroy_worker(struct worker *worker)
2 {
...
3 worker->flags |= WORKER_DIE;
4 wake_up_process(worker->task);
5}

worker->flags 필드에 OR 비트 연산으로 WORKER_DIE 플래그를 저장합니다. 이후 wake_up_process() 함수를 호출해 해당 워커 스레드를 깨웁니다. 위 destroy_worker() 함수 3~4번째 줄 코드를 실행하면 worker_thread() 함수의 7번째과 11번째 줄 코드를 실행해서 워커 스레드를 종료합니다.


다음 23번째 줄 코드를 보겠습니다.
23 worker_leave_idle(worker);

worker_leave_idle() 함수를 호출해 워커 상태를 표시하는 flags 필드에서 WORKER_IDLE 플래그를 Clear합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 static void worker_leave_idle(struct worker *worker)
2 {
3 struct worker_pool *pool = worker->pool;
4
5 if (WARN_ON_ONCE(!(worker->flags & WORKER_IDLE)))
6 return;
7 worker_clr_flags(worker, WORKER_IDLE);
8 pool->nr_idle--;
9 list_del_init(&worker->entry);
10}

이 동작은 위 함수 코드 7 번째 줄 코드에서 실행합니다.

(To be continued)
Written-by: Austin Kim, 09/29, 2019

[리눅스커널] 워크큐: create_worker() 함수에서 호출한 워크큐 커널 함수 분석하기 8. Workqueue

이번 시간에는 create_worker() 함수에서 호출한 워커 스레드 세부 제어 함수를 살펴보겠습니다.

worker_attach_to_pool() 함수 분석하기

worker_attach_pool() 함수는 워커를 워커풀에 연결하는 역할을 수행합니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c] 
01 static void worker_attach_to_pool(struct worker *worker,
02    struct worker_pool *pool)
03 {
04 mutex_lock(&pool->attach_mutex);
05
06 set_cpus_allowed_ptr(worker->task, pool->attrs->cpumask);
07 if (pool->flags & POOL_DISASSOCIATED)
08 worker->flags |= WORKER_UNBOUND;
09
10 list_add_tail(&worker->node, &pool->workers);
11
12 mutex_unlock(&pool->attach_mutex);
13 }

이 함수의 핵심 동작은 워커풀 구조체 struct worker_pool 필드인 worker(연결리스트)에 워커 struct worker 구조체 node 필드(연결리스트 타입)를 등록하는 것입니다.

10번째 줄 코드를 보겠습니다. 
10 list_add_tail(&worker->node, &pool->workers);

&worker->node와 &pool->workers 변수 모두 struct list_head 타입인 연결 리스트입니다. &pool->workers 연결 리스트에 &worker->node를 등록합니다. 이 코드가 실행하면 다음 그림과 같이 자료 구조가 업데이트됩니다.
 
[그림 7.12] 워커를 워커풀에 등록 후 변경되는 자료구조 

다른 관점으로 &pool->workers 연결 리스트는 &worker->node 주소를 가리키고 있습니다. 이후 &pool->workers 주소에 접근해서 struct worker 구조체 node 필드 오프셋을 빼서 struct work 구조체 주소에 접근합니다.


리눅스 커널에서는 이 방식을 아주 많이 씁니다. 링크드 리스트가 다른 자료구조 링크드 리스트 필드 주소를 가르키는 동작입니다. container_of 매크로 함수도 이 방식을 적용해서 구현한 것입니다.


worker_enter_idle() 함수 분석하기

worker_enter_idle() 함수는 워커의 flags를 WORKER_IDLE로 바꾸고 워커 정보를 업데이트합니다.

이어서 worker_enter_idle() 함수를 분석하겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 static void worker_enter_idle(struct worker *worker)
2 {
3 struct worker_pool *pool = worker->pool;
4
5 if (WARN_ON_ONCE(worker->flags & WORKER_IDLE) ||
6     WARN_ON_ONCE(!list_empty(&worker->entry) &&
7  (worker->hentry.next || worker->hentry.pprev)))
8 return;
9
10 worker->flags |= WORKER_IDLE;
11 pool->nr_idle++;
12 worker->last_active = jiffies;
13
14 list_add(&worker->entry, &pool->idle_list);
15
16 if (too_many_workers(pool) && !timer_pending(&pool->idle_timer))
17 mod_timer(&pool->idle_timer, jiffies + IDLE_WORKER_TIMEOUT);
18
19 WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
20      pool->nr_workers == pool->nr_idle &&
21      atomic_read(&pool->nr_running));
22 }

이 함수의 핵심 동작은 10~14번째 줄이며 나머지는 예외 처리 코드입니다.
10 worker->flags |= WORKER_IDLE;
11 pool->nr_idle++;
12 worker->last_active = jiffies;
13
14 list_add(&worker->entry, &pool->idle_list);

10번째 코드를 봅시다.
10 worker->flags |= WORKER_IDLE;

worker->flags에 WORKER_IDLE 플래그 설정해 워커를 WORKER_IDLE 타입으로 설정합니다.

다음 11번째 줄 코드를 보겠습니다.
11 pool->nr_idle++;
12 worker->last_active = jiffies;

11번째 줄 코드는 워커 풀 nr_idle 필드를 +1만큼 증감합니다. nr_idle은 워커풀에 등록된 idle 워커 개수입니다.

다음 12번째 줄 코드는 worker->last_active에 jiffies를 저장합니다. 현재 시각 정보가 있는 jiffies로 워커가 마지막에 처리된 시각을 저장하는 것입니다.

여기까지 워커를 생성하는 create_worker() 중심으로 관련 함수를 분석했습니다. 워커를 만드는 create_worker() 함수에서 다음 함수를 호출한다는 사실을 알게 됐습니다. 각 함수별 세부 동작을 요약하면 다음과 같습니다. 
kthread_create_on_node() 함수: "kworker/" 이름으로 워커 스레드 만듦
worker_attach_to_pool() 함수: 워커풀에 워커를 등록
worker_enter_idle() 함수: 워커 상태를 WORKER_IDLE로 바꿈 
wake_up_process() 함수: 워커 스레드를 깨움

다음 절에서는 워커 스레드 핸들 함수인 worker_thread() 분석으로 워커 스레드 동작을 살펴보겠습니다.


[리눅스커널] 워크큐: 워커 스레드를 생성하는 create_worker() 함수 분석하기 8. Workqueue

워커 스레드를 생성하려면 create_worker() 함수를 호출해야 합니다. 이번 시간에는 create_worker() 함수 코드를 분석하면서 워커 스레드를 생성하는 과정을 배워보겠습니다.

먼저 create_worker() 함수가 하는 주요 동작은 다음과 같습니다.
워커풀 아이디 읽어오기
워커 스레드 이름을 지정해 워커 스레드 생성 요청하기
워커풀에 워커 스레드 등록하기
워커 정보를 갱신하고 만든 워커 스레드를 깨우기

각 단계 별 코드를 자세히 살펴보겠습니다.

다음은 create_worker() 함수 구현부입니다.
1 static struct worker *create_worker(struct worker_pool *pool)
2 {
3 struct worker *worker = NULL;
4 int id = -1;
5 char id_buf[16];
6
7 id = ida_simple_get(&pool->worker_ida, 0, 0, GFP_KERNEL);
8 if (id < 0)
9 goto fail;
10
11 worker = alloc_worker(pool->node);
12 if (!worker)
13 goto fail;
14
15 worker->pool = pool;
16 worker->id = id;
17
18 if (pool->cpu >= 0)
19 snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,
20  pool->attrs->nice < 0  ? "H" : "");
21 else
22 snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);
23
24 worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
25       "kworker/%s", id_buf);
26 if (IS_ERR(worker->task))
27 goto fail;
28
29 set_user_nice(worker->task, pool->attrs->nice);
30 kthread_bind_mask(worker->task, pool->attrs->cpumask);
31
32 /* successful, attach the worker to the pool */
33 worker_attach_to_pool(worker, pool);
34
35 /* start the newly created worker */
36 spin_lock_irq(&pool->lock);
37 worker->pool->nr_workers++;
38 worker_enter_idle(worker);
39 wake_up_process(worker->task);
40 spin_unlock_irq(&pool->lock);
41
42 return worker;
43
44 fail:
45 if (id >= 0)
46 ida_simple_remove(&pool->worker_ida, id);
47 kfree(worker);
48 return NULL;
49 }

코드가 복잡해 보이지만 create_worker() 함수 핵심 코드는 18~25번째 줄입니다.
18 if (pool->cpu >= 0)
19 snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,
20  pool->attrs->nice < 0  ? "H" : "");
21 else
22 snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);
23
24 worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
25       "kworker/%s", id_buf);

18~25 번째 코드는 워커 스레드 이름을 지정한 후 프로세스 생성 요청을 합니다.

이제부터 각 단계별 소스 코드를 분석하겠습니다. 

워커풀 아이디 읽어오기

7번째 줄 코드를 분석하겠습니다. 
7 id = ida_simple_get(&pool->worker_ida, 0, 0, GFP_KERNEL);
8 if (id < 0)
9 goto fail;

ida_simple_get() 함수를 호출해서 워커 풀 아이디를 가져옵니다. 이 아이디로 워커 풀를 관리합니다. 

워커 스레드 이름을 지정해 워커 스레드 생성 요청하기

다음은 18번째 줄 코드를 분석하겠습니다. 
18 if (pool->cpu >= 0)
19 snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,
20  pool->attrs->nice < 0  ? "H" : "");
21 else
22 snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);
23
24 worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
25       "kworker/%s", id_buf);

18~22번째 줄은 다음 동작을 수행합니다.  

    워커 스레드 이름을 결정합니다.

18 번째 줄 코드는 pool->cpu >= 0 조건을 만족하는지 검사합니다.

워커풀이 percpu 타입으로 관리하는 유형이면 pool->cpu가 0보다 크니 19~20 번째 줄 코드를 실행합니다. 이외 single cpu로 관리하는 워커풀이면 22번째 줄 코드를 실행합니다.


percpu 타입으로 관리하는 워커 풀은 어떤 워크큐에서 쓸까요? 시스템 워크가 주로 씁니다. 시스템 워크큐로 워크를 생성할 때 19번째 줄 코드를 실행합니다.


pool->cpu는 워커 풀이 실행 중인 CPU 번호이고 워커 풀에서 관리하는 IDR 아이디 입니다.
19번째 줄 코드에서 이 기준으로 워커 스레드 이름을 짓습니다.

20번째 줄 코드를 보면 다음 조건에 따라 "H"이라는 스트링을 추가합니다.
pool->attrs->nice < 0

워크큐에서 2종류의 워커풀이 있습니다. 우선 순위를 높혀서 워크를 실행하는 워커 풀(cpu_worker_pools[1])은 pool->attrs->nice 값이 보통 -20입니다. 이 밖의 워커 풀(cpu_worker_pools[0]) 은 pool->attrs->nice 값이 0입니다.

라즈베리파이에서 터미널을 열고 다음 명령어를 입력하면 워커 스레드를 확인할 수 있습니다.
root@raspberrypi:/# ps -ely | grep kworker
1 S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
2 I     0     4     2  0  60 -20     0     0 worker ?        00:00:00 kworker/0:0H
3 I     0    16     2  0  60 -20     0     0 worker ?        00:00:00 kworker/1:0H
...
4 I     0    29     2  0  80   0     0     0 worker ?        00:00:00 kworker/0:1
5 I     0    30     2  0  80   0     0     0 worker ?        00:00:00 kworker/1:1

위에서 볼 수 있는 모든 워커 스레드는 percpu 타입 워커풀에서 생성됐음을 알 수 있습니다.

2~3번째 줄에서 보이는 워커 스레드 프로세스 이름 마지막에 H가 붙어 있습니다. 

    우선 순위를 높혀 처리하는 워커풀(cpu_worker_pools[1])에서 생성됐다는 정보입니다.

이렇게 워커 스레드 프로세스 이름만 봐도 워커 스레드 속성을 짐작할 수 있습니다. 

22번째 줄 코드는 singlecpu 워커 풀에서 생성된 워커 스레드 이름을 짓습니다.

다음 명령어로 워커 스레드 이름을 확인할 수 있는데 워커 스레드 중 “u” 스트링이 붙은 프로세스입니다.
root@raspberrypi:/# ps -ely | grep kworker
S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
...
I     0   466     2  0  60 -20     0     0 worker ?        00:00:00 kworker/u9:0
I     0   471     2  0  60 -20     0     0 worker ?        00:00:00 kworker/u9:2
I     0   501     2  0  80   0     0     0 worker ?        00:00:00 kworker/u8:3
I     0  1353     2  0  80   0     0     0 worker ?        00:00:00 kworker/u8:1
I     0  1470     2  0  80   0     0     0 worker ?        00:00:00 kworker/u8:0

다음 29번째 줄 코드를 보겠습니다.
29 set_user_nice(worker->task, pool->attrs->nice);
30 kthread_bind_mask(worker->task, pool->attrs->cpumask);

프로세스 우선 순위인 nice를 워커 스레드의 태스크 디스크립터 구조체 struct task_struct 필드인 static_prio에 저장합니다.

워커풀에 워커 스레드 등록하기

이번에는 워커풀에 생성한 워커 스레드를 등록하는 코드를 분석합니다.

이어서 33번째 줄 코드를 보겠습니다. 
33 worker_attach_to_pool(worker, pool);

worker_attach_to_pool() 함수를 호출해서 워커를 워커풀에 등록합니다.

다음 4번째 줄 코드와 같이 &pool->workers 연결 리스트에 노드 주소를 저장합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
01 static void worker_attach_to_pool(struct worker *worker,
02    struct worker_pool *pool)
03 {
...
04 list_add_tail(&worker->node, &pool->workers);

워커 정보를 갱신하고 만든 워커 스레드를 깨우기

마지막 단계로 워커 정보를 갱신하고 워커 스레드를 깨우는 동작을 살펴보겠습니다.

다음 37번째 코드 분석으로 들어갑니다.
37 worker->pool->nr_workers++;
38 worker_enter_idle(worker);
39 wake_up_process(worker->task);

워커를 생성했으니 워커 개수를 관리하는 nr_workers 변수를 +1만큼 증감합니다.
38번째 줄 코드는 worker_enter_idle() 함수를 호출해서 워커를 idle 상태로 변경합니다.

마지막으로 39번 째 줄 코드는 wake_up_process() 함수를 호출해 워커 스레드를 깨웁니다. 입력 인자로 태스크 디스크립터인 worker->task 필드를 전달합니다. 이렇게 워커 스레드는 생성한 후 바로 실행 요청을 합니다.

이번 소절에는 워커를 생성하는 세부 동작을 분석했습니다. 이어서 create_worker() 함수에서 호출한 워크큐 내부 함수를 분석하겠습니다.


[리눅스커널] 워크큐: 워커 스레드는 누가 언제 만들까 8. Workqueue

이번 챕터 앞 부분에서 워크큐에 대해 다음과 같이 소개했습니다. 

    워크를 처리하는 워커 스레드를 미리 생성해 놓고 워크 실행 요청이 오면 해당 워커 
    스레드가 이를 처리한다.

이번 시간에는 워커 스레드의 핸들인 워커를 언제 생성하는지 알아보겠습니다. 그렇다면 워커는 어느 함수를 실행할 때 생성할까요? 

    워커는 create_worker() 함수를 호출할 때 생성합니다. 
   
워커를 생성하는 create_worker() 함수를 분석하기 전 이 함수를 호출하는 경로를 알아보겠습니다. 
maybe_create_worker() 
get_unbound_pool() 
workqueue_prepare_cpu()

커널 내부에서 워크를 사용해 후반부 처리를 하므로 커널은 워커 스레드를 미리 생성해 놓습니다. 

그런데 리눅스 시스템에서 여러 드라이버에서 워크를 평소보다 아주 많이 큐잉할 수 있습니다. 이 조건에서 추가로 워크를 처리하기 위해 create_worker() 함수를 호출해 워커 스레드를 생성해야 합니다. 

기본으로 부팅 과정에서 워크큐 자료구조를 초기화할 때 워커 스레드를 생성합니다. 우선 이 코드부터 분석해 봅시다.

부팅 과정에서 워커 스레드를 만드는 동작 살펴보기

워커 스레드는 부팅 과정에서 만듭니다. 이 동작은 workqueue_init() 함수에서 볼 수 있습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 int __init workqueue_init(void)
2 {
3 struct workqueue_struct *wq;
4 struct worker_pool *pool;
5 int cpu, bkt;
6
7 wq_numa_init();
8
9 mutex_lock(&wq_pool_mutex);
10
...
11 /* create the initial workers */
12 for_each_online_cpu(cpu) {
13 for_each_cpu_worker_pool(pool, cpu) {
14 pool->flags &= ~POOL_DISASSOCIATED;
15 BUG_ON(!create_worker(pool));
16 }
17 }

11번째 줄 코드를 보겠습니다.
12 for_each_online_cpu(cpu) {
13 for_each_cpu_worker_pool(pool, cpu) {
14 pool->flags &= ~POOL_DISASSOCIATED;
15 BUG_ON(!create_worker(pool));
16 }

각 CPU 별로 워커 풀에 접근해서 풀 노드 정보를 저장하는 코드입니다. 각 워크풀 별로 create_worker() 함수를 호출해서 워커를 생성합니다.


리눅스 커널 메일링 리스트 구독 따라해보기 Linux Kernel Contribution

이번 시간에는 리눅스 커널 메일링 리스트를 구독하는 방법을 소개합니다.
이번에 소개하는 내용을 참고해서 '리눅스 커널 메일 리스트'를 적극적으로 활용하시길 희망합니다.


http://vger.kernel.org/vger-lists.html 홈페이지 방문

가장 먼저 다음 링크에 있는 홈페이지로 이동합시다.
http://vger.kernel.org/vger-lists.html

홈페이지로 이동하면 다음과 같은 화면이 보일 것입니다.

위에서 푸른색 글짜가 리눅스 커널 메일링 리스트입니다. 수 십가지 리눅스 커널 메일링 리스트가 보입니다.
여러분은 이 목록 중 하나를 등록하면 되는 것입니다.

메일링 리스트를 구독하기 위해 메일 발송

위 목록 중에서 linux-xfs 메일링 리스트에 등록을 하려면 어떻게 해야 할까요?
이를 위해 다음 포멧으로 이메일을 전달하면 됩니다.
수신자: majordomo@vger.kernel.org
제목: 제목 없음
이메일 내용
 subscribe linux-xfs

다음은 제 이메일 계정으로 linux-xfs 메일링 리스트를 등록을 시도하는 화면입니다.
반복하지만 수신자 이메일 주소는 'majordomo@vger.kernel.org' 이고 이메일에는 제목없이 'subscribe linux-xfs' 내용만 적으면 됩니다.


메일을 보낸 후 3분 정도 기다리면 다음과 같은 메일이 올 것입니다.

메일 내용은 간단합니다.

   * 누군가 austindh.kim@gmail.com 이메일 주소로 linux-xfs 메일링 리스트 구독을 요청한 것 같다.
   * 정말 구독을 원하면 다음 내용으로 메일을 다시 보내달라.
   * 수신자 이메일은 주소는 'majordomo@vger.kernel.org'이다.

이번에는 다음 내용을 복사해 다시 이메일로 답장을 보냅니다.
auth b370f3eb subscribe linux-xfs austindh.kim@gmail.com


메일링 리스트 구독 완료 확인

1분 후 다음과 같은 구독 완료(Confirmation) 메일이 도착합니다.

위에서 보이는 메일 내용을 정리하면 다음과 같습니다.

   * linux-xfs 메일링 리스트 구독이 완료됐다.
   * 만약 linux-xfs 메일링 리스트 구독을 취소하려면 다음 정보로 메일을 보내 달라.
      수신자: 'majordomo@vger.kernel.org'
      메일 내용: unsubscribe linux-xfs


자, 그럼 10분 정도 지나니 linux-xfs 메일링 리스트로 메일이 도착했음을 알 수 있습니다.

XFS 파일 시스템 개발자들이 제안한 패치와 토론 내용을 볼 수 있습니다.

정리

   * 생각보다 리눅스 커널 메일링 리스트를 등록하는 방법은 매우 간단합니다.
   * 리눅스 커널 메일링 리스트를 구독하는 것보다 이를 잘 활용하는 것이 중요합니다.
   * 꾸준히 메일링 리스트를 읽고 정리하는 습관을 가집시다.


[리눅스커널] 워크큐: 워커 자료구조인 struct worker 구조체 알아보기 8. Workqueue

워커를 관리하고 저장하는 자료구조는 struct worker 구조체입니다. 이번 시간에는 struct worker 구조체 세부 필드를 분석하겠습니다. 

struct worker 구조체 분석하기

다음은 struct worker 구조체 선언부입니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue_internal.h]
1 struct worker {
2 union {
3 struct list_head entry; 
4 struct hlist_node hentry; 
5 };
6 struct work_struct *current_work;
7 work_func_t current_func;
8 struct pool_workqueue *current_pwq; 
9 bool desc_valid;
10 struct list_head scheduled;
11
12 struct task_struct *task;
13 struct worker_pool *pool;
14
15 struct list_head node;
16 unsigned long last_active;
17 unsigned int flags;
18 int id;
19
20 char desc[WORKER_DESC_LEN];
21
22 struct workqueue_struct *rescue_wq;
23};

각 구조체 세부 필드를 살펴봅시다.

struct work_struct  *current_work;
struct work_struct 구조체로 현재 실행하려는 워크를 저장하는 필드입니다.

work_func_t current_func;
실행하려는 워크 핸들러 주소를 저장하는 필드입니다. 

    워크 구조체와 워크 핸들러는 커널 어느 코드에서 struct worker 구조체 필드에 
   저장할까요?

워크 구조체와 워크 핸들러는 process_one_work() 함수에서 위 current_work와 current_func 필드에 저장합니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
static void process_one_work(struct worker *worker, struct work_struct *work)
{
...
worker->current_work = work;
worker->current_func = work->func;
worker->current_pwq = pwq;

이어서 다른 필드를 분석하겠습니다.

struct task_struct *task;
워커 스레드의 태스크 디스크립터 주소입니다.

struct worker_pool *pool;
워커를 관리하는 워커 풀 주소를 저장하는 필드입니다.

struct list_head node;
워커풀에 등록된 링크드 리스트입니다.

워커와 워커 스레드는 어떤 관계일까

워커 스레드는 커널 스레드의 한 종류로 워크를 실행하는 프로세스입니다. 워커 스레드의 스레드 핸들 함수는 worker_thread() 입니다.


커널 스레드가 무슨 일을 하는지 알려면 해당 커널 스레드 핸들 함수를 분석해야 합니다. 워커 스레드도 마찬가지입니다. 워커 스레드의 스레드 핸들인 worker_thread() 함수를 열어보면 워커 스레드의 세부 동작 방식을 확인할 수 있습니다.


워커는 워커 스레드를 표현하는 자료구조이며 struct worker입니다. 워커 스레드와 워커는 비슷한 개념으로 볼 필요가 있습니다.

워커 스레드를 생성하기 위해서 우선 워커를 생성해야 합니다. 워커는 워커 스레드를 담근 그릇(container)와 비슷한 역할을 수행합니다. 만약 “워커를 해제했다” 라고 어떤 개발자가 말하면 “해당 워커 스레드를 해제했구나”와 같은 뜻입니다.

워커 스레드 구조체인 struct worker 플래그를 살펴봤으니 워커 스레드는 누가 언제 생성하는지 살펴보겠습니다.


[리눅스커널] 워크큐: 워커와 워커 스레드란 8. Workqueue

커널에서 워크큐 관련 함수를 호출하고 워크큐 자료 구조를 관리합니다. 이를 위해 생성된 프로세스가 워커 스레드이며 워커란 자료구조로 세부 동작을 관리합니다.

이번 시간에서는 워커와 워커 스레드가 무엇인지 먼저 알아보겠습니다.

라즈베리파이에서 워커 스레드 확인하기

워커 스레드는 모든 리눅스 시스템에서 배경으로 실행하는 프로세스입니다. 물론 라즈베리파이도 워커 스레드를 볼 수 있습니다. 그러면 라즈베리파이에서 터미널을 열고 'ps -ely | grep kworker' 명령어로 워커 스레드를 확인해볼까요?  
root@raspberrypi:/# ps -ely | grep kworker
1 S   UID   PID  PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
2 I     0     4     2  0  60 -20     0     0 worker ?        00:00:00 kworker/0:0H
3 I     0    16     2  0  60 -20     0     0 worker ?        00:00:00 kworker/1:0H
...
4 I     0    29     2  0  80   0     0     0 worker ?        00:00:00 kworker/0:1
5 I     0    30     2  0  80   0     0     0 worker ?        00:00:00 kworker/1:1

가장 오른쪽 줄에서 보이는 프로세스 이름은 모두 "kworker/" 로 시작하며 이런
패턴의 프로세스를 워커 스레드라고 부릅니다.

워커 스레드 전체 흐름도 소개하기

워커 스레드 동작은 다음과 같이 3단계로 분류할 수 있습니다. 

1 단계: 워커 스레드 생성
create_worker() 함수를 호출하면 워커 스레드를 만들 수 있습니다.

2 단계: 휴면 상태
휴면 상태에서 다른 드라이버가 자신을 깨워주기를 기다립니다.

3 단계: 실행
워크를 워크큐에 큐잉하면 워커 스레드를 깨우면 스레드 핸들인 worker_thread() 함수가 실행됩니다.

4 단계: 소멸
워커 스레드가 필요가 없으면 소멸됩니다.

워커 스레드는 2번과 3번 단계를 자주 실행합니다. 

임베디드 리눅스 개발자 양극화를 극복하는 방법: 리눅스 커널 메일링 리스트 활용 임베디드 에세이

제가 올린 포스팅 중 생각지도 않게 조회수가 높게 나올 때가 있습니다. 그 중 하나는 다음 포스팅입니다.

댓글을 보면 알 수 있듯 많은 분들이 공감해주셨습니다. 

   * 맞다! 임베디드 개발의 양극화는 정말 심하다!

그런데 전 이글을 올린 후 리눅스 세미나에서 다음과 같은 질문을 받았습니다.

   * 임베디드 개발의 양극화가 심하다는 것은 알겠다.
   * 그런데 그 해결책은 무엇이냐?

이 질문을 받고 바로 전 다음과 같이 대답을 했습니다.

   * 출간될 제 책을 사보세요.
   * 제 책이 임베디드 양극화의 Gap을 줄여 줄 수 있을 것이라 생각합니다.

농담 반, 진담 반으로 드렸던 대답이었습니다. 그런데 돌이켜보니 정말 *헛소리*를 했단 생각이 듭니다.
그래서 시간을 갖고 조금 더 곰곰히 고민해 봤습니다. 

   * 임베디드 리눅스 개발의 양극화를 극복하는 방법은 무엇일까?

이번 포스팅에서는 '임베디드 리눅스 개발의 양극화를 줄이는'이 방안에 대해서 조금 이야기해보려고 합니다.

리눅스 커널 메일링 리스트란 무엇인가? 

제 블로그에서 언급했지만 임베디드 리눅스는 다음과 같은 여러 단체가 협업한 결과물입니다.

   * 리눅스 커널 커뮤니티 + CPU 벤더(인텔, ARM, AMD) + SoC(퀄컴, 삼성 LSI, 브로그컴)

여러분이 어떤 임베디드 리눅스 제품을 개발하던 위 단체의 결과물과 연관이 돼 있는 것입니다.
그런데 이 단체 중 핵심은 어디일까요? 정답은 '리눅스 커널 커뮤니티'입니다. 아무리 CPU벤더나 SoC 업체가 노력한다고 한 들 리눅스 커널이 없으면 무용지물일 것입니다.

   * 리눅스 커널 커뮤니티란 무엇일까요?

'리눅스 커널 커뮤니티'는 리눅스 커널 개발자들이 메일로 패치를 주고 받고 치열하게 토론하는 포럼입니다. 주로 다음과 같은 주제로 열띤 토론을 벌입니다.

   * 각 리눅스 세부 기능(Subsystem)별로 버그 공유
   * 새로운 기능에 대한 논의 
   * 질문과 대답 
   
자, 이 부분을 읽으니 이어서 다른 의문이 생깁니다.

   * 이런 논의를 어디서 할까요? 카카오톡 같은 메신저로 대화를 할까요?

아닙니다. 리눅스 커뮤니티에서 커널 개발자들은 '리눅스 커널 메일링 리스트'를 통해 서로 의견을 주고 받습니다.
리눅스 커널 메일링 리스트는 누구나 등록할 수 있으며 리눅스 커널 개발자들이 주고 받은 '토론'을 여러분도 그대로 받아 볼 수 있습니다.

리눅스 메일링 리스트로 어떤 내용을 알 수 있는가? 

리눅스 커널 메일링 리스트를 통해 다음과 같은 내용을 알 수 있습니다.

   * 리눅스 커널 개발자들이 작성한 패치 코드
   * 커널 크래시나 락업에 대한 리포트 및 해결 패치
   * 리눅스 커널 개발자들의 토론 내용

리눅스 메일링 리스트를 활용해 정상급 커널 개발자에게 질문하기

그런데 여러분이 리눅스 커널 메일링 리스트를 통해 커널 개발자가 토론하는 내용을 읽다 보면 여러가지 궁금한 점이 생길 수 있습니다. 이 때 어떻게 하면 될까요? 

   * 메일링 리스트나 해당 개발자에게 질문을 하면 됩니다.

그러면 한 가지 예를 들어볼까요? 제가 커널 메일링 리스트를 구독해 읽던 도중 다음과 같은 메일을 받았습니다.
[PATCH] usnic: avoid overly large buffers on stack

It's never a good idea to put a 1000-byte buffer on the kernel
stack. The compiler warns about this instance when usnic_ib_log_vf()
gets inlined into usnic_ib_pci_probe():

drivers/infiniband/hw/usnic/usnic_ib_main.c:543:12: error: stack frame size of 1044 bytes in function 'usnic_ib_pci_probe' [-Werror,-Wframe-larger-than=]
...
diff --git a/drivers/infiniband/hw/usnic/usnic_ib_main.c b/drivers/infiniband/hw/usnic/usnic_ib_main.c
index 03f54eb9404b..c9abe1c01e4e 100644
--- a/drivers/infiniband/hw/usnic/usnic_ib_main.c
+++ b/drivers/infiniband/hw/usnic/usnic_ib_main.c
@@ -89,9 +89,15 @@ static void usnic_ib_dump_vf(struct usnic_ib_vf *vf, char *buf, int buf_sz)

 void usnic_ib_log_vf(struct usnic_ib_vf *vf)
 {
-       char buf[1000];
-       usnic_ib_dump_vf(vf, buf, sizeof(buf));
+       char *buf = kzalloc(1000, GFP_KERNEL);
+
+       if (!buf)
+               return;
+
+       usnic_ib_dump_vf(vf, buf, 1000);
        usnic_dbg("%s\n", buf);
+
+       kfree(buf);
 }

 /* Start of netdev section */

패치 코드 내용은 다음과 같습니다. 

   * 지역 변수 배열로 1000-byte 로 할당하면 '프로세스'의 스택 공간을 많이 써서 좋지 않다.
   * 지역 변수 배열 대신 동적 메모리를 할당해 메모리를 할당한다.
   * 결국 프로세스 스택 오버플로우를 방지할 수 있다.

아주 훌륭한 패치 코드란 생각이 들었습니다. 그런데 이 내용을 읽고 전 한 가지 의문이 생겼습니다. 

   * 어떻게 이런 사실을 알게 됐을까?

그래서 직접 이 패치를 작성한 개발자인 'Arnd Bergmann' 님께 메일로 질문을 했습니다.

Hello, Arnd Bergmann

I took a look at your patch which seems to be very practical.
I am sure that your patch might avoid _potential_ stack overflow where
callstack is very deep.

My question is
  'how did you find out this problem?'
void usnic_ib_log_vf(struct usnic_ib_vf *vf)
 {
-       char buf[1000];
-       usnic_ib_dump_vf(vf, buf, sizeof(buf));

Did you just have a code review in details? Or run a program-tool?
If you have some time, please give me a tip.

Thanks in advance.
Austin Kim

'Arnd Bergmann'님은 Linaro에 소속된 정상급 커널 개발자입니다. 떨리는 마음으로 메일을 보냈는데 5분 후 답장이 왔습니다.

> My question is
>   'how did you find out this problem?'
> void usnic_ib_log_vf(struct usnic_ib_vf *vf)
>  {
> -       char buf[1000];
> -       usnic_ib_dump_vf(vf, buf, sizeof(buf));
>
> Did you just have a code review in details? Or run a program-tool?
> If you have some time, please give me a tip.

This is a normal compile-time warning that happens on 32-bit architecutres,
you probably see it when building a x86-32 allmodconfig kernel
using "make ARCH=i386 allmodconfig ; make -skj16 '.

I found it while building random configurations using 'make randconfig'.

The compiler I used is clang-9, but it probably also happens with
most gcc versions. On 64-bit machines, we currently only warn
for stack frames larger than 2048 bytes, on 32-bit the limit is 1024.

와우, 정말 친절한 설명이었습니다. clang-9을 써서 다음과 같이 커널을 빌드했더니 WARN(경고) 메시지를 확인했다는 것입니다.

   * make ARCH=i386 allmodconfig

전 이메일을 받고 'Arnd Bergmann'님께 다음과 같이 간단히 답장을 보냈습니다.
Thanks for the tip! 

이렇게 리눅스 커널 개발자에게 직접 질문을 하고 대답을 들 을 수 있었습니다.

정리

이전 포스팅에서 언급한 바와 같이 임베디드 개발자의 양극화의 원인은 다음과 같다고 설명을 드렸습니다.

   * 실전 개발을 통해 실무를 배울 수 있는 기회 박탈 

그런데 리눅스 커널 메일링 리스트를 통해 다음과 같은 기회를 얻을 수 있습니다.

   * 정상급 리눅스 커널 개발자들의 토론
   * 정상급 리눅스 커널 개발자에게 질문하고 답을 들을 수 있음

여러분 주위 선배 실력이 형편없고 배울 수 있는 것이 없다고 불만이 생길 수 있습니다. 하지만 리눅스 커널 메일링 리스트를 활용하면 정상급 개발자와 소통할 수 있습니다.

리눅스 커널 메일링 리스트를 잘 활용해 유익한 내용을 얻기를 바랍니다.



[리눅스커널] 워크큐(workqueue): __queue_work() 함수 분석하기 8. Workqueue

워크를 워크큐에 큐잉하는 핵심 동작은 __queue_work() 함수에서 수행합니다. 코드 분석을 통해 워크를 워크큐에 어떤 방식으로 큐잉하는지 살펴보겠습니다.

코드 분석에 앞서 __queue_work() 함수 선언부와 인자를 점검합시다.
static void __queue_work(int cpu, struct workqueue_struct *wq,
struct work_struct *work);

queue_work() 함수에서 첫 번째 인자로 WORK_CPU_UNBOUND, 두 번째 인자로 system_wq 를 전달했으니 cpu는 WORK_CPU_UNBOUND, wq는 system_wq 시스템 워크큐 전역 변수 주소입니다. 함수 인자 목록을 정리하면 다음과 같습니다.
int cpu: WORK_CPU_UNBOUND
struct workqueue_struct *wq: system_wq
struct work_struct *work: struct work_struct 구조체 주소

함수 인자를 살펴봤으니 __queue_work() 함수 처리 흐름을 단계별로 알아봅시다.
1 단계: 풀워크큐 가져오기
2 단계: 워커 구조체 가져오기
3 단계: ftrace 로그 출력하기
4 단계: 워커풀에 워크 연결 리스트 등록하고 워커 스레드 깨우기 

함수 인자와 처리 단계를 알아봤으니 소스 코드를 분석할 차례입니다.
__queue_work() 함수 코드는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
01 static void __queue_work(int cpu, struct workqueue_struct *wq,
02 struct work_struct *work)
03 {
04 struct pool_workqueue *pwq;
05 struct worker_pool *last_pool;
06 struct list_head *worklist;
07 unsigned int work_flags;
08 unsigned int req_cpu = cpu;
...
09 retry:
10 if (req_cpu == WORK_CPU_UNBOUND)
11 cpu = wq_select_unbound_cpu(raw_smp_processor_id());
12
13 /* pwq which will be used unless @work is executing elsewhere */
14 if (!(wq->flags & WQ_UNBOUND))
15 pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
16 else
17 pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
18
19 last_pool = get_work_pool(work);
20 if (last_pool && last_pool != pwq->pool) {
21 struct worker *worker;
22
23 spin_lock(&last_pool->lock);
24
25 worker = find_worker_executing_work(last_pool, work);
26
27 if (worker && worker->current_pwq->wq == wq) {
28 pwq = worker->current_pwq;
30 } else {
31 /* meh... not running there, queue here */
32 spin_unlock(&last_pool->lock);
33 spin_lock(&pwq->pool->lock);
34 }
35 } else {
36 spin_lock(&pwq->pool->lock);
37 }
...
38 /* pwq determined, queue */
39 trace_workqueue_queue_work(req_cpu, pwq, work);
...
40 if (likely(pwq->nr_active < pwq->max_active)) {
41 trace_workqueue_activate_work(work);
42 pwq->nr_active++;
43 worklist = &pwq->pool->worklist;
44 if (list_empty(worklist))
45 pwq->pool->watchdog_ts = jiffies;
45 } else {
49 work_flags |= WORK_STRUCT_DELAYED;
50 worklist = &pwq->delayed_works;
51 }
52
53 insert_work(pwq, work, worklist, work_flags);

1 단계: 풀워크큐 가져오기

09번째 줄 코드부터 봅시다.
09 retry:
10 if (req_cpu == WORK_CPU_UNBOUND)
11 cpu = wq_select_unbound_cpu(raw_smp_processor_id());

req_cp 지역 변수는 이 함수에 전달되는 cpu를 그대로 저장했으니 WORK_CPU_UNBOUND입니다.
8 unsigned int req_cpu = cpu;

따라서 11번째 줄 코드를 바로 실행합니다. 

11번째 줄 코드는 raw_smp_processor_id() 함수로 현재 실행 중인 CPU 번호를 알아낸 후 wq_select_unbound_cpu() 함수에 인자를 전달합니다. wq_select_unbound_cpu() 함수는 다음와 같이 동작합니다. 

    이진수로 1111인 wq_unbound_cpumask 비트 마스크용 변수와 AND 비트 연산으로 CPU 
   번호를 얻어온다.

2 단계: 워커 구조체 가져오기

이번에는 14번째 줄 코드를 보겠습니다.
14 if (!(wq->flags & WQ_UNBOUND))
15 pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
16 else
17 pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));

대부분 워크큐는 해당 플래그인 wq->flags가 0이므로 일반적인 상황에서 15번째 코드를 실행합니다. struct workqueue_struct 구조체에서 per-cpu 타입 필드인 cpu_pwqs에 접근해서 CPU 주소를 읽어 옵니다.

다음 5번째 줄 struct workqueue_struct 구조체 cpu_pwqs 필드 선언부를 보면 __percpu 키워드를 볼 수 있습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
01 struct workqueue_struct {
02 struct list_head pwqs; /* WR: all pwqs of this wq */
03 struct list_head list; /* PR: list of all workqueues */
...
04 unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
05 struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */

per-cpu 타입 변수인 wq->cpu_pwqs 필드는 다음 코드가 실행할 때 접근합니다.
15 pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);

15번째 줄 코드 동작을 그림으로 표현하면 다음과 같습니다.
 
[그림 7.6 풀워크큐 per-cpu 자료구조]

위 그림과 같이 system_rq->cpu_pwqs 필드에 저장된 주소에 현재 실행 중인 CPU 번호 기준으로 __per_cpu_offset[cpu번호] 배열에 있는 주소를 더합니다. per-cpu 타입 변수는 CPU별로 주소 공간이 있는 것입니다. 라즈베리파이는 CPU 개수가 4개이니 4개 per-cpu 메모리 공간을 할당 받습니다.

이번에는 28줄 코드를 보겠습니다.
28 last_pool = get_work_pool(work);

struct work_struct 구조체인 work 변수로 get_work_pool() 함수를 호출합니다. 이후 struct worker_pool 구조체 주소를 last_pool 지역변수로 읽습니다. get_work_pool() 함수는 조금 후 분석할 예정입니다. 

자료구조를 반경하는 코드만 읽으면 바로 이해하기 쉽지 않습니다. 다음 그림을 같이 보면서 자료구조가 어떻게 바뀌는지 알아볼까요?
 
[그림 7.7] 워커풀에서 워커 검색 흐름 

워크를 실행한 적이 있으면 struct work_struct 구조체 data 필드는 풀워크 주소를 저장하고 있습니다. get_work_pool() 함수는 위 그림에서 [1],[2] 으로 표시된 부분과 같이 워커풀 주소를 가져옵니다.

이어서 29번째 줄 코드를 분석하겠습니다.
28 last_pool = get_work_pool(work);
29 if (last_pool && last_pool != pwq->pool) {
30 struct worker *worker;
31
32 spin_lock(&last_pool->lock);
33
34 worker = find_worker_executing_work(last_pool, work);
35
36 if (worker && worker->current_pwq->wq == wq) {
37 pwq = worker->current_pwq;
38 } else {
39 /* meh... not running there, queue here */
40 spin_unlock(&last_pool->lock);
41 spin_lock(&pwq->pool->lock);
42 }
43 } else {
44 spin_lock(&pwq->pool->lock);
45 }

29번째 줄부터 시작하는 조건문은 다음 조건을 체크합니다. 

     큐잉하는 워크를 이미 워커풀에서 실행한 적이 있는가?

워크를 처음 실행하면 struct work_struct 구조체 data 필드에 풀워크 주소를 저장합니다. data 필드에 액세스를 하면 워커풀 주소에 접근할 수 있습니다. 이미 워크를 처리한 풀워크가 있으면 이를 통해 워커풀을 재사용하려는 의도입니다.

다음 34번째 줄 코드를 보겠습니다.
34 worker = find_worker_executing_work(last_pool, work);
35
36 if (worker && worker->current_pwq->wq == wq) {
37 pwq = worker->current_pwq;

find_worker_executing_work() 함수를 호출해서 워크를 실행한 워커 스레드 구조체 주소를 worker 지역변수에 저장합니다. 워커 스레드 구조체 worker->current_pwq->wq에 저장된 워크큐 주소와 지금 큐잉하는 워크에 대응하는 워크큐와 같으면 struct pool_workqueue 구조체 주소를 가져옵니다. 그림 7.3에서 [3] 번호에 대응하는 동작입니다. 

워크를 여러 개의 워커 스레드에서 처리하지 못하게 제약 조건을 둔 것입니다. get_work_pool() 함수와 find_worker_executing_work() 함수는 다음 소절에서 분석할 예정입니다.

3 단계: ftrace 로그 출력하기

이제 3단계 처리 과정 코드를 분석하겠습니다.  
59 trace_workqueue_queue_work(req_cpu, pwq, work);
60
61 if (WARN_ON(!list_empty(&work->entry))) {
62 spin_unlock(&pwq->pool->lock);
63 return;
64 }

59번째 줄 코드는 ftrace로 workqueue_queue_work 워크큐 이벤트를 키면 실행해 다음과 같은 ftrace 로그를 출력합니다. 
make-13395 [000] d.s.   589.830896: workqueue_queue_work: work struct=b9c1aa30 function=mmc_rescan workqueue=b9c06700 req_cpu=4 cpu=0
make-13395 [000] d.s.   589.830897: workqueue_activate_work: work struct b9c1aa30

위 로그는 워크를 워크큐에 큐잉했다고 해석할 수 있습니다.  struct work_struct 구조체 주소는 b9c1aa30이고 워크 핸들러 함수인 mmc_rescan()입니다. 

schedule_work() 함수를 호출하면 워크를 보통 워크큐에 큐잉했다고 짐작합니다. 하지만 위 ftrace 로그에서 workqueue_queue_work와 workqueue_activate_work 메시지를 확인하기 전까지 제대로 동작할 것이라 확신하면 안됩니다. 워크를 워크큐에 큐잉하는 schedule_work() 함수를 호출해도 특정 상황에서 워크가 워크큐에 제대로 큐잉을 못할 수도 있기 때문입니다. 

그래서 새로운 워크를 선언한 다음 워크큐에 큐잉하는 코드를 작성했을 때 위와 같은 ftrace 메시지를 확인할 필요가 있습니다. 


실력있는 개발자가 되려면 자신이 구현한 드라이버 코드가 제대로 동작하는지 점검하는 방법도 알고 있어야 합니다. 


다음 68번째 줄 코드를 분석하겠습니다.
68 if (likely(pwq->nr_active < pwq->max_active)) {
69 trace_workqueue_activate_work(work);
70 pwq->nr_active++;
71 worklist = &pwq->pool->worklist;
72 if (list_empty(worklist))
73 pwq->pool->watchdog_ts = jiffies;
74 } else {
75 work_flags |= WORK_STRUCT_DELAYED;
76 worklist = &pwq->delayed_works;
77 }

먼저 if~else 문 조건을 살펴봅시다.

68번째 줄 코드는 if 조건문으로 현재 실행 중인 워커 개수를 점검합니다. if 문 결과에 따라 다음과 같이 처리합니다. 
풀 워크큐에서 현재 실행 중인 워커 개수가 255를 넘지 않으면 69~73번째 줄 코드를 실행 
반대의 경우 75~76번째 줄 코드를 실행 

여기서 struct pool_workqueue 구조체 필드 중 nr_active는 풀 워크큐에서 현재 실행 중인 워커 개수고 max_active는 풀 워크큐에서 실행 가능한 최대 워커 개수를 의미합니다.  pwq_adjust_max_active() 함수가 실행될 때 pwq->max_active는 255개로 설정됩니다.

74번째 줄 코드는 워크를 처리할 워커 개수가 255개를 넘어섰을 때 동작합니다. 이때 struct pool_workqueue 구조체 필드 중 delayed_works인 링크드 리스트를 worklist 지역 변수에 저장합니다. 여기서 보이는 delayed_works는 딜레이 워크(struct delayed_work)와 다른 개념입니다. 실행 중인 워커 개수가 많다는 것은 그만큼 처리해야 할 워크가 많다는 것을 의미합니다. 그래서 delayed_works 이란 연결 리스트(struct list_head) 필드에 워크를 등록하고 적절한 시점 이후 이 워크를 처리합니다.

딜레이 워크(struct delayed_work)는 특정 시각 후 워크를 실행합니다. 위 코드의 delayed_works와 딜레이 워크는 헷갈릴 수 있으니 주의하시길 바랍니다.

정리하면, 특정 상황에서 커널 서브 시스템이나 드라이버에서 워크를 아주 많이 생성해서 워크를 처리할 워커 개수가 255개를 넘어섰을 때를 제외하고 보통69~73번째 줄 코드를 실행합니다.


if~else 문 아래 78번째 줄 이후 코드는 69~73번 코드가 실행했다고 가정하고 분석하겠습니다.


if~else 문이 실행하는 조건을 점검했으니 69번째 줄 코드부터 분석을 시작합니다.

69번째 줄 코드는 ftrace에서 workqueue_activate_work 이벤트를 키면 다음과 같은 로그를 출력합니다. 코드와 ftrace 로그를 함께 보겠습니다.
69 trace_workqueue_activate_work(work);

make-13395 [000] d.s.   589.830896: workqueue_queue_work: work struct=b9c1aa30 function=mmc_rescan workqueue=b9c06700 req_cpu=4 cpu=0
make-13395 [000] d.s.   589.830897: workqueue_activate_work: work struct b9c1aa30

워크를 표현하는 struct work_struct 자료구조 주소를 출력하는데 보통 workqueue_queue_work ftrace 로그 이후에 출력합니다.

70번째 줄 코드는 현재 실행 중인 워커 개수를 1만큼 증감합니다.
70 pwq->nr_active++;

4 단계: 워커풀에 워크 연결 리스트 등록하고 워커 스레드 깨우기

__queue_work() 함수에서 가장 중요한 코드입니다.

71번과 76번째 줄 코드를 함께 보겠습니다.
71 worklist = &pwq->pool->worklist; 
...
76 worklist = &pwq->delayed_works;

먼저 71번째 줄 코드를 분석하겠습니다.
pwq 변수는 struct pool_workqueue 구조체 타입이고 pool은 struct worker_pool 구조체 타입입니다. &pwq->pool 코드로 struct worker_pool 구조체에 접근해서 worklist 필드를 worklist 지역 변수에 저장하는 코드입니다.

이번엔 76번째 줄 코드를 보겠습니다. struct pool_workqueue 구조체인 pwq 변수로 delayed_works란 필드 주소를 worklist 지역 변수로 저장합니다. 

코드만 보면 자료구조가 어떻게 바뀌는지 이해하기 어렵습니다. 다음 그림을 같이 보면서 워크를 큐잉할 때 자료구조가 어떻게 변경되는지 확인해볼까요? 
 
    [그림 7.8] 워크를 워커풀에 큐잉할 때 변경되는 자료구조  

[1] &pwq->pool->worklist는 71번째 줄 [2] &pwq->delayed_works는 76번째 줄 코드를 의미합니다. 워크큐 자료 구조를 변경하는 코드를 볼 때 이렇게 전체 자료 구조 흐름을 머릿속으로 그리면서 분석하면 이해가 더 빠릅니다.

이어서 79번째 줄 코드를 보겠습니다. 
79 insert_work(pwq, work, worklist, work_flags);

insert_work() 함수에 전달하는 주요 인자들을 살펴보고 insert_work() 함수 분석을 시작 하겠습니다.

pwq 는 per-cpu 타입인 struct pool_workqueue 구조체 주소를 담고 있고, work는 _queue_work() 함수로 워크큐에 큐잉하려는 워크입니다. worklist는 &pwq->pool->worklist 코드로 저장된 struct list_head 구조체인 링크드 리스트입니다. 

insert_work() 함수를 호출해서 &pwq->pool->worklist 연결 리스트에 다음과 같은 워크 연결리스트를 등록합니다. 
struct work_struct 구조체 entry 필드
다음 insert_work() 함수에서 wake_up_worker() 함수를 호출해서 워크를 실행할 워커 스레드를 깨웁니다.

여기까지 워크를 큐잉하는 흐름을 알아봤습니다. 다음 절에서는__queue_work_on() 함수에서 호출하는 워크큐 함수를 분석합니다.


[리눅스커널] 워크큐(workqueue): 워크를 워크큐에 큐잉하는 인터페이스 함수 분석하기 8. Workqueue

커널은 디바이스 드라이버 레벨에서 워크큐를 큐잉할 수 있는 여러 가지 함수를 지원합니다.
이번 시간에는 워크를 워크큐에 큐잉할 때 사용하는 함수를 소개하고 코드를 분석합니다. 
schedule_work()
queue_work()
queue_work_on()

먼저 schedule_work() 함수를 분석해볼까요?

schedule_work() 함수 분석하기

schedule_work() 함수 구현부 코드는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/workqueue.h]
1 static inline bool schedule_work(struct work_struct *work)
2 {
3 return queue_work(system_wq, work);
4}

schedule_work() 함수는 인라인 타입으로 함수 구현부가 간단합니다.

queue_work() 함수를 호출하는데 system_wq 전역 변수를 첫 번째 인자로 queue_work() 함수에 전달합니다. 이 코드 내용을 토대로 다음 사실을 알 수 있습니다. 

    schedule_work() 함수로 전달하는 워크는 시스템 워크큐에 큐잉된다.

시스템 워크큐는 system_wq 전역 변수로 관리하며 선언부는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
struct workqueue_struct *system_wq __read_mostly;
EXPORT_SYMBOL(system_wq);

이어서 queue_work() 함수 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/workqueue.h]
01 static inline bool queue_work(struct workqueue_struct *wq,
02       struct work_struct *work)
03 {
04 return queue_work_on(WORK_CPU_UNBOUND, wq, work);
05 }

4번째 줄 코드를 보면 WORK_CPU_UNBOUND를 첫 번째 인자로 queue_work_on() 함수를 호출합니다.

정리하면 schedule_work() 함수로 전달하는 워크는 시스템 워크큐에 큐잉되며 다음 함수 흐름으로 queue_work_on() 함수를 호출한다는 사실을 알 수 있습니다. 
queue_work()
queue_work_on()

queue_work_on() 함수 분석하기

이어서 queue_work_on() 함수 코드를 분석해봅시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
01 bool queue_work_on(int cpu, struct workqueue_struct *wq,
02    struct work_struct *work)
03 {
04 bool ret = false;
05 unsigned long flags;
06
07 local_irq_save(flags);
08
09 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }
13
14 local_irq_restore(flags);
15 return ret;
16}

먼저 7번째 줄 코드를 보겠습니다.
07 local_irq_save(flags);

7~14번째 줄 코드 구간에서 워크를 큐잉하는 9~12번째 줄 코드 구간 실행 도중에 해당 CPU라인 인터럽트를 비활성화합니다. 이 코드의 목적은 다음과 같습니다. 

    해당 코드를 실행하는 도중 인터럽트가 발생하는 동기화 문제 발생을 방지하고 싶다. 

커널 코드는 언제든 인터럽트가 발생해서 실행 흐름이 멈출 수 있습니다.

이어서 9번째 줄 코드를 보겠습니다. 
09 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }

work_data_bits(work) 매크로 함수는 struct work_struct 구조체 주소에서 data 필드를 읽습니다. 이 값이 WORK_STRUCT_PENDING_BIT(1)이면 9~12 번째 줄 코드를 실행하지 않고 바로 14 번째 줄 코드를 실행합니다.

test_and_set_bit() 함수는 리눅스 커널 자료구조 함수 중 하나입니다.
test_and_set_bit(A, B); 와 같이 호출하면 A와 B란 변수 비트를 AND 연산한 다음 결과가 1이면 1을 반환하고 반대로 0이면 0을 반환합니다. 연산 결과에 상관없이 B에 A비트를 설정합니다.

이해를 돕기 위해 9~12째 줄 코드를 다른 코드로 쉽게 표현하면 다음과 같습니다.
1 if (work->data == WORK_STRUCT_PENDING_BIT) {
3 } else
4 work->data =| WORK_STRUCT_PENDING_BIT;
5 __queue_work(cpu, wq, work);
6 ret = true;
7 }

work->data가 WORK_STRUCT_PENDING_BIT이면 if 문을 만족하니 2번째 줄 코드로 이동합니다. 그런데 2번째 줄에는 코드가 없으니 아무 동작을 안 하고 if 문을 빠져나옵니다. 대신 work->data가 WORK_STRUCT_PENDING_BIT 가 아니면 else문을 실행합니다. 

if 문 조건을 만족하면 아무 동작을 안 하는 코드는 보기 이상하니 if 문을 ! 조건으로 바꿔 코드를 작성하면 다음과 같습니다.
if ( !(work->data == WORK_STRUCT_PENDING_BIT)) {
work->data =| WORK_STRUCT_PENDING_BIT;
__queue_work(cpu, wq, work);
ret = true;
}

work->data 필드가 WORK_STRUCT_PENDING_BIT 플래그가 아니면 work->data에 WORK_STRUCT_PENDING_BIT를 저장하고 __queue_work() 함수를 호출하는 것입니다.

test_and_set_bit() 함수를 다른 코드로 바꿔서 설명을 드렸습니다. test_and_set_bit() 함수는 위에 바꾼 코드와 같이 struct work_struct구조체 data 필드가 WORK_STRUCT_PENDING_BIT가 아니면 struct work_struct 구조체 data 필드를 WORK_STRUCT_PENDING_BIT로 설정하고 0을 반환합니다.

반대로 struct work_struct 구조체 data 필드가 WORK_STRUCT_PENDING_BIT 플래그면 data 필드를 WORK_STRUCT_PENDING_BIT 플래그로 설정한 후 1을 반환합니다.

여기서 한 가지 의문이 생깁니다. 

    struct work_struct 구조체 data 필드가 WORK_STRUCT_PENDING_BIT인지 왜 
   점검하는 이유는 무엇일까?

struct work_struct 구조체 data 필드에 워크 실행 상태가 저장돼있기 때문입니다. queue_work_on() 함수 호출로 워크를 워크큐에 큐잉하기 직전에 이 필드는 WORK_STRUCT_PENDING_BIT 플래그로 바꿉니다. 

만약 워크를 워크큐에 큐잉하기 직전에 이 필드가 WORK_STRUCT_PENDING_BIT이라면 이를 어떻게 해석해야 할까요?  

    이미 워크를 워크에 큐잉한 상태로 볼 수 있습니다. 

이 조건에서는 워크를 워크큐에 큐잉하지 않습니다. 워크를 워크큐에 중복 큐잉할 때를 대비한 예외 처리 코드입니다.


커널은 디바이스 드라이버에서 중복 코드를 실행할 경우 시나리오를 생각해서 예외 처리를 수행합니다.



1 2 3 4 5 6 7 8 9 10 다음