| 가상 메모리, Page fault handling

 

[정리]

1. 주제

- Page fault는 왜 발생하는 것이며, page fault 발생시 kernel은 어떻게 동작 하는가

2. 전체 내용 요약

1) 이론 필기 내용 요약

2) page fault tracing 실습

3) page fault handler 과정 확인

 

 

[1) 이론 필기 내용 요약]

OS에선 physical memory를 할당 하는 것이 아닌, virtual memory를 할당 함

page fault는 VA <-> PA 매핑이 되어 있지 않기 떄문에 발생하는 것

Segmentation fault 또한 page fault의 일종

 

Virtual address를 사용시 장점

1) ABI (Application Binary Interface)

- OS, CPU, 컴파일러 등이 함께 약속된 규약 (SYSCALL, clling convension, VA, ELF ..)

- ABI를 적용함으로써 binary만 제공되어도 바로 사용이 가능한 것이다.

2) 메모리 절약

3) 보안

4) 메모리 확장 (swap disk 활용)

- file system format과는 전혀 관계 없음

 

$ ps -eo pdi,comm,vsz,rss | head -2

현재 동작중인 task에 대해 pid, command, vsz (virtual memory size), rss (resident set size)에 대해 확인

vsz는 virtual memory size를 나타내므로, 실제 task가 .bss, .data, .text 등 모든 section을 memory에 loading할시에 필요한 메모리 크기를 나타냄

 

[2) page fault tracing 실습]

kernel영역에서 발생하는 page fault에 대해 tracing

# cd /sys/kernel/debug/tracing/
# echo 1 > events/exceptions/page_fault_kernel/enable
# cat trace_pipe

(밑의 그림에서는 page_fault_kernel로 동작해 결과를 보여주지만,, 아래의 내용들은 kernel / user 유사한 부분이 많음)

address : page fault가 발생한 가상 주소

ip : page faule가 발생 했을 시점의 instruction 주소

error code :

$ vim ${linux}/arch/x86/include/asm/traps.h

위에서 발생한 error code=0x2의 의미는

error code : 0x2 = no page found + write + kernel mode access

커널 모드로 write를 수행하는데 page가 존재하지 않아 page fault가 발생함

 

이번에는 page fault가 발생한 부분에 대해 stacktrace를 사용해 call stack을 확인

기존 trace point 위치에서 실행
# echo 1 > options/stacktrace
# cat trace_pipe

copy_to_user 수행시 kernel mode에서 user 영역에 접근해 data를 write했을시 user 영역 주소가 존재하지 않아 page fault가 발생한 것임

보통 kernel 주소에 대해 page fault가 발생하는 경우는 거의 없음

 

[3) page fault handler 과정 확인]

$ vim ${linux}/arch/x86/entry/entry_64.S

page fault 발생시 page_fault가 호출되며, do_page_fault가 다시 호출되어 처리가 됨

이때, has_error_code, read_cr2는 밑의 do_page_fault함수의 argument (error_code = has_error_code, address = read_cr2)가 됨

cr2는 page fault를 일으킨 가상주소를 저장 함

$ vim ${linux}/arch/x86/mm/fault.c

page_fault -> do_page_fault -> __do_page_fault -> do_user_addr_fault의 순서로 동작

(참조: 수업 중 do_kern_addr_fault이 아닌 do_user_addr_fault에 대해서만 진행 됨)

task가 접근한 page fault 주소를 알고 있으며, 해당 address에 대해 task_struct -> mm_struct -> vma 영역 (text, stack, heap,...) 중 어느 영역에서 page fault가 발생 했는지 확인

handle_mm_fault -> .. -> __handle_mm_fault에서 VA-PA 변환테이블을 수정 함

조금 더 자세한 내용들은 뒤에 연속적으로 나옴

__handle_mm_fault -> handle_pte_fault -> do_anonymous_page에서 PA를 위한 page를 할당 받음

page 할당 이후 실제 page table entry 요소에 대해 업데이트 수행

 

위의 과정에 대해 다시 정리하자면

do_user_addr_fault()   : arch/x86/mm/fault.c

  -> handle_mm_fault()    :   mm/memory.c

      -> __handle_mm_fault()    :   mm/memory.c

          -> handle_pte_fault()     :   mm/memory.c

               -> ptep_set_access_flags()   :  arch/x86/mm/pgtable.c

                    -> set_pte() “페이지 테이블 한요소(pte) 수정”

 

 

| uftrace를 통해서 user + lib + kernel 호출과정 추적하기

[정리]

1. 주제

- uftrace를 통해 mv 명령의 시스템콜 rename 호출과정을 탐색

2. 전체 내용 요약

1) uftrace record 기능을 사용해 mv 동작시 발생하는 call trace 수집

2) uftrace replay 기능을 사용해 전체 호출 과정 확인

3) mv 동작을 위해 사용되는 rename syscall에 대한 호출 과정 확인 

 

[1) uftrace record 기능을 사용해 mv 동작시 발생하는 call trace 수집]

$ touch file # mv 동작을 하기 위한 file을 생성
$ sudo uftrace record --force -K 5 /bin/mv file file-mv # file에 대해 file-mv로 이름 변경

uftrace record 동작 수행시 trace 관련 파일들이 uftrace.data 경로에 생성됨

 

[2) uftrace replay 기능을 사용해 전체 호출 과정 확인]

해당 trace 파일들을 이용해 reaplay 수행을 하기 위해선 아래 명령어를 수행 해야 함

기타 옵션들이 추가적으로 많이 존재함 ex) function filtering, time resolution

tracing 과정에서 다른 함수들도 포함될 수 있으며, 시간 값 조절 및 filtering을 통해 원하는 함수의 내용을 추출 할 수 있음

$ uftrace replay -t 2ms

함수 콜 내용을 보면 application (/bin/mv)에서 rename을 호출하고 rename은 system call, vfs call 등...을 수행해 결국 file의 이름을 변경 함

 

[3) mv 동작을 위해 사용되는 rename syscall에 대한 호출 과정 확인]

/bin/mv는 여러가지 .so 파일들이 연결되어 있으며 이를 ldd(List Dynamic Dependencies)를 통해 확인 가능함

rename syscall 또한 연결된 .so 파일 중 하나 (libc.so.6)에 존재함

$ ldd /bin/mv

연결된 library 중 libc.so.6 내에서 rename이 구현된 것을 확인

$ objdump -d /lib/x86_64-linux-gnu/libc.so.6 | less
less 화면 상에서 rename 탐색

/bin/mv는 libc.so.6내 존재하는 rename을 호출하며, libc.so.6내 존재하는 rename은 위의 disassemble코드내 두번째 줄에 작성된 것 처럼 syscall을 호출함

system call table내 존재하는 vector table을 존재하기 위해 nr 값이 필요하며, 이 nr 값은 여기에서 보이는 0x52 값임

nr = 0x52 = 82, 82에 위치한 vector table을 접근해 syscall을 수행함

$ vim /usr/include/x86_64-linux-gnu/asm/unistd_64.h

Syscall이 호출되는 되는 과정에 대해 살펴 보자

$ vim ${linux}/arch/x86/entry/entry_64.S

x86_64 CPU이며, 175번째 위치한 do_syscall_64를 호출함으로써 system call을 호출하게 됨

$ vim ${linux}/arch/x86/entry/common.c

do_syscall_64가 호출되어 common.c::do_syscall_64를 호출하며, sys_call_table[nr]을 통해 table을 호출하게 됨

앞에서 언급했던 rename의 nr은 0x52 = 82이며, sys_call_table[82]에 rename 함수 포인터가 세팅되어 있음

 

rename 정의를 쉽게 찾기 위해 아래 명령을 수행

$ ag SYSCALL_DEFINE | grep rename

fs/namei.c:4671에 rename에 대한 syscall이 정의 되어 있으며, 확인 하면 아래와 같으며, do_renameat2()에서 vfs_rename을 호출하게 됨

vfs_rename 내에선 error = old_dir->i_op->rename() 부분에서 file system의 rename call을 수행하게 되고 ext4와 연결됨

 



출처: https://doitnow-man.tistory.com/102 [즐거운인생 (실패 또하나의 성공)]

struct nvme_dev {
	struct nvme_queue *queues;
	struct blk_mq_tag_set tagset;
	struct blk_mq_tag_set admin_tagset;
	u32 __iomem *dbs;
	struct device *dev;
	struct dma_pool *prp_page_pool;
	struct dma_pool *prp_small_pool;
	unsigned online_queues;
	unsigned max_qid;
	int q_depth;
	u32 db_stride;
	void __iomem *bar;
	unsigned long bar_mapped_size;
	struct work_struct remove_work;
	struct mutex shutdown_lock;
	bool subsystem;
	void __iomem *cmb;
	pci_bus_addr_t cmb_bus_addr;
	u64 cmb_size;
	u32 cmbsz;
	u32 cmbloc;
	struct nvme_ctrl ctrl;
	struct completion ioq_wait;

	/* shadow doorbell buffer support: */
	u32 *dbbuf_dbs;
	dma_addr_t dbbuf_dbs_dma_addr;
	u32 *dbbuf_eis;
	dma_addr_t dbbuf_eis_dma_addr;

	/* host memory buffer support: */
	u64 host_mem_size;
	u32 nr_host_mem_descs;
	dma_addr_t host_mem_descs_dma;
	struct nvme_host_mem_buf_desc *host_mem_descs;
	void **host_mem_desc_bufs;
};

+ Recent posts