Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

76261
1501
219118


[Linux][Kernel]메모리 디스크립터 - struct mm_struct 구조체 소개 [Linux][Kernel] MM


리눅스 커널에서 돌고 있는 프로세스는 크게 2가지로 분류할 수 있습니다.
유저 공간과 커널 공간에서 생성된 생성된 프로세스들이죠.

유저 공간에서 생성된 프로세스들은 유저 영역 가상 메모리 공간에서 실행됩니다. 이 가상 메모리에 대한 정보는 struct mm_struct 자료구조 구조체로 표현하며 해당 프로세스 태스트 디스크립터에 mm란 멤버로 존재하죠. 
(참고로 여기서 말하는 가상 메모리는 커널 공간에서 실행하는 물리/가상 메모리 개념은 아닙니다. )

그럼 태스크 디스크립터를 한번 열어볼까요? 아래 화살표 친 부분을 보면 역시 mm이란 멤버가 있네요.
  (struct task_struct *) (struct task_struct*)0xE9AFCFC0 = 0xE9AFCFC0 -> (
    (long int) state = 0x1,
    (void *) stack = 0xE8DE2000,
    (atomic_t) usage = ((int) counter = 0x2),
//..
    (struct mm_struct *) mm = 0xEA621880,  //<<--
    (struct mm_struct *) active_mm = 0xEA621880,

그럼 (struct mm_struct *)  구조체 멤버를 실제 코어 덤프에서 살펴볼까요?
상세 내용은 아래 주석문을 참고하세요.
  (struct mm_struct *) (struct mm_struct *)0xEA621880 = 0xEA621880 -> (
    (struct vm_area_struct *) mmap = 0xE8D6BEC8, // 메모리 영역 리스트
    (struct rb_root) mm_rb = ((struct rb_node *) rb_node = 0xEA9C6F30),  // VMA rb 트리,
    (u32) vmacache_seqnum = 0x0270,
    (long unsigned int (*)()) get_unmapped_area = 0xC0117040,
    (long unsigned int) mmap_base = 0xAB9AC000,
    (long unsigned int) mmap_legacy_base = 0x0,
    (long unsigned int) task_size = 0xBF000000, 
    (long unsigned int) highest_vm_end = 0xBEBDD000,
    (pgd_t *) pgd = 0xE8FC0000,
    (atomic_t) mm_users = ((int) counter = 0x7),  // 이 주소 공간을 사용하는 프로세스의 갯수
    (atomic_t) mm_count = ((int) counter = 0x2), // mm_struct 구조체의 주 참조 횟수
    (atomic_long_t) nr_ptes = ((int) counter = 0x1E),
    (int) map_count = 0x93,  // 메모리 영역 갯수
    (spinlock_t) page_table_lock = ((struct raw_spinlock) rlock // 페이지 테이블 락
    (struct rw_semaphore) mmap_sem = ((long int) count = 0x0, (struct list_head) wait_list = ((struc
    (struct list_head) mmlist = ((struct list_head *) next = 0xEABAD1E8, // 전체 mm_struct 리스트
    (long unsigned int) hiwater_rss = 0x25A6,
    (long unsigned int) hiwater_vm = 0x360E,
    (long unsigned int) total_vm = 0x3507,  // 전체 vm 메모리 사용량
    (long unsigned int) locked_vm = 0x0,
    (long unsigned int) pinned_vm = 0x0,
    (long unsigned int) shared_vm = 0x03B3,
    (long unsigned int) exec_vm = 0x022C,
    (long unsigned int) stack_vm = 0x21,
    (long unsigned int) def_flags = 0x0,
    (long unsigned int) start_code = 0x7F555000, // 코드의 시작주소
    (long unsigned int) end_code = 0x7F566B9E, // 코드의 마지막 주소
    (long unsigned int) start_data = 0x7F568A7C, // 데이터의 시작주소
    (long unsigned int) end_data = 0x7F569009,  // 데이터의 끝주소
    (long unsigned int) start_brk = 0x7FDB5000,
    (long unsigned int) brk = 0x7FDB5000,
    (long unsigned int) start_stack = 0xBEAE1960, //스택의 시작주소
    (long unsigned int) arg_start = 0xBEAE1A61, // argument 가 있는 시작 주소
    (long unsigned int) arg_end = 0xBEAE1A72,
    (long unsigned int) env_start = 0xBEAE1A72,
    (long unsigned int) env_end = 0xBEAE1FEB,
    (long unsigned int [42]) saved_auxv = (0x10, 0x003FB0D6, 0x6, 0x1000, 0x11, 0x64, 0x3, 0x7F55503
    (struct mm_rss_stat) rss_stat = ((atomic_long_t [3]) count = (((int) counter = 0x9E), ((int) cou
    (struct linux_binfmt *) binfmt = 0xC1A2A118,
    (cpumask_var_t) cpu_vm_mask_var = (((long unsigned int [1]) bits = (0xFF))),
    (mm_context_t) context = ((atomic64_t) id = ((long long int) counter = 0x000159B0), (unsigned in
    (long unsigned int) flags = 0x8C, //상태 플래그
    (struct core_state *) core_state = 0x0,
    (spinlock_t) ioctx_lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u32) sl
    (struct kioctx_table *) ioctx_table = 0x0,
    (struct file *) exe_file = 0xE8CFB000,
    (bool) tlb_flush_pending = FALSE,
    (struct uprobes_state) uprobes_state = ())

그리고 당연한 이야기지만 커널 쓰래드는 task_struct 구조체 안의 mm이 NULL이라는 점도 기억하세요.
다음은 워커쓰레드의 태스크 디스크립터입니다. 
  (struct task_struct *) (struct task_struct*)0xE8C595C0 = 0xE8C595C0 -> (
    (long int) state = 0x1,
    (void *) stack = 0xE8D6E000,
    (atomic_t) usage = ((int) counter = 0x2),
 //...
    (struct rb_node) pushable_dl_tasks = ((long unsigned int) __rb_parent_color = 0xE8C5990C, (struc
    (struct mm_struct *) mm = 0x0,
    (struct mm_struct *) active_mm = 0x0,
    (unsigned int:1) brk_randomized = 0x0,
 //...
    (struct cred *) real_cred = 0xEAAF9E80,
    (struct cred *) cred = 0xEAAF9E80,
    (char [16]) comm = "kworker/2:1H",
    (int) link_count = 0x0,
    (int) total_link_count = 0x0,

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

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

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

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




핑백

덧글

  • ckim 2020/08/12 19:04 # 삭제 답글

    안녕하세요, 위에서 active_mm은 어떻게 사용되나요? context switching을 빠르게 하기 위한 거라고, previous task의 mm값이라고 어디 써 있던데요.. (왜 process queue에 있는 previous task의 mm값을 기억하고 있는지..)
  • AustinKim 2020/08/14 09:57 #

    아래 패치 코드를 참고하시면 좋겠습니다.
    https://lkml.org/lkml/2019/7/29/561

    commit 139d025cda1da5484e7287b35c019fe1dcf9b650
    Author: Peter Zijlstra <peterz@infradead.org>
    Date: Mon Jul 29 16:05:15 2019 +0200

    sched: Clean up active_mm reference counting

    The current active_mm reference counting is confusing and sub-optimal.

    Rewrite the code to explicitly consider the 4 separate cases:
    ...
    --- a/kernel/sched/core.c
    +++ b/kernel/sched/core.c
    @@ -3214,12 +3214,8 @@ static __always_inline struct rq *
    context_switch(struct rq *rq, struct task_struct *prev,
    struct task_struct *next, struct rq_flags *rf)
    {
    - struct mm_struct *mm, *oldmm;
    -
    prepare_task_switch(rq, prev, next);

    - mm = next->mm;
    - oldmm = prev->active_mm;
    /*
    * For paravirt, this is coupled with an exit in switch_to to
    * combine the page table reload and the switch backend into
    @@ -3228,22 +3224,37 @@ context_switch(struct rq *rq, struct task_struct *prev,
    arch_start_context_switch(prev);

    /*
    - * If mm is non-NULL, we pass through switch_mm(). If mm is
    - * NULL, we will pass through mmdrop() in finish_task_switch().
    - * Both of these contain the full memory barrier required by
    - * membarrier after storing to rq->curr, before returning to
    - * user-space.
    + * kernel -> kernel lazy + transfer active
    + * user -> kernel lazy + mmgrab() active
    + *
    + * kernel -> user switch + mmdrop() active
    + * user -> user switch
    */
    - if (!mm) {
    - next->active_mm = oldmm;
    - mmgrab(oldmm);
    - enter_lazy_tlb(oldmm, next);
    - } else
    - switch_mm_irqs_off(oldmm, mm, next);
    -
    - if (!prev->mm) {
    - prev->active_mm = NULL;
    - rq->prev_mm = oldmm;
    + if (!next->mm) { // to kernel
    + enter_lazy_tlb(prev->active_mm, next);
    +
    + next->active_mm = prev->active_mm;
    + if (prev->mm) // from user
    + mmgrab(prev->active_mm);
    + else
    + prev->active_mm = NULL;
    + } else { // to user
    + /*
    + * sys_membarrier() requires an smp_mb() between setting
    + * rq->curr and returning to userspace.
    + *
    + * The below provides this either through switch_mm(), or in
    + * case 'prev->active_mm == next->mm' through
    + * finish_task_switch()'s mmdrop().
    + */
    +
    + switch_mm_irqs_off(prev->active_mm, next->mm, next);
    +
    + if (!prev->mm) { // from kernel
    + /* will mmdrop() in finish_task_switch(). */
    + rq->prev_mm = prev->active_mm;
    + prev->active_mm = NULL;
    + }
    }

    rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);
  • 2020/08/14 09:57 # 답글 비공개

    비공개 덧글입니다.
  • ckim 2020/09/01 18:12 # 삭제

    안녕하세요, 위에 보여주신 patch는 너무 복잡해서 볼 엄두가.. :)
    https://www.kernel.org/doc/html/latest/vm/active_mm.html 에 보니까 mm_active 값은 대략 현재의 kernel thread 가 돌기 전의 user process가 사용하던 mm_struct 를 가리키는 것 같네요. kernel thread인 경우 tsk->mm값이 NULL인 것도 지금 알았습니다. 아이고 어렵다..
댓글 입력 영역