87
技術社區[雲棲]
網絡子係統71_路由緩存垃圾回收
// 同步回收機製
// 返回值:
// 0,達到回收目標
// 1,沒有達到回收目標
// 注:ip_rt_gc_min_interval = HZ/2
// ip_rt_gc_timeout = RT_GC_TIMEOUT = 300HZ
// ip_rt_gc_elasticity = 8*(rt_hash_mask+1)(正常情況下)
// ip_rt_gc_elasticity = 1(當綁定緩存到鄰居子係統失敗時)
// 平衡點,gc目標數的動態調整:
// 1.當前緩存個數 < 安全閾值(安全範圍,試圖gc少緩存)
// 1.1 當前緩存個數 > 上次gc的平衡點(安全範圍內允許緩存增多)
// 1.1.1 新平衡點+=min((當前緩存個數-上次平衡點)/2 , buckets)
// 1.2.1 gc目標數=當前緩存個數-新平衡點
// 1.2.1.1 gc目標數 < 0
// 1.2.1.1.1 新平衡點+=gc目標數
// 2. 當前緩存個數 > 安全閾值(危險範圍,試圖gc多緩存)
// 2.1 gc目標數 = max((當前緩存個數-安全閾值)/2, buckets)(超過安全閾值的緩存一半將被gc)
// 2.2 新平衡點=當前緩存個數-gc目標數
// 3. gc目標數>0時,進行垃圾回收
// 注,安全閾值,每個bucket的衝突鏈中有8個bucket
// 回收過程:
// 1.令,遍曆所有bucket = 一個回收單位
// 2.如果一個回收單位釋放了gc目標數,調整expire
// 2.1 expire+=HZ/2,放寬到期時限
// 3.如果一個回收單位沒有釋放gc目標數
// 3.1 如果當前緩存個數 < 緩存允許的最小值,退出,下一次使用當前的expire作為時限
// 3.2 否則,隻有在用戶態,並且耗時<1 jiffes時,繼續回收。
// 回收時限確定:
// 1.expire, 回收單位的起始到期時限,每個bucket使用該時限作為起始時限
// 當前回收單位沒有完成gc目標數,下一個回收單位折半expire。
// 5.tmo,每個衝突鏈起始設置tmo=expire,當衝突鏈的一個節點不能被刪除時,
// tmo折半。
1.1 static int rt_garbage_collect(void)
{
//靜態變量
static unsigned long expire = RT_GC_TIMEOUT;
static unsigned long last_gc;
static int rover;
static int equilibrium;
struct rtable *rth, **rthp;
unsigned long now = jiffies;
int goal;
//回收速率限製
// 在ip_rt_gc_min_interval時間範圍內,如果緩存數量沒有超過最大值,則放棄回收
if (now - last_gc < ip_rt_gc_min_interval &&
atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size) {
RT_CACHE_STAT_INC(gc_ignored);
goto out;
}
//ipv4_dst_ops.entries保存當前緩存的個數
goal = atomic_read(&ipv4_dst_ops.entries) -
(ip_rt_gc_elasticity << rt_hash_log);//平均長度下,所有bucket的緩存個數
//當前緩存的個數沒有達到平均緩存個數
if (goal <= 0) {
if (equilibrium < ipv4_dst_ops.gc_thresh)
equilibrium = ipv4_dst_ops.gc_thresh;
//當前緩存的個數相對於上次平衡點的增長個數
goal = atomic_read(&ipv4_dst_ops.entries) - equilibrium;
//緩存個數增加
if (goal > 0) {
//在平均水平下,平衡點隨著緩存個數的增加而增加
equilibrium += min_t(unsigned int, goal / 2, rt_hash_mask + 1);
//新平衡點為剩餘緩存個數
goal = atomic_read(&ipv4_dst_ops.entries) - equilibrium;
}
//當前緩存個數超過平均緩存個數
} else {
//至少回收 number of buckets
goal = max_t(unsigned int, goal / 2, rt_hash_mask + 1);
//新平衡點為剩餘緩存個數
equilibrium = atomic_read(&ipv4_dst_ops.entries) - goal;
}
if (now - last_gc >= ip_rt_gc_min_interval)
last_gc = now;
//當前緩存總數小於平均值時,並且小於上次的平衡點
// 說明緩存個數開始遞減,則降低平衡點,不進行回收
if (goal <= 0) {
equilibrium += goal;
goto work_done;
}
do {
int i, k;
//rover記錄上次遍曆的bucket
for (i = rt_hash_mask, k = rover; i >= 0; i--) {
//tmo,bucket衝突鏈表內部使用的起始到期時限
//expire, bucket之間使用的起始到期時限
unsigned long tmo = expire;
k = (k + 1) & rt_hash_mask;
//這次應該遍曆的bucket
rthp = &rt_hash_table[k].chain;
spin_lock_bh(&rt_hash_table[k].lock);
while ((rth = *rthp) != NULL) {
//檢查緩存到期
if (!rt_may_expire(rth, tmo, expire)) {
//到期時間折半,使回收更加嚴格
tmo >>= 1;
rthp = &rth->u.rt_next;
continue;
}
*rthp = rth->u.rt_next;
rt_free(rth);
goal--;
}
spin_unlock_bh(&rt_hash_table[k].lock);
if (goal <= 0)
break;
}
rover = k;
if (goal <= 0)
goto work_done;
//到期時間已經足夠嚴格,依然沒有達到目標,放棄回收
if (expire == 0)
break;
//下一輪遍曆的到期時間折半,使到期時間更加嚴格
expire >>= 1;
if (atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size)
goto out;
} while (!in_softirq() && time_before_eq(jiffies, now));
if (atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size)
goto out;
return 1;
work_done:
expire += ip_rt_gc_min_interval;
if (expire > ip_rt_gc_timeout ||
atomic_read(&ipv4_dst_ops.entries) < ipv4_dst_ops.gc_thresh)
expire = ip_rt_gc_timeout;
out: return 0;
}
// 異步垃圾回收
// 垃圾回收定時器函數
// 函數主要任務:
// 1.計算此次gc處理的bucket數
// 2.從上次遍曆到的下一個bucket開始
// 2.1 如果dst到期時間為0,可以被釋放,從衝突鏈取下節點,釋放
// 2.2 該dst沒有到期,調整gc時限更加嚴格
// 3.重新激活gc定時器
// 注:ip_rt_gc_interval=60HZ
// 注:ip_rt_gc_timeout,ip_rt_gc_interval的關係
// 1.一個bucket至少在ip_rt_gc_timeout間隔gc一次
// 2.兩次異步gc的間隔為ip_rt_gc_interval(即定時器的到期間隔)
// 3.此次gc處理的bucket個數=(ip_rt_gc_interval*(rt_hash_mask+1))/ip_rt_gc_timeout
// 使路由緩存到期的事件:
// 1.主機接收到 ICMP UNREACHABLE 或 ICMP FRAGMENTATION NEEDED,設置為一段時間後(10min)過期。
// 2.鄰居項無法解析l3到l2的映射,目的ip不可達時,設置為立即過期。
// dst_entry->expires
// 默認情況下,路由緩存表項永遠不會過期,因此dst_entry->expires設置為0,當發生使路由緩存過期的事件時
// ,更新dst_entry->expires為緩存到期的時間戳。
2.1 static void rt_check_expire(unsigned long dummy)
{
static int rover;
int i = rover, t;
struct rtable *rth, **rthp;
unsigned long now = jiffies;
//計算可gc的bucket個數
for (t = ip_rt_gc_interval << rt_hash_log; t >= 0;
//gc定時器到期時間ip_rt_gc_timeout
t -= ip_rt_gc_timeout) {
unsigned long tmo = ip_rt_gc_timeout;
//rover記錄上次gc的bucket
i = (i + 1) & rt_hash_mask;
rthp = &rt_hash_table[i].chain;
spin_lock(&rt_hash_table[i].lock);
while ((rth = *rthp) != NULL) {
//dst->expires該dst將過期的時間戳
if (rth->u.dst.expires) {
//表示指定的過期還沒到
if (time_before_eq(now, rth->u.dst.expires)) {
//過期時限折半
tmo >>= 1;
rthp = &rth->u.rt_next;
continue;
}
//該dst還不能被釋放
} else if (!rt_may_expire(rth, tmo, ip_rt_gc_timeout)) {
tmo >>= 1;
rthp = &rth->u.rt_next;
continue;
}
//從bucket的衝突鏈取下,釋放
*rthp = rth->u.rt_next;
rt_free(rth);
}
spin_unlock(&rt_hash_table[i].lock);
//一步回收不超過1 jiffies
if (time_after(jiffies, now))
break;
}
rover = i;
//下次到期的時間
mod_timer(&rt_periodic_timer, now + ip_rt_gc_interval);
}
// 判斷緩存是否可以釋放
// 參數:
// tmo1,可快速清理的緩存使用的到期時限
// tmo2,高代價創建的緩存使用的到期時限
// tmo2 > tmo1
// 緩存釋放的條件:
// 1.引用計數=0
// 2.如果,緩存指定了到期時間,並且達到了到期時間,可清理
// 3.否則,可以快速清理,並且清理代價較低
// 可以快速清理的緩存項:
// 1.廣播多播地址
// 2.入口路由
// 3.衝突鏈表中,其後有緩存項
// 清理代價較高的緩存項:
// 1.重定向的的緩存項
3.1 static int rt_may_expire(struct rtable *rth, unsigned long tmo1, unsigned long tmo2)
{
unsigned long age;
int ret = 0;
if (atomic_read(&rth->u.dst.__refcnt))
goto out;
ret = 1;
if (rth->u.dst.expires &&
time_after_eq(jiffies, rth->u.dst.expires))
goto out;
age = jiffies - rth->u.dst.lastuse;
ret = 0;
if ((age <= tmo1 && !rt_fast_clean(rth)) ||
(age <= tmo2 && rt_valuable(rth)))
goto out;
ret = 1;
out: return ret;
}
最後更新:2017-04-03 14:53:50