이 코드는 어떤 문제를 푸나요?
NIC가 메모리에 받은 패킷은 드라이버와 NAPI를 거쳐 네트워크 계층으로 올라옵니다. 이때 공통 수신 처리는 VLAN·브리지·프로토콜 분류 등으로 패킷과 수신 장치를 바꿀 수도 있습니다. 아래 __netif_receive_skb_one_core 전체는 그 공통 처리를 거친 뒤 마지막 프로토콜 수신 함수에 넘기는 경계를 보여 줍니다. NAPI와 GRO의 전체 구현을 이 짧은 함수 하나로 대신하지는 않습니다.
읽을 범위: v6.6 · net/core/dev.c · __netif_receive_skb_one_core 5544–5555행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
sk_buff
패킷 데이터와 메타데이터를 관리하는 객체입니다. skb 포인터와 실제 데이터 시작 주소는 같은 개념이 아닙니다.
NAPI와 GRO
NAPI는 패킷 처리를 일정 작업량 단위로 폴링하고 GRO는 합칠 수 있는 흐름의 패킷을 묶어 후속 비용을 줄입니다. 모든 패킷이 항상 합쳐지는 것은 아닙니다.
packet_type
프로토콜과 연결된 수신 처리 정보를 나타냅니다. 여기서 pt_prev는 공통 수신 단계가 마지막으로 남긴 처리 대상입니다.
원래 장치와 현재 장치
수신 처리 도중 논리 장치를 거칠 수 있으므로 처음 들어온 장치와 현재 skb->dev가 다를 수 있습니다. 두 값을 모두 전달하는 데 이유가 있습니다.
처음 읽을 때
수신 화살표를 NIC에서 사용자 프로그램으로 한 번에 이어 그리지 마십시오. 드라이버, 공통 네트워크 처리, IP 계층, 전송 계층, 소켓 큐를 거쳐야 하며 이 함수는 그중 공통 처리와 IP 등의 프로토콜 경계에 있습니다.
더 깊이 살펴볼 때
__netif_receive_skb_core가 skb의 주소를 받으므로 포인터 자체를 바꾸거나 패킷을 소비할 수 있다는 점을 보십시오. pt_prev가 남았는지 확인한 뒤에만 최종 프로토콜 콜백을 실행하며, pfmemalloc은 메모리 회수용 비상 자원으로 받은 패킷의 취급을 구분합니다.
분기 그림으로 보는 마지막 수신 대상
공통 수신 코어가 pt_prev를 남겼을 때만 선택된 packet_type의 콜백을 호출합니다. 남기지 않았으면 공통 코어의 반환값을 그대로 돌려줍니다. 이 대표 함수는 NIC·NAPI·GRO 전체를 구현하는 범위가 아닙니다.
__netif_receive_skb_one_core()의 두 반환 경로. 그림 크게 보기.| 조건 | 콜백 | 최종 ret |
|---|---|---|
pt_prev == NULL | 이 함수에서 추가 호출 없음 | 공통 코어 반환값 |
pt_prev != NULL | 선택된 pt_prev->func | 콜백 반환값 |
__netif_receive_skb_one_core를 한 줄씩 읽기
줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc)
{
struct net_device *orig_dev = skb->dev;
struct packet_type *pt_prev = NULL;
int ret;
ret = __netif_receive_skb_core(&skb, pfmemalloc, &pt_prev);
if (pt_prev)
ret = INDIRECT_CALL_INET(pt_prev->func, ipv6_rcv, ip_rcv, skb,
skb->dev, pt_prev, orig_dev);
return ret;
}static int __netif_receive_skb_one_core(struct sk_buff *skb, bool pfmemalloc)패킷 하나와 비상 메모리 할당 여부를 받아 공통 수신 처리와 마지막 프로토콜 호출을 연결합니다.
struct net_device *orig_dev = skb->dev;수신 처리 중 skb->dev가 바뀌어도 원래 입력 장치를 기억하도록 별도로 보관합니다.
struct packet_type *pt_prev = NULL;아직 최종 프로토콜 처리 대상을 정하지 않았다는 뜻으로 NULL을 둡니다.
int ret;공통 처리 또는 프로토콜 함수의 반환 상태를 담습니다.
ret = __netif_receive_skb_core(&skb, pfmemalloc, &pt_prev);skb 포인터의 주소를 전달해 공통 처리에서 포인터를 갱신할 수 있게 합니다. pfmemalloc 조건과 최종 처리 대상 pt_prev도 함께 전달합니다.
if (pt_prev)공통 처리 뒤 실제로 호출할 프로토콜 수신 함수가 남아 있는지 확인합니다.
ret = INDIRECT_CALL_INET(pt_prev->func, ipv6_rcv, ip_rcv, skb,IPv6·IPv4의 흔한 수신 함수에는 간접 호출 최적화를 적용하고 그 밖에는 등록된 함수를 호출합니다. 셋을 모두 실행하는 매크로가 아닙니다.
skb->dev, pt_prev, orig_dev);현재 장치, 선택한 packet_type과 처음 입력 장치를 최종 수신 함수에 전달합니다.
return ret;패킷 처리 상태를 상위 수신 경로에 반환합니다. 사용자에게 파일 데이터를 반환하는 위치는 아닙니다.
함께 생각해 볼 질문
pt_prev가 NULL이면 ip_rcv를 무조건 불러야 하나요?
아닙니다. 공통 처리에서 패킷을 소비하거나 다른 처리를 끝냈을 수 있으므로 이 코드는 최종 콜백을 생략합니다.
skb->dev만 넘기면 왜 부족할 수 있나요?
논리 장치 처리를 거치며 현재 장치가 달라질 수 있어 처음 들어온 장치 정보도 필요합니다.
여기서 반환되면 사용자 read가 완료됐나요?
아닙니다. 프로토콜 처리, 소켓 큐 전달과 사용자 읽기는 이후의 별도 단계입니다.
출처와 읽은 범위
Linux stable v6.6 · net/core/dev.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고