Chinaunix首页 | 论坛 | 博客
  • 博客访问: 235344
  • 博文数量: 48
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 548
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-28 19:18
文章分类

全部博文(48)

文章存档

2010年(6)

2009年(6)

2008年(36)

我的朋友

分类: LINUX

2008-09-05 09:37:39

内核版本:2.6.12

一、网卡驱动程序
当然,网卡驱动程序完成了从网卡接收数据的第一部份工作,以以3com501 的驱动
linux/drivers/net/3c501.c为例(因为它排在了第一个):

设备初始化函数中,依次调用:

CODE:
int init_module(void)
->el1_probe()
-->el1_probe1()

先向内核申请注册一个以太设备,并设定设备的open函数为:

CODE:
dev->open = &el_open;(line 316)

在el_open函数中,注册了自己的中断处理程序:(line 350)

CODE:
if ((retval = request_irq(dev->irq, &el_interrupt, 0, dev->name, dev)))

el_interruupt 为中断处理程序,中断实际上是一个电信号,网卡收到数据包后,就触发这个电信号给中断控制器的某个引脚,中断控制器向CPU某引脚发送相应的信号,CPU 检测到此信号后,进入内存中操作系统预设的位置的代码,即中断处理程序入口点,这样,操作系统设用do_IRQ()处理中断,该中断函数根据中断号和 dev_id,确定中断处理函数,对于我们的例子,也就是调用对应中断函数el_interrupt(),它判断网卡传递的信号,如果是接收数据,于是读 网卡寄存器……XXXXXXXXX
完成读取读取后,调用接收函数:(p654)

CODE:
                else if (rxsr & RX_GOOD)
                {
                        /*
                         *        Receive worked.
                         */
                        el_receive(dev);
                }

参数dev 就是本身网卡的设备描述,el_receive()函数最重要的工作就是分配缓存:

CODE:
skb = dev_alloc_skb(pkt_len+2);(line724)

并关联设备:

CODE:
skb->dev = dev;(line740)

然后调用:

CODE:
netif_rx(skb);(line748)

netif_rx是网络栈向驱动程序提供的接收数据包的接口,对于驱动程序的编写者而言,只需要知道这个函数即可,不过我们要分析整个接收流程,还要来进一步剖析它。

PS:网卡驱动的整个实现比较复杂,我这里只是做了简单的描述,对于没有网络驱动编写经验的朋友,可以参考《Linux设备驱动程序》第十七章网卡驱动方面的内容,九贱对作者提供的例子做了进一步的脚注:
独孤九贱

二、下半部
因为中断处理程序本身的一些局限性,为了提高其效率,Linux将中断处理分为两部份:上半部(中断处理程序)和下半部。
一些时间任务紧迫,与硬件相关的任务,被放在了中断处理程序中,而另外一些可以“稍微延迟”的任务被放在了下半部中。例如,响应网卡中断,读取接收数据,由网卡中断处理程序完成,而对数据包的接收的进一步处理,就由下半部来完成。
Linux中下半部的完成,有许多种实现,对于网络栈来说,采用了“软中断”机制。也就是说,当从网卡接收到数据后,网络子系统触发软中断,网卡中断处理程序就返回去继续它的工作了,而内核会在尽可能快的时间内,调用网络子系统的软中断函数,进一步处理数据包。
关于下半部和软中断的介绍,《Linux内核设计与实现》第二版第七章中,有详细的介绍。

三、队列
由于中断处理的接收函数netif_rx同软中断函数不是连续处理的。也就是说,netif_rx不是等上层协议栈处理完一个数据包后,再返回处理另一个 数据包,所以,在netif_rx与软中断函数中间,自然需要一个缓冲区:netif_rx往里面写,而软中断函数从里边取。所以,从性能的角度考虑,建 立这样一个缓冲区,是非常必要的。Linux实现这一缓冲的算法是队列。

Linux用struct softnet_data结构描述了队列:

CODE:
struct softnet_data
{
        int                        throttle;
        int                        cng_level;
        int                        avg_blog;
        struct sk_buff_head        input_pkt_queue;
        struct list_head        poll_list;
        struct net_device        *output_queue;
        struct sk_buff                *completion_queue;

        struct net_device        backlog_dev;        /* Sorry. 8) */
};

throttle 用于拥塞控制,当拥塞发生时,throttle将被设置,后续进入的数据包将被丢弃;
cng_level描述一个拥塞级别,由netif_rx函数返回。
input_pkt_queue成员即为包的输入队列,在后面的分析中,我们可以看它网络子系统,如果进行入队和出队操作;
poll_list维护一个设备列表,特别的,我们并不需要针对每一个物理设备进行特别处理,比如,eth0接收一个包,eth1也接收一个包,eth0 ->eth1,再分别调用它们的处理函数poll,从队列中取出数据包并送住上层。而且采用了一个通用伪设备backlog_dev(struct softnet_data的最后一个成员),来进行处理,它的poll函数是process_backlog,在后面队列初始化中,我们会看到。

特别地,队列是一个per_cpu变量,也就是说,第一个CPU都有一个:

CODE:
DECLARE_PER_CPU(struct softnet_data,softnet_data);(net/core/dev.c line:576)

这样,每一次,都可以通过:

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

                queue = &per_cpu(softnet_data, i);
               ……
               }

来遍历每一个CPU的队列,并处理,或者是:

CODE:
struct softnet_data *queue;
queue = &__get_cpu_var(softnet_data);

取得当前CPU的队列。

四、上层协议的处理
最后一个要讨论的问题是,如何把相应的协议送往相应的上层栈,如IP协议交由tcp/ip栈,arp协议交由arp处理程序……
以太网帧头有一个类型字段,最简单的办法莫过于
switch(type)
case 0x0800
   do_ip();
case 0x0806:
   do_arp();
……

呵呵,当然Linux不会用我的笨办法了
内核用ruct packet_type结构来描述每一类协议:

CODE:
struct packet_type {
        __be16                        type;        /* This is really htons(ether_type).        */
        struct net_device                *dev;        /* NULL is wildcarded here                */
        int                        (*func) (struct sk_buff *, struct net_device *,
                                         struct packet_type *);
        void                        *af_packet_priv;
        struct list_head        list;
};

type即为协议类型值,func函数指针为对应的包处理句柄,list成员用于维护协议链表。
以ip协议(net/ipv4/af_inet.c)为例:

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

对于IP协议来讲,类型是ETH_P_IP,接收处理函数是ip_rcv。接下来,就是要把ip_packet_type添加进协议链表中去:

CODE:
inet_init(void)(line:1012)
-> ip_init();

void __init ip_init(void)
{
        dev_add_pack(&ip_packet_type);
……
}

void dev_add_pack(struct packet_type *pt)
{
        int hash;

        spin_lock_bh(&ptype_lock);
        if (pt->type == htons(ETH_P_ALL)) {
                netdev_nit++;
                list_add_rcu(&pt->list, &ptype_all);
        } else {
                hash = ntohs(pt->type) & 15;
                list_add_rcu(&pt->list, &ptype_base[hash]);
        }
        spin_unlock_bh(&ptype_lock);
}

可见,最后添加进的是一个以ptype_bash数组为首的链表,用协议值& 15,来取hash值,定位数组入口。

OK,有了这些基础,我们就可以来分析netif_rx函数了!!

未完,待续……

[ 本帖最后由 独孤九贱 于 2006-11-14 16:01 编辑 ]



您对本贴的看法:

__________________________________

擦掉眼泪,抚平伤口,站起来重建家园,一定要坚强!!!
| | 致电800-858-2903,了解DELL如何为你量身订制笔记本 |
版主   帅哥
广告杀手-老法王
侠客



UID:14893
注册:2002-8-12
最后登录: 2008-09-05
帖子:
精华:

可用积分:15469 (大富大贵)
信誉积分:
空间积分:0 (白手起家)
专家积分: (本版)

状态:...在线...

[] [] [博客]


顶部
发表于 2006-11-14 16:15 
支持九贱发文!



您对本贴的看法:

__________________________________

data Maybe a = Nothing
             | Just a

---
如何知道一个变量是什么类型?
如何知道分配的内存有多大?
如何知道 select 的 fd_set 里哪个句柄是无效的?
如何知道指针是不是有效的?
如何通过文件句柄得到文件名?
……
如何知道我昨晚把袜子脱哪儿了?
| | 致电800-858-2903,了解DELL如何为你量身订制笔记本 |
  帅哥 (九贱)
天使



UID:83191
注册:2003-8-12
最后登录: 2008-09-04
帖子:
精华:

可用积分:1336 (家境小康)
信誉积分:
空间积分:0 (白手起家)
专家积分: (本版)

来自:山城重庆
状态:...离线...

[] [] [博客]


顶部
发表于 2006-11-14 16:22 
多谢楼上的支持,我也是初看内核栈,以前都一直在看netfilter,写出来是为了和大家一起交流学习,水平有限,错误,不详之处众多,众人拾材火焰高呀!

一、网络子系统的初始化
实始化是在net/core/dev.c的init 函数中完成的

CODE:
static int __init net_dev_init(void)
{
        int i, rc = -ENOMEM;

        BUG_ON(!dev_boot_phase);

        net_random_init();

        if (dev_proc_init())
                goto out;

        if (netdev_sysfs_init())
                goto out;

/*ptype_all与ptype_base类似,不过它用于混杂模式*/
        INIT_LIST_HEAD(&ptype_all);
/*初始化hash链表首部,我们前面已经讲过它的链表的建立了,现在才看到初始化,先后顺序有问题*/
        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]);

        /*
         *        初始化每一个CPU的包接收队列.
         */

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

/*逐次取得第i个CPU的队列*/
                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);
/*设置backlog_dev设备的状态,“我已经可以接收数据了”*/
                set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
                queue->backlog_dev.weight = weight_p;
/*这一步很重要,指明backlog_dev的poll函数为process_backlog*/
                queue->backlog_dev.poll = process_backlog;
                atomic_set(&queue->backlog_dev.refcnt, 1);
        }

#ifdef OFFLINE_SAMPLE
        samp_timer.expires = jiffies + (10 * HZ);
        add_timer(&samp_timer);
#endif

        dev_boot_phase = 0;
/*接收和发送的软中断,在这里安装*/
        open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
        open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

        hotcpu_notifier(dev_cpu_callback, 0);
        dst_init();
        dev_mcast_init();
        rc = 0;
out:
        return rc;
}



CODE:
二、netif_rx

netif_rx有两个很重要的工作:
1、把backlog_dev加入进设备列表;
2、把从网卡驱动接收来的数据添加进接收包队列中;

CODE:
int netif_rx(struct sk_buff *skb)
{
        int this_cpu;
        struct softnet_data *queue;
        unsigned long flags;

        /* if netpoll wants it, pretend we never saw it */
        if (netpoll_rx(skb))
                return NET_RX_DROP;

/*如果没有设置接收的时间,更新之*/
        if (!skb->stamp.tv_sec)
                net_timestamp(&skb->stamp);

        /*
         * The code is rearranged so that the path is the most
         * short when CPU is congested, but is still operating.
         */
        local_irq_save(flags);
        this_cpu = smp_processor_id();
/*取得当前CPU的队列*/
        queue = &__get_cpu_var(softnet_data);

/*接收计数器累加*/
        __get_cpu_var(netdev_rx_stat).total++;
/*队列总数没有超过定义的最大值*/
        if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
/*这个if判断的含义是,如果阶列中没有数据包,则先添加设备,再跳转回来,将数据包入队,否则直接入队,在入队之前,还要判断throttle成员的值,即拥塞是否已经发生,如果发生,则要丢弃当前数据包*/
                if (queue->input_pkt_queue.qlen) {
                        if (queue->throttle)
                                goto drop;

enqueue:
                        dev_hold(skb->dev);
/*将数据包入队,并累加队列计算器*/
                        __skb_queue_tail(&queue->input_pkt_queue, skb);
#ifndef OFFLINE_SAMPLE
                        get_sample_stats(this_cpu);
#endif
                        local_irq_restore(flags);
                        return queue->cng_level;
                }

                if (queue->throttle)
                        queue->throttle = 0;
/*将backlog_dev设备添加进设备列表,并进一步入理*/
                netif_rx_schedule(&queue->backlog_dev);
                goto enqueue;
        }

        if (!queue->throttle) {
                queue->throttle = 1;
                __get_cpu_var(netdev_rx_stat).throttled++;
        }

drop:
/*丢弃数据包,先累加计数器,再释放skb*/
        __get_cpu_var(netdev_rx_stat).dropped++;
        local_irq_restore(flags);

        kfree_skb(skb);
        return NET_RX_DROP;
}

三、netif_rx_schedule

CODE:
static inline void netif_rx_schedule(struct net_device *dev)
{
        if (netif_rx_schedule_prep(dev))
                __netif_rx_schedule(dev);
}

netif_rx_schedule函数只是一个包裹函数,它将主动权交到了__netif_rx_schedule手中:

CODE:
static inline void __netif_rx_schedule(struct net_device *dev)
{
        unsigned long flags;

        local_irq_save(flags);
        dev_hold(dev);
/*添加设备进列表*/
        list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
        if (dev->quota < 0)
                dev->quota += dev->weight;
        else
                dev->quota = dev->weight;
/*触发接收软中断*/
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        local_irq_restore(flags);
}

接收处理函数触发软中断,负责“下半部”处理的软中断函数net_rx_action函数将会被执行。

未完,待续……

[ 本帖最后由 独孤九贱 于 2006-11-14 16:25 编辑 ]



您对本贴的看法:

__________________________________

擦掉眼泪,抚平伤口,站起来重建家园,一定要坚强!!!
| | 致电800-858-2903,了解DELL如何为你量身订制笔记本 |
  帅哥 (九贱)
天使



UID:83191
注册:2003-8-12
最后登录: 2008-09-04
帖子:
精华:

可用积分:1336 (家境小康)
信誉积分:
空间积分:0 (白手起家)
专家积分: (本版)

来自:山城重庆
状态:...离线...

[] [] [博客]


顶部
发表于 2006-11-14 16:50 


CODE:
一、接收的软中断函数net_rx_action



CODE:
static void net_rx_action(struct softirq_action *h)
{
/*取得当前CPU上的队列*/
        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        unsigned long start_time = jiffies;
/*netdev_max_backlog是一个sysctl常量,它决定了最大的排队等候的数据包*/
        int budget = netdev_max_backlog;

       
        local_irq_disable();
/*处理每一个设备上接收到的数据包,直接遍历完所有的设备,其中之一,就是netif_rx函数添加进的伪设备:backlog_dev*/
        while (!list_empty(&queue->poll_list)) {
                struct net_device *dev;

                if (budget <= 0 || jiffies - start_time > 1)
                        goto softnet_break;

                local_irq_enable();
/*取得每一个设备*/
                dev = list_entry(queue->poll_list.next,
                                 struct net_device, poll_list);
                netpoll_poll_lock(dev);
/*调用设备的poll函数*/
                if (dev->quota <= 0 || dev->poll(dev, &budget)) {
                        netpoll_poll_unlock(dev);
                        local_irq_disable();
                        list_del(&dev->poll_list);
                        list_add_tail(&dev->poll_list, &queue->poll_list);
                        if (dev->quota < 0)
                                dev->quota += dev->weight;
                        else
                                dev->quota = dev->weight;
                } else {
                        netpoll_poll_unlock(dev);
                        dev_put(dev);
                        local_irq_disable();
                }
        }
out:
        local_irq_enable();
        return;

softnet_break:
        __get_cpu_var(netdev_rx_stat).time_squeeze++;
        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
        goto out;
}

对于软中断函数,我们主要关心的是:
遍历设备列表,取得每一个设备的poll函数,在netif_rx函数中,添加进队列的是back_log设备,它的poll函数是process_backlog。

二、process_backlog

CODE:
static int process_backlog(struct net_device *backlog_dev, int *budget)
{
        int work = 0;
        int quota = min(backlog_dev->quota, *budget);
/*同样地,取得当前CPU的队列*/
        struct softnet_data *queue = &__get_cpu_var(softnet_data);
        unsigned long start_time = jiffies;

        backlog_dev->weight = weight_p;
/*这个循环中,将队列中的数据包逐一出队*/
        for (;;) {
                struct sk_buff *skb;
                struct net_device *dev;

/*数据包出队,当然,为了保护队列不至于混乱,必须关闭中断*/
                local_irq_disable();
                skb = __skb_dequeue(&queue->input_pkt_queue);
                if (!skb)
                        goto job_done;
                local_irq_enable();

                dev = skb->dev;
/*将从队列中取出的数据包,进一步交给上层函数处得*/
                netif_receive_skb(skb);

                dev_put(dev);

                work++;

                if (work >= quota || jiffies - start_time > 1)
                        break;

        }

        backlog_dev->quota -= work;
        *budget -= work;
        return -1;

job_done:
        backlog_dev->quota -= work;
        *budget -= work;

        list_del(&backlog_dev->poll_list);
        smp_mb__before_clear_bit();
        netif_poll_enable(backlog_dev);

        if (queue->throttle)
                queue->throttle = 0;
        local_irq_enable();
        return 0;
}

三、netif_receive_skb
netif_receive_skb函数最重要的工作,就是把数据包交给上层协议栈:


int netif_receive_skb(struct sk_buff *skb)
{
/*ptype和pt_prev用来遍历packet_type结构的链表,我们前面讨论过上层协议的封装与注册*/
        struct packet_type *ptype, *pt_prev;
        int ret = NET_RX_DROP;
        unsigned short type;

        /* 处理NAPI的情况*/
        if (skb->dev->poll && netpoll_rx(skb))
                return NET_RX_DROP;

        if (!skb->stamp.tv_sec)
                net_timestamp(&skb->stamp);

        skb_bond(skb);
/*接收计数器累加*/
        __get_cpu_var(netdev_rx_stat).total++;
/*更新传输层头和网络层头指针*/
        skb->h.raw = skb->nh.raw = skb->data;
        skb->mac_len = skb->nh.raw - skb->mac.raw;

        pt_prev = NULL;

        rcu_read_lock();

#ifdef CONFIG_NET_CLS_ACT
        if (skb->tc_verd & TC_NCLS) {
                skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
                goto ncls;
        }
#endif
/*处理混杂模式的情况*/
        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;
                }
        }

#ifdef CONFIG_NET_CLS_ACT
        if (pt_prev) {
                ret = deliver_skb(skb, pt_prev);
                pt_prev = NULL; /* noone else should process this after*/
        } else {
                skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
        }

        ret = ing_filter(skb);

        if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
                kfree_skb(skb);
                goto out;
        }

        skb->tc_verd = 0;
ncls:
#endif

        handle_diverter(skb);
/*进入网桥*/
        if (handle_bridge(&skb, &pt_prev, &ret))
                goto out;
/*取得上层协议类型,这个类型值,是在网卡驱动中设置的*/
        type = skb->protocol;
/*遍历每一个注册协议,调用deliver_skb函数来调用协议封装的处理函数*/
        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;
                }
        }

        if (pt_prev) {
                ret = pt_prev->func(skb, skb->dev, pt_prev);
        } else {
                kfree_skb(skb);
                /* Jamal, now you will not able to escape explaining
                 * me how you were going to use this.
                 */
                ret = NET_RX_DROP;
        }

out:
        rcu_read_unlock();
        return ret;
}

对于IP协议而言,早已注册的处理函数是ip_recv……

未完,待续!
阅读(2443) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~