Linux v6.18.37 · 개념과 코드 읽기

수신 경로: 패킷을 IPv4·IPv6 처리 함수에 넘기는 순간

이 코드는 어떤 문제를 푸나요?

NIC가 메모리에 받은 패킷은 드라이버와 NAPI를 거쳐 네트워크 계층으로 올라옵니다. 이때 공통 수신 처리는 VLAN·브리지·프로토콜 분류 등으로 패킷과 수신 장치를 바꿀 수도 있습니다. 아래 __netif_receive_skb_one_core 전체는 그 공통 처리를 거친 뒤 마지막 프로토콜 수신 함수에 넘기는 경계를 보여 줍니다. NAPI와 GRO의 전체 구현을 이 짧은 함수 하나로 대신하지는 않습니다.

읽을 범위: v6.18.37 · net/core/dev.c · __netif_receive_skb_one_core 6110–6121행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

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 전체를 구현하는 범위가 아닙니다.

공통 수신 코어 뒤 pt_prev가 있으면 선택된 콜백을 호출해 ret을 갱신하고, 없으면 콜백 없이 공통 코어의 ret을 반환하는 두 경로
그림 1. Linux v6.18.37 __netif_receive_skb_one_core()의 두 반환 경로. 그림 크게 보기.
pt_prev에 따른 결과
조건콜백최종 ret
pt_prev == NULL이 함수에서 추가 호출 없음공통 코어 반환값
pt_prev != NULL선택된 pt_prev->func콜백 반환값
기존 단순화 그림(조건 생략)

이전 선형 그림은 분기 조건을 생략한 보조 자료다. 현재 설명은 위 분기 그림을 기준으로 읽는다. 이전 PNG · 이전 GIF

__netif_receive_skb_one_core를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

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.18.37 · net/core/dev.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑