• 处于ESTABLISHED 状态的socket 却没有进程信息


    接《一次docker中的nginx进程响应慢问题定位记录》

    在排查这个问题的时候,我先使用netstat 去查看,看到底是内核协议栈的连接请求没给到进程,还是进程accept链路慢了,或者recv数据慢了,记录如下:

    netstat -anpl |grep -i 57372
    tcp        0      0 127.0.0.1:57372         127.0.0.1:7010          ESTABLISHED 15044/curl
    tcp       86      0 127.0.0.1:7010          127.0.0.1:57372         ESTABLISHED -
    

     可以看到,curl发送的请求,内核协议栈是已经接受了,但是对应的链路有个小小的细节,那就是establish状态的链接,进程号查不到。

    我以前没有看过netstat的源码,只是通过strace大概知道netstat是去读取 /proc/net/tcp (或者/proc/net/tcp6) 来获取链接信息的,

     cat /proc/net/tcp
      sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
       0: 00000000:18A7 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31915 1 ffff88057f2b9000 100 0 0 10 0
       1: 0100007F:158E 00000000:0000 0A 00000000:00000000 00:00000000 00000000  2000        0 24924 1 ffff8800acf58000 100 0 0 10 0
       2: 00000000:006F 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 9479 1 ffff880182c80000 100 0 0 10 0
       3: 00000000:18B0 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31914 1 ffff88057f2b8800 100 0 0 10 0
       4: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 17065 1 ffff88056d3d8000 100 0 0 10 0
       5: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 28681 1 ffff8802b8bd8000 100 0 0 10 0
       6: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 26547 1 ffff88057f2b8000 100 0 0 10 0
       7: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 30024 1 ffff88058e678000 100 0 0 10 0
    

      但是netstat -anpl怎么关联最后一列的进程信息不清楚,下载一份netstat源码,发现它是在循环遍历/proc下的进程,然后读取/proc/进程/fd/xx ,

    (gdb) p readlink(line, lname, sizeof(lname) - 1)
    $34 = 14
    (gdb) p lname
    $35 = "socket:[31915]00erfd]00d00000000000000"

      然后通过调用extract_type_1_socket_inode 之类的来隔离出上面的 socket 的fd为 31915 ,读取之后,将对应的进程和进程名格式化,然后和socket的inode 存在一个map中。

    遍历所有的/proc下的进程,这个过程太重,存在map之后,通过读取 /proc/net/tcp 中符合要求的socket,将其fd那一列的数字作为索引到 map 中去查找进程号,然后打印出来。

    Breakpoint 2, finish_this_one (uid=0, inode=31915, timers=0x7fffffffc010 "") at netstat.c:593
    593 {
    (gdb) bt
    #0  finish_this_one (uid=0, inode=31915, timers=0x7fffffffc010 "") at netstat.c:593
    #1  0x0000000000406eb7 in tcp_do_one (lnr=lnr@entry=1,
        line=line@entry=0x7fffffffc190 "   0: 00000000:18A7 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31915                         1 ffff88057f2b9000 100 0 0 10 0", ' ' <repeats 21 times>, "
    ", prot=prot@entry=0x41413f "tcp") at netstat.c:1056
    #2  0x000000000040728f in tcp_info () at netstat.c:1061
    #3  0x0000000000402956 in main (argc=2, argv=<optimized out>) at netstat.c:2172
    (gdb) fr 1
    #1  0x0000000000406eb7 in tcp_do_one (lnr=lnr@entry=1,
        line=line@entry=0x7fffffffc190 "   0: 00000000:18A7 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31915                         1 ffff88057f2b9000 100 0 0 10 0", ' ' <repeats 21 times>, "
    ", prot=prot@entry=0x41413f "tcp") at netstat.c:1056
    1056            finish_this_one(uid,inode,timers);
    (gdb) p line
    $46 = 0x7fffffffc190 "   0: 00000000:18A7 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31915 1 ffff88057f2                        b9000 100 0 0 10 0", ' ' <repeats 21 times>, "
    "
    (gdb) p inode
    $47 = 31915  

    知道了这个关联的原理之后,基本就可以确定,我们应用进程 并没有及时来 accept 。

    我们知道,accept的调用原理是:accept() -> sys_accept4() -> inet_accept() -> inet_csk_accept()

    可以看到,accept作用是返回一个已经建立连接的socket(即经过了三次握手),这个过程和协议栈是异步的,accept()并不亲自去处理三次握手过程,而只是监听icsk_accept_queue队列,当有socket经过了三次握手,它就会被加到icsk_accept_queue中,所以accept要做的就是等待队列中插入socket,然后被唤醒并返回这个socket。而三次握手的过程完全是协议栈本身去完成的。

    所以我们看到了上面的,已经establish的链路,由于业务进程没有及时去accept 导致可以看到 没有进程关联的链路。

    对于阻塞的socket,accept是一直等待的,则会在下面的 sk_state_change 中被唤醒。

    	case TCP_SYN_RECV:
    		if (!acceptable)
    			return 1;
    
    		/* Once we leave TCP_SYN_RECV, we no longer need req
    		 * so release it.
    		 */
    		if (req) {
    			synack_stamp = tcp_rsk(req)->snt_synack;
    			tp->total_retrans = req->num_retrans;
    			reqsk_fastopen_remove(sk, req, false);
    		} else {
    			synack_stamp = tp->lsndtime;
    			/* Make sure socket is routed, for correct metrics. */
    			icsk->icsk_af_ops->rebuild_header(sk);
    			tcp_init_congestion_control(sk);
    
    			tcp_mtup_init(sk);
    			tp->copied_seq = tp->rcv_nxt;
    			tcp_init_buffer_space(sk);
    		}
    		smp_mb();
    		tcp_set_state(sk, TCP_ESTABLISHED);//设置为establish,注意这个时候,还没通知用户accept
    		sk->sk_state_change(sk);//这个sk是新创建的sock,而不是listen的sk,唤醒等待在 sk->sk_wq 上的进程
    

      而对于非阻塞的socket,则需要在poll之类的异步收请求函数中去调用对应listen socket 去 accept 这个连接。这个nginx没有及时去accept链路的原因是,调用的lua模块出现大的死循环。

    结论:

    1.不要使用netstat,太重,大家一定要使用ss替代。

    2.在accept的调用之前,已经处于establish状态的socket的inode也是没有关联到进程的。

    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    《团队-爬取豆瓣Top250-团队一阶段互评》
    团队-爬虫豆瓣top250项目-开发文档
    结对总结
    课后作业-阅读任务-阅读提问-2
    2017-10-06-构建之法:现代软件工程-阅读笔记
    结对-结对编项目贪吃蛇-开发过程
    团队-爬取豆瓣电影TOP250-开发环境搭建过程
    个人-GIT使用方法
    课后作业-阅读任务-阅读提问-1
    课后作业-阅读任务-阅读笔记-1
  • 原文地址:https://www.cnblogs.com/10087622blog/p/9362474.html
Copyright © 2020-2023  润新知