IPVS系统的连接同步 - MASTER同步线程

在内核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

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页