在内核IPVS中存在两个同步点:函数ip_vs_in和ip_vs_conn_expire。前者用于同步连接信息更新,后者用于超时连接的同步。
static unsigned int ip_vs_in(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, int af)
{
struct ip_vs_conn *cp;
if (ipvs->sync_state & IP_VS_STATE_MASTER)
ip_vs_sync_conn(ipvs, cp, pkts);
}
static void ip_vs_conn_expire(struct timer_list *t)
{
struct ip_vs_conn *cp = from_timer(cp, t, timer);
struct netns_ipvs *ipvs = cp->ipvs;
if (ipvs->sync_state & IP_VS_STATE_MASTER)
ip_vs_sync_conn(ipvs, cp, sysctl_sync_threshold(ipvs));
}
同步连接条件
在讲述同步函数之前,有必要先看一下同步的先决条件,即ip_vs_sync_conn_needed函数,其用于判断是否有必要进行同步。首先,如果连接设置有标志IP_VS_CONN_F_TEMPLATE,表明为Persistence调度模式的控制连接(连接模板),对于此类型连接不强制进行同步,是否需要同步根据以下的条件判断。其次,如果被检查的连接由Persistence调度模式控制(依据连接模板创建),并且PROC系统变量sysctl_sync_persist_mode为真,直接返回0,不进行同步(感觉这里的逻辑写反了,应当是sysctl_sync_persist_mode为假时直接返回0?)。可通过PROC文件/proc/sys/net/ipv4/vs/sync_persist_mode修改sysctl_sync_persist_mode的值。对于依据连接模板创建的连接,理论上仅需要同步连接模板即可,根据模板创建的连接可在slave端重建,不需要进行同步。
第三,如果以上两个条件都没有满足,并且此连接为IPPROTO_TCP协议,当连接的TCP状态不等于以下五种状态的任意一种时,返回0,不进行同步。否则,如果连接的TCP状态发生了改变,置位force表明需要进行强制同步,另外,在此情况下,如果连接的新状态不等于IP_VS_TCP_S_ESTABLISHED状态,需要直接跳到函数最后的set标签,参见稍后介绍。以下的五种状态影响到IPVS中连接的处理流程,须要强制同步。
- IP_VS_TCP_S_ESTABLISHED
- IP_VS_TCP_S_FIN_WAIT
- IP_VS_TCP_S_CLOSE
- IP_VS_TCP_S_CLOSE_WAIT
- IP_VS_TCP_S_TIME_WAIT
第四,如果其上三个条件都没有满足,并且此连接为IPPROTO_SCTP协议时,当连接的SCTP状态不等于以下五种状态的任意一种时,返回0,不进行同步。否则,如果连接的SCTP状态发生了改变,置位force标志表明需要强制进行同步,另外,在此情况下,如果连接的新状态不等于IP_VS_SCTP_S_ESTABLISHED状态,需要直接跳到函数最后的set标签。
- IP_VS_SCTP_S_ESTABLISHED
- IP_VS_SCTP_S_SHUTDOWN_SENT
- IP_VS_SCTP_S_SHUTDOWN_RECEIVED
- IP_VS_SCTP_S_SHUTDOWN_ACK_SENT
- IP_VS_SCTP_S_CLOSED
第五,排除以上的条件之后,其它情况下,比如为UDP协议或者其它协议,清除force标志,不强制进行同步。根据其后的条件判断是否需要同步操作。
static int ip_vs_sync_conn_needed(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
{
unsigned long orig = READ_ONCE(cp->sync_endtime);
unsigned long now = jiffies;
unsigned long n = (now + cp->timeout) & ~3UL;
/* Check if we sync in current state */
if (unlikely(cp->flags & IP_VS_CONN_F_TEMPLATE))
force = 0;
else if (unlikely(sysctl_sync_persist_mode(ipvs) && in_persistence(cp)))
return 0;
else if (likely(cp->protocol == IPPROTO_TCP)) {
if (!((1 << cp->state) & ((1 << IP_VS_TCP_S_ESTABLISHED) | (1 << IP_VS_TCP_S_FIN_WAIT) |
(1 << IP_VS_TCP_S_CLOSE) | (1 << IP_VS_TCP_S_CLOSE_WAIT) | (1 << IP_VS_TCP_S_TIME_WAIT))))
return 0;
force = cp->state != cp->old_state;
if (force && cp->state != IP_VS_TCP_S_ESTABLISHED)
goto set;
} else if (unlikely(cp->protocol == IPPROTO_SCTP)) {
if (!((1 << cp->state) & ((1 << IP_VS_SCTP_S_ESTABLISHED) | (1 << IP_VS_SCTP_S_SHUTDOWN_SENT) |
(1 << IP_VS_SCTP_S_SHUTDOWN_RECEIVED) | (1 << IP_VS_SCTP_S_SHUTDOWN_ACK_SENT) | (1 << IP_VS_SCTP_S_CLOSED))))
return 0;
force = cp->state != cp->old_state;
if (force && cp->state != IP_VS_SCTP_S_ESTABLISHED)
goto set;
} else {
/* UDP or another protocol with single state */
force = 0;
}
默认情况下sync_refresh_period的值为零(DEFAULT_SYNC_REFRESH_PERIOD,单位为jiffies值),可通过PROC系统的文件/proc/sys/net/ipv4/vs/sync_refresh_period进行修改。如果变量sync_refresh_period大于零,并且连接的同步时间戳(sync_endtime)与连接超时时间的差值,小于sync_refresh_period限定的值,也小于连接超时时长的一半(最小为10秒钟),意味着如果连接快要超时或者剩余超时时间不超过刷新周期,不进行同步。
sync_endtime timeout
|-------------|
|---------------------| sync_refresh_period
|-----------------------| max(timeout/2, 10s)
但是,内核在sync_endtime时间戳的最后两位保存了重试次数的值,默认情况下重试次数为零(DEFAULT_SYNC_RETRIES),可通过PROC文件/proc/sys/net/ipv4/vs/sync_retries进行修改。如果重试次数大于等于限定值,直接返回0,不进行同步。如果连接的原时间戳减去连接超时时长,在加上八分之一的刷新时间sync_refresh_period,大于当前时间,直接返回0,不进行同步。最后,如果以上两个条件都不成立,重试次数加1,保存在新的时间戳内。
参数sync_refresh_period用于控制同步间隔时长,但是如果同步请求次数在八分之一的sync_refresh_period时长内超过了sync_retries值,也允许同步。
sync_refresh_period = sysctl_sync_refresh_period(ipvs);
if (sync_refresh_period > 0) {
long diff = n - orig;
long min_diff = max(cp->timeout >> 1, 10UL * HZ);
/* Avoid sync if difference is below sync_refresh_period and below the half timeout. */
if (abs(diff) < min_t(long, sync_refresh_period, min_diff)) {
int retries = orig & 3;
if (retries >= sysctl_sync_retries(ipvs))
return 0;
if (time_before(now, orig - cp->timeout + (sync_refresh_period >> 3)))
return 0;
n |= retries + 1;
}
}
以下的同步周期sync_period默认为50(DEFAULT_SYNC_PERIOD,单位为报文数量),同步阈值sync_threshold默认为3(DEFAULT_SYNC_THRESHOLD,单位为报文数量),两者都可通过PROC文件/proc/sys/net/ipv4/vs/sync_threshold进行修改。如果同步周期sync_period大于零,并且连接不是Persistence类型(IP_VS_CONN_F_TEMPLATE),而且报文数量在一个周期内还未达到阈值sync_threshold时,直接返回零,不进行同步。
如果sync_period不大于零,并且同步刷新周期sync_refresh_period为零,连接的报文数量不等于同步阈值时,直接返回零,不进行同步操作。如果以上两个条件都不成立,满足阈值要求,则进行同步。
sync_period = sysctl_sync_period(ipvs);
if (sync_period > 0) {
if (!(cp->flags & IP_VS_CONN_F_TEMPLATE) && pkts % sync_period != sysctl_sync_threshold(ipvs))
return 0;
} else if (!sync_refresh_period && pkts != sysctl_sync_threshold(ipvs))
return 0;
函数的最后, 即set标签之后代码,主要是更新连接结构中的old_state状态;以及更新同步时间戳,只有在
set:
cp->old_state = cp->state;
n = cmpxchg(&cp->sync_endtime, orig, n);
return n == orig || force;
同步连接信息
IPVS系统的同步函数存在两个版本:v0和v1,内核默认使用v1版本,也可通过PROC系统的文件/proc/sys/net/ipv4/vs/sync_version 进行修改。以下主要看一下v1版本的实现,即以下的函数ip_vs_sync_conn。
void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
{
struct ip_vs_sync_mesg *m;
union ip_vs_sync_conn *s;
struct ip_vs_sync_buff *buff;
struct ipvs_master_sync_state *ms;
/* Handle old version of the protocol */
if (sysctl_sync_ver(ipvs) == 0) {
ip_vs_sync_conn_v0(ipvs, cp, pkts);
return;
}
/* Do not sync ONE PACKET */
if (cp->flags & IP_VS_CONN_F_ONE_PACKET)
goto control;
sloop:
if (!ip_vs_sync_conn_needed(ipvs, cp, pkts))
goto control;
如果同步版本使用v0,其处理函数为ip_vs_sync_conn_v0。上一节介绍的同步判段函数ip_vs_sync_conn_needed如果返回0,表明不需要进行同步,但是对于Persistence模式衍生的连接,需要判断一下其控制连接是否需要同步,所有调转到control标签处。
以下部分为简单的合法性检查,只有master状态的同步线程才需要执行此函数。
pe_name_len = 0;
if (cp->pe_data_len) {
if (!cp->pe_data || !cp->dest) {
IP_VS_ERR_RL("SYNC, connection pe_data invalid\n");
return;
}
pe_name_len = strnlen(cp->pe->name, IP_VS_PENAME_MAXLEN);
}
spin_lock_bh(&ipvs->sync_buff_lock);
if (!(ipvs->sync_state & IP_VS_STATE_MASTER)) {
spin_unlock_bh(&ipvs->sync_buff_lock);
return;
}
对于IPv4而言同步数据结构为ip_vs_sync_v4,对于IPv6协议为ip_vs_sync_v6,如下所示。 Type字段为0即IPv4;为1即IPv6。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Protocol | Ver. | Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| State | cport |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| vport | dport |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| fwmark |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timeout (in sec.) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
| IP-Addresses (v4 or v6) |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Optional Parameters.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Param. Type | Param. Length | Param. data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| ... |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | Param Type | Param. Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Param data |
| Last Param data should be padded for 32 bit alignment |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
数据结构ip_vs_sync_v4对应于上图。字段含有参见注释。
struct ip_vs_sync_v4 {
__u8 type;
__u8 protocol; /* Which protocol (TCP/UDP) */
__be16 ver_size; /* Version msb 4 bits */
/* Flags and state transition */
__be32 flags; /* status flags */
__be16 state; /* state info */
/* Protocol, addresses and port numbers */
__be16 cport;
__be16 vport;
__be16 dport;
__be32 fwmark; /* Firewall mark from skb */
__be32 timeout; /* cp timeout */
__be32 caddr; /* client address */
__be32 vaddr; /* virtual address */
__be32 daddr; /* destination address */
/* The sequence options start here */
/* PE data padded to 32bit alignment after seq. options */
};
函数ip_vs_sync_conn接下来的处理即填充以上数据结构。首先选择使用的主同步线程,并获取到线程相关的ipvs_master_sync_state结构。其次,计算同步报文的长度,由以上介绍可知,其由三部分组成:基础的ip_vs_sync_v4/v6结构长度、选项options长度以及PE引擎数据长度。
id = select_master_thread_id(ipvs, cp);
ms = &ipvs->ms[id];
#ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6)
len = sizeof(struct ip_vs_sync_v6);
else
#endif
len = sizeof(struct ip_vs_sync_v4);
if (cp->flags & IP_VS_CONN_F_SEQ_MASK)
len += sizeof(struct ip_vs_sync_conn_options) + 2;
if (cp->pe_data_len)
len += cp->pe_data_len + 2; /* + Param hdr field */
if (pe_name_len)
len += pe_name_len + 2;
如果同步线程的ipvs_master_sync_state结构成员sync_buff的空间不能存放当前的同步报文,或者其reserved字段由值,需要将sync_buff中现有的同步数据发送出去,内核使用sb_queue_tail函数将其放置到线程的sync_queue发送队列上,之后将sync_buff指针清空。
/* check if there is a space for this one */
pad = 0;
buff = ms->sync_buff;
if (buff) {
m = buff->mesg;
pad = (4 - (size_t) buff->head) & 3;
/* Send buffer if it is for v0 */
if (buff->head + len + pad > buff->end || m->reserved) {
sb_queue_tail(ipvs, ms);
ms->sync_buff = NULL;
buff = NULL;
pad = 0;
}
}
针对sync_buff和buff为空的情况,重新分配一个ip_vs_sync_buff空间和其成员ip_vs_sync_mesg空间,用于存放新的连接同步数据。
if (!buff) {
buff = ip_vs_sync_buff_create(ipvs, len);
if (!buff) {
spin_unlock_bh(&ipvs->sync_buff_lock);
pr_err("ip_vs_sync_buff_create failed.\n");
return;
}
ms->sync_buff = buff;
m = buff->mesg;
}
以下为简单的同步结构赋值操作,需要注意的是ip_vs_sync_buff结构的head成员已经更新到了数据的末尾,如果再此添加同步数据时,可从此处开始。
p = buff->head;
buff->head += pad + len;
m->size = htons(ntohs(m->size) + pad + len);
/* Add ev. padding from prev. sync_conn */
while (pad--)
*(p++) = 0;
s = (union ip_vs_sync_conn *)p;
/* Set message type & copy members */
s->v4.type = (cp->af == AF_INET6 ? STYPE_F_INET6 : 0);
s->v4.ver_size = htons(len & SVER_MASK); /* Version 0 */
s->v4.flags = htonl(cp->flags & ~IP_VS_CONN_F_HASHED);
s->v4.state = htons(cp->state);
s->v4.protocol = cp->protocol;
s->v4.cport = cp->cport;
s->v4.vport = cp->vport;
s->v4.dport = cp->dport;
s->v4.fwmark = htonl(cp->fwmark);
s->v4.timeout = htonl(cp->timeout / HZ);
m->nr_conns++;
#ifdef CONFIG_IP_VS_IPV6
if (cp->af == AF_INET6) {
p += sizeof(struct ip_vs_sync_v6);
s->v6.caddr = cp->caddr.in6;
s->v6.vaddr = cp->vaddr.in6;
s->v6.daddr = cp->daddr.in6;
} else
#endif
{
p += sizeof(struct ip_vs_sync_v4); /* options ptr */
s->v4.caddr = cp->caddr.ip;
s->v4.vaddr = cp->vaddr.ip;
s->v4.daddr = cp->daddr.ip;
}
对于NAT转发模式,IPVS系统可能修改了TCP报文头部的序列号,此情况下,需要将序列号同步给slave进程。
if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
*(p++) = IPVS_OPT_SEQ_DATA;
*(p++) = sizeof(struct ip_vs_sync_conn_options);
hton_seq((struct ip_vs_seq *)p, &cp->in_seq);
p += sizeof(struct ip_vs_seq);
hton_seq((struct ip_vs_seq *)p, &cp->out_seq);
p += sizeof(struct ip_vs_seq);
}
另外,如果有PE数据也需要进行其同步。
/* Handle pe data */
if (cp->pe_data_len && cp->pe_data) {
*(p++) = IPVS_OPT_PE_DATA;
*(p++) = cp->pe_data_len;
memcpy(p, cp->pe_data, cp->pe_data_len);
p += cp->pe_data_len;
if (pe_name_len) {
/* Add PE_NAME */
*(p++) = IPVS_OPT_PE_NAME;
*(p++) = pe_name_len;
memcpy(p, cp->pe->name, pe_name_len);
p += pe_name_len;
}
}
函数ip_vs_sync_conn最后,即control标签后代码,判断如果当前连接属于某个控制连接,需要对其控制连接执行同步操作。
control:
/* synchronize its controller if it has */
cp = cp->control;
if (!cp)
return;
if (cp->flags & IP_VS_CONN_F_TEMPLATE)
pkts = atomic_add_return(1, &cp->in_pkts);
else
pkts = sysctl_sync_threshold(ipvs);
goto sloop;
}
同步数据的报文头部隐藏在函数ip_vs_sync_buff_create中,其结构为ip_vs_sync_mesg,包括reserved、version、syncid、size、nr_conns和spare等字段。以上介绍的同步数据可共用一个ip_vs_sync_mesg头部信息,相应的其nr_conns做累加。
static inline struct ip_vs_sync_buff *ip_vs_sync_buff_create(struct netns_ipvs *ipvs, unsigned int len)
{
struct ip_vs_sync_buff *sb;
if (!(sb=kmalloc(sizeof(struct ip_vs_sync_buff), GFP_ATOMIC)))
return NULL;
len = max_t(unsigned int, len + sizeof(struct ip_vs_sync_mesg), ipvs->mcfg.sync_maxlen);
sb->mesg = kmalloc(len, GFP_ATOMIC);
sb->mesg->reserved = 0; /* old nr_conns i.e. must be zero now */
sb->mesg->version = SYNC_PROTO_VER;
sb->mesg->syncid = ipvs->mcfg.syncid;
sb->mesg->size = htons(sizeof(struct ip_vs_sync_mesg));
sb->mesg->nr_conns = 0;
sb->mesg->spare = 0;
sb->head = (unsigned char *)sb->mesg + sizeof(struct ip_vs_sync_mesg);
sb->end = (unsigned char *)sb->mesg + len;
sb->firstuse = jiffies;
return sb;
}
主同步线程
在介绍主同步线程函数sync_thread_master之前,先来看一下其同步缓存的(ip_vs_sync_buff)由来。其使用以下函数next_sync_buff获取要处理的同步缓存结构,由两个来源:一个是由sb_dequeue函数从sync_queue中取得;否则由函数get_curr_sync_buff获取当前的sync_buff结构(还未加入到sync_queue队列中)。
static inline struct ip_vs_sync_buff *next_sync_buff(struct netns_ipvs *ipvs, struct ipvs_master_sync_state *ms)
{
struct ip_vs_sync_buff *sb;
sb = sb_dequeue(ipvs, ms);
if (sb)
return sb;
/* Do not delay entries in buffer for more than 2 seconds */
return get_curr_sync_buff(ipvs, ms, IPVS_SYNC_FLUSH_TIME);
}
如果线程同步状态结构ipvs_master_sync_state中的同步队列sync_queue不为空,从其中取出一个同步缓存ip_vs_sync_buff,并将队列长度(sync_queue_len)减一。
static inline struct ip_vs_sync_buff *sb_dequeue(struct netns_ipvs *ipvs, struct ipvs_master_sync_state *ms)
{
struct ip_vs_sync_buff *sb;
spin_lock_bh(&ipvs->sync_lock);
if (list_empty(&ms->sync_queue)) {
sb = NULL;
__set_current_state(TASK_INTERRUPTIBLE);
} else {
sb = list_entry(ms->sync_queue.next, struct ip_vs_sync_buff, list);
list_del(&sb->list);
ms->sync_queue_len--;
if (!ms->sync_queue_len)
ms->sync_queue_delay = 0;
}
spin_unlock_bh(&ipvs->sync_lock);
return sb;
}
函数get_curr_sync_buff获取当前的同步缓存,如果此同步缓存第一次赋予的时间戳以及过去2秒钟(IPVS_SYNC_FLUSH_TIME),firstuse在同步缓存创建函数ip_vs_sync_buff_create中第一次赋值为当前时间。此种情况下,返回此缓存,进行处理;否则暂不发送此缓存数据。
static inline struct ip_vs_sync_buff *get_curr_sync_buff(struct netns_ipvs *ipvs, struct ipvs_master_sync_state *ms, unsigned long time)
{
struct ip_vs_sync_buff *sb;
spin_lock_bh(&ipvs->sync_buff_lock);
sb = ms->sync_buff;
if (sb && time_after_eq(jiffies - sb->firstuse, time)) {
ms->sync_buff = NULL;
__set_current_state(TASK_RUNNING);
} else
sb = NULL;
spin_unlock_bh(&ipvs->sync_buff_lock);
return sb;
}
接下来看一下sync_thread_master主线程同步函数,其由以上介绍的next_sync_buff函数拿到同步缓存,调用函数ip_vs_send_sync_msg进行发送操作。否则,如果没有可用的同步缓存,进行调度,调度超时时间为1秒(IPVS_SYNC_CHECK_PERIOD)。下节将介绍其可能被函数sb_queue_tail的执行所唤醒。
static int sync_thread_master(void *data)
{
struct ip_vs_sync_thread_data *tinfo = data;
struct netns_ipvs *ipvs = tinfo->ipvs;
struct ipvs_master_sync_state *ms = &ipvs->ms[tinfo->id];
struct sock *sk = tinfo->sock->sk;
struct ip_vs_sync_buff *sb;
for (;;) {
sb = next_sync_buff(ipvs, ms);
if (unlikely(kthread_should_stop()))
break;
if (!sb) {
schedule_timeout(IPVS_SYNC_CHECK_PERIOD);
continue;
}
while (ip_vs_send_sync_msg(tinfo->sock, sb->mesg) < 0) {
/* (Ab)use interruptible sleep to avoid increasing the load avg. */
__wait_event_interruptible(*sk_sleep(sk), sock_writeable(sk) || kthread_should_stop());
if (unlikely(kthread_should_stop()))
goto done;
}
ip_vs_sync_buff_release(sb);
}
}
发送函数ip_vs_send_sync_msg比较简单如下所示:
static int ip_vs_send_sync_msg(struct socket *sock, struct ip_vs_sync_mesg *msg)
{
msize = ntohs(msg->size);
ret = ip_vs_send_async(sock, (char *)msg, msize);
}
static int ip_vs_send_async(struct socket *sock, const char *buffer, const size_t length)
{
struct msghdr msg = {.msg_flags = MSG_DONTWAIT|MSG_NOSIGNAL};
struct kvec iov;
iov.iov_base = (void *)buffer;
iov.iov_len = length;
len = kernel_sendmsg(sock, &msg, &iov, 1, (size_t)(length));
}
同步线程唤醒
在同步函数ip_vs_sync_conn中,看到如果同步缓存sync_buff已经不能够容纳更多的连接信息时,IPVS使用函数sb_queue_tail将当前同步缓存添加到sync_queue队列中。在此函数中,如果同步队列的长度小于其限定值(默认为nr_free_buffer_pages() / 32),可通过PROC文件 /proc/sys/net/ipv4/vs/sync_qlen_max进行修改,内核尝试唤醒主同步线程。但是有以下的条件。
如果此时同步队列的长度为零,使用延时work机制在1秒钟之后调度master_wakeup_work,其为函数master_wakeup_work_handler。
如果sync_queue_delay的值已经累加到8(IPVS_SYNC_WAKEUP_RATE),即已经添加了8个同步缓存,唤醒主同步线程sync_thread_master,尽快处理同步缓存队列。
static inline void sb_queue_tail(struct netns_ipvs *ipvs, struct ipvs_master_sync_state *ms)
{
struct ip_vs_sync_buff *sb = ms->sync_buff;
spin_lock(&ipvs->sync_lock);
if (ipvs->sync_state & IP_VS_STATE_MASTER && ms->sync_queue_len < sysctl_sync_qlen_max(ipvs)) {
if (!ms->sync_queue_len)
schedule_delayed_work(&ms->master_wakeup_work, max(IPVS_SYNC_SEND_DELAY, 1));
ms->sync_queue_len++;
list_add_tail(&sb->list, &ms->sync_queue);
if ((++ms->sync_queue_delay) == IPVS_SYNC_WAKEUP_RATE)
wake_up_process(ms->master_thread);
} else
ip_vs_sync_buff_release(sb);
在work函数master_wakeup_work_handler中,如果sync_queue_delay小于限定值IPVS_SYNC_WAKEUP_RATE,将其设置为此值,并唤醒主同步线程。
static void master_wakeup_work_handler(struct work_struct *work)
{
struct ipvs_master_sync_state *ms = container_of(work, struct ipvs_master_sync_state, master_wakeup_work.work);
struct netns_ipvs *ipvs = ms->ipvs;
spin_lock_bh(&ipvs->sync_lock);
if (ms->sync_queue_len && ms->sync_queue_delay < IPVS_SYNC_WAKEUP_RATE) {
ms->sync_queue_delay = IPVS_SYNC_WAKEUP_RATE;
wake_up_process(ms->master_thread);
}
内核版本 4.15
转载:https://blog.csdn.net/sinat_20184565/article/details/101036458