패킷의 받는 과정에서 NIC는 interrupt를 발생시킨다.
참고로 패킷을 받는 과정은 다음과 같다.
- 패킷 도착
- DMA가 디바이스 드라이버의 RX ring으로부터 패킷 디스크립터 fetch
- DMA가 패킷을 디스크립터 데이터 부분에 write
- NIC가 패킷이 도착해 copy까지 했다고 interrupt 발생시켜 CPU에게 알려줌.
- 디바이스 드라이버가 등록해놓은 interrupt handler 실행. 네트워킹 스택 스타트 (L2 - L3 - L4 - (recv queue)- 유저프로세스 순차적 실행).
이 interrupt를 어떻게 관리하느냐가 CPU utilization과 관련있기 때문에, 어떤 방식으로 interrupt를 다루는지 알아보겠다.
NIC IRQ(Interrupt Request)
-
CPU가 IRQ를 NIC로부터 받으면, 디바이스 드라이버에 의해 등록되어있는 ISR(Interrupt Service Routine(handler)) 실행
-
interrupt마다 ID가 있음. = IRQ #. 이 번호를 보고 ISR찾아감.
-
* 디바이스 드라이버가 IRQ를 request_irq()로 ISR 등록
Interrupt Handling Philosophy in Kernel
- 최대한 빨리 interrupt 처리하는 것을 목표로 함.
- 안중요한 동작들은 interrupt handle 할 때가 아니라, 나중에 처리.
--> L2, L3, L4 루틴을 프로세스처럼 스케줄링
Top-half(Hardware interrupt) : Do It Now
: interrupt 생성되었을 때, 즉시 ISR을 최소한으로 수행. 이후 bottom-half는 나중에 스케줄링.
e.g.sk_buff 하나 만들어서 NIC의 RX ring에 있는 것 데이터 커널로 복사.
Bottom-half(Software interrupt) : Do It Later
: deferred processing. 스케줄링 된 나머지 동작들
e.g. softirqs(네트워크는 이걸로 처리), tasklets, workqueues
이 단계에서 L2, L3, L4 네트워킹 스택 operation수행.
softirq
- bottom half 에서도 priority가 높은 task들의 모음. 미리 정의되어 있음.
- 딜레이된 interrupt를 처리하는 역할.
- 다른 interrupt에 의해 interrupt(preempted) 될 수 있음.
- multicore로 동시에 여러 softirq 처리 가능.
TIMER_SOFTIRQ : timer interrupt
NET_TX_SOFTIRQ : network TX
NET_RX_SOFTIRQ : network RX
TASKLET_SOFTIRQ : softirq라는 다른 bottom half의 태스크를 정의할 수 있음
등등이 미리 정의되어 있음. 수정 불가. 만약 bottom half를 커스텀해서 사용하고 싶으면, tasklet, workqueue 사용해야 함.
TX/RX with softirq
TX
- NIC가 전송 완료해 interrupt 발생시키면, ISR수행.
- ISR의 역할 : 전송한 skb를 메모리에서 free.
RX
- NIC가 DMA 수행해 메모리에 패킷 복사한 후 interrupt 발생시키면, ISR수행.
- ISR의 역할 : 데이터
sk_buff로 감싸기 + softirq를 CPU 스케줄러에 등록
other bottom half mechanism
tasklet
- softirq 의 하위 매커니즘으로 구현
- 하나의 cpu core에만 적용되므로, synchronization 고려 안해도 됨.
work queue
- 프로세스와 유사
- tasklet보다 priority 낮음.
Considering Interrupt Overheads
- 패킷이 너무 한번에 많이 오면, ISR 처리만 해도 너무 많아짐 --> livelock 발생(deadlock 아닌데도 멈춰있는 것처럼 보임).
- 지연 발생 + 패킷 또한 drop가능. -> throughput(처리량) 저하.
NAPI(New API)
매 패킷이 도착할 때마다 interrupt를 발생시키지 말고, 일정 시간 polling을 통해 계속 받은 후 한번에 처리하는 방법.
- 첫 프레임이 도착하면, polling 모드로 전환(interrupt 발생 제한)
- 모든 NIC 인터페이스(eth0, eth1 ...)에 대해 패킷을 RR방식으로 계속 받기.
- 패킷 수신이 줄어든다 싶으면, interrupt로 전환
장점
- interrupt 수 감소
- 인터페이스 간 공평한 처리 가능. 원래는 하나가 많이 패킷을 받으면, 그거 처리하느라 다른 인터페이스의 처리가 늦어짐.
단점
- IRQ 처리 시간이 늘어남. 한번에 많은 패킷에 대해 IRQ를 받아 처리하기 때문.
- Rotting Packet : polling -> interrupt 전환 과정에서 패킷이 RX ring에 도착하면, 언제 해당 패킷이 처리되는지 알기 어려움. 만약 마침 수신량이 적어져 계속 polling상태면 패킷 처리되는 시간이 너무 늘어남.
FYI) DIM(Dynamic Interrupt Moderation)
- NAPI는 소프트웨어가 NIC에게 interrupt->polling으로 전환하라고 명령.
- 최근 NIC은 arrival rate, delay, CPU utilization 등을 고려해 알아서 조절.
NIC Features
CPU 자원은 한정되어있기 때문에, NIC를 CPU 효율성을 높이기 위해 구성할 수 있다.
Multi-queue NIC
-
여러 개의 RX queue를 가지고 있는 NIC.
-
각각의 RX queue는 하나의 cpu core와 연결. -> multicore 처리
-> interrupt 분산 처리 가능 & 처리 분산 --> throughput(처리량) 상승
-
패킷이 도착하면 어느 queue에 가야 하나?
->NIC이 RSS(Receive Side Scaling) 를 통해 결정.
->IP, port 정보등을 가지고 hash 또는 mod 연산을 통해 분배.
cf) What if RSS is not supported by NIC?
소프트웨어로 구현 가능. e.g. RPS(receive packet steering), RFS(receive flow steering), aRFS(accelerated RFS) 등.
LSO & LRO
- LSO(Large Segment Offload)
: IP layer에서 fragment 해야할 것 여기서 함. *TCP layer에서도 MSS(Max Segment Size) 고려해서 fragment 될 수도 있지만, 미루고 여기서 진행.
- LRO(Large Receive Offload)
: NIC에서 받을 때, offload 확인해서 fragmented된거 붙여서 위로 보냄.
--> CPU overhead 줄일 수 있음.
- LSO : fragment에 수행하는 CPU 사용 줄이기
- LRO : 여러 패킷을 하나로 합쳐서 interrupt 진행
Checksum offloading & Promiscuous mode