- 浏览: 1407391 次
- 性别:
- 来自: 火星
-
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
首先来看如何分配内存给一个网络设备。
内核通过alloc_netdev来分配内存给一个指定的网络设备:
其中alloc_netdev_mq中的第一个元素是每个网络设备的私有数据(主要是包含一些硬件参数,比如中断之类的)的大小,也就是net_device结构中的priv的大小。第二个参数是设备名,我们传递进来一般都是一个待format的字符串,比如"eth%d",到时多个相同类型网卡设备就会依次为eth0,1(内核会通过dev_alloc_name来进行设置)... 第三个参数setup是一个初始化net_device结构的回调函数。
可是一般我们不需要直接调用alloc_netdev的,内核提供了一些包装好的函数:
这里我们只看alloc_etherdev:
这里实际是根据网卡的类型进行包装,也就类似于oo中的基类,ether_setup初始化一些所有相同类型的网络设备的一些相同配置的域:
接下来我们来看注册网络设备的一些细节。
整个网络设备就是一个链表,他需要很方便的遍历所有设备,以及很快的定位某个指定的设备。为此net_device包含了下面3个链表(有关内核中数据结构的介绍,可以去自己google下):
当设备注册成功后,还需要通知内核的其他组件,这里通过netdev_chain类型的notifier chain来通知其他组件。事件是NETDEV_REGISTER..其他设备通过register_netdevice_notifier来注册自己感兴趣的事件到此notifier chain上。
网络设备(比如打开或关闭一个设备),与用户空间的通信通过rtmsg_ifinfo函数,也就是RTMGRP_LINK的netlink。
每个设备还包含两个状态,一个是state字段,表示排队策略状态(用位图表示),一个是注册状态。
包的排队策略也就是qos了。。
这里要注意有一个全局的struct net init_net;变量,这个变量保存了全局的name,index hlist以及全局的网络设备链表。
net结构我们这里所需要的也就三个链表:
最终执行完之后,注册函数将会执行rtnl_unlock函数,而此函数则会执行netdev_run_todo方法。也就是完成最终的注册。(要注意,当取消注册这个设备时也会调用这个函数来完成最终的取消注册)
这里有一个全局的net_todo_list的链表:
而在取消注册的函数中会调用这个函数:
也就是把当前将要取消注册的函数加入到todo_list链表中。
下面来看netdev_wait_allrefs函数,我们先看它的调用流程:
内核通过alloc_netdev来分配内存给一个指定的网络设备:
#define alloc_netdev(sizeof_priv, name, setup) \ alloc_netdev_mq(sizeof_priv, name, setup, 1) struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name, void (*setup)(struct net_device *), unsigned int queue_count)
其中alloc_netdev_mq中的第一个元素是每个网络设备的私有数据(主要是包含一些硬件参数,比如中断之类的)的大小,也就是net_device结构中的priv的大小。第二个参数是设备名,我们传递进来一般都是一个待format的字符串,比如"eth%d",到时多个相同类型网卡设备就会依次为eth0,1(内核会通过dev_alloc_name来进行设置)... 第三个参数setup是一个初始化net_device结构的回调函数。
可是一般我们不需要直接调用alloc_netdev的,内核提供了一些包装好的函数:
这里我们只看alloc_etherdev:
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1) struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count) { return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count); }
这里实际是根据网卡的类型进行包装,也就类似于oo中的基类,ether_setup初始化一些所有相同类型的网络设备的一些相同配置的域:
void ether_setup(struct net_device *dev) { dev->header_ops = ð_header_ops; dev->change_mtu = eth_change_mtu; dev->set_mac_address = eth_mac_addr; dev->validate_addr = eth_validate_addr; dev->type = ARPHRD_ETHER; dev->hard_header_len = ETH_HLEN; dev->mtu = ETH_DATA_LEN; dev->addr_len = ETH_ALEN; dev->tx_queue_len = 1000; /* Ethernet wants good queues */ dev->flags = IFF_BROADCAST|IFF_MULTICAST; memset(dev->broadcast, 0xFF, ETH_ALEN); }
接下来我们来看注册网络设备的一些细节。
int register_netdev(struct net_device *dev) { int err; rtnl_lock(); /* * If the name is a format string the caller wants us to do a * name allocation. */ if (strchr(dev->name, '%')) { ///这里通过dev_alloc_name函数来对设备名进行设置。 err = dev_alloc_name(dev, dev->name); if (err < 0) goto out; } ///注册当前的网络设备到全局的网络设备链表中.下面会详细看这个函数. err = register_netdevice(dev); out: rtnl_unlock(); return err; }
整个网络设备就是一个链表,他需要很方便的遍历所有设备,以及很快的定位某个指定的设备。为此net_device包含了下面3个链表(有关内核中数据结构的介绍,可以去自己google下):
///可以根据index来定位设备 struct hlist_node index_hlist; ///可以根据name来定位设备 struct hlist_node name_hlist; ///通过dev_list,将此设备插入到全局的dev_base_head中,我们下面会介绍这个。 struct list_head dev_list;
当设备注册成功后,还需要通知内核的其他组件,这里通过netdev_chain类型的notifier chain来通知其他组件。事件是NETDEV_REGISTER..其他设备通过register_netdevice_notifier来注册自己感兴趣的事件到此notifier chain上。
网络设备(比如打开或关闭一个设备),与用户空间的通信通过rtmsg_ifinfo函数,也就是RTMGRP_LINK的netlink。
每个设备还包含两个状态,一个是state字段,表示排队策略状态(用位图表示),一个是注册状态。
包的排队策略也就是qos了。。
int register_netdevice(struct net_device *dev) { struct hlist_head *head; struct hlist_node *p; int ret; struct net *net; BUG_ON(dev_boot_phase); ASSERT_RTNL(); might_sleep(); /* When net_device's are persistent, this will be fatal. */ BUG_ON(dev->reg_state != NETREG_UNINITIALIZED); BUG_ON(!dev_net(dev)); net = dev_net(dev); ///初始化相关的锁 spin_lock_init(&dev->addr_list_lock); netdev_set_addr_lockdep_class(dev); netdev_init_queue_locks(dev); dev->iflink = -1; /* Init, if this function is available */ if (dev->init) { ret = dev->init(dev); if (ret) { if (ret > 0) ret = -EIO; goto out; } } if (!dev_valid_name(dev->name)) { ret = -EINVAL; goto err_uninit; } ///给设备分配一个唯一的identifier. dev->ifindex = dev_new_index(net); if (dev->iflink == -1) dev->iflink = dev->ifindex; ///在全局的链表中检测是否有重复的名字 head = dev_name_hash(net, dev->name); hlist_for_each(p, head) { struct net_device *d = hlist_entry(p, struct net_device, name_hlist); if (!strncmp(d->name, dev->name, IFNAMSIZ)) { ret = -EEXIST; goto err_uninit; } } ///下面是检测一些特性的组合是否合法。 /* Fix illegal checksum combinations */ if ((dev->features & NETIF_F_HW_CSUM) && (dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) { printk(KERN_NOTICE "%s: mixed HW and IP checksum settings.\n", dev->name); dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM); } if ((dev->features & NETIF_F_NO_CSUM) && (dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) { printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n", dev->name); dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM); } /* Fix illegal SG+CSUM combinations. */ if ((dev->features & NETIF_F_SG) && !(dev->features & NETIF_F_ALL_CSUM)) { printk(KERN_NOTICE "%s: Dropping NETIF_F_SG since no checksum feature.\n", dev->name); dev->features &= ~NETIF_F_SG; } /* TSO requires that SG is present as well. */ if ((dev->features & NETIF_F_TSO) && !(dev->features & NETIF_F_SG)) { printk(KERN_NOTICE "%s: Dropping NETIF_F_TSO since no SG feature.\n", dev->name); dev->features &= ~NETIF_F_TSO; } if (dev->features & NETIF_F_UFO) { if (!(dev->features & NETIF_F_HW_CSUM)) { printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no " "NETIF_F_HW_CSUM feature.\n", dev->name); dev->features &= ~NETIF_F_UFO; } if (!(dev->features & NETIF_F_SG)) { printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no " "NETIF_F_SG feature.\n", dev->name); dev->features &= ~NETIF_F_UFO; } } /* Enable software GSO if SG is supported. */ if (dev->features & NETIF_F_SG) dev->features |= NETIF_F_GSO; ///初始化设备驱动的kobject并创建相关的sysfs netdev_initialize_kobject(dev); ret = netdev_register_kobject(dev); if (ret) goto err_uninit; ///设置注册状态。 dev->reg_state = NETREG_REGISTERED; /* * Default initial state at registry is that the * device is present. */ ///设置排队策略状态。 set_bit(__LINK_STATE_PRESENT, &dev->state); ///初始化排队规则 dev_init_scheduler(dev); dev_hold(dev); ///将相应的链表插入到全局的链表中。紧接着会介绍这个函数 list_netdevice(dev); /* Notify protocols, that a new device appeared. */ ///调用netdev_chain通知内核其他子系统。 ret = call_netdevice_notifiers(NETDEV_REGISTER, dev); ret = notifier_to_errno(ret); if (ret) { rollback_registered(dev); dev->reg_state = NETREG_UNREGISTERED; } out: return ret; err_uninit: if (dev->uninit) dev->uninit(dev); goto out; }
这里要注意有一个全局的struct net init_net;变量,这个变量保存了全局的name,index hlist以及全局的网络设备链表。
net结构我们这里所需要的也就三个链表:
///设备链表 struct list_head dev_base_head; ///名字为索引的hlist struct hlist_head *dev_name_head; ///index为索引的hlist struct hlist_head *dev_index_head;
static int list_netdevice(struct net_device *dev) { struct net *net = dev_net(dev); ASSERT_RTNL(); write_lock_bh(&dev_base_lock); ///插入全局的list list_add_tail(&dev->dev_list, &net->dev_base_head); 插入全局的name_list以及index_hlist hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name)); hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex)); write_unlock_bh(&dev_base_lock); return 0; }
最终执行完之后,注册函数将会执行rtnl_unlock函数,而此函数则会执行netdev_run_todo方法。也就是完成最终的注册。(要注意,当取消注册这个设备时也会调用这个函数来完成最终的取消注册)
这里有一个全局的net_todo_list的链表:
static LIST_HEAD(net_todo_list);
而在取消注册的函数中会调用这个函数:
static void net_set_todo(struct net_device *dev) { list_add_tail(&dev->todo_list, &net_todo_list); }
也就是把当前将要取消注册的函数加入到todo_list链表中。
void netdev_run_todo(void) { struct list_head list; /* Snapshot list, allow later requests */ ///replace掉net_todo_list用list代替。 list_replace_init(&net_todo_list, &list); __rtnl_unlock(); ///当注册设备时没有调用net_set_todo函数来设置net_todo_list,因此list为空,所以就会直接跳过。 while (!list_empty(&list)) { ///通过todo_list得到当前的device对象。 struct net_device *dev = list_entry(list.next, struct net_device, todo_list); ///删除此todo_list; list_del(&dev->todo_list); if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) { printk(KERN_ERR "network todo '%s' but state %d\n", dev->name, dev->reg_state); dump_stack(); continue; } ///设置注册状态为NETREG_UNREGISTERED. dev->reg_state = NETREG_UNREGISTERED; ///在每个cpu上调用刷新函数。 on_each_cpu(flush_backlog, dev, 1); ///等待引用此设备的所有系统释放资源,也就是引用计数清0. netdev_wait_allrefs(dev); /* paranoia */ BUG_ON(atomic_read(&dev->refcnt)); WARN_ON(dev->ip_ptr); WARN_ON(dev->ip6_ptr); WARN_ON(dev->dn_ptr); if (dev->destructor) dev->destructor(dev); /* Free network device */ kobject_put(&dev->dev.kobj); } }
下面来看netdev_wait_allrefs函数,我们先看它的调用流程:
![](/upload/attachment/99616/37701951-65cd-3e7d-9668-f73d5b6e3a44.jpg)
static void netdev_wait_allrefs(struct net_device *dev) { unsigned long rebroadcast_time, warning_time; rebroadcast_time = warning_time = jiffies; while (atomic_read(&dev->refcnt) != 0) { if (time_after(jiffies, rebroadcast_time + 1 * HZ)) { rtnl_lock(); ///给netdev_chain发送NETDEV_UNREGISTER事件,通知各个子模块释放资源 /* Rebroadcast unregister notification */ call_netdevice_notifiers(NETDEV_UNREGISTER, dev); if (test_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) { /* We must not have linkwatch events * pending on unregister. If this * happens, we simply run the queue * unscheduled, resulting in a noop * for this device. */ linkwatch_run_queue(); } __rtnl_unlock(); rebroadcast_time = jiffies; } msleep(250); if (time_after(jiffies, warning_time + 10 * HZ)) { printk(KERN_EMERG "unregister_netdevice: " "waiting for %s to become free. Usage " "count = %d\n", dev->name, atomic_read(&dev->refcnt)); warning_time = jiffies; } } }
发表评论
-
Receive packet steering patch详解
2010-07-25 16:46 12207Receive packet steering简称rp ... -
内核中拥塞窗口初始值对http性能的影响分析
2010-07-11 00:20 9730这个是google的人提出的 ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9625这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12222上一篇处理ack的blog中我 ... -
内核tcp的ack的处理
2010-01-17 03:06 11217我们来看tcp输入对于ack,段的处理。 先是ack的处理, ... -
内核处理time_wait状态详解
2010-01-10 17:39 6859这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13664首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47955我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8386tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 12133我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核定时器的实现
2009-10-31 01:44 10219由于linux还不是一个实时的操作系统,因此如果需要更高精度, ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10373我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5459这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9867在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14570相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19845在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5232先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8452首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5856bind的实现: 先来介绍几个地址结构. struct ... -
linux内核中socket的实现
2009-08-15 04:38 21134首先来看整个与socket相关的操作提供了一个统一的接口sys ...
相关推荐
### 知识点总结 #### 1. 引子 - **上电**: 当电源接入计算机时,系统开始上电自检(POST),检查硬件...- **子系统的初始化**: 初始化各种子系统,如网络、设备驱动等。 - **启动shell环境**: 提供交互式命令行界面。
### 内核初始化知识点详解 #### 一、内核初始化概述 **内核初始化**是操作系统启动过程中的关键环节之一,主要涉及系统硬件资源的初始化、内核数据结构的建立、核心服务的启动等步骤。良好的初始化过程可以确保...
Linux操作系统中的网络设备初始化过程是系统启动的关键环节,它确保了网络接口的正常运作。网络设备驱动程序在Linux中通常以模块的形式存在,可以是built-in内核的一部分,也可以是动态加载的模块。无论是哪种方式,...
主要涵盖了Socket编程基础知识和Linux内核网络协议栈的工作方式,其中包括协议实现模块和网络设备驱动的初始化过程;探讨了数据链路层的功能,以及客户端、服务器的简单Socket程序和多线程并发模型的设计与注意事项...
2. **subsys_initcall(net_dev_init)**: 初始化网络设备。这里主要是设置各种网络接口设备,准备接收和发送数据包的硬件环境。 3. **fs_initcall(inet_init)**: 初始化Internet协议栈。这一步骤主要负责TCP/IP协议...
《VxWorks内核、设备驱动与BSP开发详解》第二版是一本深入探讨VxWorks实时操作系统核心、设备驱动程序开发以及板级支持包(BSP)构建的专业书籍。VxWorks是由Wind River Systems开发的一款广泛应用在嵌入式系统的实时...
### VxWorks内核、设备驱动与BSP开发详解 #### 一、VxWorks概述与应用场景 VxWorks是一款著名的嵌入式实时操作系统(RTOS),由Wind River Systems开发。由于其优秀的实时性能和高度可靠性,在航空、航天、通信、...
书中详细阐述了设备驱动开发的基本原理和步骤,包括字符设备、块设备、网络设备等各类驱动的编写方法。开发者需要理解硬件的工作模式,然后根据VxWorks驱动程序接口来实现相应的函数,以实现设备的操作和管理。 BSP...
以上内容总结了Linux内核在启动进入用户模式后的一些关键步骤和技术细节,包括PCI设备初始化、网络协议栈初始化、交换守护进程kswapd的工作原理、日志块设备驱动的加载以及devfs和pty的配置。这些过程对于理解Linux...
在这个阶段,根文件系统会被挂载,设备驱动继续初始化,网络接口、设备树等高级功能得到配置。 了解这些过程对内核移植至关重要,因为每个阶段都可能需要针对新平台进行定制。例如,可能需要修改`mach-*`目录下的...
在初始化过程中,init会启动一系列系统服务,如adbd(Android Debug Bridge Daemon),它提供与设备交互的能力;servicemanager,负责管理Android服务的生命周期;mountd,用于挂载文件系统;以及debuggerd,提供...
网络设备驱动程序可以被分为多个部分,包括驱动模块的加载与卸载、设备的探测与初始化、数据包的发送与接收处理等。 在本文件中,提到了Linux网络设备驱动编程中几个核心的编程概念,以下是根据文档内容提炼出的...
《嵌入式系统的以太网...总的来说,嵌入式系统中的以太网接口设计与Linux内核网络设备驱动涉及硬件接口、协议栈、数据传输和驱动程序的编写等多个层面。理解这些概念和技术对于开发高效稳定的嵌入式网络系统至关重要。
"嵌入式系统的以太网接口设计及Linux内核网络设备驱动" 以太网概述: 以太网(Ethernet)是当今局域网采用的最通用的通信协议标准。在以太网中,所有计算机被连接在一条电缆上,采用带冲突检测的载波侦听多路访问...
#### PCI设备初始化 在内核初始化早期阶段,会有一系列针对硬件设备的检测与配置工作。其中,PCI(Peripheral Component Interconnect)总线设备的初始化尤为关键。文档中的信息显示了内核初始化PCI设备的一部分...
嵌入式系统的以太网接口设计及 Linux 内核网络设备驱动汇编 以太网概述 以太网是当今局域网采用的最通用的通信协议标准。在以太网中,所有计算机被连接在一条电缆上,采用带冲突检测的载波侦听多路访问(CSMA/CD)...
【嵌入式系统的以太网接口设计】 以太网(Ethernet)是局域网通信的主要标准...同时,讲解了Linux内核网络驱动程序的体系结构和初始化过程,阐述了设备驱动层的关键功能,为理解嵌入式系统中的以太网通信提供了基础。