路由的添加和删除
路由表的结构
为了各种操作的快速查找,内核中定义了几个不同的哈希表, 存储着相同的结构
1. 基于掩码长度的哈希表
内核中定义了长度为33的哈希表, 分别表示掩码长度0-32, 其中掩码长度为0的表示默认路由; fib_table的tb_data是fn_hash结构:
struct fn_hash {
struct fn_zone *fn_zones[33];
struct fn_zone *fn_zone_list;
};
其中包含了一个长度为33的fn_zone数组, 用于分别表示掩码长度0-32路由信息; 所有非空的fn_zone通过fn_zone_list连接到一起;
掩码长度为0的路由, 也就是默认路由fn_zone的哈希表长度为1, 从哈希表退化为单链表, 因为一般不会有太多的默认路由项;
fib_node用于表示唯一的子网, 它的成员fn_key用于区分不同的fib_node; 特别要注意的是fib_node是表示到达一个子网的路由(长度为32的主机路由可以看成只有一台主机的子网)
fib_alias用于表示到达同一子网的不同路由, 这些路由的TOS值是不同的; fib_node的所有fib_alias是根据IP TOS升序排列的; fib_alias会指向某个fib_node, fib_node存储了真正的路由信息;
fn_zone的哈希表在entry数量达到哈希表大小的两倍时会进行扩容, 以减少查找时间;
2. 直接搜索fib_info结构的哈希表
系统中定义了两个链表, 可以直接搜索fib_info结构;
fib_info_hash: 所有的fib_info结构都链入此链表;
fib_info_laddrhash:带preferred address的路由会链入此链表;
fib_create_info会检查fib_info entry数量fib_info_cnt是否达到哈希表的大小fib_hash_size, 如果条件成立fib_info_hash 和fib_info_laddrhash都分配原先大小的两倍, 然后把就的fib_info entry移到新的链表中;
3. 以net_device为索引的哈希表, 用于搜索下一跳信息
fib_info中可能包含多个fib_nh(开启多路由的编译宏), fib_nh中包含了net_device, 表示发往下一跳的报文需通过该设备转发; 当设备shutdown时需要删除该设备关联的所有路由(fib_sync_down); 当设备启用时要从新启用通过该设备可以到达的路由;
fib_alias 和fib_info不是一对一的, 几个fib_alias可以共享一个fib_info, fib_info中有引用计数fib_treeref记录着它被几个fib_alias使用;
比如下图中:
1. 有4条路由, 因为有4个fib_alias结构
2. 这4条路由指向3个不同的子网(3个fib_node结构), 其中同一fib_node包含两个fib_alias
3. 其中有2条路由的下一跳路由相同, 两个fib_alias的fa_info指向相同的fib_info
路由Scope
RT_SCOPE_NOWHERE
表示该路由无法到达任意主机, 也就是说到该目的地址没有路由
RT_SCOPE_HOST
表示该路由目的为本机接口, 此类型路由为fib_add_ifaddr自动添加
RT_SCOPE_LINK
表示路由目的为本地网络
RT_SCOPE_UNIVERSE
表示路由目的为其他非直连网络, 也就是需要至少一个下一条网关;
路由的scope和本地配置地址的scope可以由用户显式指定或者由内核配置为默认值; 而下一跳fib_nh的scope只能由fib_check_nh指定; 给定路由和它的下一跳, 下一跳fib_nh的scope是用于到达该下一跳路由的scope; 当主机转发一条报文都会使该报文跟接近最终目的; 因此, 路由的scope必须大等于该到达下一跳路由scope;
A要发送报文给C, A到C路由的scope是RT_SCOPE_UNIVERSE, 下一跳是RT; 而A到RT路由的scope是RT_SCOPE_LINK < RT_SCOPE_UNIVERSE;
A要发送报文给A, A到A路由的scope是RT_SCOPE_HOST, 下一条为空, scope是RT_SCOPE_NOWHERE;
路由结束条件是路由查找的结果返回RT_SCOPE_HOST或者RT_SCOPE_LINK; RT_SCOPE_HOST表示目的地址是本机; RT_SCOPE_LINK表示目的地址与本机直连, 可以通过L2协议进行发送;
路由表的初始化
#ifdef CONFIG_IP_MULTIPLE_TABLES /* 可以在任意时候调用 */ struct fib_table * fib_hash_init(u32 id) #else /* 只在初始化的时候创建local和main路由表 */ struct fib_table * __init fib_hash_init(u32 id) #endif { struct fib_table *tb; if (fn_hash_kmem == NULL) fn_hash_kmem = kmem_cache_create("ip_fib_hash", sizeof(struct fib_node), 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if (fn_alias_kmem == NULL) fn_alias_kmem = kmem_cache_create("ip_fib_alias", sizeof(struct fib_alias), 0, SLAB_HWCACHE_ALIGN, NULL, NULL); tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash), GFP_KERNEL); if (tb == NULL) return NULL; tb->tb_id = id; tb->tb_lookup = fn_hash_lookup; tb->tb_insert = fn_hash_insert; tb->tb_delete = fn_hash_delete; tb->tb_flush = fn_hash_flush; tb->tb_select_default = fn_hash_select_default; tb->tb_dump = fn_hash_dump; memset(tb->tb_data, 0, sizeof(struct fn_hash)); return tb; }
路由表插入
添加或删除一条路由时, Flag的组合如下:
路由表的插入是有fn_hash_insert来完成的: static int fn_hash_insert(struct fib_table *tb, struct fib_config *cfg) { struct fn_hash *table = (struct fn_hash *) tb->tb_data; struct fib_node *new_f, *f; struct fib_alias *fa, *new_fa; struct fn_zone *fz; struct fib_info *fi; u8 tos = cfg->fc_tos; __be32 key; int err; /* 掩码长度不能大于32 */ if (cfg->fc_dst_len > 32) return -EINVAL; /* 取对应掩码长度的fn_zone, 如果不存在则创建fn_zone, 并连接到table->fn_zone_list中 */ fz = table->fn_zones[cfg->fc_dst_len]; if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len))) return -ENOBUFS; key = 0; if (cfg->fc_dst) { /* 上面说过, fib_node是表示某个子网路由, 长度为32的子网可以看出只有一个ip的子网 */ if (cfg->fc_dst & ~FZ_MASK(fz)) return -EINVAL; key = fz_key(cfg->fc_dst, fz); } /* 新建fib_info, 并初始化nh_oif, nh_gw, nh_flags, nh_scope, nh_dev等成员, 最后把fib_info链到fib_info_hash以及fib_info_laddrhash中, 把带dev的fib_nh链到fib_info_devhash中 */ fi = fib_create_info(cfg); if (IS_ERR(fi)) return PTR_ERR(fi); /* fn_zone的entry数量达到该zone哈希表大小的两倍时扩容, 并把旧的fib_node节点移动到新的哈希表中 */ if (fz->fz_nent > (fz->fz_divisor<<1) && fz->fz_divisor < FZ_MAX_DIVISOR && (cfg->fc_dst_len == 32 || (1 << cfg->fc_dst_len) > fz->fz_divisor)) fn_rehash_zone(fz); /* 查找是否已有到达该子网的路由 */ f = fib_find_node(fz, key); if (!f) fa = NULL; else fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority); /* Now fa, if non-NULL, points to the first fib alias * with the same keys [prefix,tos,priority], if such key already * exists or to the node before which we will insert new one. * * If fa is NULL, we will need to allocate a new one and * insert to the head of f. * * If f is NULL, no fib node matched the destination key * and we need to allocate a new one of those as well. */ if (fa && fa->fa_tos == tos && fa->fa_info->fib_priority == fi->fib_priority) { struct fib_alias *fa_orig; err = -EEXIST; /* 已经存在, Do not touch, if it exists */ if (cfg->fc_nlflags & NLM_F_EXCL) goto out; /* 替换该fib_alias */ if (cfg->fc_nlflags & NLM_F_REPLACE) { struct fib_info *fi_drop; u8 state; write_lock_bh(&fib_hash_lock); fi_drop = fa->fa_info; fa->fa_info = fi; fa->fa_type = cfg->fc_type; fa->fa_scope = cfg->fc_scope; state = fa->fa_state; fa->fa_state &= ~FA_S_ACCESSED; fib_hash_genid++; write_unlock_bh(&fib_hash_lock); /* 释放对原fib_info的引用 */ fib_release_info(fi_drop); /* 在路由缓存中有,则刷新缓存 */ if (state & FA_S_ACCESSED) rt_cache_flush(-1); return 0; } /* append(CREATE | APPEND)或者prepend(CREATE) */ /* Error if we find a perfect match which * uses the same scope, type, and nexthop * information. */ fa_orig = fa; fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list); /* tos和priority相同, 表示相同路由, 已经存在则插入失败 */ list_for_each_entry_continue(fa, &f->fn_alias, fa_list) { if (fa->fa_tos != tos) break; if (fa->fa_info->fib_priority != fi->fib_priority) break; if (fa->fa_type == cfg->fc_type && fa->fa_scope == cfg->fc_scope && fa->fa_info == fi) goto out; } /* prepend, 在原先fa后插入, 否则插入到tos相同的fa之后 */ if (!(cfg->fc_nlflags & NLM_F_APPEND)) fa = fa_orig; } err = -ENOENT; /* 没找到fib_alias, 则需要新建 */ if (!(cfg->fc_nlflags & NLM_F_CREATE)) goto out; err = -ENOBUFS; new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL); if (new_fa == NULL) goto out; new_f = NULL; if (!f) { new_f = kmem_cache_alloc(fn_hash_kmem, GFP_KERNEL); if (new_f == NULL) goto out_free_new_fa; INIT_HLIST_NODE(&new_f->fn_hash); INIT_LIST_HEAD(&new_f->fn_alias); new_f->fn_key = key; f = new_f; } new_fa->fa_info = fi; new_fa->fa_tos = tos; /* fib_alias的scope由配置指定 */ new_fa->fa_type = cfg->fc_type; new_fa->fa_scope = cfg->fc_scope; new_fa->fa_state = 0; /* * Insert new entry to the list. */ write_lock_bh(&fib_hash_lock); if (new_f) /* fib_node新建, 链入fn_zone的哈希表中 */ fib_insert_node(fz, new_f); /* fib_node上已存在fib_alias则链入到刚才找到的fa后面, 否则链入新建的fib_node的fn_alias */ list_add_tail(&new_fa->fa_list, (fa ? &fa->fa_list : &f->fn_alias)); fib_hash_genid++; write_unlock_bh(&fib_hash_lock); if (new_f) fz->fz_nent++; rt_cache_flush(-1); /* 通知感兴趣的app新建路由 */ rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id, &cfg->fc_nlinfo); return 0; out_free_new_fa: kmem_cache_free(fn_alias_kmem, new_fa); out: fib_release_info(fi); return err; } static struct fn_zone * fn_new_zone(struct fn_hash *table, int z) { int i; struct fn_zone *fz = kzalloc(sizeof(struct fn_zone), GFP_KERNEL); if (!fz) return NULL; if (z) { fz->fz_divisor = 16; } else { /* 默认路由哈希表大小为1, 退化为单链表 */ fz->fz_divisor = 1; } fz->fz_hashmask = (fz->fz_divisor - 1); fz->fz_hash = fz_hash_alloc(fz->fz_divisor); if (!fz->fz_hash) { kfree(fz); return NULL; } memset(fz->fz_hash, 0, fz->fz_divisor * sizeof(struct hlist_head *)); /* 掩码长度 */ fz->fz_order = z; /* 掩码 */ fz->fz_mask = inet_make_mask(z); /* 链入fib_table, Find the first not empty zone with more specific mask */ for (i=z+1; i<=32; i++) if (table->fn_zones[i]) break; write_lock_bh(&fib_hash_lock); if (i>32) { /* No more specific masks, we are the first. */ fz->fz_next = table->fn_zone_list; table->fn_zone_list = fz; } else { fz->fz_next = table->fn_zones[i]->fz_next; table->fn_zones[i]->fz_next = fz; } table->fn_zones[z] = fz; fib_hash_genid++; write_unlock_bh(&fib_hash_lock); return fz; } struct fib_info *fib_create_info(struct fib_config *cfg) { int err; struct fib_info *fi = NULL; struct fib_info *ofi; int nhs = 1; /* Fast check to catch the most weird cases, 所有类型的scope都预定义了一个最大值 */ if (fib_props[cfg->fc_type].scope > cfg->fc_scope) goto err_inval; #ifdef CONFIG_IP_ROUTE_MULTIPATH if (cfg->fc_mp) { /* 计算下一跳个数 */ nhs = fib_count_nexthops(cfg->fc_mp, cfg->fc_mp_len); if (nhs == 0) goto err_inval; } #endif #ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED if (cfg->fc_mp_alg) { if (cfg->fc_mp_alg < IP_MP_ALG_NONE || cfg->fc_mp_alg > IP_MP_ALG_MAX) goto err_inval; } #endif err = -ENOBUFS; if (fib_info_cnt >= fib_hash_size) { /* fib_info个数大于fib_node哈希表的大小时扩容 */ unsigned int new_size = fib_hash_size << 1; struct hlist_head *new_info_hash; struct hlist_head *new_laddrhash; unsigned int bytes; if (!new_size) new_size = 1; bytes = new_size * sizeof(struct hlist_head *); new_info_hash = fib_hash_alloc(bytes); new_laddrhash = fib_hash_alloc(bytes); if (!new_info_hash || !new_laddrhash) { fib_hash_free(new_info_hash, bytes); fib_hash_free(new_laddrhash, bytes); } else { memset(new_info_hash, 0, bytes); memset(new_laddrhash, 0, bytes); /* fib_info移动到新的链表中 */ fib_hash_move(new_info_hash, new_laddrhash, new_size); } if (!fib_hash_size) goto failure; } fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL); if (fi == NULL) goto failure; fib_info_cnt++; fi->fib_protocol = cfg->fc_protocol; fi->fib_flags = cfg->fc_flags; fi->fib_priority = cfg->fc_priority; fi->fib_prefsrc = cfg->fc_prefsrc; fi->fib_nhs = nhs; change_nexthops(fi) { nh->nh_parent = fi; } endfor_nexthops(fi) if (cfg->fc_mx) { struct nlattr *nla; int remaining; /* 其他属性 */ nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining) { int type = nla->nla_type; if (type) { if (type > RTAX_MAX) goto err_inval; fi->fib_metrics[type - 1] = nla_get_u32(nla); } } } if (cfg->fc_mp) { #ifdef CONFIG_IP_ROUTE_MULTIPATH err = fib_get_nhs(fi, cfg->fc_mp, cfg->fc_mp_len, cfg); if (err != 0) goto failure; if (cfg->fc_oif && fi->fib_nh->nh_oif != cfg->fc_oif) goto err_inval; if (cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw) goto err_inval; #ifdef CONFIG_NET_CLS_ROUTE if (cfg->fc_flow && fi->fib_nh->nh_tclassid != cfg->fc_flow) goto err_inval; #endif #else goto err_inval; #endif } else { struct fib_nh *nh = fi->fib_nh; /* 初始化该路由出口的device, 网关地址等 */ nh->nh_oif = cfg->fc_oif; nh->nh_gw = cfg->fc_gw; nh->nh_flags = cfg->fc_flags; #ifdef CONFIG_NET_CLS_ROUTE nh->nh_tclassid = cfg->fc_flow; #endif #ifdef CONFIG_IP_ROUTE_MULTIPATH nh->nh_weight = 1; #endif } #ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED fi->fib_mp_alg = cfg->fc_mp_alg; #endif /* 如果是特殊的路由, black hole, prohibit等 */ if (fib_props[cfg->fc_type].error) { /* 不允许配置下一跳, 出口device */ if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp) goto err_inval; goto link_it; } if (cfg->fc_scope > RT_SCOPE_HOST) goto err_inval; /* 下一跳的scope是由kernel决定 */ if (cfg->fc_scope == RT_SCOPE_HOST) { /* 本机路由 */ struct fib_nh *nh = fi->fib_nh; /* Local address is added.本机路由不需要下一跳 */ if (nhs != 1 || nh->nh_gw) goto err_inval; /* 大于cfg->fc_scope, RT_SCOPE_HOST */ nh->nh_scope = RT_SCOPE_NOWHERE; nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif); err = -ENODEV; if (nh->nh_dev == NULL) goto failure; } else { /* 其他路由, 需要下一跳网关, 或者网关为本地接口 */ change_nexthops(fi) { /* 根据前面的规则初始化fib_nh的scope */ if ((err = fib_check_nh(cfg, fi, nh)) != 0) goto failure; } endfor_nexthops(fi) } if (fi->fib_prefsrc) { if (cfg->fc_type != RTN_LOCAL || !cfg->fc_dst || fi->fib_prefsrc != cfg->fc_dst) if (inet_addr_type(fi->fib_prefsrc) != RTN_LOCAL) goto err_inval; } link_it: if ((ofi = fib_find_info(fi)) != NULL) { /* 已经存在相同的fib_info, 直接返回它 */ fi->fib_dead = 1; free_fib_info(fi); ofi->fib_treeref++; return ofi; } /* 表示已经被fib_alias引用 */ fi->fib_treeref++; /* fib_info本身被引用的次数 */ atomic_inc(&fi->fib_clntref); spin_lock_bh(&fib_info_lock); /* 链入fib_info_hash */ hlist_add_head(&fi->fib_hash, &fib_info_hash[fib_info_hashfn(fi)]); /* 指定了preferred src 则链入fib_info_laddrhash */ if (fi->fib_prefsrc) { struct hlist_head *head; head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)]; hlist_add_head(&fi->fib_lhash, head); } change_nexthops(fi) { struct hlist_head *head; unsigned int hash; if (!nh->nh_dev) continue; /* fib_nh链入fib_info_devhash */ hash = fib_devindex_hashfn(nh->nh_dev->ifindex); head = &fib_info_devhash[hash]; hlist_add_head(&nh->nh_hash, head); } endfor_nexthops(fi) spin_unlock_bh(&fib_info_lock); return fi; err_inval: err = -EINVAL; failure: if (fi) { fi->fib_dead = 1; free_fib_info(fi); } return ERR_PTR(err); } static int fib_check_nh(struct fib_config *cfg, struct fib_info *fi, struct fib_nh *nh) { int err; if (nh->nh_gw) { /* 其他路由, 带下一跳网关 */ struct fib_result res; #ifdef CONFIG_IP_ROUTE_PERVASIVE if (nh->nh_flags&RTNH_F_PERVASIVE) return 0; #endif /* 下一跳网关与本地直连 */ if (nh->nh_flags&RTNH_F_ONLINK) { struct net_device *dev; /* 路由的scope必须大于下一跳的scope, 一般为RT_SCOPE_UNIVERSE(实际数值的大小是相反的, RT_SCOPE_UNIVERSE是0) */ if (cfg->fc_scope >= RT_SCOPE_LINK) return -EINVAL; if (inet_addr_type(nh->nh_gw) != RTN_UNICAST) return -EINVAL; if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL) return -ENODEV; if (!(dev->flags&IFF_UP)) return -ENETDOWN; nh->nh_dev = dev; dev_hold(dev); nh->nh_scope = RT_SCOPE_LINK; return 0; } /* 下一跳没有与本地直连, 必须搜索到达该下一跳的路由 */ { struct flowi fl = { .nl_u = { .ip4_u = { .daddr = nh->nh_gw, .scope = cfg->fc_scope + 1, }, }, .oif = nh->nh_oif, }; /* It is not necessary, but requires a bit of thinking */ if (fl.fl4_scope < RT_SCOPE_LINK) fl.fl4_scope = RT_SCOPE_LINK; if ((err = fib_lookup(&fl, &res)) != 0) return err; } err = -EINVAL; /* 到达该下一跳必须是单播路由或本机? */ if (res.type != RTN_UNICAST && res.type != RTN_LOCAL) goto out; /* 下一跳的scope是到达该下一跳路由的scope */ nh->nh_scope = res.scope; nh->nh_oif = FIB_RES_OIF(res); if ((nh->nh_dev = FIB_RES_DEV(res)) == NULL) goto out; dev_hold(nh->nh_dev); err = -ENETDOWN; if (!(nh->nh_dev->flags & IFF_UP)) goto out; err = 0; out: fib_res_put(&res); return err; } else { /* 其他路由, 下一跳为本地接口 */ struct in_device *in_dev; if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK)) return -EINVAL; in_dev = inetdev_by_index(nh->nh_oif); if (in_dev == NULL) return -ENODEV; if (!(in_dev->dev->flags&IFF_UP)) { in_dev_put(in_dev); return -ENETDOWN; } nh->nh_dev = in_dev->dev; dev_hold(nh->nh_dev); /* 通过本地接口地址作为下一跳网关 */ nh->nh_scope = RT_SCOPE_HOST; in_dev_put(in_dev); } return 0; } 路由表的删除 static int fn_hash_delete(struct fib_table *tb, struct fib_config *cfg) { struct fn_hash *table = (struct fn_hash*)tb->tb_data; struct fib_node *f; struct fib_alias *fa, *fa_to_delete; struct fn_zone *fz; __be32 key; if (cfg->fc_dst_len > 32) return -EINVAL; if ((fz = table->fn_zones[cfg->fc_dst_len]) == NULL) return -ESRCH; key = 0; if (cfg->fc_dst) { if (cfg->fc_dst & ~FZ_MASK(fz)) return -EINVAL; key = fz_key(cfg->fc_dst, fz); } /* 查找对应fib_node */ f = fib_find_node(fz, key); if (!f) fa = NULL; else /* 查找对应fib_alias */ fa = fib_find_alias(&f->fn_alias, cfg->fc_tos, 0); if (!fa) return -ESRCH; fa_to_delete = NULL; fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list); /* 查找要删除的fib_alias */ list_for_each_entry_continue(fa, &f->fn_alias, fa_list) { struct fib_info *fi = fa->fa_info; if (fa->fa_tos != cfg->fc_tos) break; if ((!cfg->fc_type || fa->fa_type == cfg->fc_type) && (cfg->fc_scope == RT_SCOPE_NOWHERE || fa->fa_scope == cfg->fc_scope) && (!cfg->fc_protocol || fi->fib_protocol == cfg->fc_protocol) && fib_nh_match(cfg, fi) == 0) { fa_to_delete = fa; break; } } if (fa_to_delete) { int kill_fn; fa = fa_to_delete; rtmsg_fib(RTM_DELROUTE, key, fa, cfg->fc_dst_len, tb->tb_id, &cfg->fc_nlinfo); kill_fn = 0; write_lock_bh(&fib_hash_lock); list_del(&fa->fa_list); /* fib_node上还有fib_alias吗? 没有则删除fib_node */ if (list_empty(&f->fn_alias)) { hlist_del(&f->fn_hash); kill_fn = 1; } fib_hash_genid++; write_unlock_bh(&fib_hash_lock); /* 缓存中有使用则刷新 */ if (fa->fa_state & FA_S_ACCESSED) rt_cache_flush(-1); /* 释放该fib_alias对fib_info的引用 */ fn_free_alias(fa); if (kill_fn) { fn_free_node(f); fz->fz_nent--; } return 0; } return -ESRCH; } void fib_release_info(struct fib_info *fi) { spin_lock_bh(&fib_info_lock); if (fi && --fi->fib_treeref == 0) { /* 该fib_info没有人引用了, 从fib_info_hash以及fib_info_laddrhash链表中删除 */ hlist_del(&fi->fib_hash); if (fi->fib_prefsrc) hlist_del(&fi->fib_lhash); change_nexthops(fi) { if (!nh->nh_dev) continue; hlist_del(&nh->nh_hash); } endfor_nexthops(fi) fi->fib_dead = 1; /* fib_info使用次数减一, 为0则内存回收 */ fib_info_put(fi); } spin_unlock_bh(&fib_info_lock); } |