패킷 → NIC → 커널 → Poller → VT 흐름

하드웨어 인터럽트가 어떻게 user mode Java 코드까지 도달하는가
1.8초/스텝 현재 단계: 대기

Hardware외부 → NIC → CPU 인터럽트

🌐 외부 클라이언트
대기 중
🖧 NIC (네트워크 카드)
패킷 없음
🧠 CPU
Ring 3 — 다른 프로세스 실행 중

Kernel (Ring 0)인터럽트 핸들러 → epoll → wake_up

⚡ 인터럽트 핸들러 (CPU가 실행 중인 커널 코드)
① NIC 드라이버: 패킷 읽기
② TCP/IP 스택: fd 식별
③ epoll: ready list 갱신
④ wake_up(): 쓰레드 깨우기
epoll 인스턴스 #5 — ready list (fd 목록)
비어있음
epoll #5의 wait queue (잠든 쓰레드들)
Poller-T
스케줄러 ready queue (CPU만 주면 뛸 쓰레드)
bash chrome JavaCarrier-2

User (Ring 3)JVM 프로세스 — Poller, fd→VT 맵, ForkJoinPool

📡 Poller Thread (Java) SLEEPING
epoll_wait() syscall에서 잠듦 (Ring 0의 wait queue에 등록됨)
fd → VT 매핑 (JDK가 Java 힙에 보관)
fd=42 → VT-1234
ForkJoinPool — Carrier × WorkQueue
Carrier-1
idle
Carrier-2
idle
Carrier-3
idle
Carrier-4
idle
🅿️ Parked VTs (Continuation 상태로 힙에 보관)
VT-1234(fd=42 대기)

실행 로그

📖 이 시뮬레이션이 보여주는 것

출발 상태: Poller-T는 epoll_wait() syscall로 커널의 wait queue에서 잠들어 있습니다. VT-1234는 어떤 소켓(fd=42)의 응답을 기다리며 park 상태로 힙에 보관됨. 캐리어는 idle.

핵심 메시지: 아무도 폴링하지 않습니다. 모두 잡니다. 시작 트리거는 NIC의 하드웨어 인터럽트. CPU가 강제로 Ring 0로 진입하고, 그 위에서 커널 코드(인터럽트 핸들러)가 실행되어 ① 패킷을 읽고 ② fd를 식별하고 ③ epoll ready list에 추가하고 ④ wake_up()으로 wait queue의 Poller를 ready queue로 옮깁니다.

큐 두 종류: wait queue는 자원별(epoll, mutex 등) — "이 자원 기다리며 잠든 쓰레드". ready queue는 CPU별 — "지금 CPU만 주면 뛸 쓰레드". 잠들 땐 ready→wait, 깨울 땐 wait→ready.

Java 차원의 다리: 커널은 fd만 알지 VT는 모름. fd → VT 매핑은 JDK가 Java 힙에 들고 있습니다. Poller가 깨어나서 epoll_wait() 결과(fd=42)를 받으면, 이 맵을 룩업해서 VT-1234를 찾고 LockSupport.unpark(VT-1234) 호출. 그러면 VT가 ForkJoinPool의 deque로 enqueue되고 한가한 캐리어가 mount.

행위자 구분: 하드웨어(NIC, CPU)는 신호 발사·명령어 실행만. 커널 코드가 큐 조작의 주체(wait↔ready). Java 코드가 fd→VT 변환과 ForkJoinPool 운영을 담당.