BCC probe 함수 추가해보기

TAEWOO HA·2023년 6월 28일
0

프로젝트 #2

목록 보기
3/8

Page fault 에 대해서

• Process 는 모두 자기가 많은 양의 메모리를 가지고 있다고 알고있음
(Virtual memory)
• 그래서 Process 가 자기가 가진 메모리 공간에 접근하려고 하면
Linux kernel 은 해당 메모리 주소를 physical memory 주소로 변환
해서 process 가 접근하게함
• 이 때, 이 physical memory 에 대한 접근이 불가능 할 경우 page
fault event 를 발생시키게됨.

• 이 과정에서 2가지 종류의 page fault 가 존재,
• Minor(or Soft) fault - 요청한 페이지(메모리)가 Dram 에 있지만,
테이블 엔트리가 아직 변경되지 않아서 접근 불가능한 상태
• Major(or Hard) fault - 요청한 페이지가 메모리에 없어서 디스크에
서 불러와야하는 상황. 이 경우, 페이지를 물리 메모리로 로드해야 하
므로 시간이 더 오래걸림

• 그럼 TLB 랑 메모리위의 테이블 엔트리랑 뭐가 달라요?
• Linux 의 Dram 위의 페이지 테이블은 여러 스탭을 거치도록 구현되
어있음. (pgd_t, pud_t, pmd_t, pte_t)
• 그렇다면 TLB(Translation Lookaside Buffer) 는?
• 가장 최근에 참조된 페이지 테이블의 데이터를 기록하는 캐시
• 페이지 테이블을 조회하는 횟수를 줄여줌

  • 페이지테이블엔트리 : 프로세스의 가상메모리를 물리메모리로 매핑하는 데이터구조
  • 엔트리 => 하나의 가상 페이지에 해당하는 물리페이지 매핑
  • 페이지 테이블 전체를 메모리에 저장하면 공간이 많이 필요, 변환시 시간이 오래걸림
    • 하드웨어의 도움을 받음 => TLB

Slub allocator

• 리눅스의 메모리 할당자 중 하나
• 효율과 성능을 최적화 하려는 목적으로 개발됨
• Slab allocator 과 buddy allocator 를 사용해서 개발

• 5단계의 과정을 거쳐서 메모리를 할당함
• Local fast path -> per-cpu free-list // free list가 없어지면 Local Free를 찾는다
• Local free -> per-cpu free page // 상위레벨 free list로 올려줌, 메모리 할당을 빠르게 리턴
• Local partial -> per-cpu slab // 슬랩에서 다쓰고 남은 메모리를 정리해서 위로 올려서 캐시처럼 다시 사용할 수 있게
• Remote free -> Check other nodes // 한 소켓을 node라 함 , 안쓴거 남은거 가져옴. 1,2,3,4단계가 버디단계보다 빠르다.
• Buddy

  • __ : 좀 더 조심스럽게 다뤄줘야함. 바깥에서 보면 안되는 function

목표

  • 물리 주소를 알아보자

  • fault 난 곳의 주소를 찾고싶다.

  • handle_mm_fault 에서 불렀다.

  • pte_fault
    ==> 이것을 보자. BCC

  • not traceble : 따라갈 수 없다. => 존재하지 않거나(x) inlined(x) , or notrace
  • marked as trace로 만들어야함.

• Inlined
• 컴파일시에 직접 삽입되어서 심볼로 존재하지를 않음
• 심볼을 통해 probing 이 불가능

실습

import time

MB = 1 & int(1e6)

#1GB ~ 1e9B ~ 10MB * 100
blocks = 100;

memory_blocks = []

for _ in range(blocks):
    memory_blocks.append(bytearray(MB))

print("1GB of memory has been allocated.")
  • 1GB를 10MB를 나눠서 메모리블럭으로 사용하는 app을 실행 후 프로파일링
from bcc import BPF

prog = """
#include <linux/sched.h>
#include <linux/mm.h>
BPF_HASH(start, u32);
BPF_PERF_OUTPUT(events);
struct data_t {
    u32 pid;
    u32 cpu;
    char comm[16];
    unsigned long address;
    unsigned int flags;
};
int kprobe__handle_mm_fault(struct pt_regs *ctx, struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, unsigned int flags) {
    u32 pid = bpf_get_current_pid_tgid();
    u32 cpu = bpf_get_smp_processor_id();
    u64 ts = bpf_ktime_get_ns();
    struct data_t data = {};
    bpf_get_current_comm(&data.comm, sizeof(data.comm));
    for (int i = 0; i < sizeof(data.comm); i++) {
        if (data.comm[i] == 'p' && data.comm[i + 1] == 'y' && data.comm[i + 2] == 't' && data.comm[i + 3] == 'h' && data.comm[i + 4] == 'o' && data.comm[i + 5] == 'n') {
            data.pid = pid;
            data.cpu = cpu;
            data.address = address;
            data.flags = flags;
            events.perf_submit(ctx, &data, sizeof(data));
            start.update(&pid, &ts);
            break;
        }
    }
    return 0;
}
"""

b = BPF(text=prog)

def print_event(cpu, data, size):
    event = b["events"].event(data)
    print("PID: %d, CPU: %d, Comm: %s, Address: %lx, Flags: %s" % (event.pid, event.cpu, event.comm, event.address, format_flags(event.flags)))

def format_flags(flags):
    flags_str = []
    if flags & 0x01: flags_str.append("FAULT_FLAG_ALLOW_RETRY")
    if flags & 0x02: flags_str.append("FAULT_FLAG_RETRY_NOWAIT")
    if flags & 0x04: flags_str.append("FAULT_FLAG_KILLABLE")
    if flags & 0x08: flags_str.append("FAULT_FLAG_USER")
    if flags & 0x10: flags_str.append("FAULT_FLAG_REMOTE")
    if flags & 0x20: flags_str.append("FAULT_FLAG_TRIED")
    if flags & 0x40: flags_str.append("FAULT_FLAG_ALLOW_ASYNC")
    if flags & 0x80: flags_str.append("FAULT_FLAG_MKWRITE")
    if flags & 0x100: flags_str.append("FAULT_FLAG_NO_KILL")
    return ", ".join(flags_str)

b["events"].open_perf_buffer(print_event)

print("Tracing handle_mm_fault for python processes... Ctrl-C to end.")
while True:
    try:
        b.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

  • 결과

  • DRAM에 페이지가 연결 , hadle_mm_fault를 확인하자.
    ==> real address를 확인 가능하다.

from bcc import BPF

bpf_text = """
#include <linux/sched.h>
#include <linux/mm_types.h>
#include <linux/mm.h>

struct data_t {
    u32 pid;
    u64 cpu;
    u64 addr;
    u64 pgoff;
    char comm[TASK_COMM_LEN];
};

BPF_PERF_OUTPUT(events);

int kprobe__handle_pte_fault(struct pt_regs *ctx, struct vm_fault *vmf) {
    struct data_t data = {};
    u64 pid_tgid = bpf_get_current_pid_tgid();
    data.pid = pid_tgid >> 32;
    bpf_get_current_comm(&data.comm, sizeof(data.comm));
    if (data.comm[0] == 'p' && data.comm[1] == 'y' && data.comm[2] == 't' && data.comm[3] == 'h' && data.comm[4] == 'o' && data.comm[5] == 'n') {
        data.cpu = bpf_get_smp_processor_id();
        data.addr = vmf->address;
        data.pgoff = vmf->pgoff;
        events.perf_submit(ctx, &data, sizeof(data));
    }
    return 0;
}
"""

bpf = BPF(text=bpf_text)
bpf.attach_kprobe(event="handle_pte_fault", fn_name="kprobe__handle_pte_fault")

def print_event(cpu, data, size):
    event = b["events"].event(data)
    print(f"CPU: {event.cpu}, PID: {event.pid}, Comm: {event.comm}, Address: {hex(event.addr)}, Pgoff: {event.pgoff}")

b["events"].open_perf_buffer(print_event)

while True:
    try:
        bpf.perf_buffer_poll()
    except KeyboardInterrupt:
        exit()

  • not traceble : 따라갈 수 없다. => 존재하지 않거나(x) inlined(x) , or notrace
  • marked as trace로 만들어야함.
  • 함수의 심볼을 export 해주면 될까?
    • 함수의 static을 지워줘야한다. (커널 파일 수정)
    • 유저가 볼 수 있는 헤더에 추가해줘야한다. (커널 헤더파일 수정)

==> 물리 메모리에 대해서도 알 수 있다.

0개의 댓글