UDP Receiving
1. NIC가 네크워크를 통해 자신의 local MAC address에 해당하는 이더넷 프레임이나 Link Layer용 Broadcast Frame을 받게 되면 Interrupt가 발생하게 된다. 2. NIC는 DMA나 PIO등의 메커니즘을 통해 데이터를 RAM으로 옮겨놓고 sk buffer를 할당 받은후 커널로 처리과정(netif_rx)을 넘기게 된다. 3. Kernel 버전이 2.4로 향상되면서 softirq라는 매카니즘이 추가되면서 network stack 관리를 Bottom Half에서 softirq가 담당하게 되었다. Bottom Half는 여러 개의 CPU가 장착된 시스템이라도 한 순간에 하나의 CPU에서만 처리될 수 있었던 것에 반해 softirq매커니즘은 각 CPU당 하나씩 처리될 수 있으므로 SMP환경에서 성능을 극대화시킬 수 있게 된다.
[3COM 3c509 ethernet card일 경우]
1. el3_interrupt(int irq, void *dev_id, struct pt_regs *regs) [drivers/net/3c509.c] { ... el3_rx(); ... }
2. el3_rx() [drivers/net/3c509.c] { ... dev_alloc_skb(); [include/linux { /* sk_buff를 새로 하나 할당 받는다. __dev_alloc_skb 함수를 호출하며 GFP_ATOMIC 를 gfp_mask의 인자로 넘긴다. 이 옵션은 인터럽트에 의해서만 사용되며 또한 인터럽트는 이 옵션으로만 이 함수를 호출. */ return __dev_alloc_skb(length, GFP_ATOMIC); } ... insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len), (pkt_len + 3) >> 2); --> "ipaddr+RX_FIFO" 로부터 "(pkt_len + 3) >> 2" 만큼의 데이터를 "skb_put(skb,pkt_len)"으로 복사해온다. ... }
3. insl () [include/asm_sh/io.h] { Device로부터 Kernel 영역의 메모리로 주어진 길이 만큼의 데이터를 복사해온다. }
4. net_if_rx() [net/core/dev.c] { /* device로부터 패킷을 읽어들여 상위 layer에서 처리할 수 있도록 큐에 저장한다. */ ... this_cpu = smp_processor_id();
// softnet_data 구조체는 수신되는 패킷들을 저장해 두는 큐로 사용. // 각 CPU에 하나씩 부착되어 있다. queue = &softnet_data[this_cpu];
netdev_rx_stat[this_cpu].total++;
/* 큐의 크기가 backlog한계치를 넘어서지 않았을 경우 */ if (queue->input_pkt_queue.qlen <= netdev_max_backlog) { if (queue->input_pkt_queue.qlen) { // 패킷이 있을 경우 if (queue->throttle) // queue full일 경우 버린다. goto drop;
enqueue: dev_hold(skb->dev);
/* 큐의 끝의 패킷을 저장한다. */ __skb_queue_tail(&queue->input_pkt_queue,skb);
local_irq_restore(flags);
#ifndef OFFLINE_SAMPLE get_sample_stats(this_cpu); #endif return queue->cng_level; // congestion level반환 }
/* 수신된 패킷은 없으나 현재 수신 큐가 full인 경우 */ if (queue->throttle) { queue->throttle = 0; // 꽉차 있지 않다고 설정 변경
#ifdef CONFIG_NET_HW_FLOWCONTROL // flow control이 활성화 된 경우 netdev_dropping값을 낮추고 // net device를 활성화시킨다. if (atomic_dec_and_test(&netdev_dropping)) netdev_wakeup(); #endif }
netif_rx_schedule(&queue->blog_dev); }
/* 수신큐의 길이가 backlog의 한계치를 넘어선 경우이다. */ if (queue->throttle == 0) { queue->throttle = 1; // 수신큐 풀 셋팅 netdev_rx_stat[this_cpu].throttled++; // queue full 횟수 증가 #ifdef CONFIG_NET_HW_FLOWCONTROL atomic_inc(&netdev_dropping); // netdev_dropping값으로 flow control이 수행됨 #endif }
drop: // 자원 해제 netdev_rx_stat[this_cpu].dropped++; local_irq_restore(flags);
kfree_skb(skb); return NET_RX_DROP; }
5. netif_rx_schedule [include/linux/netdevice.h] // 커널의 스케줄링 큐에 등록 --> do_softirq() 호출 { if (netif_rx_schedule_prep(dev)) __netif_rx_schedule(); { ... __cpu_raise_softirq(cpu, NET_RX_SOFTIRQ); /* cpu의 softirq를 NET_RX_SOFTIRQ만큼 raise(pending)시킨후 cpu의 local irq와 local bottom half의 카운트가 모두 0일경우 softirq daemon을 활성화 시킨다. 여기서는 NET_RX_SOFTIRQ값을 설정해서 net_rx_action() [net/core/dev.c] 이 실행된다. */ } }
6. net_rx_action [ net/core/dev.c ] { CPU Queue로 부터 패킷을 꺼내어 패킷 타입(struct packet_type)을 살펴 처리함수 (packet_type.func)에 패킷과 함게 처리를 넘긴다. UDP Packet일 경우 IP Packet을 처리하는 함수 ip_rcv가 호출된다. }
7. ip_rcv [ net/ipv4/ip_input.c ] { /* IP Packet의 처리가 시작되는 뿌리이다. */
... // skb를 pull(한쪽방향으로 데이타를 밀어 넣음) 시킬 수 있는가를 확인 if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto inhdr_error;
...
// validation 처리 후
...
/* 버퍼의 크기가 패킷 길이 보다 클경우 자원낭비방지를 위해 버퍼를 잘라낸다. */ if (skb->len > len) { __pskb_trim(skb, len); if (skb->ip_summed == CHECKSUM_HW) skb->ip_summed = CHECKSUM_NONE; }
// network filter를 후킹한다. return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); }
8. NF_HOOK () [ include/linux/netfilter.h ] { 넘겨진 ip_rcv_finish함수를 skb를 인자로 넘겨 호출한다. 나머지 인자들은 debug시에 쓰이든데 debug모드에서는 NF_HOOK가 nf_hook_slow() [ net/core/netfilter.c ] 로 선언된다. }
9. ip_rcv_finish() [net/ipv4/ip_input.c] { ... if (iph->ihl > 5) { ...
/* skb의 헤더공간이 부족할 경우 pskb_expand_head()를 통해 충분한 공간을 원자적으로 할당 받아 헤더를 복사한다. */ if (skb_cow(skb, skb_headroom(skb)) goto drop;
/* 옵션의 유효성 검사를 수행하고 skb의 옵션 필드들에 값을 채운다. opt여기선 NULL이므로 skb->data는 IP 헤더를 가리켜야한다. skb의 값이 유효하나 처리 중 에러가 발생했을 경우 패킷 송신자에게 parameter 에 error가 존재함을 알리는 ICMP 패킷을 전송한다. */ if (ip_options_compile(NULL, skb)) goto inhdr_error; }
...
return skb->dst->input(skb); // udp packet일 경우 udp_rcv를 가리킨다. }
10. udp_rcv() [ net/ipv4/udp.c ] { ...
/* multicast/broadcast 용 패킷일 경우 multicast처리용 함수로 작업 이전 */ if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST)) return udp_v4_mcast_deliver(skb, uh, saddr, daddr); { // 패킷의 목적지 주소에 해당하는 수신큐의 리스트를 hast테이블에서 얻는다. sk = udp_hash[ntohs(uh->dest) & (UDP_HTABLE_SIZE - 1)];
...
do { // 모든 수신큐에 대해 struct sk_buff *skb1 = skb;
sknext = udp_v4_mcast_next(sk->next, uh->dest, daddr, uh->source, saddr, dif); if(sknext) skb1 = skb_clone(skb, GFP_ATOMIC); // 패킷 복사해서
if(skb1) udp_queue_rcv_skb(sk, skb1); // 수신큐의 끝에 집어 넣는다. sk = sknext; } while(sknext); ... }
// udp hash table에서 현 패킷에 해당하는 sock을 찾아낸다. sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);
// 찾았을 경우 sk의 수신 큐의 마지막에 skb를 추가한다. if (sk != NULL) { udp_queue_rcv_skb(sk, skb); sock_put(sk); return 0; } ...
// 이곳까지 넘어왔다는 것은 원치 않는 패킷을 받은 경우이다. // 송신자에게 Destination Unreachable/Port Unreachable ICMP 패킷을 전송 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
}
11. udp_queue_rcv_skb [ net/ipv4/udp.c ] { // 주어진 소켓의 수신큐에 skb를 추가[sock_queue_rcv_skb(sk, skb)를 호출]한다. // 만약 큐가 꽉 차 있다면 패킷(skb)을 버린다. ... if (sock_queue_rcv_skb(sk,skb)<0) { UDP_INC_STATS_BH(UdpInErrors); IP_INC_STATS_BH(IpInDiscards); ip_statistics[smp_processor_id()*2].IpInDelivers--; kfree_skb(skb); return -1; } UDP_INC_STATS_BH(UdpInDatagrams); return 0; }
12. sock_queue_rcv_skb [ include/net/sock.h ] { ... // skb의 소유자가 sk임을 명시, skb의 파괴자 명시 // 파괴자는 소켓버퍼가 더 이상 필요가 없어지게 되면 커널에 의해 호출돼 자원회수 skb_set_owner_r(skb, sk); { skb->sk = sk; skb->destructor = sock_rfree; atomic_add(skb->truesize, &sk->rmem_alloc); }
skb_queue_tail(&sk->receive_queue, skb); // sk의 수신큐에 넣는다. ...
}
-------------------------------------
사용자(어플리케이션)가 데이타 수신을 위해 recvfrom 함수를 호출하면 system call이 발생 (interrupt vector에서 index 0x80에 해당하게 된다.) "#define SYSCALL_VECTOR 0x80" [ include/asm-i386/hw_irq.h]
system call interrupt handler 에 의해 sys_socketcall함수가 호출되며 이 경우 UDP 데이터 수신 과정이므로 call인자로 SYS_RECVFROM이 넘겨지므로 sys_recvfrom 함수가 호출된다.
1. asmlinkage long sys_recvfrom [ net/socket.c ] { ...
sock = sockfd_lookup(fd, &err); // fd에 해당하는 소켓을 구한다.
... err=sock_recvmsg(sock, &msg, size, flags); // 패킷을 읽어들인다.
...
if(err >= 0 && addr != NULL && msg.msg_namelen) { // User 영역으로 address를 넘긴다. err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len); if(err2<0) err=err2; } }
2. sock_recvmsg() [ net/socket.c ] { struct scm_cookie scm; // socket control message cookie ...
// sock의 operation 함수 포인터에서 데이터 수신용 함수 호출 // 여기선 inet 패킷이기 때문에 inet_recvmsg() 함수 호출 size = sock->ops->recvmsg(sock, msg, size, flags, &scm); --> inet_recvmsg()
if (size >= 0) scm_recv(sock, msg, &scm, flags); // msg의 control message를 읽어 들임.
return size; // 읽어들인 데이타의 길이를 반환 }
3. inet_recvmsg () [ net/ipv4/af_inet.c ] { sk->prot->recvmsg(수신된 패킷의 상위 layer의 메시지 수신 처리 함수)를 호출하는데 설정상 UDP패킷이기때문에 udp_recvmsg()호출 }
4. udp_recvmsg() [ net/ipv4/udp.c ] { ... // sk 수신큐로부터 패킷을 하나 읽어온다. skb = skb_recv_datagram(sk, flags, noblock, &err); ... copied = skb->len - sizeof(struct udphdr); // 헤더 길이 제외한 복사할 데이터의 길이 ... // option에 따라 if (skb->ip_summed==CHECKSUM_UNNECESSARY) { // checksum불필요 --> User IO Vector에 버퍼를 복사 err = skb_copy_datagram_iovec(...); } else if (msg->msg_flags&MSG_TRUNC) { // MSG_TRUNC flag 설정 : checksum 확인후 복사 err = skb_copy_datagram_iovec(...); } else { err = skb_copy_and_csum_datagram_iovec(...); { 주어진 skb의 checksum을 수행하고 User의 iovec에 skb를 복사 } } // 중 선택 실행
... sock_recv_timestamp(msg, sk, skb); // 수신된 시간 기록 ... if (sin) // address 복사 { sin->sin_family = AF_INET; sin->sin_port = skb->h.uh->source; sin->sin_addr.s_addr = skb->nh.iph->saddr; memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); } if (sk->protinfo.af_inet.cmsg_flags) // control message flag가 설정되어 있다면 ip_cmsg_recv(msg, skb); // 메시지 수신
}
[EOF] |