shutdown-rd 分析

以前的印象中,shutdown  rd后,就不能从fd里面读到数据,今天测试时和预计的不一样!

可以看到shutdown后 rd后,还是可以read 数据。分析tcp_rcvmsg可以看到确实能读到数据

 

// 定义一个接收TCP消息的函数
static int tcp_recvmsg_locked(struct sock *sk,  // 指向表示网络连接的sock结构体的指针
                              struct msghdr *msg, // 指向msghdr结构体的指针,用来存储接收的消息头
                              size_t len, // 指定希望接收的字节数
                              int flags, // 指定接收行为的标志位,如MSG_PEEK等
                              struct scm_timestamping_internal *tss, // 用于存储时间戳信息的结构体
                              int *cmsg_flags) // 用来输出控制消息标志的整数指针
{
    // 将传入的sock结构体转换为tcp_sock结构体
    struct tcp_sock *tp = tcp_sk(sk);
    int copied = 0; // 已复制的字节数
    u32 peek_seq; // 用于MSG_PEEK操作时的序列号
    u32 *seq; // 指向当前处理的数据序列号
    unsigned long used; // 未使用的变量,可以移除
    int err; // 错误处理变量
    int target; // 最小读取字节数
    long timeo; // 接收超时时间
    struct sk_buff *skb, *last; // 指向数据包的指针
    u32 urg_hole = 0; // 紧急数据处理时使用的变量

    // 如果socket未连接,则设置错误状态为ENOTCONN
    err = -ENOTCONN;
    if (sk->sk_state == TCP_LISTEN) // 如果socket处于监听状态
        goto out; // 跳转到函数末尾处理错误

    // 如果设置了接收消息队列的标志
    if (tp->recvmsg_inq) {
        *cmsg_flags = TCP_CMSG_INQ; // 设置控制消息标志
        msg->msg_get_inq = 1; // 设置消息标志
    }
    // 设置接收超时时间
    timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);

    // 处理紧急数据
    if (flags & MSG_OOB) // 如果有MSG_OOB标志,则处理外带数据
        goto recv_urg; // 跳转到处理紧急数据的代码段

    // 如果在修复模式下
    if (unlikely(tp->repair)) {
        err = -EPERM; // 设置错误为"操作不允许"
        if (!(flags & MSG_PEEK)) // 如果没有设置MSG_PEEK标志
            goto out; // 直接处理错误

        if (tp->repair_queue == TCP_SEND_QUEUE) // 如果是发送队列
            goto recv_sndq; // 跳转到处理发送队列的代码段

        err = -EINVAL; // 设置错误为"无效参数"
        if (tp->repair_queue == TCP_NO_QUEUE) // 如果没有队列
            goto out; // 处理错误

        // 处理常见的接收队列中的MSG_PEEK
    }

    // 设置序列号指针
    seq = &tp->copied_seq;
    if (flags & MSG_PEEK) { // 如果设置了MSG_PEEK标志
        peek_seq = tp->copied_seq; // 复制当前序列号
        seq = &peek_seq; // 指向peek_seq
    }

    // 设置目标读取字节数
    target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);

    /* 继续处理接收消息循环 */
    do {
        u32 offset; // 计算从skb中复制数据的偏移量

        /* 如果当前是紧急数据,并且已经有复制的数据或者有挂起的SIGURG信号 */
        if (unlikely(tp->urg_data) && tp->urg_seq == *seq) {
            if (copied) // 如果已经复制了数据,则退出循环
                break;
            if (signal_pending(current)) { // 如果有挂起的信号
                copied = timeo ? sock_intr_errno(timeo) :
                         -EAGAIN; // 根据超时设置错误码
                break;
            }
        }

        /* 获取接收队列中的下一个数据包 */
        last = skb_peek_tail(
            &sk->sk_receive_queue); // 获取接收队列的最后一个skb
        skb_queue_walk(&sk->sk_receive_queue, skb)
        { // 遍历接收队列中的每个skb
            last = skb;
            /* 检查序列号,防止异常 */
            if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
                 "TCP recvmsg seq # bug: copied %X, seq %X, rcvnxt %X, fl %X\n",
                 *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
                 flags))
                break;

            offset = *seq - TCP_SKB_CB(skb)->seq; // 计算偏移量
            if (unlikely(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) {
                pr_err_once(
                    "%s: found a SYN, please report !\n",
                    __func__);
                offset--; // 如果是SYN包,则偏移量减1
            }
            if (offset <
                skb->len) // 如果偏移量小于skb长度,说明找到了合适的skb
                goto found_ok_skb;
            if (TCP_SKB_CB(skb)->tcp_flags &
                TCPHDR_FIN) // 如果遇到FIN标志,则处理结束
                goto found_fin_ok;
            WARN(!(flags & MSG_PEEK),
                 "TCP recvmsg seq # bug 2: copied %X, seq %X, rcvnxt %X, fl %X\n",
                 *seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt, flags);
        }

        /* 如果有后台数据,现在尝试处理它 */
        if (copied >= target && !READ_ONCE(sk->sk_backlog.tail))
            break;

        if (copied) {
            if (!timeo || sk->sk_err || sk->sk_state == TCP_CLOSE ||
                (sk->sk_shutdown & RCV_SHUTDOWN) ||
                signal_pending(current))
                break;
        } else {
            if (sock_flag(sk, SOCK_DONE))
                break;

            if (sk->sk_err) {
                copied = sock_error(
                    sk); // 如果有socket错误,设置copied为错误码
                break;
            }

            if (sk->sk_shutdown &
                RCV_SHUTDOWN) // 如果socket关闭了接收功能
                break;

            if (sk->sk_state == TCP_CLOSE) {
                /* 用户试图从未连接的socket读取时出现 */
                copied = -ENOTCONN;
                break;
            }

            if (!timeo) {
                copied = -EAGAIN; // 没有超时设置,返回EAGAIN
                break;
            }

            if (signal_pending(current)) { // 检查是否有信号等待处理
                copied = sock_intr_errno(
                    timeo); // 根据超时返回错误
                break;
            }
        }

        if (copied >= target) {
            /* 不睡眠,直接处理后台数据 */
            __sk_flush_backlog(sk);
        } else {
            tcp_cleanup_rbuf(sk, copied); // 清理接收缓冲区
            err = sk_wait_data(sk, &timeo, last); // 等待数据到达
            if (err < 0) {
                err = copied ?: err; // 如果出错,设置错误码
                goto out;
            }
        }

        if ((flags & MSG_PEEK) &&
            (peek_seq - copied - urg_hole != tp->copied_seq)) {
            net_dbg_ratelimited(
                "TCP(%s:%d): Application bug, race in MSG_PEEK\n",
                current->comm, task_pid_nr(current));
            peek_seq =
                tp->copied_seq; // 更新peek序列号以处理MSG_PEEK竞态条件
        }
        continue;
found_ok_skb:
        // 计算可以使用的数据量
        used = skb->len - offset;
        if (len < used)
            used = len;

        // 检查当前位置是否有紧急数据
        if (unlikely(tp->urg_data)) {
            u32 urg_offset = tp->urg_seq - *seq;
            if (urg_offset < used) {
                if (!urg_offset) {
                    if (!sock_flag(sk, SOCK_URGINLINE)) {
                        WRITE_ONCE(
                            *seq,
                            *seq + 1); // 更新序列号,跳过紧急指针
                        urg_hole++; // 紧急数据洞增加
                        offset++; // 偏移量增加
                        used--; // 可用数据减少
                        if (!used)
                            goto skip_copy; // 如果没有可用数据,则跳过复制
                    }
                } else
                    used = urg_offset; // 如果紧急数据在中间,调整可用数据长度
            }
        }

        if (!(flags & MSG_TRUNC)) {
            err = skb_copy_datagram_msg(
                skb, offset, msg,
                used); // 从skb复制数据到用户msg
            if (err) {
                // 如果复制出错
                if (!copied)
                    copied =
                        -EFAULT; // 如果还没复制任何数据,则返回错误
                break;
            }
        }

        WRITE_ONCE(*seq, *seq + used); // 更新序列号
        copied += used; // 更新已复制的总字节数
        len -= used; // 减少还需复制的长度

        tcp_rcv_space_adjust(sk); // 调整接收窗口

skip_copy:
        if (unlikely(tp->urg_data) &&
            after(tp->copied_seq, tp->urg_seq)) {
            WRITE_ONCE(tp->urg_data,
                   0); // 如果处理完紧急数据,清除紧急数据标志
            tcp_fast_path_check(sk); // 检查是否可以快速处理路径
        }

        if (TCP_SKB_CB(skb)->has_rxtstamp) {
            tcp_update_recv_tstamps(
                skb, tss); // 如果有接收时间戳,更新时间戳
            *cmsg_flags |= TCP_CMSG_TS; // 设置控制消息标志
        }

        if (used + offset < skb->len)
            continue; // 如果skb中还有未处理的数据,继续处理

        if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
            goto found_fin_ok; // 如果遇到FIN标志,处理连接关闭
        if (!(flags & MSG_PEEK))
            tcp_eat_recv_skb(sk, skb); // 如果不是peek操作,释放skb
        continue;

found_fin_ok:
        // 处理FIN包
        WRITE_ONCE(*seq, *seq + 1); // 更新序列号
        if (!(flags & MSG_PEEK))
            tcp_eat_recv_skb(sk, skb); // 如果不是peek操作,如果一个skb内数据被拷贝完, 释放skb 
        break;
    } while (len > 0);

    // 清理读取的数据,发送ACK
    tcp_cleanup_rbuf(sk, copied);
    return copied; // 返回复制的字节数

out:
    return err; // 返回错误码

recv_urg:
    err = tcp_recv_urg(sk, msg, len, flags); // 处理紧急数据接收
    goto out;

recv_sndq:
    err = tcp_peek_sndq(sk, msg, len); // 处理发送队列的peek
    goto out;
}

 

 

也就是正常逻辑为:found_ok_skb---->skb_copy_datagram_msg----->skip_copy--->continue-->继续查找ok_skb,如果找不到,则根据copied值来决定不同分支情况处理

如果是被RCV_SHUTDOWN则会触发,found_ok_skb---->skb_copy_datagram_msg----->skip_copy--->continue-->继续查找ok_skb,如果找不到,, copied大于0, 进入

if (sk->sk_shutdown & RCV_SHUTDOWN)  --->break; 最后处理完!

 

 

// Verify behavior for the sequence:
// shutdown(SHUT_RD), receive, send, shutdown(SHUT_WR), close().

`../common/defaults.sh`

// Initialize a server socket.
    0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
   +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
   +0 bind(3, ..., ...) = 0
   +0 listen(3, 1) = 0

   +0 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
   +0 > S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 8>
   +0 < . 1:1(0) ack 1 win 257

   +0 accept(3, ..., ...) = 4

  +0.1 shutdown(4, SHUT_RD) = 0

   +0.1 read(4, ..., 1000) = 0

// You would think that after SHUT_RD we would respond to incoming
// data with a RST and not queue the data for reading, but we actually
// ACK the data, enqueue it for reading, and can read() the data.
// AFAICT in 2003 Andi Kleen seems to have decided that this case is too
// obscure to slow down the fast path for receiving and reading data:
//   http://marc.info/?l=linux-netdev&m=105774722214242&w=2
// So....
// Verify that receiving and reading still works.
   +0 < . 1:1001(1000) ack 1 win 257
   +0 > . 1:1(0) ack 1001
   +0`sleep 2`
   +0 read(4, ..., 1000) = 1000

    +0 < . 1001:2001(1000) ack 1 win 257
   +0 > . 1:1(0) ack 2001
    +0`sleep 2`
    +0 read(4, ..., 1000) = 1000

// Verify that writing and sending still works.
 +.01 write(4, ..., 1000) = 1000
   +.01 > P. 1:1001(1000) ack 2001
   +0 < . 2001:2001(0) ack 1001 win 257

 +.01 shutdown(4, SHUT_WR) = 0
   +0 > F. 1001:1001(0) ack 2001
   +0 < .  2001:2001(0) ack 1002 win 257
   +0 write(4, ..., 1000) = -1 EPIPE (Broken pipe)

 +.01 close(4) = 0

 +.01 < F. 2001:2001(0) ack 1002 win 257
   +0 > .  1002:1002(0) ack 2002

 

posted @ 2024-12-26 20:17  codestacklinuxer  阅读(26)  评论(0)    收藏  举报