이 코드는 어떤 문제를 푸나요?
소켓에서 만든 패킷은 라우팅과 qdisc 등을 거쳐 NIC 드라이버로 내려갑니다. 드라이버의 송신 큐가 꽉 차면 남은 패킷을 잃지 않고 나중에 다시 처리할 수 있어야 합니다. dev_hard_start_xmit 전체는 연결된 skb 목록을 하나씩 넘기면서 누가 각 패킷을 소유하는지 정리하는 경계입니다.
읽을 범위: v6.18.37 · net/core/dev.c · dev_hard_start_xmit 3882–3908행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
qdisc
송신 패킷의 대기·순서·정책을 다루는 계층입니다. NIC의 하드웨어 디스크립터 링과는 별도입니다.
NETDEV_TX_BUSY
드라이버가 현재 패킷을 접수하지 못했다는 송신 상태입니다. 이 경우 상위가 다시 처리할 수 있도록 패킷을 유지해야 합니다.
패킷 소유권
드라이버가 패킷을 받아 소비한 뒤에는 상위가 그 skb를 마음대로 다시 사용하거나 해제하면 안 됩니다. 반대로 접수되지 않은 패킷은 남겨야 합니다.
전송 접수와 전송 완료
드라이버가 skb를 받았다는 사실은 실제 네트워크 전송이나 상대방 수신 완료와 다릅니다.
처음 읽을 때
설명용으로 skb 세 개가 연결됐다고 가정하고 두 번째 패킷에서 BUSY가 되면 어느 포인터를 반환해야 할지 그려 보십시오. 남은 목록의 첫 패킷을 정확히 보존하는 것이 핵심입니다.
더 깊이 살펴볼 때
skb->next를 미리 저장하고 목록 표시를 지운 뒤 콜백을 호출하는 순서에 주목하십시오. 콜백이 skb를 소비할 수 있으므로 호출 후에 원래 skb를 따라 다음 항목을 읽어서는 안 됩니다.
그림으로 보는 변화

1. 목록에서 하나를 준비합니다
현재 skb의 다음 포인터를 먼저 저장하고 현재 패킷의 목록 연결 표시를 정리합니다.
화살표는 목록 순회 순서입니다. 메모리 주소가 증가하는 방향을 뜻하지 않습니다.
2. 드라이버에 넘깁니다
xmit_one으로 하나를 발행합니다. 접수됐다면 다음 패킷으로 이동합니다.
화살표는 패킷 소유권이 드라이버 쪽으로 넘어갈 수 있는 호출 경계입니다.
3. 남은 목록을 반환합니다
BUSY 또는 큐 정지 시 미전송 skb부터 남겨 호출자가 재처리하도록 합니다.
되돌아가는 화살표는 미전송 패킷 반환입니다. 이미 접수된 패킷을 회수하는 뜻이 아닙니다.
dev_hard_start_xmit를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,
struct netdev_queue *txq, int *ret)
{
struct sk_buff *skb = first;
int rc = NETDEV_TX_OK;
while (skb) {
struct sk_buff *next = skb->next;
skb_mark_not_on_list(skb);
rc = xmit_one(skb, dev, txq, next != NULL);
if (unlikely(!dev_xmit_complete(rc))) {
skb->next = next;
goto out;
}
skb = next;
if (netif_tx_queue_stopped(txq) && skb) {
rc = NETDEV_TX_BUSY;
break;
}
}
out:
*ret = rc;
return skb;
}struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,송신할 skb 목록의 첫 항목과 대상 장치를 받습니다.
struct netdev_queue *txq, int *ret)대상 송신 큐와 상태를 돌려줄 정수 포인터를 추가로 받습니다. 함수 반환 포인터는 남은 패킷을 나타냅니다.
struct sk_buff *skb = first;현재 처리할 skb를 목록의 시작으로 둡니다.
int rc = NETDEV_TX_OK;아직 실패가 없으므로 전송 접수 상태를 NETDEV_TX_OK로 준비합니다.
while (skb) {미처리 skb가 있는 동안 하나씩 처리합니다.
struct sk_buff *next = skb->next;드라이버가 현재 skb를 소비할 수 있으므로 다음 포인터를 호출 전에 저장합니다.
skb_mark_not_on_list(skb);현재 skb를 목록에 연결된 상태로 취급하지 않도록 표시를 정리합니다.
rc = xmit_one(skb, dev, txq, next != NULL);현재 패킷을 하나 전송합니다. 다음 패킷 유무도 넘겨 연속 발행 최적화에 사용할 수 있게 합니다.
if (unlikely(!dev_xmit_complete(rc))) {드라이버가 패킷을 접수해 소유권 처리가 끝난 결과인지 검사합니다. 실제 선로 전송 완료 검사와는 다릅니다.
skb->next = next;접수되지 않은 패킷이면 다음 링크를 복원해 남은 목록이 끊어지지 않게 합니다.
goto out;현재 패킷부터 다시 처리할 수 있도록 반환 절차로 갑니다.
skb = next;현재 패킷 처리가 끝났으므로 미리 저장한 다음 skb로 이동합니다.
if (netif_tx_queue_stopped(txq) && skb) {큐가 정지했고 아직 남은 패킷도 있는지 함께 확인합니다.
rc = NETDEV_TX_BUSY;뒤의 패킷들을 지금 더 보낼 수 없다는 BUSY 상태를 설정합니다.
break;남은 목록을 유지한 채 순회를 끝냅니다.
out:정상 종료와 접수 실패가 모이는 반환 위치입니다.
*ret = rc;마지막 전송 접수 상태를 호출자가 준 변수에 기록합니다.
return skb;아직 전송하지 못한 첫 skb를 반환합니다. 모두 처리했으면 NULL입니다.
함께 생각해 볼 질문
드라이버 호출 뒤 skb->next를 읽으면 왜 위험하나요?
드라이버가 skb를 소비하거나 해제했을 수 있으므로 호출 전에 다음 포인터를 저장해야 합니다.
반환 skb가 NULL이면 상대가 받았나요?
이 목록에서 상위에 남길 패킷이 없다는 뜻입니다. 원격 수신을 확인하는 것은 아닙니다.
큐 정지와 현재 패킷의 BUSY는 같은 검사인가요?
현재 패킷을 접수하지 못한 경우와, 접수 후 다음 패킷부터 보낼 수 없는 경우를 별도로 처리합니다.
출처와 읽은 범위
Linux stable v6.18.37 · net/core/dev.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
