精华内容
下载资源
问答
  • linux net namespace优化代码分析

    千次阅读 2015-01-07 10:35:18
    linux net namespace优化代码分析Linux协议栈中引入网络命名空间, 是为了支持网络协议栈的多个实例, 而这些协议栈的隔离就是由命名空间来实现的(有点像进程的线性地址空间, 协议栈不能访问其他
    03


    转载自:  http://hi.baidu.com/supperwangli/item/fbfe42c2eecf7a4ea8ba9420

    linux net namespace优化代码分析

    在Linux协议栈中引入网络命名空间, 是为了支持网络协议栈的多个实例, 而这些协议栈的隔离就是由命名空间来实现的(有点像进程的线性地址空间, 协议栈不能访问其他协议栈的私有数据).

    需要纳入命名空间的元素包括进程, 套接字, 网络设备. 进程创建的套接字必须属于某个命名空间, 套接字的操作也必须在命名空间内进行, 网络设备也必须属于某个命名空间, 但可能会改变, 因为网络设备属于公共资源.

    为了实现网络命名空间, 整个协议栈的代码都需要更新, 工作量非常大.

    试想有两个并行的协议栈, 那么所有协议栈相关的全局变量都必须修改为协议栈私有. 最好的办法就是让这些全局变量成为一个per net namespace变量的成员, 然后为协议栈的函数调用都加入一个namespace参数.

    但是内核开发者面临几个问题:

    1. 最好让现有的内核代码隐式的使用命名空间内的变量, 而不要更新所有的内核代码, 否则工作量太大;

    2. 性能损耗应该非常小, 使得使用命名空间与否对用户没有影响;

    [1] http://lwn.net/Articles/219597/
    [2] http://lwn.net/Articles/218595/

    内核源代码有一个patch是优化net_namespace的释放
    git format-patch 2b035b39970740722598f7a9d548835f9bdd730f -1
    Subject: [PATCH] net: Batch network namespace destruction.
     
    It is fairly common to kill several network namespaces at once.  Either
    because they are nested one inside the other or because they are cooperating
    in multiple machine networking experiments.  As the network stack control logic
    does not parallelize easily batch up multiple network namespaces existing
    together.

     
    To get the full benefit of batching the virtual network devices to be
    removed must be all removed in one batch.  For that purpose I have added
    a loop after the last network device operations have run that batches
    up all remaining network devices and deletes them.
     
    An extra benefit is that the reorganization slightly shrinks the size
    of the per network namespace data structures replaceing a work_struct
    with a list_head.
     
    In a trivial test with 4K namespaces this change reduced the cost of
    a destroying 4K namespaces from 7+ minutes (at 12% cpu) to 44 seconds
    (at 60% cpu).  The bulk of that 44s was spent in inet_twsk_purge.
     
    Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
    Signed-off-by: David S. Miller <davem@davemloft.net>
    ---
     include/net/net_namespace.h |    2 +-
     net/core/net_namespace.c    |   66 +++++++++++++++++++++++++++++++++++++-----
     2 files changed, 59 insertions(+), 9 deletions(-)
     
    ...
    +static DEFINE_SPINLOCK(cleanup_list_lock);
    +static LIST_HEAD(cleanup_list);  /* Must hold cleanup_list_lock to touch */
    +
     static void cleanup_net(struct work_struct *work)
     {
            struct pernet_operations *ops;
    -       struct net *net;
    +       struct net *net, *tmp;
    +       LIST_HEAD(net_kill_list);
     
    -       net = container_of(work, struct net, work);
    +       /* Atomically snapshot the list of namespaces to cleanup */
    +       spin_lock_irq(&cleanup_list_lock);
    +       list_replace_init(&cleanup_list, &net_kill_list);
    +       spin_unlock_irq(&cleanup_list_lock);
     
            mutex_lock(&net_mutex);
     
            /* Don't let anyone else find us. */
            rtnl_lock();
    -       list_del_rcu(&net->list);
    +       list_for_each_entry(net, &net_kill_list, cleanup_list)
    +               list_del_rcu(&net->list);
            rtnl_unlock();
     
            /*
    @@ -170,8 +201,18 @@ static void cleanup_net(struct work_struct *work)
     
            /* Run all of the network namespace exit methods */
            list_for_each_entry_reverse(ops, &pernet_list, list) {
    -               if (ops->exit)
    -                       ops->exit(net);
    +               if (ops->exit) {
    +                       list_for_each_entry(net, &net_kill_list, cleanup_list)
    +                               ops->exit(net);
    +               }
    +               if (&ops->list == first_device) {
    +                       LIST_HEAD(dev_kill_list);
    +                       rtnl_lock();
    +                       list_for_each_entry(net, &net_kill_list, cleanup_list)
    +                               unregister_netdevices(net, &dev_kill_list);
    +                       unregister_netdevice_many(&dev_kill_list);
    +                       rtnl_unlock();
    +               }
            }
     
            mutex_unlock(&net_mutex);
    @@ -182,14 +223,23 @@ static void cleanup_net(struct work_struct *work)
            rcu_barrier();
     
            /* Finally it is safe to free my network namespace structure */
    -       net_free(net);
    +       list_for_each_entry_safe(net, tmp, &net_kill_list, cleanup_list) {
    +               list_del_init(&net->cleanup_list);
    +               net_free(net);
    +       }
     }
    +static DECLARE_WORK(net_cleanup_work, cleanup_net);
     
     void __put_net(struct net *net)
     {
            /* Cleanup the network namespace in process context */
    -       INIT_WORK(&net->work, cleanup_net);
    -       queue_work(netns_wq, &net->work);
    +       unsigned long flags;
    +
    +       spin_lock_irqsave(&cleanup_list_lock, flags);
    +       list_add(&net->cleanup_list, &cleanup_list);
    +       spin_unlock_irqrestore(&cleanup_list_lock, flags);
    +
    +       queue_work(netns_wq, &net_cleanup_work);
     }
     EXPORT_SYMBOL_GPL(__put_net);
    ...
     
    从代码分析,每次释放net namespace时,都会调用__put_net来释放,
    释放操作会放入工作队列,延迟执行。
    当有很多net namespace需要释放时,会造成很多个工作队列需要执行。
    优化代码将需要释放的net namespace放入一个链表,
    由于工作队列会延迟执行,所以当真正要释放时,链表上可能有很多需要释放的,
    这时会在一次释放操作中,释放多个需要释放的net namespace,
    省去了多次在工作队列中的释放。


    展开全文
  • 1 数据库初始化br_fdb_init() 该函数调用kmem_cache_create 来创建一个新缓存(这个函数并没有向缓存分配任何... sizeof(struct net_bridge_fdb_entry), 0, SLAB_HWCACHE_ALIGN, NULL); if (!br...

    目录

    1 数据库初始化br_fdb_init()

    2 插入转发表项 br_fdb_insert()

    2.1 插入转发表项 fdb_insert()

    3 创建转发表项 fdb_create()

    4 查找转发表项 fdb_find() / __br_fdb_get()

    5 转发表项更行 br_fdb_update()

    5.1 同一个网桥端口可以与多个fdb entry关联吗?

    5.2 转发表项添加分类

    5.2.1 local标签 fdb entry添加

    5.2.2 非local标签的fdb entry添加

    6 网桥端口变更mac地址更新转发表 br_fdb_changeaddr

    7 转发表项超时清理 br_fdb_cleanup

    8 转发表项删除 

    8.1 单个表项删除 fdb_delete()

    8.2 批量删除所有非静态表项 br_fdb_flush()


    1 数据库初始化br_fdb_init()

    该函数调用kmem_cache_create 来创建一个新缓存(这个函数并没有向缓存分配任何内存)

    int __init br_fdb_init(void)
    {
    	br_fdb_cache = kmem_cache_create("bridge_fdb_cache",
    					 sizeof(struct net_bridge_fdb_entry),
    					 0,
    					 SLAB_HWCACHE_ALIGN, NULL);
    	if (!br_fdb_cache)
    		return -ENOMEM;
    
    	get_random_bytes(&fdb_salt, sizeof(fdb_salt));
    	return 0;
    }

    2 插入转发表项 br_fdb_insert()

    该函数只是 fdb_insert() 的封装,增加了自旋锁机制。

    /* Add entry for local address of interface */
    int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
    		  const unsigned char *addr, u16 vid)
    {
    	int ret;
    
    	spin_lock_bh(&br->hash_lock);
    	ret = fdb_insert(br, source, addr, vid);
    	spin_unlock_bh(&br->hash_lock);
    	return ret;
    }

    2.1 插入转发表项 fdb_insert()

    1.首先判断mac地址是否有效
    2.从fdb表中查找是否已经存在该mac地址相关的表项,
       a) 若存在,则判断该fdb entry是否是本地的
           i)若是本地的,则直接返回
          ii)若不是本地的,则调用fdb_delete删除该fdb entry,然后再重新创建fdb entry
        b)若不存在,则调用fdb_create,创建fdb entry表项,并插入到相应的hash表中
    3.将该转发表项fdb entry 属性设置成 本地+静态。
    4.br_fdb_insert() 主要是用于在网桥中添加端口或vlan时调用。

    static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
    		  const unsigned char *addr, u16 vid)
    {
    	struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
    	struct net_bridge_fdb_entry *fdb;
    
    	if (!is_valid_ether_addr(addr))
    		return -EINVAL;
    
    	fdb = fdb_find(head, addr, vid);
    	if (fdb) {
    		/* it is okay to have multiple ports with same
    		 * address, just use the first one.
    		 */
    		if (fdb->is_local)
    			return 0;
    		br_warn(br, "adding interface %s with same address "
    		       "as a received packet\n",
    		       source ? source->dev->name : br->dev->name);
    		fdb_delete(br, fdb);
    	}
    
    	fdb = fdb_create(head, source, addr, vid);
    	if (!fdb)
    		return -ENOMEM;
    
    	fdb->is_local = fdb->is_static = 1;
    	fdb_notify(br, fdb, RTM_NEWNEIGH);
    	return 0;
    }

    3 创建转发表项 fdb_create()

    该函数的逻辑也比较简单

    1. 首先调用 kmem_cache_alloc 为该 fdb entry申请缓存
    2. 然后将该fdb 添加到 hash 表头 head 中
    3. 最后设置fdb entry的dst port等其他成员并返回fdb entry
    static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,
    					       struct net_bridge_port *source,
    					       const unsigned char *addr,
    					       __u16 vid)
    {
    	struct net_bridge_fdb_entry *fdb;
    
    	fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
    	if (fdb) {
    		memcpy(fdb->addr.addr, addr, ETH_ALEN);
    		fdb->dst = source;
    		fdb->vlan_id = vid;
    		fdb->is_local = 0;
    		fdb->is_static = 0;
    		fdb->updated = fdb->used = jiffies;
    		hlist_add_head_rcu(&fdb->hlist, head);
    	}
    	return fdb;
    }

    4 查找转发表项 fdb_find() / __br_fdb_get()

    fdb_find() 函数的作用是从转发数据库中查找fdb entry,该函数与__br_fdb_get的作用类似,唯一不同的是,该函数没有判断查找到的fdb entry是否过期,该函数只是用来判断fdb数据库中是否存在与指定mac地址相关联的fdb entry,其主要是被fdb_insert、br_fdb_update调用,用来确定是否需要创建新的fdb entry。

    那如果当网桥接收到数据后,需要知道是否对数据包进行扩散或者转发到指定网桥端口,其是调用哪个函数获取fdb entry呢?其是通过函数 __br_fdb_get() 来实现的。下面是该函数的实现代码,我们发现与fdb_find相比,其会判断fdb entry是否过期,对于数据转发来说,首先就需要判断 fdb entry的有效性。

    下面是 __br_fdb_get() 函数的分析:
    1.首先根据br_mc_hash,生成hash值,从而找到相应的hash表
    2.调用hlist_for_each_entry_rcu,遍历hash表中的每一个fdb entry,并进行如下判断:
       a)判断mac地址是否相同:
         i)若mac地址相同,则判断该fdb entry是否过期,若过期则返回NULL;
         ii)若不相同则继续遍历

    static struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head,
    					     const unsigned char *addr,
    					     __u16 vid)
    {
    	struct net_bridge_fdb_entry *fdb;
    
    	hlist_for_each_entry(fdb, head, hlist) {
    		if (ether_addr_equal(fdb->addr.addr, addr) &&
    		    fdb->vlan_id == vid)
    			return fdb;
    	}
    	return NULL;
    }
    
    /* No locking or refcounting, assumes caller has rcu_read_lock */
    struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br,
    					  const unsigned char *addr,
    					  __u16 vid)
    {
    	struct net_bridge_fdb_entry *fdb;
    
    	hlist_for_each_entry_rcu(fdb,
    				&br->hash[br_mac_hash(addr, vid)], hlist) {
    		if (ether_addr_equal(fdb->addr.addr, addr) &&
    		    fdb->vlan_id == vid) {
    			if (unlikely(has_expired(br, fdb)))
    				break;
    			return fdb;
    		}
    	}
    
    	return NULL;
    }

    5 转发表项更行 br_fdb_update()

    该函数主要是由入口数据包处理函数br_handle_frame处理入口流量时,对于源mac地址进行学习时,就会调用fdb_update更新fdb数据库,下面对该函数进行分析,该函数执行的功能大致如下:
    1.如果br网桥想要一直处于flood状态,则不对mac地址进行学习
    2.如果网桥的状态不是处于learning或者forward状态则直接返回
    3.调用fdb_find,查找该mac地址是否已经在fdb表中?
        在:如果该 fdb entry 为 local 则不更新,非local 更新转发表项出口以及时间戳。
        不在:则调用 fdb_create,创建一个非本地的表项

    void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
    		   const unsigned char *addr, u16 vid)
    {
    	struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
    	struct net_bridge_fdb_entry *fdb;
    
    	/* some users want to always flood. */
    	if (hold_time(br) == 0)
    		return;
    
    	/* ignore packets unless we are using this port */
    	if (!(source->state == BR_STATE_LEARNING ||
    	      source->state == BR_STATE_FORWARDING))
    		return;
    
    	fdb = fdb_find_rcu(head, addr, vid);
    	if (likely(fdb)) {
    		/* attempt to update an entry for a local interface */
    		if (unlikely(fdb->is_local)) {
    			if (net_ratelimit())
    				br_warn(br, "received packet on %s with "
    					"own address as source address\n",
    					source->dev->name);
    		} else {
    			/* fastpath: update of existing entry */
    			fdb->dst = source;
    			fdb->updated = jiffies;
    		}
    	} else {
    		spin_lock(&br->hash_lock);
    		if (likely(!fdb_find(head, addr, vid))) {
    			fdb = fdb_create(head, source, addr, vid);
    			if (fdb)
    				fdb_notify(br, fdb, RTM_NEWNEIGH);
    		}
    		/* else  we lose race and someone else inserts
    		 * it first, don't bother updating
    		 */
    		spin_unlock(&br->hash_lock);
    	}
    }

    5.1 同一个网桥端口可以与多个fdb entry关联吗?

    通过对fdb_insert、fdb_find这些函数的理解我们知道,调用调用fdb_find,查找fdb数据库中是否存在某个表项时,是根据mac地址的,而并不是根据网桥端口。这从侧面说明在一个转发数据库中,网桥端口并不仅仅与一个fdbentry关联。

    又通过分析br_fdb_delete_by_port,我们发现,在以网桥端口为依据删除与网桥端口相关联的fdb entry时,在发现一个符合条件的fdb entry后,函数并不是释放该fdb entry后就直接返回了,而是继续循环并查找是否有继续符合条件的fdbentry。这就强有力的证明了一个网桥端口可以与多个fdb entry关联。

    结论:网桥是一个端口对应多个mac转发地址,这一点和交换机不同,交换机是一个端口对应一个mac地址,具体参见《集线器、交换机、网桥区别》

    5.2 转发表项添加分类

    对于函数 br_fdb_updat e来说,提供的有效信息并不是简单的更新fdb entry那么简单。首先对于fdb entry建立的分类,我们分析一下:

    5.2.1 local标签 fdb entry添加

    我们知道,在添加网桥端口时,我们会调用br_fdb_insert创建一个local的fdb entry。

    5.2.2 非local标签的fdb entry添加

    这类fdb entry是通过网桥的mac地址学习得到的。是由处理入口流量的 br_handle_frame 间接调用生成的。然后,我们再回到br_fdb_update,在该函数里,若发现要update的mac地址所对应的fdb entry已经存在,函数还会判断这个fdb entry是否是local的。若是local的,说明什么了呢? 说明br_handle_frame处理的入口数据包的mac地址是属于网桥端口的,这就说明了该网桥下的桥接端口出现了环路。这就是该函数的另一大功能,通过该函数我们能判断网桥下的端口是否环路了。

    6 网桥端口变更mac地址更新转发表 br_fdb_changeaddr

    当一个桥端口的mac地址改变以后,会调用该函数更新fdb表,并且其只更新local类型的fdb entry,逻辑如下:
    1.调用hlist_for_each遍历br中hash数组中的每一个hash表,查找符合条件的 fdb_entry,条件:转发表项出口为本端口,且为本地转发表项。
    2.若找到符合条件的fdb entry,则需要遍历bridge_port_list,  判断是否有别的端口的mac地址与该表项中的 mac 地址相等? 
      若相等,则更新该表项的dst值为该桥接端口,并调用 fdb_insert,为已更改 mac 地址的桥端口,重新加入一个 fdb 表项;
      若没有相等的桥端口,则将找到的 fdb_entry 从 hash 表中删除并调用 fdb_insert 将更改后的值重新添加到 hash 表中。
    这里加一点自己的理解:
    a.本地网桥端口的mac地址发生变更?除非是创建的虚拟网卡。
    b.转发表项出口指向变更端口,又是本地类型的转发表项,说明转发表项的mac地址就是变更端口的mac地址。如果还存在其它网桥端口的地址和该地址相同,说明都是虚拟网卡设备。这里可能是二层收包负荷分担设备。

    void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
    {
    	struct net_bridge *br = p->br;
    	bool no_vlan = (nbp_get_vlan_info(p) == NULL) ? true : false;
    	int i;
    
    	spin_lock_bh(&br->hash_lock);
    
    	/* Search all chains since old address/hash is unknown */
    	for (i = 0; i < BR_HASH_SIZE; i++) {
    		struct hlist_node *h;
    		hlist_for_each(h, &br->hash[i]) {
    			struct net_bridge_fdb_entry *f;
    
    			f = hlist_entry(h, struct net_bridge_fdb_entry, hlist);
    			if (f->dst == p && f->is_local) {
    				/* maybe another port has same hw addr? */
    				struct net_bridge_port *op;
    				u16 vid = f->vlan_id;
    				list_for_each_entry(op, &br->port_list, list) {
    					if (op != p &&
    					    ether_addr_equal(op->dev->dev_addr,
    							     f->addr.addr) &&
    					    nbp_vlan_find(op, vid)) {
    						f->dst = op;
    						goto insert;
    					}
    				}
    
    				/* delete old one */
    				fdb_delete(br, f);
    insert:
    				/* insert new address,  may fail if invalid
    				 * address or dup.
    				 */
    				fdb_insert(br, p, newaddr, vid);
    
    				/* if this port has no vlan information
    				 * configured, we can safely be done at
    				 * this point.
    				 */
    				if (no_vlan)
    					goto done;
    			}
    		}
    	}
    
    done:
    	spin_unlock_bh(&br->hash_lock);
    }

    7 转发表项超时清理 br_fdb_cleanup

    该功能主要是由函数br_fdb_cleanup来实现,该函数是br->gc_timer的定时器处理函数。

    br_fdb_cleanup 用于桥周期性检查是否有转发数据表项过期的处理函数在网桥初始化时,当调用br_stp_timer_init进行定时器初始化化时,就会建立br->gc_timer的定时器处理函数,定时调用br_fdb_cleanup,清理CAM表缓存。

    void br_stp_timer_init(struct net_bridge *br)
    {
        ...
    	setup_timer(&br->gc_timer, br_fdb_cleanup, (unsigned long) br);
    }

    下面分析该函数的执行流程:

    1. 调用 spin_lock_bh 进行上锁操作
    2. 循环br网桥的所有hash表(hash表的大小为BR_HASH_SIZE 目前大小为256)
    3. 对于每一个hash表,调用hlist_for_each_entry_safe遍历该hash表里的所有hash表项。(因为遍历的同时会删除指针,所以此处会调用函数 hlist_for_each_entry_safe)  调用time_before_eq判断该fdb是否过期,若过期,则调用fdb_delete进行删除操作。                                                                                       
    4. 更新桥检查定时器 
    void br_fdb_cleanup(unsigned long _data)
    {
    	struct net_bridge *br = (struct net_bridge *)_data;
    	unsigned long delay = hold_time(br);
    	unsigned long next_timer = jiffies + br->ageing_time;
    	int i;
    
    	spin_lock(&br->hash_lock);
    	for (i = 0; i < BR_HASH_SIZE; i++) {
    		struct net_bridge_fdb_entry *f;
    		struct hlist_node *n;
    
    		hlist_for_each_entry_safe(f, n, &br->hash[i], hlist) {
    			unsigned long this_timer;
    			if (f->is_static)
    				continue;
    			this_timer = f->updated + delay;
    			if (time_before_eq(this_timer, jiffies))
    				fdb_delete(br, f);
    			else if (time_before(this_timer, next_timer))
    				next_timer = this_timer;
    		}
    	}
    	spin_unlock(&br->hash_lock);
    
    	mod_timer(&br->gc_timer, round_jiffies_up(next_timer));
    }

    8 转发表项删除 

    8.1 单个表项删除 fdb_delete()

    static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
    {
    	hlist_del_rcu(&f->hlist);
    	fdb_notify(br, f, RTM_DELNEIGH);
    	call_rcu(&f->rcu, fdb_rcu_free);
    }
    
    static void fdb_rcu_free(struct rcu_head *head)
    {
    	struct net_bridge_fdb_entry *ent
    		= container_of(head, struct net_bridge_fdb_entry, rcu);
    	kmem_cache_free(br_fdb_cache, ent);
    }
    

    8.2 批量删除所有非静态表项 br_fdb_flush()

    遍历每一个hash表中的所有转发数据表项,对于每一个转发数据表项,若该转发数据表项不是静态的,则调用 fdb_delete(),释放该表项所占用的缓存。

    /* Completely flush all dynamic entries in forwarding database.*/
    void br_fdb_flush(struct net_bridge *br)
    {
    	int i;
    
    	spin_lock_bh(&br->hash_lock);
    	for (i = 0; i < BR_HASH_SIZE; i++) {
    		struct net_bridge_fdb_entry *f;
    		struct hlist_node *n;
    		hlist_for_each_entry_safe(f, n, &br->hash[i], hlist) {
    			if (!f->is_static)
    				fdb_delete(br, f);
    		}
    	}
    	spin_unlock_bh(&br->hash_lock);
    }

     

     

     

     

    展开全文
  • 标签:Linux IPv4代码分析 原文出处:http://blog.chinaunix.net/uid-23392781-id-2426603.html    在(一)中分析了inet_init的初始化部分,但在这之前还是有一部分的初始化需要说明一下,这个就是/...
    
    

    原文出处:http://blog.chinaunix.net/uid-23392781-id-2426603.html

     

     在(一)中分析了inet_init的初始化部分,但在这之前还是有一部分的初始化需要说明一下,这个就是/net/socket.c/sock_init()。它的初始化在inet_init之前,详解对应函数的定义。

     

    core_initcall(sock_init);/* early initcall */
    fs_initcall(inet_init);
    #define core_initcall(fn) __define_initcall("1",fn,1)
    #define fs_initcall(fn) __define_initcall("5",fn,5)

     

    下面简单分析一下sock_init

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

    sk_init();
    /*
    * Initialize skbuff SLAB cache
    */

    skb_init();
    //这个函数的初始化比较有意思,因为它的内部申请了两个内存头节点。为了说明这个用处,给出一个链接,一句话还是不能说清楚的,内存管理的确比较复杂。
    https:
    //www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/


    /*
    * Initialize the protocols module.
    */

    init_inodecache();//同上
    register_filesystem(&sock_fs_type);
    sock_mnt = kern_mount(&sock_fs_type);
    //这里添加了socket文件系统的支持,可以cat /proc/filesystems查看当前支持多少种文件系统,其中的”nodev sockfs”就是要注册的文件系统。注册socket文件系统的好处就是可以像操作文件一样进行发送接收数据。看看这个文件:
    srw-rw---- 1 root admin 0 Apr 20 03:46 networkd_uds

    /* The real protocol initialization is performed in later initcalls.
    */


    //下面这个功能就是大名鼎鼎的netfilter了,虽然是可选的组件,但现在的内核默认都是编译到内核中了,见arch/xx/xx_defconfig文件
    CONFIG_NETFILTER=y
    Netfilter功能强大, 支持L2,L3,L4层的包过滤,数据包修改,NAT,转发策略,拦截策略。另外可以自己写module实现很多其他的功能,比如xt_limit module能够实现简单的流量控制等功能。好东西啊。

    #ifdef CONFIG_NETFILTER
    netfilter_init();
    #endif
    return 0;
    }


    /net/socket.c这个文件结构很清晰,也比较好分析,都说socket编程,多少也应该了解一下实现吧。
    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 
    首先SYSCALL_DEFINE3这个宏定义了socket系统调用的实现。而EXPORT_SYMBOL修饰的函数是供内核其他模块使用的。

    SYSCALL_DEFINE3通过跟踪不难发现最终定义了sys_socket系统调用,并加入系统调用数组sys_call_table中,linux的宏定义还是比较烦的,估计玩过连连看的兄弟应该能很快的找到定义:)
    a)#define SYSCALL_DEFINE3(name,...) SYSCALL_DEFINEx(3, _##name,__VA_ARGS__)
    b)#define SYSCALL_DEFINEx(x, sname,...) \
    __SYSCALL_DEFINEx(x, sname,__VA_ARGS__)
    c)asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
    再看include/asm-generic/unistd.h
    #define __NR_socket 198
    __SYSCALL(__NR_socket, sys_socket)

    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

    SYSCALL_DEFINE3(socket,int, family,int, type,int, protocol)
    {
    int retval;
    structsocket*sock;
    int flags;

    /* Check the SOCK_* constants for consistency. */
    BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
    BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK)!= SOCK_TYPE_MASK);
    BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
    BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);

    flags = type &~SOCK_TYPE_MASK;
    if(flags &~(SOCK_CLOEXEC | SOCK_NONBLOCK))
    return-EINVAL;
    type &= SOCK_TYPE_MASK;

    if(SOCK_NONBLOCK != O_NONBLOCK &&(flags & SOCK_NONBLOCK))
    flags =(flags &~SOCK_NONBLOCK)| O_NONBLOCK;

    //sock_create->sock_alloc->new_inode 通过调用关系可以看出,这个函数的本质是申请一个inode节点;同时还需要注意的是,在__sock_create函数中调用了函数指针int (*create)(struct net *net, struct socket *sock, int protocol),用于各类型socket初始化及函数指针挂接。
    retval = sock_create(family, type, protocol,&sock)


    if(retval < 0)
    goto out;

    retval = sock_map_fd(sock, flags &(O_CLOEXEC | O_NONBLOCK)); //在当前进程中找到一个空闲的fd,把fd和inode关联起来。
    if(retval < 0)
    goto out_release;

    out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;

    out_release:
    sock_release(sock);
    return retval;
    }

    至于sys_bind, sys_listen, sys_connect, sys_send, sys_recv等相关函数这里并没有具体实现,因为socket类型有多种,这里基于可扩展性考虑,根据socket传入参数来定位socket类型,进而调用对应的函数指针。
    sys_accept在这里的实现类似sys_socket。
    socket类型的注册和卸载见这两个函数,
    int sock_register(const struct net_proto_family *ops)
    void sock_unregister(int family)
    另外还有一些供kernel调用的函数见kernel_****,开头的函数。
    由于目前只考虑ipv4的实现,那么在net/ipv4/下,调用sock_register的地方是在(一)中分析的inet_init函数里调用的。那么我们在用户进程中调用socket函数时,kernel调用了/net/ipv4/af_inet.c/inet_create函数,看看inet_create都干了什么。

    /*
    * Create an inet socket.
    */


    staticint inet_create(struct net *net,structsocket*sock,int protocol)
    {
    struct sock *sk;
    struct inet_protosw *answer;
    struct inet_sock *inet;
    struct proto *answer_prot;
    unsignedchar answer_flags;
    char answer_no_check;
    int try_loading_module = 0;
    int err;

    if(unlikely(!inet_ehash_secret))
    if(sock->type !=SOCK_RAW&& sock->type !=SOCK_DGRAM)
    build_ehash_secret();

    sock->state = SS_UNCONNECTED;//设置socket状态

    /* Look for the requested type/protocol pair. */
    lookup_protocol:
    err =-ESOCKTNOSUPPORT;
    rcu_read_lock();

    //下面这部分片段定位到inet的socket子类型,TCP/UDP/IP
    list_for_each_entry_rcu(answer,&inetsw[sock->type],list){

    err = 0;
    /* Check the non-wild match. */
    if(protocol == answer->protocol){
    if(protocol !=IPPROTO_IP)
    break;
    }else{
    /* Check for the two wild cases. */
    if(IPPROTO_IP== protocol){
    protocol = answer->protocol;
    break;
    }
    if(IPPROTO_IP== answer->protocol)
    break;
    }
    err =-EPROTONOSUPPORT;
    }

    if(unlikely(err)){//这里面有一个自动加载的功能,如果功能模块在编译时指定为独立模块,kernel会按照下面的命名规则找到对应模块,并加载到内核当中。
    if(try_loading_module < 2){
    rcu_read_unlock();
    /*
    * Be more specific, e.g. net-pf-2-proto-132-type-1
    * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
    */

    if(++try_loading_module == 1)
    request_module("net-pf-%d-proto-%d-type-%d",
    PF_INET, protocol, sock->type);
    /*
    * Fall back to generic, e.g. net-pf-2-proto-132
    * (net-pf-PF_INET-proto-IPPROTO_SCTP)
    */

    else
    request_module("net-pf-%d-proto-%d",
    PF_INET, protocol);
    goto lookup_protocol;
    }else
    goto out_rcu_unlock;
    }

    err =-EPERM;
    if(answer->capability > 0 &&!capable(answer->capability))
    goto out_rcu_unlock;

    err =-EAFNOSUPPORT;
    if(!inet_netns_ok(net, protocol))
    goto out_rcu_unlock;

    sock->ops = answer->ops; //这里挂接的函数指针。
    answer_prot = answer->prot;
    answer_no_check = answer->no_check;
    answer_flags = answer->flags;
    rcu_read_unlock();

    WARN_ON(answer_prot->slab ==NULL);

    err =-ENOBUFS;

    //为sock结构分配内存,并初始化里面的各字段。每个字段的含义没有必要都要搞清楚
    sk = sk_alloc(net,PF_INET, GFP_KERNEL, answer_prot);
    if(sk ==NULL)
    goto out;

    err = 0;
    sk->sk_no_check = answer_no_check;
    if(INET_PROTOSW_REUSE & answer_flags)
    sk->sk_reuse = 1;

    inet = inet_sk(sk);
    inet->is_icsk =(INET_PROTOSW_ICSK & answer_flags)!= 0;

    if(SOCK_RAW== sock->type){
    inet->num = protocol;
    if(IPPROTO_RAW== protocol)
    inet->hdrincl = 1;
    }

    if(ipv4_config.no_pmtu_disc)
    inet->pmtudisc = IP_PMTUDISC_DONT;
    else
    inet->pmtudisc = IP_PMTUDISC_WANT;

    inet->id = 0;

    sock_init_data(sock, sk);

    sk->sk_destruct = inet_sock_destruct;
    sk->sk_protocol = protocol;
    sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;

    inet->uc_ttl =-1;
    inet->mc_loop = 1;
    inet->mc_ttl = 1;
    inet->mc_all = 1;
    inet->mc_index = 0;
    inet->mc_list =NULL;

    sk_refcnt_debug_inc(sk);

    if(inet->num){
    /* It assumes that any protocol which allows
    * the user to assign a number at socket
    * creation time automatically
    * shares.
    */

    inet->sport =htons(inet->num);
    /* Add to protocol hash chains. */
    sk->sk_prot->hash(sk);
    }

    if(sk->sk_prot->init){
    err = sk->sk_prot->init(sk);
    if(err)
    sk_common_release(sk);
    }
    out:
    return err;
    out_rcu_unlock:
    rcu_read_unlock();
    goto out;
    }

    展开全文
  • Linux IPv4代码分析系列(1)

    千次阅读 2013-09-30 13:46:41
    标签:IPv4 kernel ... ...最近一直在看linux...之前对linux/net目录下的代码一直没有头绪,不知从何处下手。现在稍微理了一下,觉得有必要做个记录。看代码之前先理清思路是必要的,linux代码一坨坨,如果没有
    标签:IPv4 kernel

    原文出处:http://blog.chinaunix.net/uid-23392781-id-2426602.html

     

    最近一直在看linux(2.6.32)ipv4部分的代码。之前对linux/net目录下的代码一直没有头绪,不知从何处下手。现在稍微理了一下,觉得有必要做个记录。看代码之前先理清思路是必要的,linux的代码一坨坨,如果没有漫无目的的看,估计就像看天书一样了。我的目的很明确,就是先了解ipv4,其他所有和其相关的模块能忽略就忽略,比较关键的支撑模块也没有必要一定要看他的实现,从其他方面途径(google or anything else)了解用途即可。代码写了一堆往往是为了实现一个简单的事情,但需要考虑的异常处理有很多,所以代码就变得复杂。 

    之前对tcp/ip只有理论上的认识,所以要想更深入的了解网络部分代码的原委,也只好捏着鼻子看源代码了,看代码是痛苦的,但看懂以后是愉快的,嘿嘿。
    对于一个模块,我想还是先从init开始吧,任何模块都应该从这里开始,i think。
    af_inet.c/inet_init()
    这是一个静态函数,linux的初始化的确搞的与众不同,他已经把代码的灵活性、扩展性发挥的淋漓尽致,你找这个函数的调用关系,根本没有!
    其实有关初始化的猫腻都藏在/include/linux/init.h这个文件里面,如果分析这个文件,可能有点离题,网上应该有不少相关的分析,我才懒得在写,这不符合我看代码的习惯,大概简单一句话的说就是把xx_initcall修饰的函数放入特殊的代码段中(init text segment?),有8个优先级,便于初始化顺序关系的处理,然后在/init/main.c/do_initcalls()中调用。这是预编译指令,编译器,链接器共同协作的结果。有兴趣的可以自己研究。正好我之前调试kernel的时候把这部分的调试开关打开了,我顺便贴在后面,便于查阅。
    fs_initcall(inet_init);
    __init指示编译器(gcc)把这个函数发在特殊的代码段里,
    static int __init inet_init(void)
    {
    struct sk_buff *dummy_skb;
    struct inet_protosw *q;
    struct list_head *r;
    int rc = -EINVAL;
    BUILD_BUG_ON(sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb));
    //proto_register这个函数实现了list_add的功能,不信自己在里面找找。所以这个函数也是可以一句话说清楚的。它的表头是proto_list,真正用到它的,就是遍历他的地方,自己找吧:) 看看/proc/net/protocols就明白了
    rc = proto_register(&tcp_prot, 1); //tcp的
    if (rc)
    goto out;
    rc = proto_register(&udp_prot, 1);//udp的
    if (rc)
    goto out_unregister_tcp_proto;
    rc = proto_register(&raw_prot, 1);//raw的,相信大部分人都用过这个吧
    if (rc)
    goto out_unregister_udp_proto;
    /*
    * Tell SOCKET that we are alive...
    */
    (void)sock_register(&inet_family_ops);//注册一个socket类型,要注意这个实现可不是链表,是一个数组,因为这是一个可确定的东西,有固定的长度,还记得我们在写c程序时的socket()吧,其中第一个参数一般都会写AF_INET,我们在这个文件中注册的就是这个函数了,如果没有注册这个socket,kernel可不鸟你:),你调用了socket(),通过系统调用(x86 cpu 为int xx指令),到了kernel就变成了sock_create函数了(这个过程写起来也够一遍小文,这里就不费劲了),它根据你传递的第一个参数找到了应该调用那个socket组下的函数指针,传错了可不行,你不能想用INET的功能,却传入PF_NETLINK。扯得有点多,继续往下。
    #ifdef CONFIG_SYSCTL //参考/proc/sys/net/ipv4
    ip_static_sysctl_init();
    #endif
    下面这部分相当有用,实现了tcp/ip的报文接收处理的挂接,inet_add_protocol这个函数实现的是数组的赋值,ps:请原谅我说的这么直白,说白了干的就是这个活。到里面看一下这个函数干的活实在是太简单了,一句话就是一个等号完事。简单归简单,接收这个值的全局变量就不简单了,这个全局变量inet_protos被多个地方使用,其中最重要的地方应该是ip_local_deliver_finish用的了,他通过这个变量实现了ip层报文向上层传递的功能(如果我不手懒,能坚持下去的话,会分析这个函数滴)。你是tcp报文,那么调用tcp_protocol.tcp_v4_rcv实现向上传递,如果是udp的报文,则同理。至于调用哪个,相信了解ip头的人都很清楚了。
    有对VPN感兴趣的没,看看这个
    Tunnel4.c (net\ipv4): if (inet_add_protocol(&tunnel4_protocol, IPPROTO_IPIP)) :)

    /*
    * Add all the base protocols.
    */

    if(inet_add_protocol(&icmp_protocol,IPPROTO_ICMP)< 0
    printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
    if(inet_add_protocol(&udp_protocol,IPPROTO_UDP)< 0)
    printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
    if(inet_add_protocol(&tcp_protocol,IPPROTO_TCP)< 0)
    printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
    #ifdef CONFIG_IP_MULTICAST
    if(inet_add_protocol(&igmp_protocol,IPPROTO_IGMP)< 0)
    printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");
    #endif


    /* Register the socket-side information for inet_create.*/
    for(= &inetsw[0]; r < &inetsw[SOCK_MAX];++r)
    INIT_LIST_HEAD(r);//先初始化链表
    for(= inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN];++q)
    inet_register_protosw(q);//在初始化实体,这里面挂接了socket编程中用到的函数指针和对应协议中用到的函数指针。应该是对sock_register的序。


    /*
    * Set the ARP module up
    */
    arp_init();

    void __init arp_init(void) //IPV4的实现离不开arp,这里是对他的初始化
    {
    neigh_table_init(&arp_tbl);//这个里面做了很多事情,初始化neigh_table结构,挂接函数指针和设置arp需要的参数。neighbour是一个复杂的系统,估计又有一篇小文了。。

    dev_add_pack(&arp_packet_type);//添加arp报文的处理,这里不同于inet_add_protocol,因为不是同一层的协议。这个是注册二层报文解析的handle,而inet_add_protocol是注册对三层报文解析的handle。如果你想自己独创一套三层报文协议,那么就调用dev_add_pack添加你的实现就可以了。呵呵,有难度哦。


    arp_proc_init(); //参考/proc/net/arp


    #ifdef CONFIG_SYSCTL //参考/proc/sys/net/ipv4/...
    neigh_sysctl_register(NULL,&arp_tbl.parms, NET_IPV4,
    NET_IPV4_NEIGH,"ipv4",NULL,NULL);
    #endif
    register_netdevice_notifier(&arp_netdev_notifier); //注册对设备状态变化的处理,arp协议只处理了ip地址变化的事件NETDEV_CHANGEADDR,他的处理也很简单,清空对应ip地址的接口的arp表项
    }



    /*
    * Set the IP module up
    */
    ip_init();

    void __init ip_init(void)
    {
    ip_rt_init();//由于接收,发送的部分只要挂接指针就可以了,这里面的初始化涉及路由系统的初始化,初始化路由表项以及想proc文件系统注册文件等。
    inet_initpeers();//

    #if defined(CONFIG_IP_MULTICAST)&& defined(CONFIG_PROC_FS)
    igmp_mc_proc_init();//参考/proc/net/igmp
    #endif
    }


    tcp_v4_init(); 
    /* Setup TCP slab cache for open requests. */
    tcp_init(); //初始化TCP协议用到的链表,设置tcp的默认参数
    /* Setup UDP memory threshold */
    udp_init(); //初始化TCP协议用到的链表,设置udp的默认参数
    /* Add UDP-Lite (RFC 3828) */
    udplite4_register(); //实现轻量级UDP协议,不同于普通的UDP协议。
    /*
    * Set the ICMP layer up
    */
    if (icmp_init() < 0) //内核创建raw socket用于处理icmp报文。
    panic("Failed to create the ICMP control socket.\n");
    /*
    * Initialise the multicast router
    */
    #if defined(CONFIG_IP_MROUTE)
    if (ip_mr_init())
    printk(KERN_CRIT "inet_init: Cannot init ipv4 mroute\n");
    #endif
    /*
    * Initialise per-cpu ipv4 mibs
    */
    if (init_ipv4_mibs()) //MIB支持?懒得看
    printk(KERN_CRIT "inet_init: Cannot init ipv4 mibs\n");
    ipv4_proc_init(); // proc/net下一些相关文件
    ipfrag_init(); //ip分片的默认参数,及proc接口。/pros/sys/net/ipfrag_*
    dev_add_pack(&ip_packet_type); //这里添加了对二层数据解析的支持,同arp
    rc = 0;
    out:
    return rc;
    out_unregister_udp_proto:
    proto_unregister(&udp_prot);
    out_unregister_tcp_proto:
    proto_unregister(&tcp_prot);
    goto out;
    }
    由此可见,inet的初始化并不简单,但总体来说依赖外部模块的部分还不多,但内部还是错综复杂的。根据这个初始化的线索向下分析思路会清晰的多。根据初始化的流程,后续的分析思路可以是这样:
    socket编程接口实现 sock_register(&inet_family_ops);
    icmp报文接收/发送处理 inet_add_protocol(&icmp_protocol
    udp报文接收/发送处理 inet_add_protocol(&udp_protocol
    tcp报文接收/发送处理 inet_add_protocol(&tcp_protocol
    arp的实现 arp_init
    路由系统实现 ip_init
    tcp的实现 这个复杂的很,看代码都能看吐了
    第一次写blog,抛砖引玉。每一个课题都不小,不过不急,时间还长,嘿嘿。待续>>>
    ==================================================================================
    附一: init_call 调用顺序,不同的kernel配置结果会不同。
    Calling initcall 0xc000ec50: ptrace_break_init+0x0/0x2c()
    Calling initcall 0xc000fb84: consistent_init+0x0/0xd0()
    Calling initcall 0xc00122fc: sysctl_init+0x0/0x24()
    Calling initcall 0xc0012c2c: helper_init+0x0/0x40()
    Calling initcall 0xc0012f00: init_jiffies_clocksource+0x0/0x1c()
    Calling initcall 0xc0013010: pm_init+0x0/0x40()
    Calling initcall 0xc00130b0: ksysfs_init+0x0/0x40()
    Calling initcall 0xc00150ac: filelock_init+0x0/0x54()
    Calling initcall 0xc0015cc0: init_script_binfmt+0x0/0x1c()
    Calling initcall 0xc0015cdc: init_elf_binfmt+0x0/0x1c()
    Calling initcall 0xc001720c: random32_init+0x0/0x2c()
    Calling initcall 0xc001aed4: sock_init+0x0/0x88()
    Calling initcall 0xc001b7c4: netlink_proto_init+0x0/0x174()
    Calling initcall 0xc00170ec: kobject_uevent_init+0x0/0x54()
    Calling initcall 0xc00172c0: pcibus_class_init+0x0/0x1c()
    Calling initcall 0xc00178c4: pci_driver_init+0x0/0x1c()
    Calling initcall 0xc0017dd4: tty_class_init+0x0/0x3c()
    Calling initcall 0xc00187d4: vtconsole_class_init+0x0/0xdc()
    Calling initcall 0xc000eea8: customize_machine+0x0/0x2c()
    Calling initcall 0xc000eed4: topology_init+0x0/0x30()
    Calling initcall 0xc0011368: ixdp425_pci_init+0x0/0x38()
    Calling initcall 0xc001140c: wgrdv2_pci_init+0x0/0x20()
    Calling initcall 0xc00129d4: param_sysfs_init+0x0/0x194()
    Calling initcall 0xc005baf8: pm_sysrq_init+0x0/0x24()
    Calling initcall 0xc00158dc: init_bio+0x0/0x148()
    Calling initcall 0xc0016f58: genhd_device_init+0x0/0x64()
    Calling initcall 0xc00181d8: misc_init+0x0/0x9c()
    Calling initcall 0xc0019d90: phy_init+0x0/0x38()
    Calling initcall 0xc001a0c8: usb_init+0x0/0x11c()
    Calling initcall 0xc001a830: serio_init+0x0/0x98()
    Calling initcall 0xc001a900: input_init+0x0/0x120()
    Calling initcall 0xc001abb0: rtc_init+0x0/0x58()
    Calling initcall 0xc001ac08: rtc_sysfs_init+0x0/0x1c()
    Calling initcall 0xc001ac24: rtc_proc_init+0x0/0x1c()
    Calling initcall 0xc001ac40: rtc_dev_init+0x0/0xc0()
    Calling initcall 0xc001ad20: i2c_init+0x0/0x4c()
    Calling initcall 0xc001ae2c: hwmon_init+0x0/0x50()
    Calling initcall 0xc001ae7c: leds_init+0x0/0x3c()
    Calling initcall 0xc001afd8: proto_init+0x0/0x48()
    Calling initcall 0xc001b178: net_dev_init+0x0/0x1e4()
    Calling initcall 0xc001b5dc: wireless_nlevent_init+0x0/0x2c()
    Calling initcall 0xc001b650: fib_rules_init+0x0/0x1c()
    Calling initcall 0xc001b66c: pktsched_init+0x0/0xb4()
    Calling initcall 0xc001b73c: tc_filter_init+0x0/0x44()
    Calling initcall 0xc001b780: tc_action_init+0x0/0x44()
    Calling initcall 0xc001b938: genl_init+0x0/0xc4()
    Calling initcall 0xc0010f20: alignment_init+0x0/0x94()
    Calling initcall 0xc0012ddc: clocksource_done_booting+0x0/0x24()
    Calling initcall 0xc001500c: init_pipe_fs+0x0/0x54()
    Calling initcall 0xc0017ad4: chr_dev_init+0x0/0x94()
    Calling initcall 0xc001c548: inet_init+0x0/0x39c()
    Calling initcall 0xc000e508: populate_rootfs+0x0/0xec()
    Calling initcall 0xc000f728: timer_init_sysfs+0x0/0x90()
    Calling initcall 0xc00115d4: fpe_init+0x0/0x84()
    Calling initcall 0xc0011dc8: create_proc_profile+0x0/0x74()
    Calling initcall 0xc00122a0: ioresources_init+0x0/0x5c()
    Calling initcall 0xc0012320: timekeeping_init_device+0x0/0x38()
    Calling initcall 0xc0012610: uid_cache_init+0x0/0xb0()
    Calling initcall 0xc0012b68: init_posix_timers+0x0/0xc4()
    Calling initcall 0xc0012c6c: init_posix_cpu_timers+0x0/0xe8()
    Calling initcall 0xc0012da0: latency_init+0x0/0x3c()
    Calling initcall 0xc0012e98: init_clocksource_sysfs+0x0/0x68()
    Calling initcall 0xc0012f1c: init_timer_list_procfs+0x0/0x40()
    Calling initcall 0xc0012f5c: init+0x0/0x7c()
    Calling initcall 0xc0012fd8: kallsyms_init+0x0/0x38()
    Calling initcall 0xc0013050: ikconfig_init+0x0/0x60()
    Calling initcall 0xc0013280: utsname_sysctl_init+0x0/0x20()
    Calling initcall 0xc0013d64: init_per_zone_pages_min+0x0/0x50()
    Calling initcall 0xc0014684: pdflush_init+0x0/0x28()
    Calling initcall 0xc00146e4: kswapd_init+0x0/0x34()
    Calling initcall 0xc001478c: procswaps_init+0x0/0x38()
    Calling initcall 0xc00147c4: init_tmpfs+0x0/0xec()
    Calling initcall 0xc00148d4: cpucache_init+0x0/0x1c()
    Calling initcall 0xc0015060: fasync_init+0x0/0x4c()
    Calling initcall 0xc00157d0: aio_setup+0x0/0x98()
    Calling initcall 0xc0015acc: inotify_setup+0x0/0x24()
    Calling initcall 0xc0015af0: inotify_user_setup+0x0/0xf0()
    Calling initcall 0xc0015be0: eventpoll_init+0x0/0xe0()
    Calling initcall 0xc0015cf8: init_mbcache+0x0/0x30()
    Calling initcall 0xc0015d28: dnotify_init+0x0/0x4c()
    Calling initcall 0xc0016234: init_devpts_fs+0x0/0x48()
    Calling initcall 0xc001627c: init_ext3_fs+0x0/0x90()
    Calling initcall 0xc0016404: journal_init+0x0/0xf8()
    Calling initcall 0xc00164fc: init_ext2_fs+0x0/0x80()
    Calling initcall 0xc0016598: init_ramfs_fs+0x0/0x1c()
    Calling initcall 0xc0016780: init_jffs2_fs+0x0/0xe4()
    Calling initcall 0xc0016910: ipc_init+0x0/0x20()
    Calling initcall 0xc0016bb0: ipc_sysctl_init+0x0/0x20()
    Calling initcall 0xc0016bd0: init_mqueue_fs+0x0/0xe4()
    Calling initcall 0xc0016cb4: crypto_algapi_init+0x0/0x18()
    Calling initcall 0xc0016d00: cryptomgr_init+0x0/0x1c()
    Calling initcall 0xc0016d1c: hmac_module_init+0x0/0x1c()
    Calling initcall 0xc0016d38: init+0x0/0x1c()
    Calling initcall 0xc0016d54: init+0x0/0x1c()
    Calling initcall 0xc0016d70: crypto_cbc_module_init+0x0/0x1c()
    Calling initcall 0xc0016d8c: init+0x0/0x44()
    Calling initcall 0xc0016fbc: noop_init+0x0/0x1c()
    Calling initcall 0xc0016fd8: as_init+0x0/0x1c()
    Calling initcall 0xc0016ff4: deadline_init+0x0/0x1c()
    Calling initcall 0xc0017010: cfq_init+0x0/0xdc()
    Calling initcall 0xc01164e4: pci_init+0x0/0x3c()
    Calling initcall 0xc00178e0: pci_sysfs_init+0x0/0x50()
    Calling initcall 0xc0017930: pci_proc_init+0x0/0x90()
    Calling initcall 0xc0017b84: rand_initialize+0x0/0x38()
    Calling initcall 0xc0017c00: tty_init+0x0/0x1d4()
    Calling initcall 0xc0017e10: pty_init+0x0/0x3c8()
    Calling initcall 0xc00188e0: pcf8594c_nvram_init+0x0/0x58()
    Calling initcall 0xc0018cf8: serial8250_init+0x0/0x11c()
    Calling initcall 0xc0018f18: serial8250_pci_init+0x0/0x28()
    Calling initcall 0xc00196e0: rd_init+0x0/0x1c8()
    Calling initcall 0xc00198d4: loop_init+0x0/0x290()
    Calling initcall 0xc0019b64: nbd_init+0x0/0x22c()
    Calling initcall 0xc0019e4c: net_olddevs_init+0x0/0xcc()
    Calling initcall 0xc0019f18: loopback_init+0x0/0x1c()
    Calling initcall 0xc0019f34: init_mtd+0x0/0x48()
    Calling initcall 0xc0019f7c: init_mtdchar+0x0/0x98()
    Calling initcall 0xc001a014: init_mtdblock+0x0/0x1c()
    Calling initcall 0xc001a030: cfi_probe_init+0x0/0x20()
    Calling initcall 0xc001a050: jedec_probe_init+0x0/0x20()
    Calling initcall 0xc001a070: ixp4xx_flash_init+0x0/0x1c()
    Calling initcall 0xc001a08c: nand_base_init+0x0/0x14()
    Calling initcall 0xc001a0a0: wg_nand_init+0x0/0x28()
    Calling initcall 0xc001a2d0: mon_init+0x0/0xb4()
    Calling initcall 0xc001a428: ehci_hcd_init+0x0/0x18c()
    Calling initcall 0xc001a5b4: usb_serial_init+0x0/0x214()
    Calling initcall 0xc001a7c8: pl2303_init+0x0/0x68()
    Calling initcall 0xc001a8c8: serport_init+0x0/0x38()
    Calling initcall 0xc001aa20: mousedev_init+0x0/0xec()
    Calling initcall 0xc001ab0c: atkbd_init+0x0/0x28()
    Calling initcall 0xc001ab34: psmouse_init+0x0/0x7c()
    Calling initcall 0xc001ad00: s35390a_rtc_init+0x0/0x20()
    Calling initcall 0xc001ad6c: i2c_dev_init+0x0/0xa4()
    Calling initcall 0xc001ae10: ixp4xx_i2c_init+0x0/0x1c()
    Calling initcall 0xc001aeb8: ixp4xxgpioled_init+0x0/0x1c()
    Calling initcall 0xc001b490: flow_cache_init+0x0/0x14c()
    Calling initcall 0xc001b720: blackhole_module_init+0x0/0x1c()
    Calling initcall 0xc001cbf8: init_syncookies+0x0/0x24()
    Calling initcall 0xc001cc1c: ah4_init+0x0/0x74()
    Calling initcall 0xc001cc90: esp4_init+0x0/0x74()
    Calling initcall 0xc001cd04: xfrm4_beet_init+0x0/0x20()
    Calling initcall 0xc001cd24: xfrm4_transport_init+0x0/0x20()
    Calling initcall 0xc001cd44: xfrm4_tunnel_init+0x0/0x20()
    Calling initcall 0xc01fba78: ipv4_netfilter_init+0x0/0x1c()
    Calling initcall 0xc001cd64: inet_diag_init+0x0/0x80()
    Calling initcall 0xc001cde4: tcp_diag_init+0x0/0x1c()
    Calling initcall 0xc001ce00: cubictcp_register+0x0/0x88()
    Calling initcall 0xc001d10c: xfrm_shim_init+0x0/0x108()
    Calling initcall 0xc001d214: xfrm_user_init+0x0/0xa4()
    Calling initcall 0xc001d2b8: af_unix_init+0x0/0x80()
    Calling initcall 0xc001d338: packet_init+0x0/0x70()
    Calling initcall 0xc000fad4: xscale_cp0_init+0x0/0xb0()
    Calling initcall 0xc0017238: random32_reseed+0x0/0x34()
    Calling initcall 0xc0017b68: seqgen_init+0x0/0x1c()
    Calling initcall 0xc0018ff0: early_uart_console_switch+0x0/0x98()
    Calling initcall 0xc001c27c: tcp_congestion_default+0x0/0x1c()
    展开全文
  • 1 linux 网桥功能 2 相关数据结构 2.1 网桥struct net_bridge 2.2 网桥端口struct net_bridge_port 2.3转发表项struct net_bridge_fdb_entry 2.4 嵌套关系 1 linux 网桥功能 《linux 网桥浅析》 2 相关数据...
  • 1.1 申请并初始化 net_device、net_bridge 1.1.1 初始化网桥br_dev_setup() 1.1.2 网桥操作函数集合 2网桥删除br_del_bridge() 3 网桥端口添加br_add_if() 3.1 网桥端口创建new_nbp() 4 网桥端口删除br_del_if...
  • Linux-kernel 网桥代码分析

    千次阅读 2014-11-06 13:55:37
    本文分析的kernel版本为:2.6.24.4,网桥代码目录为:linux-2.6.24.4/net/bridge。  网桥是kernel网络模块中相于独立的module,读者具有简单的kernel网络设备驱动开发和kerenl网络协议的基础知识即可。我在2007 年...
  • 一、linux内核网络栈代码的准备知识 1. linux内核ipv4网络部分分层结构: BSD socket层:这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构体现。这一部分 的文件主要有/...
  • 场景 Host上运行qemu kvm虚拟机...数据包进入虚拟机代码分析 首先看vhost-net模块注册,主要使用linux内核提供的内存注册机制,这部分开发过linux kernel的人都应该 很了解啦 1 2 3 4 5 6 ...
  • stmmac_open是在stmmac_netdev_ops结构体里的,这个ops在probe时就已经注册到了net_device结构体里,在网卡对于stmmac_open函数调用的时间我还不确定是否是在网卡link up时 下面看看stmmac_open函数,文件位置: ...
  • 一.linux内核网络栈代码的准备知识   1. linux内核ipv4网络部分分层结构:   BSD socket层: 这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构体现。这一部分的文件   主要有:/...
  • 主要分析一下驱动的主要框架,必要地方细致分析下 drivers/net/ethernet/stmicro/stmmac/stmmac_pci.c module_pci_driver(stmmac_pci_driver); stmmac_pci_driver结构体如下,里面包含了id_table、probe、remove。...
  • 一.linux内核网络栈代码的准备知识 1. linux内核ipv4网络部分分层结构: BSD socket层: 这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构体现。这一部分的文件 主要有:/...
  • http://blog.csdn.net/column/details/linux-kernel-net.html 转载于:https://www.cnblogs.com/zengkefu/p/6706375.html
  • linux_蓝牙驱动代码分析

    千次阅读 2013-08-10 09:41:01
    net/hci_core.c HCI 在主机端的驱动主要是为上层提供一个统一的接口,让上层协议不依赖于具体硬件的实现。HCI在硬件中的固件 与HCI在主机端的驱动通信方式有多种,比如像 UART、USB和PC Card等等。hci_core.c...
  • 本文分析的kernel版本为:2.6.24.4,网桥代码目录为:linux-2.6.24.4/net/bridge。  网桥是kernel网络模块中相于独立的module,读者具有简单的kernel网络设备驱动开发和kerenl网络协议的基础知识即可。我在2007年...
  • Linux内核packet_rcv代码分析

    千次阅读 2014-06-26 10:28:14
    static int packet_rcv(struct sk_buff *skb, struct net_device *dev,  struct packet_type *pt, struct net_device *orig_dev) { struct sock *sk; struct sockaddr_ll *sll; struct packet_sock *po; u
  • Linux内核网络栈源代码分析(专栏)

    千次阅读 2012-12-19 18:10:05
    专栏地址:http://blog.csdn.net/column/details/linux-kernel-net.html[系统运维] Linux内核--网络栈实现分析(一)--网络栈初始化[系统运维] Linux内核--网络栈实现分析(二)--数据包的传递过程(上)[系统运维...
  • 1.Linux网络栈下两层实现 ...下两层涉及到具体的硬件设备,日趋完善的Linux内核已经做到了很好的代码隔离,对网络设备驱动也是如此,如下图所示: 这里要注意的是,Linux下的网络设备net_dev并不

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 218
精华内容 87
关键字:

linuxnet代码分析

linux 订阅