精华内容
下载资源
问答
  • linux网络协议栈学习资料
  • 中断及任务调度管理Linux 书籍中常说的...但必须要作出一些说明,因为这是理解 Linux 内核与其它嵌入式/实时操作系统的不同,以及理解网络协议栈收报文的基础。 Linux 支持 CPU 的外部硬件中断和内部中断。严格来说,内

    中断及任务调度管理

    Linux 书籍中常说的 BottomHalf 已然不见了,它们被转成 tasklets,这是支持 SMP 的。但其思想基本 一致。

    中断及软中断模型

    我们在此不会对中断及异常的原理和机制做深入的介绍。但必须要作出一些说明,因为这是理解 Linux 内核与其它嵌入式/实时操作系统的不同,以及理解网络协议栈收报文的基础。 Linux 支持 CPU 的外部硬件中断和内部中断。严格来说,内部中断包含系统调用陷入和异常,在一 般的嵌入式操作系统(比如 VxWorks)中是没有系统调用这个概念的,所以对于一直从事嵌入式软件开 发的人初次进入到大型操作系统(比如 Linux 和 Windows)开发环境中,会面临内核空间与用户空间概 念上的困惑。其实说到底,所谓系统调用就是软件有计划地调用 CPU 提供的特殊指令,触发 CPU 内部 产生一个中断,于是完成一次核内核外运行空间的切换,具体可以参考许多书籍。而所谓异常就是软件 无意的执行了一个非法指令(比如除 0)从而造成 CPU 内部引发一次中断。
    外部中断特指外部设备发出的中断信号。但这几种中断的 CPU 处理过程基本相同,即:在执行完当 前指令后,或在执行当前指令期间,根据中断源所提供的“中断向量”,在内存中找到相应的 ISR(中断 服务例程)然后调用之。
    不管是内部还是外部中断,系统都会根据接收到的中断信息,查询 idt 表。idt 表依照中断源的位置按序组成,并对应中断服务程序(以及异常处理程序)的入口地址。Linux 系统在初始化页式虚存管理的 初始化以后,便调用 trap_init 和 init_IRQ 两个函数进行中断机制的初始化。我们只介绍init_IRQ

    中断系统和软中断

    中断向量?中断请求号?这是个问题。 现在分析试图给大家解释一下,看看是不是这样的:IRQ 是设备相关的号码,一般生产厂商都会使 自己的设备分配到一个合适的号码。而中断向量就纯粹是操作系统中关于如何处理中断的内存组织结构, 它们之间存在某种映射关系,这种关系是由 CPU 体系结构以及操作系统决定的。那么在 IA32 体系的 Linux 中,是一种直接映射的关系,所有的 IRQ 号产生的中断全部映射到从 INT_vec[32]开始的内存中。为什么 要从第 32 个单元开始呢?

    这里写图片描述

    上图中彩色部分都是系统能处理的中断,intel CPU 保留使用的中断向量是 0~32,根本不可能有哪 一种设备会使用这个区域的中断向量,这一部分就是我们常说的异常处理函数,还有一个比较特殊的中 断向量号 0x80(即 128)就是系统调用号,由于不可能由外部设备引发这类中断,它们就被统称为内部 中断,这就是为什么要从第 32 个单元开始的原因。内核用调用 trap_init函数挂接与之相应的中断处理函 数。接着系统调用init_IRQ 函数来初始化外部中断向量,其中断处理函数的挂接由各驱动程序自己完成。 由上图可以看出中断向量和中断请求号是相关但却不是一个东西:前者是内核中存在的一块内存,专门 存放中断处理函数的地址(指逻辑上,具体实现比较复杂) ,而后者就是一个概念,在内核中不必存在这 么一种变量,它可能就是中断向量的下标。
    在 2.6 的内核中,中断相关的宏已经变化了,2.4 内核中的中断概念请看《Linux 内核源代码情景分 析 》。 在 2.6 内核的 entry.S文件中,有一 个 interrupt 的定义, 它 放在.data 节中,然 后 ,在 include/asm-i386/hw_irq.h 中引用这个变量,最后在 arch/i386/kernel/i8259.c 中初始化这个变量。
    下面两个代码片断是 2.4 内核关于这个 interrupt 变量的初始化:

     1 #define IRQ(x,y) \ 
     2 IRQ##x##y##_interrupt  
     3  
     4 #define IRQLIST_16(x) \  
     5 IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \  
     6 IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \  
     7 IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \  
     8 IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f) 
     9
     10 void (*interrupt[NR_IRQS])(void) = {  
     11 IRQLIST_16(0x0),  
     12 ... 
    

    经过编译器的预处理,interrupt 这个函数指针数组变成:

    1 void (*interrupt[NR_IRQS])(void) = {  
    2     IRQ0x00_interrupt,  
    3     IRQ0x01_interrupt,  
    4     IRQ0x02_interrupt,  
    5    ……  
    6    IRQ0x0f_interrupt,  
    7 }
    

    从代码中看出,这样的初始化不太灵活,扩展性比较差。下面给出 2.6 内核关于 interrupt 的使用方式。 首先在 entry.S 中汇编代码如下:
    这里写图片描述

    hw_irq.h 中有这样的定义:extern void (*interrupt[NR_IRQS])(void);在此,NR_IRQS224。具体 的初始化如下:

     1 void __init init_IRQ(void) 
     2 {  
     3     int i;  
     4  
     5 /* all the set up before the call gates are initialised */  
     6     pre_intr_init_hook();  
     7  
     8 /*  
     9 * 扫描整个中断向量表  
     10 */  
     11    for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {  
     12        int vector = FIRST_EXTERNAL_VECTOR + i;  
     13        if (i >= NR_IRQS)  
     14            break;     //如果是系统调用中断号,就初始化这个中断号  
     15        if (vector != SYSCALL_VECTOR)  
     16           set_intr_gate(vector, interrupt[i]);  
     17 }  
     18 ......  
     19 /*  
     20 * Set the clock to HZ Hz, we already have a valid vector now:  
     21 */  
     22     setup_pit_timer ();  
     23  
     24 ......  
     25  
     26      irq_ctx_init(smp_processor_id());  
     27 }  
    

    下面是关于中断上下文的一些宏,说明中断处理到达一种什么样的程度:
    这里写图片描述

    在 include/linux/irq.h 文件中,有关于中断控制器的描述,中断控制器描述符. 它包含了所有低层硬件 的信息。
    其中一个实例是 i8259A_irq_type,它定义在 arch/i386/kernel/i8259.c 中:
    这里写图片描述

    下面这个数组的定义利用了 GCC 编译器的特点,只定义了一个单元的值,然后使用[0 … NR_IRQS-1] 使整个数组的值都初始化为同样的值:
    这里写图片描述

    现在让我们从整体上看中断和软中断的处理过程,do_IRQ 是直接被调用的:
    这里写图片描述

    每个外部中断都会调用 do_IRQ,此函数根据当时的 EAX 寄存器(i386 体系)值来判断当前属于哪 个 IRQ 去调用__do_IRQ

    __do_IRQ把 action 传入了 handle_IRQ_event,然后在其中执行action->handler,此 handle 就是每个设备驱动程 序挂接的 ISR

    asmlinkage int handle_IRQ_event(unsigned int irq,
               struct pt_regs *regs, struct irqaction *action)
    {
          int status = 1;  /* Force the "do bottom halves" bit */
          int ret, retval = 0;
    
          //如果没有设置SA_INTERRUPT.将CPU 中断打开
          //应该尽量的避免CPU关中断的情况,因为CPU屏弊本地中断,会使
          //中断丢失
          if (!(action->flags & SA_INTERRUPT))
               local_irq_enable();
    
          //遍历运行中断处理程序
          do {
               ret = action->handler(irq, action->dev_id, regs);
               if (ret == IRQ_HANDLED)
                     status |= action->flags;
               retval |= ret;
               action = action->next;
          } while (action);
          if (status & SA_SAMPLE_RANDOM)
               add_interrupt_randomness(irq);
          //关中断
          local_irq_disable();
          return retval;
    }

    上面介绍的是硬件过来的中断,而传说中的软件中断(不是软中断)是怎么工作的呢?其实很简单, 下面这副图是否能满足您的求知欲呢?那些曲线箭头表示代码的执行路径。要注意的是在 socket( )函数的 实现过程中,那些 mov 指令就是告诉内核要跳转的系统调用函数以及用户待传入内核的参数地址。不同 的 CPU 结构上会使用不同的寄存器,这里就不详细说明了。

    这里写图片描述

    所以,软件中断的处理方式和硬件的处理路径是完全不一样的,它不必经过do_IRQ这个函数,而是 直接跳转到内核中的代码执行 sys_socketcall。在 2.6.18 的内核中,BSD 网络接口的方式已经变成了使用 sys_socketcall 来解复用不同的系统调用,这样做的好处是减少系统调用表的大小,可以集中管理网络方 面的 API:

    asmlinkage long sys_socketcall(int call, unsigned long __user *args)
    {
        unsigned long a[6];
        unsigned long a0, a1;
        int err;
    
        if (call < 1 || call > SYS_ACCEPT4)
            return -EINVAL;
    
        /* 调用copy_from_user函数,从用户空间的内存地址拷贝参数到内核空间 */
        if (copy_from_user(a, args, nargs[call]))
            return -EFAULT;
    
        err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
        if (err)
            return err;
    
        a0 = a[0];
        a1 = a[1];
    
        switch (call) {
        case SYS_SOCKET:
            err = sys_socket(a0, a1, a[2]);
            break;
        case SYS_BIND:
            err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
            break;
        case SYS_CONNECT:
            err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
            break;
        case SYS_LISTEN:
            err = sys_listen(a0, a1);
            break;
        case SYS_ACCEPT:
            err = sys_accept4(a0, (struct sockaddr __user *)a1,
                     (int __user *)a[2], 0);
            break;
        case SYS_GETSOCKNAME:
            err =
             sys_getsockname(a0, (struct sockaddr __user *)a1,
                     (int __user *)a[2]);
            break;
        case SYS_GETPEERNAME:
            err =
             sys_getpeername(a0, (struct sockaddr __user *)a1,
                     (int __user *)a[2]);
            break;
        case SYS_SOCKETPAIR:
            err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]);
            break;
        case SYS_SEND:
            err = sys_send(a0, (void __user *)a1, a[2], a[3]);
            break;
        case SYS_SENDTO:
            err = sys_sendto(a0, (void __user *)a1, a[2], a[3],
                     (struct sockaddr __user *)a[4], a[5]);
            break;
        case SYS_RECV:
            err = sys_recv(a0, (void __user *)a1, a[2], a[3]);
            break;
        case SYS_RECVFROM:
            err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],
                     (struct sockaddr __user *)a[4],
                     (int __user *)a[5]);
            break;
        case SYS_SHUTDOWN:
            err = sys_shutdown(a0, a1);
            break;
        case SYS_SETSOCKOPT:
            err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);
            break;
        case SYS_GETSOCKOPT:
            err =
             sys_getsockopt(a0, a1, a[2], (char __user *)a[3],
                     (int __user *)a[4]);
            break;
        case SYS_SENDMSG:
            err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);
            break;
        case SYS_RECVMSG:
            err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]);
            break;
        case SYS_ACCEPT4:
            err = sys_accept4(a0, (struct sockaddr __user *)a1,
                     (int __user *)a[2], a[3]);
            break;
        default:
            err = -EINVAL;
            break;
        }
        return err;
    }

    目前为止,我们我们讨论的都是真正的中断,那么什么是软中断呢?请读者回顾在中断即将退出的 时候会调用irq_exit,它内部会判断是否还有中断要处理,如果已经没有了就调用 invoke_softirq,这是一 个宏,它被定义成 do_softirq,此函数最终调用__do_softirq,这也就是说,实际上软中断是在处理完所有 中断之后才会处理的。而且处理软中断的时候还是处于中断上下文中。不过有一些限制,详见下面对 __do_softirq代码的分析。
    在目前 Linux 内核中定义了 6 种软中断,而且告诫我们不要轻易的再定义新的软中断,原话如下:

    PLEASE, avoid to allocate new softirqs, if you need not _really_ high frequency threaded job scheduling. For almost all the purposes tasklets are more than enough. F.e. all serial device BHs et al. should be converted to tasklets, not to softirqs 
    

    这里写图片描述
    软中断向量 0(即HI_SOFTIRQ)用于实现高优先级的软中断,软中断向量 3(即 TASKLET_SOFTIRQ) 则用于实现诸如 tasklet 这样的一般性软中断。
    Tasklet 机制是一种较为特殊的软中断。Tasklet 一词的原意是“小片任务”的意思,这里是指一小段可 执行的代码,且通常以函数的形式出现。软中断向量 HI_SOFTIRQTASKLET_SOFTIRQ均是用 tasklet 机制来实现的。 从某种程度上讲,tasklet 机制是 Linux 内核对 BH 机制的一种扩展。在 2.4 内核引入了 softirq 机制后, 原有的 BH 机制正是通过 tasklet 机制这个桥梁来纳入 softirq 机制的整体框架中的。正是由于这种历史的 延伸关系,使得 tasklet 机制与一般意义上的软中断有所不同,而呈现出以下两个显著的特点:
    1. 与一般的软中断不同,某一段 tasklet 代码在某个时刻只能在一个 CPU 上运行,而不像一般的软 中断服务函数(即softirq_action结构中的 action 函数指针)那样??在同一时刻可以被多个 CPU 并发 地执行。
    2. 与 BH 机制不同,不同的 tasklet 代码在同一时刻可以在多个 CPU 上并发地执行,而不像 BH 机制 那样必须严格地串行化执行(也即在同一时刻系统中只能有一个 CPU 执行 BH 函数)。 Bottom Half 机制在新的 softirq 机制中被保留下来,并作为 softirq 框架的一部分。其实现也似乎更为 复杂些,因为它是通过 tasklet 机制这个中介桥梁来纳入 softirq 框架中的。实际上,软中断向量 HI_SOFTIRQ 是内核专用于执行 BH 函数的。

    void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
    {   //nr 即软中断向量编号
        softirq_vec[nr].data = data;
        softirq_vec[nr].action = action;
    }
    

    我们会发现__do_softirq 内部最多只处理 10 个软中断,如果系统内部的软中断事件太多,那么就会 通知 ksoftirqd 内核线程处理软中断。这样,就不会占用太多的中断上下文执行时间。
    图解:
    这里写图片描述

    asmlinkage void __do_softirq(void)
    {
        struct softirq_action *h;
        __u32 pending;
        int max_restart = MAX_SOFTIRQ_RESTART;
        int cpu;
    
        pending = local_softirq_pending();
        account_system_vtime(current);
    
    /*软中断处理中,禁止软中断再次进入,软中断处理是不可重入的*/
        __local_bh_disable((unsigned long)__builtin_return_address(0));
        trace_softirq_enter();
        cpu = smp_processor_id();
    restart:
        /* Reset the pending bitmask before enabling irqs
        下面首先清除pending,以便系统可以激活其它软件中断,
        然后使能外部中断
        系统在下面的处理中,将使能外部中断以提高系统的响应,
        注意,必须先清除pending,再使能外部中断,否则死锁*/
        set_softirq_pending(0);
    
        local_irq_enable();
    
        h = softirq_vec;
    
    /*下面按照从高到低调用open_softirq注册的句柄*/
        do {
            if (pending & 1) {
                h->action(h);  //关键的一句,tasklet、内核timer、网络中断都是在这里搞的
                rcu_bh_qsctr_inc(cpu);
            }
            h++;
            pending >>= 1;
        } while (pending);
    
        local_irq_disable();
    
        pending = local_softirq_pending();
        if (pending && --max_restart)
            goto restart;
    
        if (pending)
            wakeup_softirqd();
    
        trace_softirq_exit();
    
        account_system_vtime(current);
        _local_bh_enable();
    }

    设备驱动挂接 ISR

    设备驱动程序要处理硬件中断,必须挂接 ISR,则挂接一个 ISR 可以用这个函数

    这里写图片描述
    要注意的是你在挂接 ISR 之前要正确的初始化你的设备,并且要保证用正确的顺序挂接中断。里面 调用 setup_irq 就是把创建的irqaction{}挂接到对应中断的链表上,以至于handle_IRQ_event能根据 irq 号直接找到对应handler

    展开全文
  • 一个是前几天要写《Linux网络协议栈--UDP》结果卡在这个函数这了。 另外一个就是这个函数又是UDP报文必经之路,而且对其理解对于套接口中发送队列的理解非常有帮助,所以认真去学习了下。 文章定位: (1)尽...

    开场白:

    要分析这个函数原因有两个:

    一个是前几天要写《Linux网络协议栈--UDP》结果卡在这个函数这了。

    另外一个就是这个函数又是UDP报文必经之路,而且对其理解对于套接口中发送队列的理解非常有帮助,所以认真去学习了下。


    文章定位:

    (1)尽可能撇开一些不需要的细节,重点介绍流程

    (2)说明发送队列是如何组织起来的

    (3)书本肯定都比我说的好,要真的看明白,还是需要看书,这里能做的就是将其结构重新组织,并且将不同的书的内容重新揉合,帮忙看的更快一些


    参考书籍:

    (1)《Understand Linux Kernel Internel》

    (2)《Linux内核源码剖析-TCP/IP实现》

    (3)linux内核源码--我使用的版本是3.2.4

    注:虽然这写书都有电子版的,不过我还是希望大家能够支持正版。作者写这些书不容易。



    一、cork

    一开始先来说一个单词,以下内容为个人理解,仅供参考,如果有错恳请指正。

    UDP相关的数据经常会存储在一个名为cork的变量中,第一次看的时候非常的让人感觉疑惑。

    cork在英文中是软木塞的意思,那软木塞又和UDP有什么关系?

    可以将UDP底部到IP的部分看成一个漏斗,如果从UDP下来的数据都是小数据(比如都只有几十个字节),那无疑会加重下层处理数据的负担,而且会让网络充斥各种小报文。所以cork给人的感觉就是将这个漏斗底部给堵住,等在一定时候再拔掉这个塞子,这样就可以把各种小数据汇集成一个大数据了。

    不过需要注意的是,cork的标志是需要应用层来设置,所以这个塞塞子和拔塞子的动作都掌握在应用层手中,所以可以掐头去尾的看了。


    二、ip_append_data在做什么

    就是将上层下来的数据进行整形,如果是大数据包进行切割,变成多个小于或等于MTU的SKB。如果是小数据包,并且开启了聚合,就会将若干个数据包整合。

    说的简单,但是在实现的时候做起来就复杂了,因为函数中考虑到了

    (1)如何填充队列中上一个skb中未填充的部分?

    (2)如何将队列中上一个skb中不能进行对齐的数据部分移动到新的skb中

    (3)如何什么时候分配skb,而且skb的大小是多少

    (4)如何在分配skb的时候为下层预留足够的空间

    (5)需要将数据重用户空间拷贝到内核空间,那怎么拷贝效率才高

    (6)如何才能减少内存的拷贝消耗

    (7)……

    因为考虑的事情太多,所以做起来就比较繁琐了。


    三、参数

    先介绍下参数,虽然参数多,但是关键参数却很少

    注:该参数是3.2.4中的,可能与其他版本的不一样,不过不影响整体介绍

    struct *sk : 

    struct flowi4 *fl4 : 

    struct sk_buff_head *queue : 

    struct inet_cork *cork : 输出数据块的地址。

    int getfrag() : 将数据复制到SKB中,其为一个函数指针,会有不同的选择,在udp_sendmsg最开始的时候会进行初始化。其可能的函数如图1-1所示(见《Linux内核源码剖析-TCP/IP实现》表11-12

    图 1-1

    void *from : 

    int length : 数据长度

    int transhdrlen : 传输层首部长度,同时也是标志是否为第一个fragment的标志

    struct ipcm_cookie *ipc :

    struct rtable **rpt : 

    unsigned int flags : 处理标志,如图1-2所示(见《Linux内核源码剖析-TCP/IP实现》表23-1),在ip_append_data中只用到其中两个MSG_PROBE和MSG_MORE。其余暂时不关心


    图1-2


    三、几个标志

    ip_append_data代码非常大,主要是它存在多个分支,不过令人高兴的是,它的分支的标志都比较清晰,所以看到如下标志就需要多注意了: 

    1、copy

    队列中最后一个skb剩余的空间,存在3种情况,如图3-1所示(《Understand Linux Kernel Internal》章节21.1.4.9):


    图3-1

    注 : 不过在函数中是分为两种情况分别是:copy <= 0 和 copy > 0


    2、flag & MSG_MORE

    MSG_MORE在图1-2中就介绍了,在ip_append_data中这个标志也是分支的判断依据之一

     

    3、rt->dst.dev->features&NETIF_F_SG

    这个标识是判断是否开启 聚合分散 I/O的标识,也是分支的判断依据之一


    下文会慢慢介绍这几个分支是怎么回事。


    四、流程及代码分析

    内核3.2.4中ip_append_data函数流程和以前的基本一样,区别就是将最主要的循环部分包裹到了__ip_append_data函数中。

    1、ip_append_data流程图

    图4-1是ip_append_data的主要流程图(见《Linux内核源码剖析-TCP/IP实现》图11-8),不过个人感觉这个图画的也不是太好,主要是下半部分画的不是太清晰,后面会用其他的流程图替换该图下半部分说明。


    图4-1

    (1)在3.2.4内核中将图中蓝色框中的部分包裹到了函数__ip_append_data中,不过这个部分本来就是整个函数的重点,是最主要的循环。

    (2)绿色框内的部分包裹在ip_ufo_append_data中,这个函数相对比较简单,最后会给介绍。

    (3)现在说明函数上半部分内容:

    int ip_append_data(struct sock *sk, struct flowi4 *fl4,
    		   int getfrag(void *from, char *to, int offset, int len,
    			       int odd, struct sk_buff *skb),
    		   void *from, int length, int transhdrlen,
    		   struct ipcm_cookie *ipc, struct rtable **rtp,
    		   unsigned int flags)
    {
    	struct inet_sock *inet = inet_sk(sk);
    	int err;
    
    	if (flags&MSG_PROBE) /*见《Linux内核源码剖析TCP/IP实现》表23-1 */
    		return 0;
    
    	if (skb_queue_empty(&sk->sk_write_queue)) {
    		err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
    		if (err)
    			return err;
    	} else { /*队列不为空,则使用上次的路由,IP选项,以及分片长度 */
    		transhdrlen = 0;
    	}
    这里就只需要注意一个参数:MSG_PROBE,内容见图1-2的说明。
    (4)然后就进入下半部的内容:

    	return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base, getfrag,
    				from, length, transhdrlen, flags);
    }
    


    2、__ip_append_data流程图

    图4-2本来是ip_append_data的主要循环,在3.2.4中是__ip_append_data的流程图,不伤大雅。(见《Understand Linux Kernel Internel》图21-11

    为了便于后面说明,给图中每个分支进行了编号,图中标识的(图21-X是该分支最后得到的数据结构图,这些图在《Understand Linux Kernel Internel》中可以找到

    图 4-2

    2.1、分支(1)

    该分支的代码部分如下:

    static int __ip_append_data(struct sock *sk,
    			    struct flowi4 *fl4,
    			    struct sk_buff_head *queue,
    			    struct inet_cork *cork,
    			    int getfrag(void *from, char *to, int offset,
    					int len, int odd, struct sk_buff *skb),
    			    void *from, int length, int transhdrlen,
    			    unsigned int flags)
    {
    	struct inet_sock *inet = inet_sk(sk);
    	struct sk_buff *skb;
    
    	struct ip_options *opt = cork->opt;
    	int hh_len;
    	int exthdrlen;
    	int mtu;
    	int copy;
    	int err;
    	int offset = 0;
    	unsigned int maxfraglen, fragheaderlen;
    	int csummode = CHECKSUM_NONE;
    	struct rtable *rt = (struct rtable *)cork->dst;
    
    	skb = skb_peek_tail(queue); /*这里skb有两种情况,如果队列为空,
                                      则skb = NULL,否则为尾部skb的指针 */
    
    
        /*这部分内容最好参考《understand linux network internal》图21-10*/
    	exthdrlen = !skb ? rt->dst.header_len : 0;
    	mtu = cork->fragsize;
    
    	hh_len = LL_RESERVED_SPACE(rt->dst.dev); /*链路层首部长度 */
    
    	fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0); /* IP首部(包括IP选项)长度 */
    	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen; /* 最大IP首部长度,注意对齐 */
    
    	if (cork->length + length > 0xFFFF - fragheaderlen) { /*一个IP数据包最大大小不能超过64K */
    		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
    			       mtu-exthdrlen);
    		return -EMSGSIZE;
    	}
    
    	/*
    	 * transhdrlen > 0 means that this is the first fragment and we wish
    	 * it won't be fragmented in the future.
    	 */
    	if (transhdrlen &&
    	    length + fragheaderlen <= mtu &&
    	    rt->dst.dev->features & NETIF_F_V4_CSUM &&
    	    !exthdrlen)
    		csummode = CHECKSUM_PARTIAL; /*由硬件执行校验和计算 */
    
    	cork->length += length; /*更新数据长度 */
    
        /* 对于UDP报文,新加的数据长度大于MTU,并且需要进行分片,则需要
         * 进行分片处理
         * 这里相当于《understand linux network internel》图21-11最左边的那条支线
         * 注意:这里需要加入判断skb是否为NULL*/
    	if (((length > mtu) || (skb && skb_is_gso(skb))) &&
    	    (sk->sk_protocol == IPPROTO_UDP) &&
    	    (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len) {
            /* ufo = UDP fragmentation offload*/
    		err = ip_ufo_append_data(sk, queue, getfrag, from, length,
    					 hh_len, fragheaderlen, transhdrlen,
    					 maxfraglen, flags);
    		if (err)
    			goto error;
    		return 0;
    	}
    
    	/* So, what's going on in the loop below?
    	 *
    	 * We use calculated fragment length to generate chained skb,
    	 * each of segments is IP fragment ready for sending to network after
    	 * adding appropriate IP header.
    	 */
    
    	if (!skb)
    		goto alloc_new_skb;
    (1)通过skb_peek_tail得到发送队列中最后一个skb,如果队列为空,则该函数返回NULL


    (2)代码最后2行就是分支的判断语句


    (3)中间的是很多初始化操作,这里很有必要提一点的就是skb_peek_tail语句后面的几行代码,这里涉及到了8字节对齐的内容,见图4-3(见《Understand Linux Kernel Internel》图21-10


    图 4-3

    注:代码中的变量对应图中看就基本能看懂了。


    (4)在最后分支语句上的一个分支语句需要返回图4-1看绿色框部分的内容


    2.2、分支(2)

        /* 这个循环最好参照《understand linux network internel》图21-11
         * 主要可以分为4条支线,copy <= 0和copy > 0两种与是否设置NETIF_F_SG
         * 标志两种的组合。
         * 这几种组合可以结合《understand linux network internel》图21-3~图21-6
         * 来看。*/
    	while (length > 0) {
    		/* Check if the remaining data fits into current packet. */
    		copy = mtu - skb->len;
            /* copy > 0 : 最后一个skb还有一些空余空间
             * copy = 0 : 最后一个skb已经被填满
             * copy < 0 : 有些数据必须从当前IP片段中删除移动到新的片段*/
    		if (copy < length)
    			copy = maxfraglen - skb->len; /*获取一次可以拷贝的份额 */
    (1)while (lengh > 0)就是分支2的内容


    2.3、分支(3) -> yes

    分支(3)的依据是copy的值,在图4-2中已经标明,这里先看分支(3) -> yes部分内容:

    		if (copy <= 0) {
    			char *data;
    			unsigned int datalen;
    			unsigned int fraglen;
    			unsigned int fraggap;
    			unsigned int alloclen;
    			struct sk_buff *skb_prev;
    alloc_new_skb:
    			skb_prev = skb;
    			if (skb_prev) /*需要计算从上一个skb中复制到新的新的skb中的数据长度 */
    				fraggap = skb_prev->len - maxfraglen; /*明显就是copy取反 */
    			else
    				fraggap = 0;
    
    			/*
    			 * If remaining data exceeds the mtu,
    			 * we know we need more fragment(s).
    			 */
    			datalen = length + fraggap;
    			if (datalen > mtu - fragheaderlen)
    				datalen = maxfraglen - fragheaderlen;
    			fraglen = datalen + fragheaderlen;
    
                /* 对应图21-11中MSG_MORE?的分支*/
    			if ((flags & MSG_MORE) &&
    			    !(rt->dst.dev->features&NETIF_F_SG))
    				alloclen = mtu; /*最大尺寸分配缓冲区,参考图21-3 */
    			else
    				alloclen = fraglen; /*确切尺寸分配,参考图21-4
                                          注:fraglen = datalen + fragheaderlen*/
    (1)这部分代码已经项图4-2中虚线框部分的内容全部包括进去了(即包含了分支(5)和分支(7))

    (2)需要注意的是,这里仅仅只是确定(而且还只是初步确定)了之后需要分配的skb缓存大小

    (3)alloc_new_skb是分支(1) -> yes的跳转后的入口

    (4)fraggap的内容请参考图4-3


    之后的代码就是进一步确定分配空间,然后分配,最后将成功分配的skb插入到发送队列中,代码不难读懂:

    			alloclen += exthdrlen; /*扩展长度支持 */
    
    			/* The last fragment gets additional space at tail.
    			 * Note, with MSG_MORE we overallocate on fragments,
    			 * because we have no idea what fragment will be
    			 * the last.
    			 */
    			if (datalen == length + fraggap)
    				alloclen += rt->dst.trailer_len;
    
                /* 分配SKB的空间*/
    			if (transhdrlen) {
    				skb = sock_alloc_send_skb(sk,
    						alloclen + hh_len + 15,
    						(flags & MSG_DONTWAIT), &err);
    			} else {
    				skb = NULL;
    				if (atomic_read(&sk->sk_wmem_alloc) <=
    				    2 * sk->sk_sndbuf)
    					skb = sock_wmalloc(sk,
    							   alloclen + hh_len + 15, 1,
    							   sk->sk_allocation);
    				if (unlikely(skb == NULL))
    					err = -ENOBUFS;
    				else
    					/* only the initial fragment is
    					   time stamped */
    					cork->tx_flags = 0;
    			}
    			if (skb == NULL)
    				goto error;
    
    			/*
    			 *	Fill in the control structures
    			 */
    			skb->ip_summed = csummode;
    			skb->csum = 0;
    			skb_reserve(skb, hh_len);
    			skb_shinfo(skb)->tx_flags = cork->tx_flags;
    
    			/*
    			 *	Find where to start putting bytes.
    			 */
    			data = skb_put(skb, fraglen + exthdrlen); /*预留L2,L3首部空间 */
    			skb_set_network_header(skb, exthdrlen); /*设置L3层的指针 */
    			skb->transport_header = (skb->network_header +
    						 fragheaderlen);
    			data += fragheaderlen + exthdrlen;
    
    			if (fraggap) { /*填充原来的skb尾部的空间 */
    				skb->csum = skb_copy_and_csum_bits(
    					skb_prev, maxfraglen,
    					data + transhdrlen, fraggap, 0);
    				skb_prev->csum = csum_sub(skb_prev->csum,
    							  skb->csum);
    				data += fraggap;
    				pskb_trim_unique(skb_prev, maxfraglen);
    			}
    
    			copy = datalen - transhdrlen - fraggap;
    			if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
    				err = -EFAULT;
    				kfree_skb(skb);
    				goto error;
    			}
    
                /* 计算下次需要复制的数据长度*/
    			offset += copy;
    			length -= datalen - fraggap;
    			transhdrlen = 0; /*注意 */
    			exthdrlen = 0; /*注意 */
    			csummode = CHECKSUM_NONE;
    
    			/*
    			 * Put the packet on the pending queue.
    			 */
    			__skb_queue_tail(queue, skb); /*将skb添加的尾部 */
    			continue;
    		}

    (1)这里第一个需要注意的就是fraggap的内容,还是参照图4-3来看

    (2)最后需要计算下次需要复制的数据的长度。

    可能只看代码会比较让人头晕,所以下面给出这些内存分配后数据组织的情况:

    注:在代码中这些部分内容都揉在一起,也不是不能拆,只是觉得拆了以后代码太零散,看的更让人头晕。

    (a)分支(5) -> yes (见《Understand Linux Kernel Internel》图21-5


    图 4-4

    这里需要注意PMTU大小和skb缓存实际大小。


    (b)分支(7) -> no (见《Understand Linux Kernel Internel》图21-3


    图 4-5

    注:这里需要注意一些参数之间的关系:


    (c) 分支(7)-> yes (见《Understand Linux Kernel Internel》图21-4


    图 4-6

    注:这里注意skb缓存大小的分配。


    2.4 分支(3)-> no

    该分支对应copy > 0的情况,根据分支4中的情况会分为两条支线。

    2.4.1 分支(4) -> no

    		if (copy > length)
    			copy = length;
    
    		if (!(rt->dst.dev->features&NETIF_F_SG)) { /*不支持分散聚合,《understand 
                                                         linux netowrk internel》图21-11 
                                                         中的分支,直接填充缓存*/
    			unsigned int off;
    
    			off = skb->len;
    			if (getfrag(from, skb_put(skb, copy),
    					offset, copy, off, skb) < 0) {
    				__skb_trim(skb, off);
    				err = -EFAULT;
    				goto error;
    			}
    copy > 0 说明有足够的空间,如果不开启分散聚合 I/O,就直接拷贝,之后的数据结构如下图(见 《Understand Linux Kernel Internel》图21-6(b)

    图 4-7

    2.4.2 、分支(4) ->  yes

    		} else {
    			int i = skb_shinfo(skb)->nr_frags;
    			skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
    			struct page *page = cork->page;
    			int off = cork->off;
    			unsigned int left;
    
    			if (page && (left = PAGE_SIZE - off) > 0) { /*已经分配了页面 */
    				if (copy >= left)
    					copy = left;
    				if (page != skb_frag_page(frag)) {
    					if (i == MAX_SKB_FRAGS) {
    						err = -EMSGSIZE;
    						goto error;
    					}
    					skb_fill_page_desc(skb, i, page, off, 0);
    					skb_frag_ref(skb, i);
    					frag = &skb_shinfo(skb)->frags[i];
    				}
    			} else if (i < MAX_SKB_FRAGS) {
                    /* 注: MAC_SKB_FRAGS是最大片段数量,即skb->frags数组的最大下标
                     * 注2: 由此可以推断一个IP封包最大不能超过64K + MTU
                     * 注3: IP封包大小不是IP报文大小,IP报文大小根据MTU的值规定*/
    				if (copy > PAGE_SIZE)
    					copy = PAGE_SIZE;
    				page = alloc_pages(sk->sk_allocation, 0); /*分配一个页面 */
    				if (page == NULL)  {
    					err = -ENOMEM;
    					goto error;
    				}
    				cork->page = page;
    				cork->off = 0;
    
    				skb_fill_page_desc(skb, i, page, 0, 0);
    				frag = &skb_shinfo(skb)->frags[i];
    			} else {
    				err = -EMSGSIZE;
    				goto error;
    			}
    			if (getfrag(from, skb_frag_address(frag)+skb_frag_size(frag), /*将数据拷贝到页面内 */
    				    offset, copy, skb->len, skb) < 0) {
    				err = -EFAULT;
    				goto error;
    			}
    			cork->off += copy;
    			skb_frag_size_add(frag, copy);
    			skb->len += copy;
    			skb->data_len += copy;
    			skb->truesize += copy;
    			atomic_add(copy, &sk->sk_wmem_alloc);
    		}
    		offset += copy;
    		length -= copy;
    	}
    
    	return 0;
    注:上述代码已经包含了页面分配的内容

    《Understand Linux Kernel Internel》图21-7

    图 4-8

    注:这里注意下MAX_SKB_FRAGS,源代码定义是16,一个page是4K,所以一个IP封包最大的大小是64K.


    2.5 扫尾。

    	return 0;
    
    error:
    	cork->length -= length;
    	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
    	return err;
    }


    结束:

    关于ip_ufo_append_data函数,如果弄懂上面的过程,那个看起来就很简单了。ip_append_data函数涉及的参数很多,但是涉及的知识面却是很少的,主要都是在缓存分配和数据结构组织上。所有的内容去其实都在《understand linux network internal》图21-2~图21-8中。对照源码以及《Linux内核源码剖析-TCP/IP实现》中的说明,看起来应该还是比较简单的。

    另外还有ip_append_page函数,我没去看,感觉应该和这个差不多。












    展开全文
  • Linux内核网络协议栈笔记

    千次阅读 2018-04-13 17:14:42
    Linux内核网络协议栈笔记0:序言(附参考书籍)转自:http://www.th7.cn/system/lin/2011/08/11/18810.shtml自己是研究网络的,但实际上对Linux网络协议栈的实现知之甚少。最近看完《深入理解Linux内核》前几章...

    Linux内核网络协议栈笔记0:序言(附参考书籍)

    转自:http://www.th7.cn/system/lin/2011/08/11/18810.shtml

    自己是研究网络的,但实际上对Linux中网络协议栈的实现知之甚少。最近看完《深入理解Linux内核》前几章之后(特别是与网络子系统密切相关的软中断),觉得可以而且应该看一下网络协议栈了。这部分网上的文章大部分都没有什么结构和思路,很少有能够条分缕析的把协议栈讲述明白的。当然,个人水平有限,还是希望朋友们能够批评指正。
    参考书籍《Understanding Linux Network Internals》以及《The Linux Networking Architecture Design and Implementation of Network Protocols in the Linux Kernel》,在我的Skydrive里(点这里)可以下到英文chm版。
    先大体说说这两本巨著吧。前者确实是一本关于internals的书,前三个part:General Background/System Initialization/Transmission and Reception以及第5个part:IPv4比较有用,而且思路也与本文所采用的吻合:从系统初始化到数据包的发送与接收。而后者也确实是一本architecture的书,采用了与TCP/IP协议栈(不是OSI 7层)一样的5层架构自底向上讲述了Linux内核的相关内容。

    全系列文章都基于Linux内核2.6.11版本,如果最新版本(当前是2.6.30)有较大变化,也会给与标出。


      

    Linux内核网络协议栈笔记1:协议栈分层/层次结构

    大家都知道TCP/IP协议栈现在是世界上最流行的网络协议栈,恐怕它的普及的最重要的原因就是其清晰的层次结构以及清晰定义的原语和接口。不仅使得上层应用开发者可以无需关心下层架构或者内部机制,从而相对透明的操作网络。这个明显的层次结构也可以在Linux内核的网络协议栈中观察到。

    主要的参考文献是:Linux网络栈剖析(中文版)/Anatomy of Linux networking stack(英文原版)by Tim Jones.

    以及:Linux内核2.4.x的网络接口结构

    另外一些参考资料可以从这个页面找到:http://www.ecsl.cs.sunysb.edu/elibrary/linux/network/ (纽约州立大学石溪分校的页面)

    Linux内核网络协议栈采用了如下的层次结构:

    内核中的五层分别是(从上到下):

    系统调用接口(详见Jones的另一篇文章:使用Linux系统调用的内核命令

    协议无关接口(BSD socket层)

    网络协议(或者简称网络层。这是一个协议的集合,从链路层到传输层的协议都包括在内。不同的协议在/net文件夹下除core以外的子目录下,例如基于IP的协议簇都在/net/ipv4目录下,以太网协议在/net/ethernet目录下)

    驱动无关接口(又称通用设备层--generic device layer/驱动接口层/设备操作层--device handling layer。属于网络协议栈最核心的部分,文件位于内核/net/core文件夹下,所以又叫网络核心层。其中包括了核心的数据结构skbuff文件中的sk_buff/dev.c文件中net_device,这些数据结构将在下篇文章中介绍)

    设备驱动程序(在/driver/net文件夹内)

    不像OSI或者TCP/IP协议栈,事实上并没有一个命名标准,因此在这里,这些层次的名称并不是通用的,但是其语义是清晰的,而且在大多数其他的文章里只是个别字上的差别。分层详细介绍可以参考Jones的文章。

     

    Linux内核网络协议栈笔记2:初始化

    参考文献《Understanding Linux Network Internals》中用了整整一章(part II)来介绍system initialization。本文只提供一个简单的概述,如果需要详细信息,还请看参考文献。

    我们这里所说的初始化过程指的是从硬件加电启动,到可以从网络接收或发送数据包之前的过程。在Linux系统中,网卡拥有双重身份:struct pci_dev和struct net_device。pci_dev对象代表通用硬件的性质,是作为一个标准的PCI的设备插入了PCI的卡槽,由驱动程序进行管理;另一方面,net_device对象代表网络传输的性质,与内核的网络协议栈交互,进行数据传输。因此我们也必须通过两个方面来进行初始化,但是后者是我们的重点。而且我们并不关心内核与硬件的交互细节,例如寄存器读写与I/O映射等。

    内核在初始化时,也会初始化一些与网络相关的数据结构;而且对应我们前面的日志所提及的内核网络协议栈层次结构(点这里),内核也需要一定的初始化工作来建立这种层次结构。

    笔者认为初始化最重要的就是重要数据结构(通常用粗体标注)。因此也希望读者能够记住重要的数据结构的作用。

    下面我们将分层,自底向上的分析整个初始化过程:

    (一)驱动程序层

    本文中以一个realtek 8139系列网卡作为例子,因为其驱动只有一个c文件(/drivers/net/8139too.c),比较容易分析。读者也可以参考e1000网卡的另一篇文章(点这里)。内核版本基于2.6.11。

    驱动程序加载/注册主要包括以下的步骤:

    (a)将设备驱动程序(pci_driver)添加到内核驱动程序链表中;

    (b)调用每个驱动中的probe函数(其中重要一步就是初始化net_device对象)。

    下面进行详细分解。

    通常,在Linux中使用insmod命令加载一个驱动程序模块,例如8139too.o目标文件。加载之后,Linux会默认执行模块中的module_init(rtl8139_init_module)宏函数,其中的参数rtl8139_init_module是一个函数指针,指向在具体的驱动程序8139too.o中声明的rtl8139_init_module函数。这个函数定义如下:

    static int __init rtl8139_init_module (void )
    return pci_module_init (&rtl8139_pci_driver); }

    pci_module_init是一个宏定义,实际上就等于pci_register_driver函数。(在2.6.30内核版本中,直接变成了return pci_register_driver(&rtl8139_pci_driver) )。pci_register_driver函数的注释说明了它的作用:register a new pci driver.Adds the driver structure to the list of registered drivers。也就是把如下的这样一个驱动程序(pci_driver类型)挂到系统的驱动程序链表中:

    static struct pci_driver rtl8139_pci_driver =  {
    .name   
    =
     DRV_NAME,
    .id_table 
    =
     rtl8139_pci_tbl,
    .probe   
    =
     rtl8139_init_one,
    .remove   
    =
     __devexit_p(rtl8139_remove_one),
    #ifdef CONFIG_PM
    .suspend 
    =
     rtl8139_suspend,
    .resume   
    =
     rtl8139_resume,
    #endif /* CONFIG_PM */

    };

    这一步我们应该这样理解(熟悉面向对象编程的读者):所有的pci_driver应该提供一致的接口(比如remove卸载/suspend挂起);但是这些接口的每个具体实现是不同的(pci声卡和pci显卡的挂起应该是不同的),所以采用了这样的函数指针结构。这个pci_driver结构其中最重要的就是probe函数指针,指向rtl8139_init_one,具体后面会解释。
    但是pci_register_driver并不仅仅完成了注册驱动这个任务,它内部调用了driver_register函数(/drivers/base/driver.c中):

    int driver_register(struct device_driver *  drv)
    {
    INIT_LIST_HEAD(
    &drv->
    devices);
    init_MUTEX_LOCKED(
    &drv->
    unload_sem);
    return
     bus_add_driver(drv);
    }

    前两个就是实现了添加到链表的功能,bus_add_driver才是主要的函数(/drivers/base/bus.c中),内部又调用了driver_attach函数,这个函数的主体是一个list_for_each循环,对链表中的每一个成员调用driver_probe_device函数(哈哈,出现了probe!),这个函数就调用了drv->probe(dev)(drv就是pci_driver类型的对象)!这样也就调用了驱动程序中的probe函数指针,也就是调用了rtl8139_init_one函数。

    函数rtl8139_init_one的主要作用就是给net_device对象分配空间(分配空间由函数rtl8139_init_board完成)并初始化。分配空间主要是内存空间。分配的资源包括I/O端口,内存映射(操作系统基本概念,请自行google)的地址范围以及IRQ中断号等。而初始化主要是设置net_device对象中的各个成员变量及成员函数,其中比较重要的是hard_start_xmit(通过硬件发送数据)/poll(轮训)/open(启动)等函数(粗体标注),代码如下:

    static int __devinit rtl8139_init_one (struct pci_dev *pdev, const structpci_device_id * ent)
    {
        
    struct net_device *dev =
     NULL;
         rtl8139_init_board (pdev, 
    &
    dev);
    /* The Rtl8139-specific entries in the device structure. */

             dev
    ->open =  rtl8139_open;
             dev
    ->hard_start_xmit =
     rtl8139_start_xmit;
             dev
    ->poll =
     rtl8139_poll;
             dev
    ->stop =
     rtl8139_close;
             dev
    ->do_ioctl =
     netdev_ioctl;
    }

    整个的调用链如下:pci_register_driver ==> driver_register ==> bus_add_driver ==> driver_attach ==> driver_probe_device ==> drv->probe ==> rtl8139_init_one(生成net_device)

    一个简单的net_device生命周期示意图如下(左边为初始化,右边为卸载):

    这个net_device数据结构的生成,标志着网络硬件和驱动程序层初始化完毕。也意味着,网络协议栈与硬件之间的纽带已经建立起来。

    (二)设备无关层/网络协议层/协议无关接口socket层

    Linux内核在启动后所执行的一些内核函数如下图所示:

    系统初始化的过程中会调用do_basic_setup函数进行一些初始化操作。其中2.6.11内核中就直接包括了driver_init()驱动程序初始化,以及sock_init函数初始化socket层。然后do_initcalls()函数调用一组前缀为__init类型(这个宏就表示为需要在系统初始化时执行)的函数。与网络相关的以__init宏标记的函数有:net_dev_init初始化设备无关层;inet_init初始化网络协议层。

    (fs_initcall和module_init这两个宏也具有类似的作用。由于这一阶段处于系统初始化,宏定义比较多,欲详细了解各种宏的使用的读者请参阅参考文献《Understanding Linux Network Internals》Part II Chapter 7)

    我们下面详细介绍一下这三个初始化函数都进行了哪些工作。

    (a)net_dev_init(在文件/net/core/dev.c中):设备操作层

    static int __init net_dev_init(void )
    {
        
    if
     (dev_proc_init())
        
    if
     (netdev_sysfs_init())
         INIT_LIST_HEAD(
    &
    ptype_all);
        
        
    for (i = 0; i < 16; i++

             INIT_LIST_HEAD(
    &
    ptype_base[i]);
        
    for (i = 0; i < ARRAY_SIZE(dev_name_head); i++
    )
             INIT_HLIST_HEAD(
    &
    dev_name_head[i]);
        
    for (i = 0; i < ARRAY_SIZE(dev_index_head); i++
    )
             INIT_HLIST_HEAD(
    &
    dev_index_head[i]);

        
    //Initialise the packet receive queues.

        for (i = 0; i < NR_CPUS; i++ ) {
            
    struct softnet_data *
    queue;

             queue 
    = &
    per_cpu(softnet_data, i);
             skb_queue_head_init(
    &queue->
    input_pkt_queue);
             queue
    ->throttle = 0
    ;
             queue
    ->cng_level = 0
    ;
             queue
    ->avg_blog = 10/* arbitrary non-zero */

             queue
    ->completion_queue =  NULL;
             INIT_LIST_HEAD(
    &queue->
    poll_list);
             set_bit(__LINK_STATE_START, 
    &queue->
    backlog_dev.state);
             queue
    ->backlog_dev.weight =
     weight_p;
             queue
    ->backlog_dev.poll =
     process_backlog;
             atomic_set(
    &queue->backlog_dev.refcnt, 1
    );
         }

         open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
         open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
    }

    这个函数所做的具体工作主要包括:初始化softnet_data这个数据结构(每个CPU都有一个这样的队列,表示要交给此CPU处理的数据包);注册网络相关软中断(参见我关于软中断的文章,点这里)。

    (b)inet_init(在文件/net/ipv4/af_inet.c中):网络层

    由于各种网络协议是按照协议族(protocol family,PF或者address family,AF)为单位组织起来的。我们在这里仅以Internet协议族(AF_INET或者PF_INET,在内核中这二者是等价的)为例。

    有时候这一层又被称为INET socket层(对应的数据结构为struct sock),请注意与BSD socket层区别(对应数据结构为struct socket):BSD socket层提供一组统一的接口,与协议无关;但具体到网络层就必须与协议相关了,因此操作也会有一些不同。

    代码如下(有删节):

    static int __init inet_init(void )
    {
        
    struct sk_buff *
    dummy_skb;
        
    struct inet_protosw *
    q;
        
    struct list_head *
    r;

         rc 
    = sk_alloc_slab(&tcp_prot, "tcp_sock"
    );
         rc 
    = sk_alloc_slab(&udp_prot, "udp_sock"
    );
         rc 
    = sk_alloc_slab(&raw_prot, "raw_sock"
    );

        
    //Tell SOCKET that we are alive 

           (void)sock_register(& inet_family_ops);

        
    //Add all the base protocols.

        if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0 );
        
    if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0
    );

        
    /* Register the socket-side information for inet_create. */

        
    for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++ r)
             INIT_LIST_HEAD(r);
        
    for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++
    q)
            inet_register_protosw(q);

        
    //Set the ARP module up

         arp_init();
          
    //Set the IP module up

         ip_init();
         tcp_v4_init(
    &
    inet_family_ops);
        
    /* Setup TCP slab cache for open requests. */

         tcp_init();
        
        
    //dev_add_pack(&ip_packet_type);
    }
    module_init(inet_init);

    这个函数中包括的主要函数包括:

    sock_register:在以前的文章中说过,Linux内核网络协议栈采用了分层结构,所以层与层之间肯定是松耦合的。上层的socket层并不知道下面的网络协议层都具体包括哪些协议。因此本函数有两个作用:(a)周知INET协议族;(b)注册到socket模块上。

    inet_add_protocol:在协议族中添加具体的协议。

    inet_register_protosw:各种inet协议族中的协议在内核中是保存在inetsw_array[]数组,其中元素为inet_protosw类型(/include/net/protocol.h文件中),每一个元素对应一个协议。每一个协议又有两个数据结构:struct proto/struct proto_ops。这两个结构中都是一些函数操作,但proto表示每个协议独特的操作,而proto_ops是通用的socket操作(包含在struct socket中);这种区别就类似于INET socket和BSD socket的区别。

    (c)sock_init(在文件/net/socket.c中):BSD socket层

    定义如下(代码有删节):

    void __init sock_init(void )
    {
        
    //Initialize sock SLAB cache.

         sk_init();
        
    //Initialize skbuff SLAB cache 

         skb_init();
        
    //Initialize the protocols module. 

         init_inodecache();
         register_filesystem(
    &
    sock_fs_type);
         sock_mnt 
    = kern_mount(&
    sock_fs_type);
        
    //The real protocol initialization is performed when do_initcalls is run.  

         netfilter_init();
    }

    此函数主要执行的工作是:初始化socksk_buff数据结构(这些重要的数据结构在后面的文章中还会涉及);由于sock属于linux文件系统的一部分,因此要注册成为文件系统;以及如果使用netfilter,也要进行初始化。

    Linux内核网络协议栈笔记3:重要数据结构sk_buff

    sk_buff,全称socket buffers,简称skb,中文名字叫套接字缓存。它可以称得上是Linux内核网络协议栈中最重要的数据结构,没有之一。它作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递。

    主要参考资料是《Understanding Linux Network Internals》Part I--Chapter 2.1中关于sk_buff的介绍。由于这个结构比较庞大,笔者主要选择比较重要的部分进行介绍,其余的还请看参考资料。

    (1)一些重要字段

    struct sock *sk;

        指向拥有这个缓存的那个sock(注意,这里是sock,不是socket,缓存是由具体协议拥有的)。

    struct net_device *dev;

        接收或者发送这个数据包队列的网络设备。参见笔者前面所写的文章(点这里)。

    (2)sk_buff的创建和销毁

    dev_alloc_skb/dev_kfree_skb函数负责sk_buff的创建和销毁,其内部分别调用了alloc_skb/kfree_skb。其内部实现就不再介绍了,但关于销毁和资源释放有一点需要注意:由于同一个sk_buff有可能被很多对象使用,所以使用了引用计数(reference count),当没有人使用之后,才能释放其资源。

    (3)布局(Layout)

    sk_buff的实现方式是双向链表。不仅每个节点有next指针指向下一元素,prev指针指向前一元素,又包含一个指向头结点的指针;同时链表的头结点比较特殊,包括了链表长度和一个自旋锁用于同步,因此形成了如下的结构:

    关于布局,sk_buff实现的另一个重要功能就是:当数据包从最底层的数据链路层向上传递到传输层时,进行一些处理。例如,IP层把数据包交给传输层时,就需要把IP头去掉。下面的图演示了一个UDP的数据包在发送时添加UDP报头和IP报头的过程:

    这个功能还需要另外三个成员变量,分别指向了传输层的头部(h),网络层的头部(nh=network header),数据链路层的头部(mac):

    union {...} h

    union {...} nh

    union {...} mac

    在2.6.30内核中这三个字段改为了:

    sk_buff_data_t          transport_header;
    sk_buff_data_t          network_header;
    sk_buff_data_t          mac_header;

    下面的例子表示了在经过mac层的传输过程中data字段的变化,被mac层接收之后,data指向了IP包头开始的地方(请回忆相关的网络知识):

    (4)管理函数

    skb_reserve, skb_put, skb_push, and skb_pull等函数可以用于缓存的空间管理。例如,在数据包从上层向下传输时,因为要添加下层协议的报头,这个时候就可以根据最大可能添加的长度预留空间,这样就无需每经过一层分配一次空间,降低了系统开销。

    还有另外一些以skb_queue开头的函数,表示对双向链表操作的函数。



    Linux内核网络协议栈笔记4:接收网络数据包详细过程

    网络数据接收过程,从数据包到达网卡的物理接口开始,然后由网卡的驱动程序交给网络协议栈,最后经过协议栈的一层层处理之后交给应用程序。大致上是这样的过程,但实际上有更多的细节。本文中主要介绍第一个和第二个步骤。

    我们本文中依然以一个Realtek 8139网卡为例(驱动程序为/drivers/net/8139too.c)。请注意在内核代码中receive都是用rx简写的。

    (1)注册与激活软中断

    在生成net_device对象及初始化的函数rtl8139_init_one中已经初始化dev->open方法为rtl8139_open函数(在本系列文章2:初始化中的net_device对象中已经介绍,点这里查看)。在rtl8139_open函数(这个函数在网卡启动时被调用)中注册了一个中断函数rtl8139_interrupt:

    retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);

    所以只要当网卡开启后(状态为up),当网络数据包到达时,都会产生一个硬件中断(这不同于后面的软中断)。这个硬件中断由内核调用中断处理程序rtl8139_interrupt函数处理。这个函数比较重要,网卡发送或者接收数据时内核都会调用这个函数处理中断,而中断的类型是根据网卡状态寄存器的不同而确定的。本文中仅涉及接收数据的中断,因此只给出了接收的代码:

    static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs* regs)
    {
        
    if (status &
     RxAckBits){
            
    if
     (netif_rx_schedule_prep(dev))
                     __netif_rx_schedule (dev);
         }
    }

    主要函数为__netif_rx_schedule(函数名意为:network interface receive schedule,即网络接口接收调度),因为当网卡接收到数据包之后,马上告知CPU在合适的时间去启动调度程序,轮询(poll)网卡。

    请注意:Linux接收网络数据实际上有两种方式。
    (a)中断。每个数据包到达都会产生一个中断,然后由内核调用中断处理程序处理。
    (b)NAPI(New API)。Linux内核2.6版本之后加入的新机制,核心方法是:不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后以POLL的方法来轮询数据。
    因此本文中只介绍NAPI的接收方式。我们不再详细介绍这种机制,网上可找到比较多的资料,可以参考IBM的技术文章:NAPI 技术在 Linux 网络驱动上的应用和完善。

    __netif_rx_schedule函数的定义如下:

    static inline void __netif_rx_schedule(struct net_device * dev)
    {
             local_irq_save(flags);
    //
    disable interrupt
        
    //Add interface to tail of rx poll list

             list_add_tail(&dev->poll_list, & __get_cpu_var(softnet_data).poll_list);
        
    //activate network rx softirq

             __raise_softirq_irqoff(NET_RX_SOFTIRQ);
             local_irq_restore(flags);
    }

    这个函数最核心的就是三步:

    (a)local_irq_save:禁用中断

    (b)list_add_tail:将设备添加到softnet_data的poll_list中。

    (c)激活一个软中断NET_RX_SOFTIRQ。

    ======================================

    说到这里我们必须介绍一个关键数据结构softnet_data,每个CPU都拥有一个这样的网络数据队列(所以函数中使用了__get_cpu_var函数取得),定义如下:

    struct  softnet_data
    {
        
    int             throttle;    /*为 1 表示当前队列的数据包被禁止*/

        
    int             cng_level;    /*表示当前处理器的数据包处理拥塞程度*/
        
    int             avg_blog;    /*某个处理器的平均拥塞度*/
        
    struct sk_buff_head     input_pkt_queue;    /*接收缓冲区的sk_buff队列*/
        
    struct list_head             poll_list;    /*POLL设备队列头*/
        
    struct net_device              output_queue;     /*网络设备发送队列的队列头*/
        
    struct sk_buff         completion_queue; /*完成发送的数据包等待释放的队列*/
        struct net_device     backlog_dev;    /*表示当前参与POLL处理的网络设备*/
    };

    大致说明一下这个数据结构的意义。某个网卡产生中断之后,内核就把这个网卡挂载到轮询列表(poll_list)中。一个CPU会轮询自己的列表中的每一个网卡,看看它们是不是有新的数据包可以处理。我们需要先用一个比喻说明这个数据结构与轮询的关系:网卡就是佃户,CPU就是地主。佃户有自己种的粮食(网络数据包),但地主家也有粮仓(softnet_data)。地主要收粮的时候,就会挨家挨户的去催佃户交粮,放到自己的粮仓里。

    =======================================

    (2)软中断处理

    我们知道:激活软中断之后,并不是马上会被处理的。只有当遇到软中断的检查点时,系统才会调用相应的软中断处理函数。

    所有的网络接收数据包的软中断处理函数都是net_rx_action。这个函数的详细注释可以看IBM的那篇技术文章。其核心语句就是一个轮询的函数:

    dev->poll

    就调用了相应设备的poll函数。也就是说,当CPU处理软中断时,才去轮询网卡,把数据放入softnet_data中。

    下面是整个中断和轮询过程的一个示意图:

    下面我们解释一下poll函数具体干了什么事情。

    而我们知道,在Realtek 8139网卡的net_device对象中我们已经注册了一个poll函数:

    dev->poll = rtl8139_poll

    那么一次poll就表示从网卡缓冲区取出一定量的数据。而rtl8139_poll函数中调用的主要函数就是rtl8139_rx函数。这个函数是完成从网卡取数据,分配skb缓冲区的核心函数。其核心代码如下:

    static int rtl8139_rx(struct net_device *dev, struct rtl8139_private *tp, int budget)
    {
         skb 
    = dev_alloc_skb (pkt_size + 2
    );
         eth_copy_and_sum (skb, 
    &rx_ring[ring_offset + 4], pkt_size, 0);//memcpy

         skb->protocol =  eth_type_trans (skb, dev);
         netif_receive_skb (skb);
    }

    工作主要分为4部分:

    (a)给sk_buff数据结构(skb)分配空间。

    (b)从网卡的环形缓冲区rx_ring中拷贝出网络数据包放到sk_buff对象skb中。这个函数实质上就是一个memcpy函数。

    (c)在skb中标识其协议为以太网帧。

    (d)调用netif_receice_skb函数。
    netif_receive_skb函数相对比较重要。函数主体是两个循环:

    list_for_each_entry_rcu(ptype, & ptype_all, list) 
    {
        
    if (!ptype->dev || ptype->dev == skb->
    dev) 
         {
            
    if
     (pt_prev) 
                 ret 
    =
     deliver_skb(skb, pt_prev);
             pt_prev 
    =
     ptype;
             }
    }

    list_for_each_entry_rcu(ptype, 
    &ptype_base[ntohs(type)&15
    ], list) {
        
    if (ptype->type == type && (!ptype->dev || ptype->dev == skb->
    dev)) 
         {
            
    if
     (pt_prev) 
                 ret 
    =
     deliver_skb(skb, pt_prev);
             pt_prev 
    =
     ptype;
         }
    }

    两个循环分别遍历了两个链表:ptype_all和ptype_base。前者是内核中注册的sniffer,后者则是注册到内核协议栈中的网络协议类型。如果skb中的协议类型type与ptype_base中的类型一致,那么使用deliver_skb函数发送给这个协议一份,定义如下:

    static __inline__ int deliver_skb(struct sk_buff *skb, struct packet_type* pt_prev)
    {
             atomic_inc(
    &skb->
    users);
            
    return pt_prev->func(skb, skb->
    dev, pt_prev);
    }

    这个函数只是一个封装函数,实际上调用了每个packet type结构中注册的处理函数func。

    struct  packet_type {
             unsigned 
    short           type;   

            
    struct net_device               *dev;   
             int                      (*func) (struct sk_buff *
                              struct net_device *struct packet_type *
    );
            
    void                    *
    af_packet_priv;
            
    struct
     list_head         list;
    };

    例如:IP包类型的处理函数就是ip_rcv(定义在/net/ipv4/ip_output.c文件中),定义如下:

    static struct packet_type ip_packet_type =  {
             .type 
    =
     __constant_htons(ETH_P_IP),
             .func 
    =
     ip_rcv,
    };

    这个包的类型是在ip_init协议初始化时添加到全局的ptype_base哈希数组中的:

    void __init ip_init(void )
    {
             dev_add_pack(
    &
    ip_packet_type);
    }
    展开全文
  • Linux 内核网络协议栈源码剖析】ARP地址解析协议


    http://blog.csdn.net/wenqian1991/article/details/46814201


    ARP地址解析协议理论前篇参见ARP与RARP,这里则通过源码(Linux kernel 1.2.13;net\inet\arp.c)来剖析其内部原理及实现过程。

    一、ARP表项

    1. /* 
    2.  *  This structure defines the ARP mapping cache. As long as we make changes 
    3.  *  in this structure, we keep interrupts of. But normally we can copy the 
    4.  *  hardware address and the device pointer in a local variable and then make 
    5.  *  any "long calls" to send a packet out. 
    6.  */  
    7.  //ARP缓存中的每一个由arp_table结构表示,将这些表项串联起来构成链表,就构成了ARP缓存  
    8.  //每个字段英文注释已经说的很清楚了  
    9. struct arp_table  
    10. {  
    11.     struct arp_table        *next;          /* Linked entry list        */  
    12.     unsigned long           last_used;      /* For expiry           */  
    13.     unsigned int            flags;          /* Control status       */  
    14.     unsigned long           ip;         /* ip address of entry      */  
    15.     unsigned long           mask;           /* netmask - used for generalised proxy arps (tridge)       */  
    16.     unsigned char           ha[MAX_ADDR_LEN];   /* Hardware address     */  
    17.     unsigned char           hlen;           /* Length of hardware address   */  
    18.     unsigned short          htype;          /* Type of hardware in use  */  
    19.     struct device           *dev;           /* Device the entry is tied to  */  
    20.   
    21.     /* 
    22.      *  The following entries are only used for unresolved hw addresses. 
    23.      */  
    24.       
    25.     struct timer_list       timer;          /* expire timer         */  
    26.     int             retries;        /* remaining retries        */  
    27.     struct sk_buff_head     skb;            /* list of queued packets   */  
    28. };  
    值得说明的是,ARP缓存其实是一个链式哈希表,其数据结果和前面文章我们介绍的 array_sock 是一样的结构。该哈希表的索引关键字就是目的 ip 地址,arp 地址解析就是已知对方ip地址,要获得其硬件地址,以完成链路层首部的创建。

    鉴于哈希表强大的查找功能(时间复杂度O(1)),所以在内核中应用广泛。

    二、从arp缓存中清除过期的arp表项——arp_check_expire 函数

    有限的空间不能老是让一些人占着,得常换换血液。

    1. /* 
    2.  *  Check if there are too old entries and remove them. If the ATF_PERM 
    3.  *  flag is set, they are always left in the arp cache (permanent entry). 
    4.  *  Note: Only fully resolved entries, which don't have any packets in 
    5.  *  the queue, can be deleted, since ARP_TIMEOUT is much greater than 
    6.  *  ARP_MAX_TRIES*ARP_RES_TIME. 
    7.  */  
    8.  //从ARP缓存中清除过期的ARP表项  
    9. static void arp_check_expire(unsigned long dummy)  
    10. {  
    11.     int i;  
    12.     unsigned long now = jiffies;  
    13.     unsigned long flags;  
    14.     save_flags(flags);  
    15.     cli();  
    16.    //遍历整个ARP缓存表,该缓存表实则是一个链式哈希表(数组,然后数组元素为arp结构链表)  
    17.     for (i = 0; i < FULL_ARP_TABLE_SIZE; i++)  
    18.     {  
    19.         struct arp_table *entry;  
    20.         struct arp_table **pentry = &arp_tables[i];//获得arp表项  
    21.   
    22.         while ((entry = *pentry) != NULL)  
    23.         {  
    24.         //对每个表项上次使用时间标志进行检查,如果使用时间在ARP_TIMEOUT之前,  
    25.         //表示这是一个过期的表项,如果该表项不是一个永久表项,则需要清除  
    26.             if ((now - entry->last_used) > ARP_TIMEOUT  
    27.                 && !(entry->flags & ATF_PERM))  
    28.             {  
    29.                 *pentry = entry->next;   /* remove from list */  
    30.                 del_timer(&entry->timer);    /* 停止该表项设置的定时器 */  
    31.                 kfree_s(entry, sizeof(struct arp_table));//清除占有的内存空间  
    32.             }  
    33.             else  
    34.                 pentry = &entry->next;//切换到下一个表项 /* go to next entry */  
    35.         }  
    36.     }  
    37.     restore_flags(flags);  
    38.   
    39.     /* 
    40.      *  Set the timer again. 
    41.      */  
    42.     //剖析了那么多源码,tcp内核协议栈重置的操作一般都是:清除现有的,然后新建一个再插入到链表中  
    43.     //而不是直接修改现有的,直接修改会造成内核状态的不一致  
    44.     del_timer(&arp_timer);//清除已有定时器  
    45.     arp_timer.expires = ARP_CHECK_INTERVAL;//重新设置一个定时器  
    46.     add_timer(&arp_timer);//添加到定时器链表中  
    47. }  
    三、释放一个arp表项——arp_release_entry 函数
    1. /* 
    2.  *  Release all linked skb's and the memory for this entry. 
    3.  */  
    4.  //该函数用于释放一个ARP缓存表项,参数指向要释放的表项  
    5.  //不仅要释放表项,还要释放其可能缓存的待发送的数据包  
    6. static void arp_release_entry(struct arp_table *entry)  
    7. {  
    8.     struct sk_buff *skb;  
    9.     unsigned long flags;  
    10.   
    11.     save_flags(flags);  
    12.     cli();  
    13.     /* Release the list of `skb' pointers. */  
    14.     //找到缓存在该表项中的待发送的数据包,并释放  
    15.     while ((skb = skb_dequeue(&entry->skb)) != NULL)  
    16.     {  
    17.         skb_device_lock(skb);  
    18.         restore_flags(flags);  
    19.         dev_kfree_skb(skb, FREE_WRITE);//释放数据包  
    20.     }  
    21.     restore_flags(flags);  
    22.     del_timer(&entry->timer);//将定时器从系统中删除,不然定时器到期了,会访问到已经不存在的资源  
    23.     kfree_s(entry, sizeof(struct arp_table));//释放空间  
    24.     return;  
    25. }  
    四、系统事件处理,即处理皮之不存事故——arp_device_event 函数
    1. /* 
    2.  *  Purge a device from the ARP queue 
    3.  */  
    4.  //对系统事件做出响应,主要指网络设备停止工作,即NETDEV_DOWN事件  
    5.  //因为每个arp表项都绑定在一个网络设备上,如果对应的网络设备不再工作,  
    6.  //那么这些被绑定的arp表项就不可再被使用  
    7. int arp_device_event(unsigned long event, void *ptr)  
    8. {  
    9.     struct device *dev=ptr;//对应网络设备  
    10.     int i;  
    11.     unsigned long flags;  
    12.       
    13.     if(event!=NETDEV_DOWN)//本函数只处理网络设备停止工作这一事件  
    14.         return NOTIFY_DONE;  
    15.     /* 
    16.      *  This is a bit OTT - maybe we need some arp semaphores instead. 
    17.      */  
    18.        
    19.     save_flags(flags);  
    20.     cli();  
    21.     //下面原理很简单,遍历arp缓存,找到对应网络设备dev的arp表项,并清除  
    22.     for (i = 0; i < FULL_ARP_TABLE_SIZE; i++)  
    23.     {  
    24.         struct arp_table *entry;  
    25.         struct arp_table **pentry = &arp_tables[i];  
    26.   
    27.         while ((entry = *pentry) != NULL)  
    28.         {  
    29.             if(entry->dev==dev)  
    30.             {  
    31.                 *pentry = entry->next;   /* remove from list */  
    32.                 del_timer(&entry->timer);    /* Paranoia */  
    33.                 kfree_s(entry, sizeof(struct arp_table));  
    34.             }  
    35.             else  
    36.                 pentry = &entry->next;   /* go to next entry */  
    37.         }  
    38.     }  
    39.     restore_flags(flags);  
    40.     return NOTIFY_DONE;  
    41. }  

    好,神秘的arp地址解析过程就要开始揭开序幕了,看过之后,其实也不怎么神秘。

    五、创建一个arp报文并发送出去——arp_send 函数
    1. /* 
    2.  *  Create and send an arp packet. If (dest_hw == NULL), we create a broadcast 
    3.  *  message. 
    4.  */  
    5.  //创建一个arp请求报文并发送出去  
    6. void arp_send(int type, int ptype, unsigned long dest_ip,   
    7.           struct device *dev, unsigned long src_ip,   
    8.           unsigned char *dest_hw, unsigned char *src_hw)  
    9. {  
    10.     struct sk_buff *skb;  
    11.     struct arphdr *arp;  
    12.     unsigned char *arp_ptr;  
    13.   
    14.     /* 
    15.      *  No arp on this interface. 
    16.      */  
    17.       
    18.     if(dev->flags&IFF_NOARP)//非ARP协议  
    19.         return;  
    20.   
    21.     /* 
    22.      *  Allocate a buffer 
    23.      */  
    24.     //创建一个arp报文,其也是一个sk_buff数据包  
    25.     //其大小=arp报头+源端和目的端对应的MAC地址长度和IP地址长度+以太网帧头  
    26.     //dev->addr_len表示MAC地址,后面+4是+ip地址长度  
    27.     skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)  
    28.                 + dev->hard_header_len, GFP_ATOMIC);  
    29.     if (skb == NULL)  
    30.     {  
    31.         printk("ARP: no memory to send an arp packet\n");  
    32.         return;  
    33.     }  
    34.     //数据包字段设置  
    35.     skb->len = sizeof(struct arphdr) + dev->hard_header_len + 2*(dev->addr_len+4);  
    36.     skb->arp = 1;//表示已完成MAC首部的创建  
    37.     skb->dev = dev;//绑定设备  
    38.     skb->free = 1;//数据包发送后立即释放  
    39.   
    40.     /* 
    41.      *  Fill the device header for the ARP frame 
    42.      */  
    43.     //调用eth_header函数创建MAC首部,该函数的说明参见前面博文connect函数剖析(二)  
    44.     dev->hard_header(skb->data,dev,ptype,dest_hw?dest_hw:dev->broadcast,src_hw?src_hw:NULL,skb->len,skb);  
    45.   
    46.     /* Fill out the arp protocol part. */  
    47.     //得到arp报头。arp报文内存布局:以太网帧头 | arp报头 | 地址类  
    48.     arp = (struct arphdr *) (skb->data + dev->hard_header_len);  
    49.     //设置arp报头字段  
    50.     arp->ar_hrd = htons(dev->type);  
    51. #ifdef CONFIG_AX25  
    52.     arp->ar_pro = (dev->type != ARPHRD_AX25)? htons(ETH_P_IP) : htons(AX25_P_IP);  
    53. #else  
    54.     arp->ar_pro = htons(ETH_P_IP);  
    55. #endif  
    56.     arp->ar_hln = dev->addr_len;  
    57.     arp->ar_pln = 4;  
    58.     arp->ar_op = htons(type);  
    59.   
    60.     //往后偏移一个arp报头位置,定位到地址部分  
    61.     //arp_ptr的内存布局为:源mac地址 | 源ip地址 | 目的mac地址 | 目的ip地址  
    62.     arp_ptr=(unsigned char *)(arp+1);  
    63.   
    64.    //源mac地址设置  
    65.     memcpy(arp_ptr, src_hw, dev->addr_len);  
    66.     arp_ptr+=dev->addr_len;  
    67.     //源ip地址设置  
    68.     memcpy(arp_ptr, &src_ip,4);  
    69.     arp_ptr+=4;//ip地址长度固定为4,定位到目的mac地址  
    70.     if (dest_hw != NULL)  
    71.         memcpy(arp_ptr, dest_hw, dev->addr_len);  
    72.     else  
    73.         memset(arp_ptr, 0, dev->addr_len);//传参为空,就赋0  
    74.     arp_ptr+=dev->addr_len;  
    75.     memcpy(arp_ptr, &dest_ip, 4);//目的ip地址设置  
    76.   
    77.  //将该数据包传递给驱动程序,由驱动程序最终将数据发送到物理介质上  
    78.     dev_queue_xmit(skb, dev, 0);//参数具体解释参见前面博文:数据包发送  
    79. }  
    六、arp报文接收处理——arp_rcv 函数
    1. /*  
    2.  *  Receive an arp request by the device layer. Maybe I rewrite it, to 
    3.  *  use the incoming packet for the reply. The time for the current 
    4.  *  "overhead" isn't that high... 
    5.  */  
    6.   //该函数是所有使用arp协议进行数据包传送的总入口函数  
    7.   //arp数据包分为arp请求数据包和arp应答数据包  
    8.   //skb:接收的arp协议数据包;dev:接收数据包的网络设备;pt:指向arp协议本身的packe_type结构  
    9. int arp_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)  
    10. {  
    11. /* 
    12.  *  We shouldn't use this type conversion. Check later. 
    13.  */  
    14.       
    15.     struct arphdr *arp = (struct arphdr *)skb->h.raw;//arp首部  
    16.     unsigned char *arp_ptr= (unsigned char *)(arp+1);//arp报文中的地址位置  
    17.     struct arp_table *entry;  
    18.     struct arp_table *proxy_entry;  
    19.     int addr_hint,hlen,htype;  
    20.     unsigned long hash;  
    21.     unsigned char ha[MAX_ADDR_LEN]; /* So we can enable ints again. */  
    22.     long sip,tip;  
    23.     unsigned char *sha,*tha;  
    24.   
    25. /* 
    26.  *  The hardware length of the packet should match the hardware length 
    27.  *  of the device.  Similarly, the hardware types should match.  The 
    28.  *  device should be ARP-able.  Also, if pln is not 4, then the lookup 
    29.  *  is not from an IP number.  We can't currently handle this, so toss 
    30.  *  it.  
    31.  */    
    32.  //地址检查以及标志等检查,实现程序的健壮性,检查参数的有效性这都是必须的  
    33.     if (arp->ar_hln != dev->addr_len    ||   
    34.             dev->type != ntohs(arp->ar_hrd) ||   
    35.         dev->flags & IFF_NOARP          ||  
    36.         arp->ar_pln != 4)  
    37.     {  
    38.         kfree_skb(skb, FREE_READ);  
    39.         return 0;  
    40.     }  
    41.   
    42. /* 
    43.  *  Another test. 
    44.  *  The logic here is that the protocol being looked up by arp should  
    45.  *  match the protocol the device speaks.  If it doesn't, there is a 
    46.  *  problem, so toss the packet. 
    47.  */  
    48.  //根据硬件类型采取相应操作  
    49.     switch(dev->type)  
    50.     {  
    51. #ifdef CONFIG_AX25  
    52.         case ARPHRD_AX25:  
    53.             if(arp->ar_pro != htons(AX25_P_IP))  
    54.             {  
    55.                 kfree_skb(skb, FREE_READ);  
    56.                 return 0;  
    57.             }  
    58.             break;  
    59. #endif  
    60.         case ARPHRD_ETHER:  
    61.         case ARPHRD_ARCNET:  
    62.             if(arp->ar_pro != htons(ETH_P_IP))  
    63.             {  
    64.                 kfree_skb(skb, FREE_READ);  
    65.                 return 0;  
    66.             }  
    67.             break;  
    68.   
    69.         default:  
    70.             printk("ARP: dev->type mangled!\n");  
    71.             kfree_skb(skb, FREE_READ);  
    72.             return 0;  
    73.     }  
    74.   
    75. /* 
    76.  *  Extract fields 
    77.  */  
    78.   //变量初始化  
    79.     hlen  = dev->addr_len;  
    80.     htype = dev->type;  
    81.   
    82.     sha=arp_ptr;//sha为源端mac地址  
    83.     arp_ptr+=hlen;  
    84.     memcpy(&sip,arp_ptr,4);//sip是源ip地址  
    85.     arp_ptr+=4;  
    86.     tha=arp_ptr;//tha为目的端mac地址  
    87.     arp_ptr+=hlen;  
    88.     memcpy(&tip,arp_ptr,4);//这里tip是目的ip地址  
    89.     
    90. /*  
    91.  *  Check for bad requests for 127.0.0.1.  If this is one such, delete it. 
    92.  */  
    93.  //检查接收端主机ip地址是否为回环地址  
    94.     if(tip == INADDR_LOOPBACK)  
    95.     {  
    96.     //如果是,表示这个arp数据包是由本机发送的  
    97.         kfree_skb(skb, FREE_READ);  
    98.         return 0;  
    99.     }  
    100.   
    101. /* 
    102.  *  Process entry.  The idea here is we want to send a reply if it is a 
    103.  *  request for us or if it is a request for someone else that we hold 
    104.  *  a proxy for.  We want to add an entry to our cache if it is a reply 
    105.  *  to us or if it is a request for our address.   
    106.  *  (The assumption for this last is that if someone is requesting our  
    107.  *  address, they are probably intending to talk to us, so it saves time  
    108.  *  if we cache their address.  Their address is also probably not in  
    109.  *  our cache, since ours is not in their cache.) 
    110.  *  
    111.  *  Putting this another way, we only care about replies if they are to 
    112.  *  us, in which case we add them to the cache.  For requests, we care 
    113.  *  about those for us and those for our proxies.  We reply to both, 
    114.  *  and in the case of requests for us we add the requester to the arp  
    115.  *  cache. 
    116.  */  
    117.  //处理完arp数据包后,还会在本地arp缓存中加入远方主机的arp映射表项  
    118.  //arp数据包分为请求数据包和应答数据包  
    119.    
    120.     //检查arp首部中目的ip地址,判断该arp报文是否发送给本机  
    121.     addr_hint = ip_chk_addr(tip);  
    122.   
    123.     //检查数据包是否为一个arp应答报文  
    124.     if(arp->ar_op == htons(ARPOP_REPLY))//应答报文  
    125.     {  
    126.         if(addr_hint!=IS_MYADDR)//如果是一个应答报文,但不是发送给本机的  
    127.         {  
    128. /*  
    129.  *  Replies to other machines get tossed.  
    130.  */  
    131.             kfree_skb(skb, FREE_READ);  
    132.             return 0;  
    133.         }  
    134. /* 
    135.  *  Fall through to code below that adds sender to cache.  
    136.  */  
    137.     }  
    138.     else//请求报文  
    139. //请求数据包分为两种情况:一是发送给本机的arp请求,二是发送给由本机进行代理的主机的arp请求  
    140.     {   
    141. /*  
    142.  *  It is now an arp request  
    143.  */  
    144. /* 
    145.  * Only reply for the real device address or when it's in our proxy tables 
    146.  */  
    147.  //tip是目的ip地址  
    148.  //可能发送给本机也有可能是发送给本机代理的主机的  
    149.  //  
    150.         if(tip!=dev->pa_addr)  
    151.         {//处理发送给由本机代理的主机的  
    152. /* 
    153.  *  To get in here, it is a request for someone else.  We need to 
    154.  *  check if that someone else is one of our proxies.  If it isn't, 
    155.  *  we can toss it. 
    156.  */  
    157.             cli();  
    158.     //该for循环式遍历arp缓存中代理表项(arp缓存最后一个索引元素指向)  
    159.     //对ip地址进行匹配检查  
    160.             for(proxy_entry=arp_tables[PROXY_HASH];  
    161.                 proxy_entry;  
    162.                 proxy_entry = proxy_entry->next)  
    163.             {  
    164.               /* we will respond to a proxy arp request 
    165.                  if the masked arp table ip matches the masked 
    166.                  tip. This allows a single proxy arp table 
    167.                  entry to be used on a gateway machine to handle 
    168.                  all requests for a whole network, rather than 
    169.                  having to use a huge number of proxy arp entries 
    170.                  and having to keep them uptodate. 
    171.                  */  
    172.                  //ip地址、网络设备、硬件地址类型同时进行精确匹配  
    173.               if (proxy_entry->dev != dev && proxy_entry->htype == htype &&  
    174.                   !((proxy_entry->ip^tip)&proxy_entry->mask))  
    175.                 break;  
    176.   
    177.             }  
    178.             if (proxy_entry)//bingo,找到  
    179.             {  
    180.             //根据表项中硬件地址(来源于匹配表项)进行arp应答  
    181.             //该硬件地址一般就是代理主机对应网段网络设备的硬件地址  
    182.                 memcpy(ha, proxy_entry->ha, hlen);  
    183.                 sti();  
    184.                 arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,ha);  
    185.                 kfree_skb(skb, FREE_READ);  
    186.                 return 0;//这里直接返回  
    187.             }  
    188.             else//没找到  
    189.             {  
    190.                 sti();  
    191.                 kfree_skb(skb, FREE_READ);  
    192.                 return 0;  
    193.             }  
    194.         }  
    195.         else//发往本机的,直接作出应答  
    196.         {  
    197. /* 
    198.  *  To get here, it must be an arp request for us.  We need to reply. 
    199.  */  
    200.             arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr);  
    201.         //这里并没有直接返回,将进行后面的arp表项添加操作  
    202.         }  
    203.     }  
    204.   
    205.  //既然远端主机在询问本地硬件地址,则表示远端主机极有可能将要与本机进行通信,  
    206.  //那么同时本机主机也需要发送相关数据包给远端主机,为此本地主机在其arp缓存中添加一个  
    207.  //远端主机的arp表项,之后发送数据包给远端时,就不用进行对远端主机的arp地址解析过程  
    208.   
    209. /* 
    210.  * Now all replies are handled.  Next, anything that falls through to here 
    211.  * needs to be added to the arp cache, or have its entry updated if it is  
    212.  * there. 
    213.  */  
    214.  //下面就是在arp缓存中添加一个远方主机的arp表项,提高以后的命中率  
    215.  //之后发送数据包给远方主机时,可以不用进行对远方主机的arp地址解析过程  
    216.    
    217.     hash = HASH(sip);//哈希表散列函数,这里是用远端ip地址作为索引查找  
    218.     cli();  
    219.     //下面for循环检查arp缓存中是否存在一个未完成表项,对方收到一个arp应答的情况  
    220.     for(entry=arp_tables[hash];entry;entry=entry->next)  
    221.         if(entry->ip==sip && entry->htype==htype)  
    222.             break;  
    223.   
    224.     if(entry)//找到表项  
    225.     {  
    226. /* 
    227.  *  Entry found; update it. 
    228.  */  
    229.         memcpy(entry->ha, sha, hlen);//(源端)硬件字段赋值  
    230.         entry->hlen = hlen;  
    231.         entry->last_used = jiffies;//最后一次使用时间,过了一定时间,会清除过时的表项  
    232.         if (!(entry->flags & ATF_COM))//该表项是未完成表项  
    233.         {  
    234. /* 
    235.  *  This entry was incomplete.  Delete the retransmit timer 
    236.  *  and switch to complete status. 
    237.  */  
    238.             del_timer(&entry->timer);//删除定时器  
    239.             entry->flags |= ATF_COM;  
    240.             sti();  
    241. /*  
    242.  *  Send out waiting packets. We might have problems, if someone is  
    243.  *  manually removing entries right now -- entry might become invalid  
    244.  *  underneath us. 
    245.  */  
    246.  //滞留的数据包重发  
    247.  //因为arp请求的主动发起是在发送普通数据包时无法建立链路层首部的情况下进行的(不知道对端硬件地址)  
    248.  //内核在进行arp地址解析的过程中,会将这个数据包暂存在新创建的arp表项(未完成,缺少对端硬件地址)  
    249.  //的相关队列中,等到新创建的表项完成所有字段的初始化(主要是硬件地址),才可以把之前滞留的数据包发送出去  
    250.  //这里便是通过arp_send_q函数实现的,其函数内部创建了链路层首部  
    251.             arp_send_q(entry, sha);  
    252.         }  
    253.         else//是已经完成的表项,那么数据包自然已经发送出去了,不作处理  
    254.         {  
    255.             sti();  
    256.         }  
    257.     }  
    258.     else//没有找到,则需要创建一个新的表项插入  
    259.     {  
    260. /* 
    261.  *  No entry found.  Need to add a new entry to the arp table. 
    262.  */      
    263.  //创建一个表项  
    264.         entry = (struct arp_table *)kmalloc(sizeof(struct arp_table),GFP_ATOMIC);  
    265.         if(entry == NULL)  
    266.         {  
    267.             sti();  
    268.             printk("ARP: no memory for new arp entry\n");  
    269.   
    270.             kfree_skb(skb, FREE_READ);  
    271.             return 0;  
    272.         }  
    273.   //新表项字段设置  
    274.         entry->mask = DEF_ARP_NETMASK;  
    275.         entry->ip = sip;  
    276.         entry->hlen = hlen;  
    277.         entry->htype = htype;  
    278.         entry->flags = ATF_COM;  
    279.         init_timer(&entry->timer);  
    280.         memcpy(entry->ha, sha, hlen);  
    281.         entry->last_used = jiffies;  
    282.         entry->dev = skb->dev;  
    283.         skb_queue_head_init(&entry->skb);//将数据包插入队列中  
    284.     //插入到arp缓存ip地址对应索引的首部位置  
    285.         entry->next = arp_tables[hash];  
    286.         arp_tables[hash] = entry;  
    287.         sti();  
    288.     }  
    289.   
    290. /* 
    291.  *  Replies have been sent, and entries have been added.  All done. 
    292.  */  
    293.  //arp报文功成身就,寿终正寝  
    294.     kfree_skb(skb, FREE_READ);  
    295.     return 0;  
    296. }  
    297. 七、arp地址查询——arp_find 函数  
    298. /* 
    299.  *  Find an arp mapping in the cache. If not found, post a request. 
    300.  */  
    301.  //根据目的IP地址在系统ARP缓存中查找匹配的表项从而完成数据帧中链路层首部的创建工作  
    302.  //这就是ARP地址解析过程的代码实现,已知对方ip地址获得mac地址,以完成数据包以太网帧头的创建  
    303.  //arp地址解析的目的就是通过目的ip地址获得硬件地址,以帮助完成数据帧首部的创建工作  
    304. int arp_find(unsigned char *haddr, unsigned long paddr, struct device *dev,  
    305.        unsigned long saddr, struct sk_buff *skb)  
    306. {  
    307.     struct arp_table *entry;  
    308.     unsigned long hash;  
    309. #ifdef CONFIG_IP_MULTICAST  
    310.     unsigned long taddr;  
    311. #endif    
    312.   
    313.     //对目的ip地址的类型进行检查并做相应的处理  
    314.     switch (ip_chk_addr(paddr))  
    315.     {  
    316.         case IS_MYADDR://本地地址  
    317.             printk("ARP: arp called for own IP address\n");  
    318.             memcpy(haddr, dev->dev_addr, dev->addr_len);  
    319.             skb->arp = 1;  
    320.             return 0;  
    321. #ifdef CONFIG_IP_MULTICAST  
    322.         case IS_MULTICAST://多播地址  
    323.             if(dev->type==ARPHRD_ETHER || dev->type==ARPHRD_IEEE802)  
    324.             {  
    325.                 haddr[0]=0x01;  
    326.                 haddr[1]=0x00;  
    327.                 haddr[2]=0x5e;  
    328.                 taddr=ntohl(paddr);  
    329.                 haddr[5]=taddr&0xff;  
    330.                 taddr=taddr>>8;  
    331.                 haddr[4]=taddr&0xff;  
    332.                 taddr=taddr>>8;  
    333.                 haddr[3]=taddr&0x7f;  
    334.                 return 0;  
    335.             }  
    336.         /* 
    337.          *  If a device does not support multicast broadcast the stuff (eg AX.25 for now) 
    338.          */  
    339. #endif  
    340.           
    341.         case IS_BROADCAST://广播地址  
    342.             memcpy(haddr, dev->broadcast, dev->addr_len);  
    343.             skb->arp = 1;  
    344.             return 0;  
    345.     }  
    346.   
    347.     hash = HASH(paddr);//散列函数  
    348.     cli();  
    349.   
    350.     /* 
    351.      *  Find an entry 
    352.      */  
    353.      //完成具体的arp表项查询,根据ip地址查询arp缓存,返回查询结果  
    354.     entry = arp_lookup(paddr, PROXY_NONE);  
    355.   
    356.     if (entry != NULL)  /* It exists */  
    357.     {  
    358.             if (!(entry->flags & ATF_COM))  
    359.             {  
    360.             /* 
    361.              *  A request was already send, but no reply yet. Thus 
    362.              *  queue the packet with the previous attempt 
    363.              */  
    364.               
    365.             if (skb != NULL)  
    366.             {  
    367.             //将该数据包插入到arp表项相关的数据包队列中  
    368.             //就是将待发送的数据包缓存在arp相关队列中  
    369.                 skb_queue_tail(&entry->skb, skb);  
    370.                 skb_device_unlock(skb);  
    371.             }  
    372.             sti();  
    373.             return 1;  
    374.         }  
    375.   
    376.         /* 
    377.          *  Update the record 
    378.          */  
    379.           
    380.         entry->last_used = jiffies;//更新记录  
    381.         memcpy(haddr, entry->ha, dev->addr_len);//硬件地址赋值  
    382.         if (skb)  
    383.             skb->arp = 1;//置位,表示已经完成mac首部的建立  
    384.         sti();  
    385.         return 0;  
    386.     }  
    387.   
    388.     /* 
    389.      *  Create a new unresolved entry. 
    390.      */  
    391.     //如果没有找到,就需要创建一个表项  
    392.     entry = (struct arp_table *) kmalloc(sizeof(struct arp_table),  
    393.                     GFP_ATOMIC);  
    394.     if (entry != NULL)  
    395.     {  
    396.     //arp表项字段设置  
    397.         entry->mask = DEF_ARP_NETMASK;  
    398.         entry->ip = paddr;  
    399.         entry->hlen = dev->addr_len;  
    400.         entry->htype = dev->type;  
    401.         entry->flags = 0;  
    402.         memset(entry->ha, 0, dev->addr_len);  
    403.         entry->dev = dev;  
    404.         entry->last_used = jiffies;  
    405.         init_timer(&entry->timer);  
    406.         entry->timer.function = arp_expire_request;  
    407.         entry->timer.data = (unsigned long)entry;  
    408.         entry->timer.expires = ARP_RES_TIME;  
    409.         entry->next = arp_tables[hash];  
    410.         arp_tables[hash] = entry;  
    411.         add_timer(&entry->timer);  
    412.         entry->retries = ARP_MAX_TRIES;  
    413.         skb_queue_head_init(&entry->skb);  
    414.         if (skb != NULL)  
    415.         {//待发送数据包的缓存工作  
    416.             skb_queue_tail(&entry->skb, skb);  
    417.             skb_device_unlock(skb);  
    418.         }  
    419.     }  
    420.     else  
    421.     {  
    422.         if (skb != NULL && skb->free)  
    423.             kfree_skb(skb, FREE_WRITE);  
    424.     }  
    425.     sti();  
    426.   
    427.     /* 
    428.      *  If we didn't find an entry, we will try to send an ARP packet. 
    429.      */  
    430.     //没有找到arp表项,则创建并发送一个arp请求报文,启动arp地址解析过程  
    431.     //该函数将发送上面缓存在arp表项相关队列中的滞留数据包  
    432.     arp_send(ARPOP_REQUEST, ETH_P_ARP, paddr, dev, saddr, NULL,   
    433.          dev->dev_addr);  
    434.   
    435.     return 1;  
    436. }  

    八、arp匹配表项查询——arp_lookup 函数

    1. /* 
    2.  *  This will find an entry in the ARP table by looking at the IP address. 
    3.  *      If proxy is PROXY_EXACT then only exact IP matches will be allowed 
    4.  *      for proxy entries, otherwise the netmask will be used 
    5.  */  
    6. //完成具体的arp匹配表项查询工作  
    7. static struct arp_table *arp_lookup(unsigned long paddr, enum proxy proxy)  
    8. {  
    9.     struct arp_table *entry;  
    10.     unsigned long hash = HASH(paddr);//根据目的ip地址确定哈希表索引  
    11.   
    12.     //查找arp缓存对应索引位置的arp表项链表  
    13.     for (entry = arp_tables[hash]; entry != NULL; entry = entry->next)  
    14.         if (entry->ip == paddr) break;  
    15.   
    16.     /* it's possibly a proxy entry (with a netmask) */  
    17.     //代理arp表项情况,不加大括号,这编码风格...  
    18.     if (!entry && proxy != PROXY_NONE)  
    19.         //定位到代理表项,arp缓存的最后一个位置  
    20.     for (entry=arp_tables[PROXY_HASH]; entry != NULL; entry = entry->next)  
    21.         //如果是精确匹配,则全32位网络地址匹配  
    22.         //如果不是则只要网络部分地址相同就行  
    23.       if ((proxy==PROXY_EXACT) ? (entry->ip==paddr)  
    24.                                : !((entry->ip^paddr)&entry->mask))   
    25.         break;      
    26.   
    27.     return entry;//不管匹配成功与否都返回,没成功entry==null  
    28. }  
    建议结合 ARP地址解析过程理论部分理解。

    通过上面分析我们知道:

    ARP缓存其实就是一个链式哈希表(本质是一个数组,但其每个数组元素又是一个链表),链表元素为 ARP 表项;

    每个ARP表项由一个 arp_table 结构表示,该结构中含有ip地址,硬件地址映射关系,表项状态标识以及其他辅助字段(重要的就是数据包暂存队列);

    对ARP缓存的操作,具体的就是对每个 arp_table 结构的操作

    本人能力有限,对于arp地址解析过程难免有理解偏差或没理解的地方,欢迎指正,交流进步。

    参考书籍:《Linux 内核网络栈源代码情景分析》、Linux kernel 1.2.13

    展开全文
  • 详解Linux协议栈的数据流向,SOCKET的操作流程,unicast multicast等等的区别。
  • Linux 内核网络协议栈源码剖析】connect 函数剖析(二)
  • 目录 IP协议简介 IP头部 IP数据包的发送 IP数据包的接收 ...当然,这篇文章并不是介绍IP协议的原理,有关IP协议的原理可以参考经典的书籍《TCP/IP协议详解》,而这篇文章主要介绍的是 Linu...
  • Linux TCP 协议栈数据流走读

    千次阅读 2016-08-24 22:34:39
    在这里主要是根据书籍《The Linux Networking Architecture》 24章,以及结合网上的资料,走读一遍tcp协议的数据流的发送和接收。而内核源码是Centos 7.2的标准内核3.10。这个版本和书中的介绍的函数有点不一样。tcp...
  • Linux内核网络协议栈阅读 内核版本为2.6.35 参考书籍是<< linux>> 个人在注释的基础上再次增加注释。 我个人的工作其实并不涉及到内核的植入,但是同样也涉及到交换和转发,读取这份原始文件纯粹是自己找点事情做...
  • 3.Linux 网络协议栈 4.Linux 网卡收包时的中断处理问题 5.Linux 网络启动的准备工作 6.Linux网络包:中断到网络层接收 7.总结 【温馨提示】文章略长,请耐心观看! Linux网络子系统的分层 Linux网络子系统...
  • Linux内核学习总结 作者: 北京—小武 ...Linux操作系统以GPL作为限制条款进行开源,对计算机界产生了巨大影响。...Linux内核从产生到现在一直在不断被改进,...学习所用书籍是美国Robert Love著的《linux内核设计和
  • Linux TCP/IP协议栈笔记

    千次阅读 2007-04-26 16:58:00
    网络设备使用struct net_device结构来描述,这个结构非常之大,许多重要的参考书籍对它都有较为深入的描述,可以参考《Linux设备驱动程序》中网卡驱动设计的相关章节。我会在后面的内容中,对其重要的成员进行注释...
  • 本文分析了Linux和freeBSD两个操作系统的部分网络实现代码,并通过研究一个完整的I/0探作流程,遍历操作系统中TCP/IP协议栈的内核实现,最后对相关实现作出了相应的评价。
  • 所以我只准备粗略地看下它的TCP/IP协议栈,然后记点心得。 嗯,我研究的内核版本是目前最新的,linux-2.6.33.1,其实应该都大同小异。学习源码时,首先应该把它的整体脉络理清楚,然后再局部细看,这是最简捷的。...
  • 有时候觉得好难,有时候觉得也就那么回事,这种感觉反反复复持续了一段时间,直到现在可以较为平静的面对这些问题的时候,决定对网络协议栈的学习做个梳理和记录,下面是阅读到的一些书籍: TCP IP详解卷1:协议 ...
  • Linux TCP/IP协议栈之Socket的实现分析

    千次阅读 2013-01-04 14:45:03
    网络设备使用struct net_device结构来描述,这个结构非常之大,许多重要的参考书籍对它都有较为深入的描述,可以参考《Linux设备驱动程序》中网卡驱动设计的相关章节。我会在后面的内容中,对其重要的成员进行注释;...
  • 很好的理解linux网络协议栈书籍,中文版!
  • tcp_recvmsg:从网络协议栈接收数据的动作,自上而下的触发动作一直到这个函数为止,出现了一次等待的过程.函数tcp_recvmsg可能会被动地等待在sk的接收数据队列上,也就是说,系统中肯定有其他地方会去修改这个队列使得...
  • CHAPTER2:寻路的向导——ARP5、 arp_in.c ...在前面的《网络接口层》一文中我们看到,当packet_rx函数从pcap得到数据包后,通过调用ni_in.c将数据包传送给协议栈,ni_in.c根据数据包的不同类型进行多路分解,传送给不
  • CHAPTER2:寻路的向导——ARP一、简介我们使用TCP/IP进行通信,从高层来看使用的是IP...ARP地址解析协议所要做的就是将物理地址和IP地址绑定,让高层协议通过使用IP地址即能和目的地产生通信。ARP的工作方式在comer的
  • linux网络内核书合集01

    2015-10-27 11:17:26
    本合集是本人搜集的关于linux网络内核的书籍,都是经典,包含:(1)Linux网络体系结构:Linux内核中网络协议的设计与实现.chm (2)Linux网络内核分析与开发.pdf (3)嵌入式Linux网络体系结构设计与TCP/IP协议栈....
  • linux网络内核书合集03

    2015-10-27 11:36:50
    本合集是本人搜集的关于linux网络内核的书籍,都是经典,包含:(1)Linux网络体系结构:Linux内核中网络协议的设计与实现.chm (2)Linux网络内核分析与开发.pdf (3)嵌入式Linux网络体系结构设计与TCP/IP协议栈....
  • linux网络内核书合集07

    2015-10-27 11:53:00
    本合集是本人搜集的关于linux网络内核的书籍,都是经典,包含:(1)Linux网络体系结构:Linux内核中网络协议的设计与实现.chm (2)Linux网络内核分析与开发.pdf (3)嵌入式Linux网络体系结构设计与TCP/IP协议栈....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,461
精华内容 2,984
热门标签
关键字:

linux网络协议栈书籍

linux 订阅