coarse_stat_fs는 /proc/self/schedstat과 /proc/stat의 대략적인 정보를 읽기 전용 공유 메모리 페이지로 제공하는 리눅스 커널 모듈이다. 한 번 open과 mmap을 마치면 스냅샷을 시스템 호출 없이 읽을 수 있으며, 제작자는 스냅샷 비용이 rdtscp와 비슷하다고 설명한다.

뜨거운 루프에서 느린 작업만 골라 프로파일링하는 용도로 쓸 수 있다. 각 작업 전에 스냅샷을 얻고 rdtscp로 시간을 잰 다음, 느린 작업이면 다시 스냅샷을 얻어 경과 시간이 CPU 실행, 실행 대기열, 수면, I/O 대기 등에 어떻게 나뉘었는지 출력한다.

c
#include "lib/coarse_stat_fs.h"

struct csf_snap before, after;
char buf[CSF_DIFF_BUFSZ]; /* fits any csf_format_diff() output */

csf_thread_snap(&before); /* ~16 ns, no syscall */
uint64_t t0 = csf_rdtsc();
op();
if (csf_rdtsc() - t0 > slow) {
 csf_thread_snap(&after);
 csf_format_diff(buf, sizeof(buf), &before, &after);
 puts(buf);
}

25ms 동안 수면한 작업의 출력 예시는 다음과 같다.

wall 25.119ms = cpu 0.017 + runq 0.000 + sleep 25.102 + dsleep 0.000 + iowait 0.000 | preempt 0 block 1 migrate 0 | flt 4/0 | sysc r0 w0 | io r0B w0B

examples/eventloop.c에는 완전한 프로그램이 들어 있으며, 각 필드를 개별적으로 읽는 방법도 보여준다. 자세한 내용은 스냅샷 읽기를 참고하면 된다.

“Coarse”는 값의 최신성을 가리킨다. 시간 버킷은 사이클 단위로 정확하지만 다른 필드는 해당 스레드의 마지막 문맥 전환 또는 CPU 페이지의 마지막 갱신 시점만큼 오래될 수 있다.

데이터해상도최신 시점이유
스레드 시간 버킷(*_cyc)과 s.tscTSC 1사이클(2GHz에서 0.5ns)스냅샷(rdtscp) 시점커널은 전환 때마다 값을 더하고, 읽는 쪽은 현재 버킷을 s.tsc까지 외삽한다.
전환 카운터(nr_*), state, cpu정확한 횟수마지막 문맥 전환전환 훅이 값을 기록한다.
sum_exec_ns, run_delay_ns, pcount, nvcsw, nivcsw, 폴트 및 I/O 카운터정확한 값(ns, 횟수, 바이트)마지막 CPU 이탈 시점커널은 작업이 CPU를 떠날 때만 태스크에서 값을 복사한다.
utime_ns, stime_ns스케줄러 틱 1회(CONFIG_HZ=1000이면 1ms, 250이면 4ms)마지막 CPU 이탈 시점nohz_full CPU를 제외하면 커널이 틱마다 값을 샘플링한다.
CPU 페이지 행period_us 갱신(기본 10ms, 최소 1ms); user, system, irq, softirq도 틱 단위마지막 갱신 시점(update_mono_ns)지연 작업이 CPU별 카운터를 복사하며, 커널은 이 값 대부분을 틱 단위로 계산한다.

비교하면 /proc/stat과 /proc/<tid>/stat의 utime, stime 필드는 10ms 단위의 USER_HZ 틱으로 계산한다.

설치와 시작

필요한 환경은 CONFIG_PREEMPT_NOTIFIERS와 CONFIG_SCHED_INFO를 켠 x86-64 커널 6.1 이상이다. CI는 6.1, 6.12, 7.2에서 테스트한다. 제작자에 따르면 대부분의 배포판 커널은 조건을 만족한다. KVM이 선점 알림 기능을 선택하고, 스케줄 통계 기능이 SCHED_INFO를 선택하기 때문이다. 해당 커널의 헤더도 필요하다. 예를 들면 Arch의 linux-headers, Debian의 linux-headers-amd64 패키지를 설치하면 된다. 빌드에는 gcc와 make가 필요하고, 테스트에는 QEMU와 virtme-ng가 필요하다. 린트에는 clang-format, clang-tidy, bear와 선택 사항인 shellcheck가 필요하다.

빌드와 시험은 다음 순서로 진행한다.

  • make로 모듈, 도구, 예제, 테스트를 빌드한다.
  • 실제 컴퓨터에서 모듈을 올리기 전에 임시 가상 머신에서 make check를 실행한다. 스케줄러 훅의 버그는 실행 중인 커널을 충돌시킬 수 있으므로, 가상 머신이 호스트 커널로 부팅해 그 안에서 모듈을 실행한다. 모든 테스트가 통과하면 실행이 RESULT 0으로 끝난다.
  • 모듈을 넣고 파일 시스템을 마운트한다. 두 단계 모두 root 권한이 필요하다.
sh
sudo insmod kmod/coarse_stat_fs.ko
sudo mount -t coarse_stat_fs none /sys/kernel/coarse_stat_fs
  • 페이지를 읽는다.
sh
tools/csfcat cpu # the cpu page, in /proc/stat format
tools/csfcat self 100 # sleep 50 ms, spin 50 ms, print the diff
build/eventloop # the example: reports only the slow operations

csfcat self 100은 다음과 비슷한 한 줄을 출력한다.

sleep 50 ms + spin 50 ms: wall 100.141ms = cpu 50.014 + runq 0.000 + sleep 50.127 + ...

사용을 마치면 마운트를 해제하고 모듈을 제거한다.

sh
sudo umount /sys/kernel/coarse_stat_fs
sudo rmmod coarse_stat_fs

저장소 구성과 개발

include/uapi/coarse_stat_fs.h에는 모듈과 사용자 공간 프로그램이 함께 사용하는 ABI, 페이지 레이아웃, 상수, 플래그가 들어 있다. kmod/는 커널 모듈 코드로, main.c가 초기화·파일 시스템·시계를, thread.c가 스레드별 페이지와 선점 알림을, cpu.c가 CPU 페이지를 담당한다. compat.h에는 구형 커널을 위한 대체 정의가 있다. lib/coarse_stat_fs.h는 매핑, 스냅샷, 차이 형식을 제공하는 MIT 라이선스 헤더 전용 사용자 공간 라이브러리다. tools/에는 페이지를 텍스트로 출력하는 csfcat, examples/에는 느린 작업만 보고하는 eventloop.c, tests/에는 테스트 프로그램과 벤치마크, 가상 머신 구동 코드가 있다.

tests/debian/은 CI처럼 Debian 커널에서 테스트하는 코드다. scripts/에는 커널 모듈용 컴파일 데이터베이스를 만드는 kmod-compdb.py와 git 훅 pre-commit이 있다. packaging/에는 systemd 마운트 유닛과 modules-load 설정이 있으며, dkms.conf는 DKMS 빌드 설정이다. .github/workflows/ci.yml은 린트와 Debian 커널 테스트를 수행하는 CI 설정이다. .clang-format, .clang-tidy, kmod/.clang-tidy, .editorconfig는 코드 스타일과 정적 검사 설정이다. 라이선스는 LICENSE, LICENSES/에 있으며, 전체 라이선스 내용은 라이선스 안내에서 확인할 수 있다.

모든 작업에는 make 대상이 있다. make help를 실행하면 목록을 볼 수 있다. make는 모듈·도구·예제·테스트를 빌드하고, make check는 호스트 커널로 부팅한 가상 머신에서 모든 테스트를 실행한다. make bench는 메모리 디버깅을 끈 가상 머신에서 벤치마크를 수행한다. make debian SUITE=bookworm은 CI와 같은 방식으로 Debian 커널에서 테스트한다. make format은 모든 C 소스의 형식을 clang-format으로 맞추고, make lint는 clang-format, checkpatch, shellcheck, clang-tidy, W=1 빌드를 실행한다. make compdb는 clangd와 clang-tidy용 compile_commands.json을 만들며, make clean은 빌드 결과물을 지운다.

다시 빌드한 뒤 호스트에서 모듈을 새로 올릴 때는 선택적으로 갱신 주기를 지정하고 커널 로그를 확인할 수 있다.

sh
sudo umount /sys/kernel/coarse_stat_fs; sudo rmmod coarse_stat_fs
sudo insmod kmod/coarse_stat_fs.ko period_us=2000
sudo mount -t coarse_stat_fs none /sys/kernel/coarse_stat_fs
sudo dmesg | tail -n 50

등록된 스레드가 살아 있으면 rmmod는 실패한다. 자세한 제약은 주의 사항에 있다. make는 실행 중인 커널의 헤더인 /lib/modules/$(uname -r)/build를 사용한다. 이 경로는 헤더 패키지가 설치한다. 헤더가 없으면 빌드가 중단되고 필요한 패키지를 알려준다. 다른 커널용으로 빌드하려면 KDIR을 지정한다. 예를 들어 make KDIR=/usr/src/linux-headers-6.12.0-amd64를 실행한다.

커널 코드와 사용자 공간 코드는 모두 100열 제한이 있는 리눅스 커널 스타일을 따른다. .clang-format이 스타일을 정의하므로 커밋 전 make format을 실행하면 된다. 커널 코드(kmod/, include/uapi/)는 헤더에 포함된 checkpatch.pl, kmod/.clang-tidy를 사용한 clang-tidy의 정적 분석 및 linuxkernel-* 검사, W=1 빌드를 거친다. 모듈은 -Werror로 빌드한다. 사용자 공간 코드(lib/, tools/, tests/, examples/)는 .clang-tidy 설정으로 검사한다. 일부 검사를 끈 이유는 해당 파일의 주석에 설명돼 있다. make lint가 검사를 모두 실행한다. 커밋마다 빠른 검사를 하려면 다음 훅을 설치한다.

sh
ln -s ../../scripts/pre-commit .git/hooks/pre-commit

편집기 설정에는 make compdb를 한 번 실행하고 파일을 추가한 뒤 다시 실행한다. 사용자 공간 코드용 compile_commands.json과 모듈용 kmod/compile_commands.json을 만든다. kbuild가 호스트 커널의 gcc 플래그로 모듈 데이터베이스를 만들기 때문에 scripts/kmod-compdb.py가 clang에서 거부하는 플래그를 제거한다. 데이터베이스가 없으면 clangd는 사용자 공간만 다루는 compile_flags.txt를 사용한다.

모듈은 현재 커널을 대상으로 하며, kmod/compat.h가 6.1까지의 대체 정의를 제공한다. 각 대체 정의에는 실제 인터페이스가 도입된 릴리스가 적혀 있다. 새 커널에서 빌드가 깨지면 호출 위치가 아니라 이 파일에 대체 정의를 추가하고, 가장 오래된 배포판과 최신 배포판에서 make debian SUITE=<suite>를 실행한다.

테스트와 CI

make check는 tests/vm.sh를 통해 QEMU에서 호스트 커널을 부팅하고 virtme-ng로 모듈과 테스트를 게스트에서 실행한다. 게스트는 panic_on_warn, slub_debug, init_on_free, KFENCE를 켜고 부팅한다. 따라서 커널 경고가 하나라도 있으면 테스트가 실패한다.

sh
tests/vm.sh # cpu thread life stress tools
tests/vm.sh thread life # only these
VM_DEBUG=0 tests/vm.sh bench # benchmarks without memory debugging

로그는 build/vm/tests.log에 저장된다. 게스트가 종료되면 tests/vm.sh가 커널 oops나 패닉이 담긴 build/vm/console.log의 끝부분을 출력한다. VM_TIMEOUT은 실행 제한 시간(초)이며 기본값은 600이다.

테스트는 t_cpu가 페이지 레이아웃과 갱신 주기, CPU별 행과 합계 행의 일치 여부, /proc/stat과의 비교를 확인한다. t_thread는 바쁜 대기, 수면, 중단 불가능 대기(vfork), O_DIRECT I/O, 실행 대기열 경합, CPU 이동에서 시간 버킷의 정확도를 검사하고 /proc/thread-self/{schedstat,stat,io}와 대조한다. t_life는 소유자 및 비소유자 해제, 재활성화, 매핑이 살아 있는 상태에서의 스레드 종료, fork, exec 등 참조 횟수와 수명을 검사한다. t_stress는 20초 동안 무작위 해제 순서로 약 8만 개 스레드를 만들고 모듈 참조 횟수가 기준값으로 돌아오는지 확인한다. tools 테스트는 csfcat과 예제를 다룬다. 매 실행마다 객체가 살아 있는 동안 rmmod가 실패하고 객체가 정리된 뒤 성공하는지, 커널 로그에 경고가 없는지도 확인한다.

Debian 커널 테스트는 tests/debian/run.sh SUITE로 CI 작업을 로컬에서 재현한다. 이 스크립트는 mmdebstrap으로 해당 배포판 커널·헤더·컴파일러가 포함된 루트 파일 시스템을 만들고 저장소 복사본을 넣는다. QEMU가 그 커널로 부팅한 뒤 tests/debian/guest.sh가 게스트의 init으로 실행되어 모듈과 테스트를 빌드하고 실행한다.

sh
tests/debian/run.sh bookworm # kernel 6.1
tests/debian/run.sh trixie-backports # newest stable kernel
tests/debian/run.sh sid thread life # a subset of the tests

스크립트에는 sudo, mmdebstrap, qemu-system-x86, e2fsprogs, curl이 필요하다. 모든 테스트 출력이 담긴 콘솔 로그는 build/debian/<suite>/console.log에 저장된다. Arch처럼 mmdebstrap이 없는 호스트에서는 같은 인수를 받는 tests/debian/docker.sh를 쓸 수 있다. 이 스크립트는 CI 실행기처럼 Ubuntu 컨테이너 안에서 run.sh를 실행한다.

테스트는 tests/t.h를 사용하는 일반 C 프로그램이다.

c
#include "t.h"

int main(void)
{
 struct csf_snap a, b;

 TEST("my scenario") {
 t_snap(&a); /* dies if the snapshot fails */
 t_sleep_ns(10000000);
 t_snap(&b);
 CHECK(t_diff(&a, &b).sleep > 9, "slept too little");
 }
 return t_done("t_mine");
}

새 프로그램은 tests/Makefile의 TESTS에 추가하고 tests/vm-inner.sh에도 한 줄을 넣는다. CI 실행기는 공유 가상 머신이므로 시간 측정 검사는 여유 있게 잡아야 한다. 호스트가 빼앗은 시간도 스레드의 현재 상태로 계산되기 때문이다.

.github/workflows/ci.yml은 푸시와 풀 리퀘스트마다, 그리고 새 Debian 커널을 확인하기 위해 매주 실행된다. lint 작업은 로컬에서 쓰는 버전에 고정된 도구로 clang-format, checkpatch, shellcheck, 사용자 공간 clang-tidy 검사를 수행한다. debian 작업은 bookworm(6.1), bookworm-backports와 trixie(6.12), trixie-backports와 forky, sid(7.2)에서 tests/debian/run.sh를 실행하고 각 작업의 콘솔 로그를 아티팩트로 올린다. 작업은 컨테이너를 쓰지 않는다. GitHub의 Ubuntu 실행기에 KVM이 있어 게스트가 거의 네이티브 속도로 동작한다.

인터페이스와 스냅샷

모듈은 coarse_stat_fs 파일 시스템을 등록하고 /sys/kernel/coarse_stat_fs에 마운트 지점을 만든다. thread-self는 호출 스레드의 페이지(struct csf_thread_page) 하나를, cpu는 /proc/stat 형식의 전체 합계 및 CPU별 행(struct csf_cpu_page)을 제공한다. 두 파일의 권한은 모두 0444다. 권한이 없는 스레드도 매핑할 수 있지만 읽기 전용이어야 하며, MAP_SHARED와 오프셋 0을 사용해야 한다. 매핑 길이는 열린 파일에 fstat()을 호출해 st_size로 확인한다. thread-self는 한 페이지이고, cpu는 헤더와 가능한 CPU마다 한 행으로 구성된다. lib/coarse_stat_fs.h가 이 작업을 처리하므로 페이지 크기를 하드코딩할 필요가 없다.

include/uapi/coarse_stat_fs.h가 ABI를 정의한다. 각 페이지는 64바이트 struct csf_hdr로 시작하며, 여기에는 매직 번호, ABI 버전, 페이지 종류와 크기, 플래그, TSC 주파수, 사이클에서 나노초로 바꾸는 변환 정보가 들어간다.

스레드 페이지는 해당 스레드가 thread-self를 처음 연 시점부터의 시간을 TSC 사이클 기준 다섯 버킷으로 기록한다. oncpu는 실행 중, runq는 깨어난 뒤 대기까지 포함해 실행 가능 상태로 CPU를 기다린 시간, sleep은 중단 가능한 수면, dsleep은 I/O가 아닌 중단 불가능 수면, iowait는 in_iowait가 설정된 채 막혀 있던 시간이다. 다섯 버킷의 합은 경과한 벽시계 시간과 정확히 일치한다.

페이지에는 현재 상태와 CPU, 마지막 전환 시점의 TSC, 전환 진입·선점·블록·CPU 이동 횟수도 담긴다. 각 CPU 이탈 시 태스크에서 복사한 필드에는 schedstat 세 값, nvcsw, nivcsw, utime, stime, minor·major 폴트, /proc/<pid>/io 카운터가 포함된다. 커널은 등록된 스레드의 문맥 전환마다 선점 알림을 통해 seqcount 아래에서 페이지를 갱신한다. 읽는 쪽은 rdtscp - last_switch_tsc로 현재 버킷을 외삽한다. 따라서 커널이 전환 때만 쓰더라도 스냅샷은 사이클 단위로 정확하다.

thread-self를 열 때마다 파일 디스크립터는 새로 생기지만, 한 스레드의 디스크립터는 모두 참조 횟수로 관리되는 하나의 객체, 즉 페이지 하나와 알림 기능 하나를 공유한다. 스레드가 종료되거나 마지막 파일을 닫으면 객체가 없어진다. 다른 스레드가 마지막 참조를 가지고 있다면, 예를 들어 소유자가 매핑을 넘긴 뒤 그 참조만 닫는 경우 객체는 일시 정지 상태가 된다. 이 상태에서는 계측을 멈춘다. 소유자가 다음에 다시 열면 페이지를 초기화하고 계측을 재개한다. 페이지 메모리는 연 파일을 연 스레드의 메모리 cgroup에 부과된다.

lib/coarse_stat_fs.h는 처음 사용할 때 스레드마다 페이지를 한 번 매핑해 스레드 로컬 저장소에 보관하고, 스레드가 끝날 때 매핑을 해제한다. 매핑은 VM_DONTCOPY이므로 fork() 뒤 자식은 자신의 페이지를 매핑한다.

csf_thread_snap()은 struct csf_snap을 채운다. s.p에는 스레드 페이지의 사본, s.tsc에는 그 사본을 만든 시점의 TSC가 들어가며, 현재 버킷을 s.tsc까지 외삽했다면 s.extrapolated가 설정된다. 사본은 독립적이므로 이후에는 재시도 없이 원하는 때 필드를 읽을 수 있다. 스냅샷 두 개의 필드를 빼면 된다.

c
#include <stdio.h>

#include "lib/coarse_stat_fs.h"

struct csf_snap a, b;

if (csf_thread_snap(&a))
 return -1; /* errno says why, for example ENOENT when not mounted */
op();
csf_thread_snap(&b);

/* Time: TSC cycles; the header holds the conversion to ns. */
const struct csf_hdr *h = &b.p.hdr;
double wall_ms = csf_cyc_to_ns(h, b.tsc - a.tsc) / 1e6;
double iowait_ms = csf_cyc_to_ns(h, b.p.iowait_cyc - a.p.iowait_cyc) / 1e6;

/* Counters: plain differences. */
unsigned long long blocked = b.p.nr_block - a.p.nr_block;
unsigned long long major_faults = b.p.maj_flt - a.p.maj_flt;

printf("%.1f ms, %.1f ms in iowait, blocked %llu times, %llu major faults\n", wall_ms,
 iowait_ms, blocked, major_faults);

두 스냅샷 모두 외삽된 경우 b와 a의 다섯 버킷 차이를 더하면 b.tsc - a.tsc가 된다. 가장 큰 버킷을 보면 작업이 주로 무엇을 했는지 알 수 있다. CPU에 계속 머무는 스레드에서는 전환 때 복사되는 필드가 뒤처진다. 예제 실행 중 30ms 바쁜 대기는 oncpu_cyc에서 30.0ms로 나타났지만 sum_exec_ns에는 18.7ms만 기록됐다. CPU에서 마지막으로 실행된 구간이 아직 복사되지 않았기 때문이다. CPU 시간 측정에는 버킷을 사용한다.

전체 컴퓨터를 보려면 csf_cpu_snap(&c, CSF_CPU_ALL)로 합계 행을 복사하고, 특정 CPU 행은 csf_cpu_snap(&c, cpu)로 복사한다. 행의 단위는 나노초이며 최신성은 period_us 갱신 주기에 달려 있다. c.update_mono_ns에서 측정 시점을 확인할 수 있다.

examples/eventloop.c는 이 기능을 모아 느린 작업마다 결과를 출력한다. 예를 들어 25.1ms 동안 수면한 작업은 대부분 수면 상태였다고 표시하고, 벽시계 시간과 CPU·실행 대기·수면·중단 불가능 수면·I/O 대기 시간을 나눠 보여준다. 또한 선점·블록·이동 횟수, 현재 스레드 ID와 CPU, 폴트, 읽기·쓰기 시스템 호출과 바이트 수, 블록 계층의 I/O, /proc/<tid>/schedstat에 대응하는 실행·대기 시간, 모든 CPU의 사용자·커널·I/O 대기·steal 시간을 출력한다. CPU 전체 정보는 기본 10ms 간격으로 갱신된다.

CPU 페이지는 0번 행에 모든 CPU의 합계를 담고, 나머지 행에 가능한 CPU별 값을 담는다. 각 행에는 user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice의 나노초 값과 인터럽트 및 softirq 합계가 있다. 값의 출처는 /proc/stat과 같은 kcpustat_cpu_fetch()이며, 7.2보다 오래된 커널에서는 NO_HZ 유휴 카운터와 kstat_cpu_irqs_sum()도 사용한다.

지연 작업은 period_us 마이크로초마다 행을 갱신한다. 기본값은 10000, 허용 범위는 1000~1000000이며, cpu 파일을 하나 이상 열었을 때만 갱신한다. update_tsc와 update_mono_ns로 데이터가 얼마나 오래됐는지 확인할 수 있다. 몇 주기보다 짧은 작업에서 행의 차이는 컴퓨터가 바빴는지를 보여줄 뿐, 정확한 사용량 배분은 아니다. 앞서 제시한 30ms 바쁜 대기 동안 모든 CPU의 사용자 시간은 21.9ms 증가했다. 행 값을 /proc/stat의 USER_HZ 틱으로 바꾸려면 1e9 / sysconf(_SC_CLK_TCK)로 나눈다. 갱신 주기는 다음과 같이 바꾼다.

sh
echo 2000 | sudo tee /sys/module/coarse_stat_fs/parameters/period_us

비용과 설치 방식

아래 수치는 메모리 디버깅을 끈 make bench를 Zen, 2GHz TSC 개발 호스트의 KVM 게스트에서 실행한 결과다.

작업ns
csf_thread_snap16.4
rdtscp 단독14.6
csf_cpu_snap(CSF_CPU_ALL)2.2
clock_gettime(CLOCK_THREAD_CPUTIME_ID)101
pread /proc/thread-self/schedstat159
pread /proc/stat1843

등록된 스레드의 문맥 전환에는 약 35ns가 추가된다. futex 핑퐁에서는 전환당 643ns가 678ns가 됐다. thread-self를 한 번도 열지 않는 스레드는 비용을 내지 않는다. 모듈은 preempt_notifier_inc()를 한 번 호출해 선점 알림 정적 키를 활성화한다.

DKMS로 설치된 모든 커널용 모듈을 빌드하려면 저장소를 /usr/src/coarse_stat_fs-0.1에 복사하거나 링크한 뒤 실행한다.

sh
sudo dkms install coarse_stat_fs/0.1

부팅할 때 모듈을 올리고 마운트하려면 packaging/의 파일을 설치한다.

sh
sudo install -m644 packaging/coarse_stat_fs.conf /etc/modules-load.d/
sudo install -m644 packaging/sys-kernel-coarse_stat_fs.mount /etc/systemd/system/
sudo systemctl enable sys-kernel-coarse_stat_fs.mount

주의 사항과 라이선스

사이클 수는 CSF_F_TSC_CONSTANT가 설정된 경우에만 의미가 있다. 이는 TSC가 일정한 속도로 계속 동작한다는 뜻이다. 라이브러리가 다른 CPU의 전환 시점에서 값을 외삽하는 것은 CSF_F_TSC_SYNCED도 설정됐을 때뿐이다. 이 플래그는 커널이 TSC를 불안정하다고 표시하지 않았음을 뜻한다.

다른 스레드가 수면 중일 때 그 스레드의 페이지를 읽으면 수면 전체가 블록 원인 버킷에 들어간다. 깨어난 뒤 실행 대기열에서 보낸 시간은 다음 CPU 진입 시점에야 runq로 옮겨진다. 자신의 스냅샷에서는 측정 시 CPU에 올라와 있으므로 이 문제가 생기지 않는다.

가상 머신에서는 호스트가 CPU 실행을 빼앗은 시간이 스레드가 당시 있던 상태에 더해진다. TSC는 하이퍼바이저가 vCPU의 실행을 중단해도 계속 움직이므로 CPU에서 실행 중이던 스레드는 빼앗긴 시간을 CPU 시간으로 계산한다. 게스트에서 반가상화 steal 회계를 사용하면 스케줄러 자체 시계인 sum_exec_ns는 그 시간을 제외할 수 있다.

irqs 합계는 /proc/stat에 포함되는 NMI, LOC 등 x86 아키텍처 카운터를 제외하므로 두 값이 정확히 일치하지 않는다. vfork()의 부모는 종료 가능 수면(killable sleep) 상태로 기다리므로 대기 시간이 dsleep에 표시된다. 살아 있는 스레드 객체는 모듈 참조를 유지한다. 등록된 스레드가 모두 종료하거나 파일을 닫기 전에는 rmmod가 실패한다. 모듈은 file_operations.mmap을 사용하므로 mmap_prepare를 요구하는 커널에는 작은 이식 작업이 필요하다.

coarse_stat_fs는 GNU General Public License 버전 2만 적용되는 GPL-2.0으로 배포한다. 모듈, 도구, 테스트에 이 라이선스가 적용되며 전문은 LICENSE에 있다. 프로그램이 GPL이 아닌 라이선스로도 사용할 수 있도록 두 부분에는 더 완화된 조건을 적용한다. include/uapi/coarse_stat_fs.h는 커널의 UAPI 헤더와 같은 GPL-2.0 WITH Linux-syscall-note를 따른다. lib/coarse_stat_fs.h와 examples/eventloop.c는 MIT 라이선스이므로 어떤 라이선스의 프로그램에도 복사해 사용할 수 있다. 각 소스 파일에는 SPDX-License-Identifier 줄로 라이선스가 표시돼 있다.