跟踪一次网络发送
一、报文的分层转发
当我们上层通过write来发送消息的时候,会走到socket文件系统的发送接口。
首先,socket是整个系统中所有网络设备在用户态的一个抽象,也就是一个socket可以为appletalk,bluetooth一样,代表不同的协议类型。所以此时首先经过一次协议类型的转发,整个最为上层的就是我们通常最为常见的IPV6协议,调用的接口为
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};
当消息确定为IPV4协议之后,就可以继续确定是以太网上的哪个协议,此时就可以有TCP/UDP/Raw之类的再次转发,此时经过的就是TCP的send
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
注意:这个函数是IPV4和IPV6公用的一个函数。
在该函数中,完成的操作为
tcp_push--->>__tcp_push_pending_frames--->>tcp_write_xmit---->>tcp_transmit_skb--->>ip_queue_xmit(注意,这个函数使用了我们另一个比较关系的概念,就是路由表查询的概念)-->>ip_local_out-->>ip_output--->>ip_finish_output-->>dev_queue_xmit-->>dev_queue_xmit--->>>dev_hard_start_xmit,然后以我们最为熟悉的realtek为例说明:
static const struct net_device_ops ne2k_netdev_ops = {
.ndo_open = ne2k_pci_open,
.ndo_stop = ne2k_pci_close,
.ndo_start_xmit = ei_start_xmit,
.ndo_tx_timeout = ei_tx_timeout,
二、网络设备的中断申请
根据Linux当前的设备分类方法,网络设备已经和block char 设备一样,成为系统中一个单独的、和前两者主流设备并列的设备。
通过内核的调试,可以知道系统中刚启动的时候是通过ioctrl的IOUP来设置一个设备开始进入工作状态,然后对应的可以通过down或者close来关闭一个设备,这些也就是我们经常来进行一个设备启动和关闭时进行的操作。
同样对于realtek设备的初始化路径
devinet_ioctl-->>>dev_change_flags---->>>dev_change_flags--->>>__dev_open-->>>ne2k_pci_open
其中对于设备的打开操作,其中执行了一个比较华丽的操作:
if ((old_flags ^ flags) & IFF_UP) { /* Bit is different ? */
ret = ((old_flags & IFF_UP) ? __dev_close : __dev_open)(dev);这是对函数指针直接的操作,看来没有做不到,只有想不到。
在这个设备的打开过程中,执行了中断向量的申请操作。
int ret = request_irq(dev->irq, ei_interrupt, IRQF_SHARED, dev->name, dev);
三、中断函数的处理__ei_interrupt
if (interrupts & ENISR_OVER)
ei_rx_overrun(dev);
else if (interrupts & (ENISR_RX+ENISR_RX_ERR))
{
/* Got a good (?) packet. */
ei_receive(dev);
}
/* Push the next to-transmit packet through. */
if (interrupts & ENISR_TX)
ei_tx_intr(dev);
else if (interrupts & ENISR_TX_ERR)
ei_tx_err(dev);
者三个处理函数和我们用户进程看到的东西类似,包含了标准输出、标准输入和标准错误三个接口,分别处理网卡的接受、输入和错误。
现在看一下接收到的数据时如何进行的
skb = dev_alloc_skb(pkt_len+2);
skb_reserve(skb,2); /* IP headers on 16 byte boundaries */
skb_put(skb, pkt_len); /* Make room */
ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame));
skb->protocol=eth_type_trans(skb,dev);
netif_rx(skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += pkt_len; 一些统计信息的增加。
if (pkt_stat & ENRSR_PHY)
dev->stats.multicast++;
接下来的操作为:
enqueue_to_backlog--->>>____napi_schedule
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
可以看到,对于网卡数据的接受是通过软中断来实现的,而且也可以看到,对于网卡接收到的数据是最早挂在一个设备对应的全局结构中的。
通过代码可以知道,其中的全局队列就是
__skb_queue_tail(&sd->input_pkt_queue, skb);
中使用的input_pkt_queue,所有的网卡接受的信息都应该放在这个设备上,从而等待进一步的路由。
四、网卡软中断的处理
linux-2.6.37.1\net\core\dev.c
static int __init net_dev_init(void)
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
接下来是软中断中的处理:
sd->backlog.poll = process_backlog;
其中在软中断中的处理
static void net_rx_action(struct softirq_action *h)
奇怪的是其中使用的是这个polllist
while (!list_empty(&sd->poll_list)) {
这个结构的设置位于enqueue_to_backlog函数
/* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);
向上继续操作
process_backlog-->>>__netif_receive_skb--->>deliver_skb
以ARP报文为例说明一下
static struct packet_type arp_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_ARP),
.func = arp_rcv,
};
arp_rcv--->>>arp_process--->>>arp_send-->>arp_xmit--->>>dev_queue_xmit
这样我们就完成了一个闭环的操作。