TCPの送信シリーズ-送信キャッシュの管理(二)


主な内容:TCPレベルから送信キャッシュの申請が合法かどうかを判断し、プロセスは送信キャッシュが不足しているため睡眠待ちを行う.
                    キャッシュ書き込み可能なイベントが送信されたために起動します.
カーネルバージョン:3.15.2
私のブログ:http://blog.csdn.net/zhangskd
 
TCPの送信キャッシュ管理は、単一ソケットとTCPレイヤ全体の2つのレベルで行われる.
前回のblogでは、単一Socketレベルでの送信キャッシュ管理について説明しましたが、TCPレベル全体での送信キャッシュ管理を見てみましょう.
 
TCPレベルからキャッシュを送信する申請が合法かどうかを判断する
 
キャッシュの送信を申請するとsk_が呼び出されます.stream_memory_free()はsock送信キューのサイズが
sock送信キャッシュの上限を超えたら、sockの送信キャッシュ書き込み可能イベントを待つために睡眠に入ります.
これは、送信キャッシュの割り当てが許可されているか否かを単一のsocketレベルから判断するものである.
 
sk_を呼び出すstream_alloc_skb()は,キャッシュの送信を申請した後,TCPレベルから今回の申請が合法か否かを判断する.
合法でない場合は__を使用します.kfree_skb()は、申請したskbを解放する.キャッシュを送信する申請は、2つのレベルを通過する必要があります.
 
TCPレベルからキャッシュ送信申請が正当か否かを判断するには,TCPレベル全体のメモリ使用量,およびこのsocketを考慮する必要がある.
の送信キャッシュ使用量.sk->sk_forward_allocはsockにキャッシュのサイズを予め割り当て、sockが未使用のメモリを事前に割り当てたものです.
新しい送信キャッシュを申請するとsk->sk_forward_alloctruesize、すなわちプリアサイメントキャッシュが光を用い、
sk_を呼び出す必要があるwme_schedule()はTCPレベルから合法性を判断し、そうでなければ検査をしなくてもよい.
static inline bool sk_wmem_schedule(struct sock *sk, int size)
{
    /* TCP          ,       */
    if (! sk_has_account(sk))
        return true;

    /*          skb->truesize,  sk             ,      
     *      。     TCP                 。
     */
    return size <= sk->sk_forward_alloc || __sk_mem_schedule(sk, size, SK_MEM_SEND);
}

static inline bool sk_has_account(struct sock *sk)
{
    /* return ture if protocol supports memory accounting */
    return !! sk->sk_prot->memory_allocated;
}
 

/* return minimum truesize of one skb containing X bytes of data */
#define SKB_TRUESIZE(X) ((X) + \
    SKB_DATA_ALIGN(sizeof(struct sk_buff)) + \
    SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))

 
__sk_mem_schedule()は、TCPレベルから今回のキャッシュ送信の申請が合法かどうかを判断するために使用され、合法であれば、
プリアサイメントキャッシュsk->sk_が更新されますforward_allocとTCP層の合計メモリ使用量tcp_memory_allocated,
後者の単位はページです.
 
Q.どのような場合にキャッシュを送る申請が合法なのでしょうか.
1.TCP層のメモリ使用量が最小値sysctl_より低いtcp_mem[0].
2.sockの送信キャッシュ使用量が最小値sysctl_より低いtcp_wmem[0].
3.TCP層がメモリ圧力状態にない、すなわちTCP層のメモリ使用量がsysctl_より低いtcp_wmem[1].
4.TCP層はメモリ圧力状態にあるが、現在のsocketで使用されているメモリはまだ高すぎない.
5.TCP層のメモリ使用量が最大値sysctl_を超えるtcp_wmem[2]、送信キャッシュの上限を下げると、送信キューの総サイズが
    送信キャッシュの上限になりました.そのため、その後は睡眠に入って待機するため、合法と判定された.
 
TCPのメモリ使用量が限界に達していない限り、ほとんどの場合、キャッシュを送信する申請は合法であることがわかります.
今回のキャッシュ申請の送信の正当性を判断する以外に、_sk_mem_schedule()は、次のことをしました.
1.TCPのメモリ使用量が最小値sysctl_より低い場合tcp_mem[0]、TCPのメモリ圧力フラグtcp_をクリアmemory_pressure.
2.TCPのメモリ使用量が圧力値sysclt_より高い場合tcp_mem[1]、TCPのメモリ圧力フラグtcp_をmemory_pressureを1に設定します.
3.TCPのメモリ使用量が最大値sysctl_より高い場合tcp_mem[2]では、sock送信キャッシュの上限sk->sk_を小さくするsndbuf.
 
戻り値が1の場合、キャッシュを送信する申請が合法であることを示す.戻り値が0の場合は、不正を示します.
/* increase sk_forward_alloc and memory_allocated
 * @sk: socket
 * @size: memory size to allocate
 * @kind: allocation type
 * If kind is SK_MEM_SEND, it means wmem allocation.
 * Otherwise it means rmem allocation. This function assumes that 
 * protocols which have memory pressure use sk_wmem_queued as
 * write buffer accounting.
 */

int __sk_mem_schedule(struct sock *sk, int size, int kind)
{
    struct proto *prot = sk->sk_prot; /*    tcp_prot */
    int amt = sk_mem_pages(size); /*  size     ,     */
    long allocated;
    int parent_status = UNDER_LIMIT;

    sk->sk_forward_alloc += amt * SK_MEM_QUANTUM; /*            */

    /*     TCP     tcp_memory_allocated,     */
    allocated = sk_memory_allocated_add(sk, amt, &parent_status);

    /* Under limit.   TCP           sysctl_tcp_mem[0] */
    if (parent_status == UNDER_LIMIT && allocated <= sk_prot_mem_limits(sk, 0)) {
        sk_leave_memory_pressure(sk); /*   TCP        tcp_memory_pressure */
        return 1;
    }

    /* Under pressure. (we or our parents).
     *   TCP           sysclt_tcp_mem[1], TCP        
     * tcp_memory_pressure  1。
     */
    if ((parent_status > SOFT_LIMIT) || allocated > sk_prot_mem_limits(sk, 1))
        sk_enter_memory_pressure(sk);

    /* Over hard limit (we or our parents).
     *   TCP            sysctl_tcp_mem[2],   sock       
     * sk->sk_sndbuf。
     */
    if ((parent_status == OVER_LIMIT || (allocated > sk_prot_mem_limits(sk, 2)))
        goto suppress_allocation;

    /* guarantee minimum buffer size under pressure */
    /*            ,    sock   sysctl_tcp_{r,w}mem[0]      */
    if (kind == SK_MEM_RECV) {
        if (atomic_read(&sk->sk_rmem_alloc) < prot->sysctl_rmem[0])
            return 1;

    } else { /* SK_MEM_SEND */
        if (sk->sk_type == SOCK_STREAM) {
            if (sk->sk_wmem_queued < prot->sysctl_wmem[0])
                return 1;
        } else if (atomic_read(&sk->sk_wmem_alloc) < prot->sysctl_wmem[0])
            return 1;
    }

   if (sk_has_memory_pressure(sk)) {
        int alloc;

        /*   TCP         ,     */
        if (! sk_under_memory_pressure(sk))
            return 1;

        alloc = sk_sockets_allocated_read_positive(sk); /*     TCP socket   */

        /*     socket           ,    */
        if (sk_prot_mem_limits(sk, 2) > alloc * sk_mem_pages(sk->sk_wmem_queued +
             atomic_read(&sk->sk_rmem_alloc) + sk->sk_forward_alloc))
            return 1;
    }

suppress_allocation:
    if (kind == SK_MEM_SEND && sk->sk_type == SOCK_STREAM) {

        /*   sock        ,  sndbuf             ,
         *          MIN_TRUESIZE。
         */
        sk_stream_moderate_sndbuf(sk); 

        /* Fail only if socket is under its sndbuf.
         * In this case we cannot block, so that we have to fail.
         */
        if (sk->sk_wmem_queued + size >= sk->sk_sndbuf)
            return 1;
    } 
    trace_sock_exceed_buf_limit(sk, prot, allocated);
 
    /*     ,                ,            */
    /* Alas. Undo changes. */
    sk->sk_forward_alloc -= amt * SK_MEM_QUANTUM;
    sk_memory_allocated_sub(sk, amt);
    return 0;
}

/*     amt     ,     */
static inline int sk_mem_pages(int amt)
{
    return (amt + SK_MEM_QUANTUM - 1) >> SK_MEM_QUANTUM_SHIFT;
}
#define SK_MEM_QUANTUM ((int) PAGE_SIZE)

/*       TCP    tcp_memory_allocated,     */
static inline long sk_memory_allocated_add(struct sock *sk, int amt, int *parent_status)
{
    struct proto *prot = sk->sk_prot;

    /* Cgroup  ,     */
    if (mem_cgroup_sockets_enabled && sk->sk_cgrp) {
        ...
    }

    return atomic_long_add_return(amt, prot->memory_allocated);
}

sysctl_tcp_mem[0]:最小値
sysctl_tcp_mem[1]:圧力値
sysctl_tcp_mem[2]:最大値
static inline long sk_prot_mem_limits(const struct sock *sk, int index)
{
    long *prot = sk->sk_prot->sysctl_mem;

    /* Cgroup   */
    if (mem_cgroup_sockets_enabled && sk->sk_cgrp)
        prot = sk->sk_cgrp->sysctl_mem;

    return prot[index];
}

 
送信キャッシュが不足してスリープ待機
 
tcp_sendmsg()では、送信キューの合計サイズsk_wmem_queuedが送信キャッシュの上限sk_以上であるsndbuf,
あるいは,送信キャッシュでまだ送信されていないデータ量がユーザの設定値を超えると,スリープ待ちに入る.
リクエスト送信キャッシュが失敗すると、スリープ待ちも行われます.
 
(1)判断条件
sk_stream_memory_free()は、sockに残りの送信キャッシュがあるかどうかを判断するために使用される.
static inline bool sk_stream_memory_free(const struct sock *sk)
{
    if (sk->sk_wmem_queued >= sk->sk_sndbuf)
        return false;

    return sk->sk_prot->stream_memory_free ? sk->sk_prot->stream_memory_free(sk) : true;
}

static inline bool tcp_stream_memory_free(const struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    u32 notsent_bytes = tp->write_seq - tp->snd_nxt; /*           */

    /*         ,       ,    。
     *                  。
     */
    return notsent_bytes < tcp_notsent_lowat(tp);
}

TCPを使用している場合NOTSENT_LOWATオプションは、ユーザーが設定した値を使用します.
そうでなければsysctl_を使用しますtcp_notsent_lowat、デフォルトは無限大です.
static inline u32 tcp_notsent_lowat(const struct tcp_sock *tp)
{
    return tp->notsent_lowat ?: sysctl_tcp_notsent_lowat;
}

 
(2)睡眠待ち
送信キューの合計サイズsk_がwmem_queuedが送信キャッシュの上限sk_以上であるsndbuf,
あるいは,送信キャッシュでまだ送信されていないデータ量がユーザの設定を超えて待機する.
TCP層のメモリ不足により申請送信キャッシュが失敗した場合も,スリープ待ちを行う.
 
Q:睡眠はどのくらい待ちますか?
2つの状況に分ける必要があります.
1.待機の原因はTCP層のメモリ不足である.
    関数に入ると、sockの送信キャッシュが上限に達しているかどうかを判断します.
    このときsockにまだ送信キャッシュ額がある場合は、TCP層のメモリが不足して送信キャッシュ申請が失敗したことを示す、
    待ち時間を2~202 msの擬似乱数に設定し,タイムアウト後に待ちを終了する.
2.待機の原因はsockの送信キャッシュ不足である.
    スリープ中に、使用可能な送信キャッシュがある場合、プロセスは起動され、待機が終了します.
    タイムアウト時間に達していない場合は、エラーが返されます.
/* Wait for more memory for a socket
 * @sk: socket to wait for memory
 * @timeo_p: for how long
 */

int sk_stream_wait_memory(struct sock *sk, long *timeo_p)
{
    int err = 0;
    long vm_wait = 0;
    long current_timeo = *timeo_p;
    DEFINE_WAIT(wait); /*         */

    /*   sock        ,   TCP        。
     *           2~202ms     。
     */
    if (sk_stream_memory_free(sk))
        current_timeo = vm_wait = (prandom_u32() % (HZ / 5)) + 2;

    while (1) {
        /*        ,          */
        set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

        /*         socket      ,        TASK_INTERRUPTIBLE */
        prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);

        /*        ,          ,    -EPIPE */
        if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
            goto do_error;

        /*        ,       ,  -EAGAIN */
        if (! *timeo_p)
            goto do_nonblock;

        /*            ,            -ERESTARTSYS,
         *     -EINTR.
         */
        if (signal_pending(current))
            goto do_interrupte;
 
        clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

        /*   sock           。         :
         * 1.        sock       。 
         * 2.        TCP     ,       vm_wait  0。 
         */
        if (sk_stream_memory_free(sk) && ! vm_wait)
            break;
 
        set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
        sk->sk_write_pending++;

        /*        */
        sk_wait_event(sk, ¤tt_timeo, sk->sk_err ||
            (sk->sk_shutdown & SEND_SHUTDOWN) || 
            (sk_stream_memory_free(sk) && ! vm_wait)); 
        sk->sk_write_pending--;

        /*   vm_wait  0,  2~202ms ,  vm_wait    */
        if (vm_wait) {
            vm_wait -= current_timeo;
            current_timo = *timeo_p;

            if (current_timeo != MAX_SCHEDULE_TIMEOUT &&
                (current_timeo -= vm_wait) < 0)
                current_timeo = 0;

            vm_wait = 0;
        }

        *timeo_p = current_timeo; /*             */
    }

out:
    /*              ,          TASK_RUNNING */
    finish_wait(sk_sleep(sk), &wait);
    return err;

do_error:
    err = -EPIPE;
    goto out;

do_nonblock:
    err = -EAGAIN;
    goto out;

do_interrupted:
    err = sock_intr_errno(*timeo_p);
    goto out;
}

 
送信キャッシュ書き込み可能イベントにより起動
 
sk->sk_write_スペースの例はsock_def_write_space().
SOCK_だったらSTREAMタイプの場合、関数ポインタの値がsk_に更新されますstream_write_space().
sk_stream_write_space()のTCPでの呼び出しパスは、次のとおりです.
tcp_rcv_established/tcp_rcv_state_process
    tcp_data_snd_check
        tcp_check_space
            tcp_new_space
static void tcp_check_space(struct sock *sk)
{
    /*         skb     */
    if (sock_flag(sk, SOCK_QUEUE_SHRUNK)) {

        sock_reset_flag(sk, SOCK_QUEUE_SHRUNK);

        /*           ,          */
        if (sk->sk_socket && test_bit(SOCK_NOSPACE, &sk->sk_socket->flags))
            tcp_new_space(sk); /*        */
    }
}
/* When incoming ACK allowed to free some skb from write_queue,
 * we remember this event in flag SOCK_QUEUE_SHRUNK and wake up socket
 * on the exit from tcp input handler.
 */
static void tcp_new_space(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (tcp_should_expand_sndbuf(sk)) {
        tcp_sndbuf_expand(sk);
        tp->snd_cwnd_stamp = tcp_time_stamp;
    }

    /*                 */
    sk->sk_write_space(sk);
}
void sk_stream_write_space(struct sock *sk)
{
    struct socket *sock = sk->sk_socket;
    struct socket_wq *wq; /*             */

    /*                    1/3,                */
    if (sk_stream_is_writeable(sk) && sock) {
        clear_bit(SOCK_NOSPACE, &sock->flags); /*             */

        rcu_read_lock();
        wq = rcu_dereference(sk->sk_wq); /* socket             */
        if (wq_has_sleeper(wq)) /*          ,          */
            wake_up_interruptible_poll(&wq->wait, POLLOUT | POLLWRNORM | POLLWRBAND);

        /*          ,        。
         *   sock             ,       SIGIO  ,         
         *           。
         */
        if (wq && wq->fasync_list && !(sk->sk_shutdown & SEND_SHUTDOWN))
            sock_wake_async(sock, SOCK_WAKE_SPACE, POLL_OUT);

        rcu_read_unlock();
    }
}

#define wake_up_interruptible_poll(x, m) \
    __wake_up(x, TASK_INTERRUPTIBLE, 1, (void *) (m))

残りの送信キャッシュが送信キャッシュの上限の1/3より大きく、まだ送信されていないデータが一定値未満である場合、送信がトリガーされます
書き込み可能なイベントをキャッシュします.
static inline bool sk_stream_is_writeable(const struct sock *sk)
{
    return sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) &&
        sk_stream_memory_free(sk);
}

static inline int sk_stream_wspace(const struct sock *sk)
{
    return sk->sk_sndbuf - sk->sk_wmem_queued;
}

static inline int sk_stream_min_wspace(const struct sock *sk)
{
    return sk->sk_wmem_queued >> 1;
}

まだ送信されていないデータが十分かどうかを確認し、ユーザーが設定した値を超えると、送信キャッシュ書き込み可能なイベントをトリガーする必要はありません.
メモリを使いすぎないようにします.
static inline bool sk_stream_memory_free(const struct sock *sk)
{
    if (sk->sk_wmem_queued >= sk->sk_sndbuf)
        return false;

    return sk->sk_prot->stream_memory_free ? sk->sk_prot->stream_memory_free(sk) : true;
}

static inline bool tcp_stream_memory_free(const struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    u32 notsent_bytes = tp->write_seq - tp->snd_nxt; /*           */

    /*         ,       ,             。
     *                  。
     */
    return notsent_bytes < tcp_notsent_lowat(tp);
}

TCPを使用している場合NOTSENT_LOWATオプションは、ユーザーが設定した値を使用します.
そうでなければsysctl_を使用しますtcp_notsent_lowat、デフォルトは無限大です.
static inline u32 tcp_notsent_lowat(const struct tcp_sock *tp)
{
    return tp->notsent_lowat ?: sysctl_tcp_notsent_lowat;
}