- Model: Samsung Galaxy S22
- Application Binary Interface (ABI): arm64-v8a
# enable tracing f2fs
for f in /sys/kernel/tracing/events/f2fs/*/enable; do echo 1 > "$f"; done
# enable tracing ext4
for f in /sys/kernel/tracing/events/ext4/*/enable; do echo 1 > "$f"; done
# enable tracing block I/O
for f in /sys/kernel/tracing/events/block/*/enable; do echo 1 > "$f"; done
# pid – ppid
echo 1 > /sys/kernel/tracing/events/sched/sched_process_fork/enable
# TGID (Thread Group ID)
echo 1 > /sys/kernel/tracing/options/record-tgid- [Bash / PowerShell] 시작
adb shell "echo 1 > /sys/kernel/tracing/tracing_on" adb exec-out su -c "cat /sys/kernel/tracing/trace_pipe" > ftrace_live.txt
- [ADB Shell] 중지
echo 0 > /sys/kernel/tracing/tracing_on
- [Bash / PowerShell]
조금 기다렸다가
Ctrl + C로 파일 스트리밍 중지
Note
/sys/kernel/tracing/tracing_on을 끄지 않은 상태로 [Bash / PowerShell] 에서 Ctrl + C로 중지하면 trace의 끝부분이 로그파일에 다 적히지 않은채로 중지되어 버리니 주의
* 해당 방식은 로그파일(/data/local/tmp/ftrace_live.txt)에 write 하는 것까지 전부 기록해서 로그파일 크기가 기하급수적으로 커지는 문제 발생. pid 지정 등 다른 방식을 함께 사용하는 것 고려하기
# 시작 (백그라운드)
echo 1 > /sys/kernel/tracing/tracing_on
cat /sys/kernel/tracing/trace_pipe > /data/local/tmp/ftrace_live.txt &
# 정지 (안전하게 멈추기)
echo 0 > /sys/kernel/tracing/tracing_on # 먼저 기록 중단
pkill -f "cat /sys/kernel/tracing/trace_pipe" # cat 프로세스 종료
# 파일 가져오기
adb pull /data/local/tmp/ftrace_live.txt .python main.py -t ftrace_live.txt -o systrace_split.csv- [Bash / PowerShell] inode 목록 파일을 Android 디바이스에 넣기
adb push ./inodes.csv /data/local/tmp/ftrace-log/
- [ADB Shell] 다음과 같이 shell script 작성 후 실행
INPUT_FILE='/data/local/tmp/ftrace-log/inodes.csv' DELIMITER=',' OUTPUT_FILE='/data/local/tmp/ftrace-log/file_inode.csv' > "$OUTPUT_FILE" # Leave blank before running for line in `cat $INPUT_FILE` do IFS=$DELIMITER read -r MAJ MIN INO <<< "$line" SU_CMD='MP=$(awk -v mm="'"${MAJ}:${MIN}"'" '"'"'$3==mm{print $5; exit}'"'"' /proc/self/mountinfo); su -c "find \"$MP\" -xdev -inum \"$INO\" -print 2>/dev/null"' FILENAME=$(eval "$SU_CMD" | head -n 1) if [[ $INO == "ino" ]] then FILENAME="filename" # 못 찾은 경우: /proc/*/fd 전체 스캔 (삭제된 파일도 표시됨) elif [ -z "$FILENAME" ] then FILENAME=$(su -c "ls -l /proc/*/fd/* 2>/dev/null | grep 'inode $INO ' | head -n 1 | awk '{print \$NF}'") if [ -z "$FILENAME" ] then FILENAME="NOT_FOUND" fi fi echo "$MAJ,$MIN,$INO,$FILENAME" >> "$OUTPUT_FILE" done
- [Bash / PowerShell] PC로 파일 빼내기
adb pull /data/local/tmp/ftrace-log/file_inode.csv .
예) dev_major: 254, dev_minor: 48, inode: 89120 일 때
MAJ=254; MIN=48; INO=89120
MP=$(awk -v mm="${MAJ}:${MIN}" '$3==mm{print $5; exit}' /proc/self/mountinfo) \
&& echo "mountpoint: $MP" \
&& su -c "find '$MP' -xdev -inum '$INO' -print 2>/dev/null"예) '.google.android.tts', '/system/fonts/' 를 제외한 다른 파일 접근은 필터링
python main.py -t ftrace_live.txt -o systrace_split.csv -i file_inode.csv -f .google.android.tts /system/fonts/1. Linux Virtual File System (https://www.kernel.org/doc/html/next/filesystems/vfs.html)
cf. Block IO Queueing Mechanism (blk-mq) (https://www.kernel.org/doc/html/next/block/blk-mq.html)
read/write/mmap(syscall)
↓
VFS ──(공통 인터페이스 호출: fops/aops 등)──→ ext4 또는 f2fs (구체 구현)
↓
페이지 캐시 / 블록 레이어
↓
저장장치
- fops (
file_operations) : file descriptor 레벨 진입점.open/llseek/read_iter/write_iter/mmap/fsync/ioctl/fallocate등이 핵심 - aops (
address_space_operations) : 페이지 캐시 레벨.readpage/write_start/write_end같은 콜백이 핵심- F2FS +v6.17-rc1
- Read Page: f2fs_readpage, f2fs_readpages (+read_folio, readahead)
- Write Page: f2fs_write_end
- Direct I/O: f2fs_direct_IO_exit
- ext4 Delayed Allocation +aops, v6.17-rc1, Delayed Allocation, v6.17-rc1
- Read Page: ext4_readpage, ext4_readpages
- Write Page: ext4_write_end, ext4_da_write_end
- Direct I/O: ext4_direct_IO_exit
- F2FS +v6.17-rc1
- iops(
inode_operations) : 링크/디렉터리 조작 같은 메타데이터 중심 - sops(
super_operations) : 마운트/슈퍼블록 관리 (fops의 상위/동일 레벨)
%%{init: {
"theme": "base",
"themeVariables": {
"edgeLabelBackground": "#FFFFFF"
}
}}%%
flowchart LR
%% 1) Row alignment links (put first so they are link 0,1,2)
BR --- BW --- MR --- MW
%% 2) Hide those three links
linkStyle 0 stroke-width:0px,stroke:transparent
linkStyle 1 stroke-width:0px,stroke:transparent
linkStyle 2 stroke-width:0px,stroke:transparent
classDef later stroke-dasharray: 5 3;
%% ========= Buffered Read =========
subgraph BR[Buffered Read]
direction TB
A0["**read(2)**"] --> A1["**fops.read_iter**"] --> A2{"Cache miss? <br/> (Page is not in cache?)"}
A2 -- Yes --> A3["**aops.read_folio / aops.readpage(s)**"] --> A4["return from page cache"]
A2 -- No --> A4
end
%% ========= Buffered Write =========
subgraph BW[Buffered Write]
direction TB
B0["**write(2)**"] --> B1["**fops.write_iter**"] --> B2["**aops.write_begin**"]
B2 --> B3["copy: user buffer -> page cache"] --> B4["**aops.write_end**"]
B4 --> B5["**aops.dirty_folio / aops.set_page_dirty** <br/>to mark the page as dirty"] -.-> B6["later: **aops.writepage(s)** for writeback"]
class B6 later;
end
%% ========= mmap Read =========
subgraph MR[MMAP Read]
direction TB
C0["page fault"] --> C1["**vm_ops.fault** <br/>(ex: f2fs_filemap_fault)"] --> C2{"Cache miss?"}
C2 -- Yes <br/> (major fault; load page) --> C3["**aops.read_folio / aops.readpage(s)**"] --> C4["access mapped page"]
C2 -- No <br/> (minor fault) --> C4
end
%% ========= mmap Write (MAP_SHARED & MAP_PRIVATE) =========
subgraph MW[MMAP Write]
direction TB
%% 공통 시작
%% classDef empty width:0px,height:0px;
classDef empty fill:none,stroke:none,color:transparent,width:0.5px,height:0.5px;
D0["page fault"]
D1["**vm_ops.fault**"]
D2{"Cache miss?"}
D3["**aops.read_folio / aops.readpage(s)**"]
M1[ ]:::empty
M2[ ]:::empty
%%classDef empty fill:none,stroke:none;
D0 --> D1 --> D2
D2 -- Yes --> D3 --- M1
D2 -- No --- M1
M1 -- Access mapped page <br/>(to write data) --- M2
M2 --> MWS
M2 --> MWP
%% MAP_SHARED 경로
subgraph MWS[MAP_SHARED]
direction TB
S3["**vm_ops.page_mkwrite** (FS prepares space/journal/quota; may reserve blocks)"]
S4["**aops.dirty_folio / aops.set_page_dirty** <br/>to mark the page as dirty"]
S5["allow page to become dirty"]
S6["later: **aops.writepage(s)** for writeback"]
S3 --- S4 --> S5 -.-> S6
end
%% MAP_PRIVATE 경로
subgraph MWP[MAP_PRIVATE]
direction TB
P3["Copy-On-Write: allocate anonymous page, copy contents"]
P4["mark CoW page dirty (not file-backed)"]
P5["write-back not needed (changes stay private)"]
P3 --> P4 --> P5
end
class S6 later;
end
%% ========= Colors =========
classDef readNode fill:#E3F2FD,stroke:#1E88E5,color:#0D47A1,stroke-width:1px;
classDef writeNode fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20,stroke-width:1px;
classDef mreadNode fill:#FFF8E1,stroke:#F9A825,color:#4E342E,stroke-width:1px;
classDef mwriteNode fill:#F3E5F5,stroke:#6A1B9A,color:#4A148C,stroke-width:1px;
class A0,A1,A2,A3,A4 readNode;
class B0,B1,B2,B3,B4,B5,B6 writeNode;
class C0,C1,C2,C3,C4 mreadNode;
class D0,D1,D2,S3,S4,S5,S6,P3,P4,P5 mwriteNode;
style A3 fill:#1E88E5,color:#E3F2FD,stroke-width:3px;
style B4 fill:#2E7D32,color:#E8F5E9,stroke-width:3px;
style C3 fill:#F9A825,color:#FFF8E1,stroke-width:3px;
style D3 fill:#6A1B9A,color:#F3E5F5,stroke-width:3px;
%% ========= (Optional) style subgraph boxes =========
style BR fill:#F0F7FF,stroke:#1E88E5,stroke-width:1px,color:#0D47A1
style BW fill:#F1F8E9,stroke:#2E7D32,stroke-width:1px,color:#1B5E20
style MR fill:#FFFDE7,stroke:#F9A825,stroke-width:1px,color:#4E342E
style MW fill:#F7F0FF,stroke:#6A1B9A,stroke-width:1px,color:#4A148C
style MWS fill:#F7F0FF,stroke:#6A1B9A,stroke-width:1px,color:#4A148C
style MWP fill:#F7F0FF,stroke:#6A1B9A,stroke-width:1px,color:#4A148C
https://lwn.net/Articles/357767/
When a process calls
mmap(), the kernel sets up Virtual Memory Address (VMA) region to map the pages of the file to disk. It assigns the file'sstruct vm_operationstovma->vm_ops.struct vm_operationscontains pointers to a set of functions which assist in getting the pages to memory on demand.vm_operations.fault()is called when the user access a virtual memory area not present in main memory. It is responsible for fetching the page from disk and putting it into memory. If the vma is shared,vm_operations.page_mkwrite()makes the page writable, otherwise the page is duplicated using COW(Copy On Write).page_mkwrite()is responsible for keeping track of all information required by the filesystem, such as buffer_heads, to put the data back on disk. Typically, this means preparing the block for write, checking that there is enough disk space (returningENOSPCif not), and committing the write.
3. ftrace Options (https://www.kernel.org/doc/Documentation/trace/ftrace.txt)
- Trace files
trace(/sys/kernel/tracing/trace)- trace를 읽어도 버퍼가 비워지지 않음 (스냅샷 읽기)
- 버퍼가 가득 차면 오래된 내용부터 덮어쓰기(overwrite)
- 버퍼 크기는
/sys/kernel/tracing/buffer_size_kb로 설정
trace_pipe:/sys/kernel/tracing/trace_pipe- 읽으면서 소비(라이브 스트림)
- "Once data is read from this file, it is consumed, and will not be read again with a sequential read"
- Thread Group IDs (TGID)
https://stackoverflow.com/questions/4517301/difference-between-pid-and-tid
예) Binder:12758/12766 [001] ... → 12758 = TGID(프로세스), 12766 = TID(스레드)
record-tgid: "When any event or tracer is enabled, a hook is enabled in the sched_switch trace point to fill the cache of mapped Thread Group IDs (TGID) mapping to pids."saved_tgids
- Tracing with specific pids
set_ftrace_pid: "Have the function tracer only trace the threads whose PID are listed in this file."function-fork: "When set, tasks with PIDs listed in set_ftrace_pid will have the PIDs of their children added to set_ftrace_pid when those tasks fork."