애플리케이션에서 send()를 호출하면 데이터가 어떤 경로로 네트워크 카드까지 도달할까요? 리눅스 네트워크 스택의 전체 흐름을 이해하면 네트워크 성능 튜닝과 트러블슈팅의 기반을 갖출 수 있습니다.


전체 구조

PLAINTEXT
애플리케이션 (유저 공간)
│  send() / recv()
├─────────────────────── 시스템 콜 경계
│  소켓 계층 (Socket Layer)
│  │
│  프로토콜 계층 (TCP/UDP/IP)
│  │
│  네트워크 디바이스 계층
│  │
│  디바이스 드라이버
│  │
├─────────────────────── HW/SW 경계
NIC (네트워크 카드)

리눅스 네트워크 스택 구조


송신 과정 (send)

PLAINTEXT
1. 애플리케이션: send(socket, data, len, 0)

2. 소켓 계층:
   │ - 소켓 버퍼(sk_buff) 생성
   │ - 데이터를 커널 버퍼에 복사

3. TCP 계층:
   │ - 세그먼트 분할 (MSS 크기)
   │ - TCP 헤더 추가 (시퀀스 번호, 체크섬 등)
   │ - 재전송 큐에 사본 저장
   │ - 혼잡 제어 윈도우 확인

4. IP 계층:
   │ - IP 헤더 추가 (출발/목적 IP)
   │ - 라우팅 테이블 조회 → 출력 인터페이스 결정
   │ - 필요 시 IP 단편화
   │ - Netfilter (iptables) 처리

5. 이더넷 계층:
   │ - ARP로 MAC 주소 확인
   │ - 이더넷 헤더 추가

6. 디바이스 드라이버:
   │ - TX 큐에 패킷 추가
   │ - DMA로 NIC에 전송

7. NIC: 물리적 전송

수신 과정 (recv)

PLAINTEXT
1. NIC: 패킷 수신 → DMA로 링 버퍼에 저장

2. 인터럽트 + NAPI:
   │ - 하드웨어 인터럽트 발생
   │ - NAPI(New API)로 폴링 모드 전환 (인터럽트 폭주 방지)
   │ - 여러 패킷을 한 번에 처리

3. 네트워크 계층:
   │ - 이더넷 헤더 파싱
   │ - IP 헤더 확인, 체크섬 검증
   │ - Netfilter 처리

4. TCP 계층:
   │ - TCP 헤더 파싱
   │ - 시퀀스 번호 확인, ACK 생성
   │ - 수신 버퍼에 데이터 저장
   │ - 소켓에 데이터 도착 알림

5. 소켓 계층:
   │ - 수신 버퍼에서 데이터 대기

6. 애플리케이션: recv()로 데이터 읽기
   - 커널 버퍼 → 유저 버퍼 복사

sk_buff (소켓 버퍼)

리눅스 네트워크 스택의 핵심 자료구조입니다. 패킷 하나를 표현합니다.

PLAINTEXT
sk_buff 구조:
┌─────────────────────────────┐
│ head ─────────────────────→ │ 버퍼 시작
│ data ────────→ ┌──────────┐│
│                │ 이더넷 헤더│ │
│                │ IP 헤더   ││
│                │ TCP 헤더  ││
│                │ 페이로드   ││
│ tail ────────→ └──────────┘│
│ end ──────────────────────→ │ 버퍼 끝
└─────────────────────────────┘

각 계층은 헤더를 추가/제거할 때 data/tail 포인터만 이동합니다 (복사 없이).


NAPI (New API)

고속 네트워크에서 인터럽트 기반 → 폴링 기반 으로 전환하여 성능을 높이는 메커니즘입니다.

PLAINTEXT
기존: 패킷마다 인터럽트 → 10Gbps에서 초당 수백만 번 인터럽트 → CPU 과부하

NAPI:
1. 첫 패킷: 인터럽트 발생
2. 인터럽트 비활성화
3. 폴링 모드: 링 버퍼에서 패킷을 모아서 처리
4. 패킷이 없으면 다시 인터럽트 모드로

성능 관련 커널 파라미터

BASH
# 소켓 버퍼 크기
cat /proc/sys/net/core/rmem_max        # 수신 버퍼 최대
cat /proc/sys/net/core/wmem_max        # 송신 버퍼 최대

# TCP 버퍼 자동 튜닝
cat /proc/sys/net/ipv4/tcp_rmem        # min default max
cat /proc/sys/net/ipv4/tcp_wmem

# 네트워크 백로그
cat /proc/sys/net/core/netdev_max_backlog   # NIC → 커널 큐 크기
cat /proc/sys/net/core/somaxconn            # listen() 백로그

# RSS (Receive Side Scaling)
cat /proc/interrupts | grep eth0    # NIC 인터럽트의 CPU 분산 확인

핵심 포인트

  • send()가 반환되었다고 전송 완료가 아님: 커널 버퍼에 복사되었을 뿐
  • NAPI의 필요성: 고속 네트워크에서 인터럽트 폭주(interrupt storm) 방지
  • zero-copy: sendfile(), splice()로 커널-유저 간 불필요한 복사 제거
  • sk_buff 포인터 조작: 각 계층이 헤더를 추가/제거할 때 메모리 복사 없이 포인터만 이동

정리

리눅스 네트워크 스택은 소켓-TCP-IP-디바이스 드라이버-NIC의 계층 구조입니다. 각 계층이 sk_buff를 통해 패킷을 전달하고, NAPI로 고속 처리를 지원합니다. 이 흐름을 이해하면 네트워크 성능 문제의 원인을 파악할 수 있습니다.

댓글 로딩 중...