• socket里面那个又爱又恨的锁


    查一个问题:结果看了一下软中断以及系统 所耗cpu,心中满是伤痕啊-------

    perf 结果一眼可以看到:主要是锁

     那么这个lock 是用来干什么的呢?? 

    A:TCP socket的使用者有两种:进程(线程)和软中断。同一时间可能会有两个进程(线程),或位于不同CPU的两个软中断,或进程(线程)与软中断访问同一个socket。所以为了使socket在同一时刻只能被一个使用者访问,那么互斥机制是如何实现的呢?----是使用锁完成的, 也就是这个锁lock sock

    struct sock {
    ...
        socket_lock_t        sk_lock;
    ...
    }
    /* This is the per-socket lock.  The spinlock provides a synchronization
     * between user contexts and software interrupt processing, whereas the
     * mini-semaphore synchronizes multiple users amongst themselves.
     */
    typedef struct {
        spinlock_t        slock;//该自旋锁是用于同步进程上下文和软中断上下文的关键;
        int            owned;//取值为1表示该传输控制块已经被进程上下文锁定,取值为0表示没有被进程上下文锁定;
        wait_queue_head_t    wq;//wq:等待队列,当进程上下文需要持有该传输控制块,但是其当前又被软中断锁定时,进程会等待
        /*
         * We express the mutex-alike socket_lock semantics
         * to the lock validator by explicitly managing
         * the slock as a lock variant (in addition to
         * the slock itself):
         */
    #ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map dep_map;
    #endif
    } socket_lock_t;

    进程上下文的访问操作

    进程上下文在访问该传输控制块之前需要调用lock_sock()锁定,在访问完成后调用release_sock()将其释放

    //__lock_sock()将进程挂到sk->sk_lock中的等待队列wq上,直到没有进程再持有该该传输
        //控制块时返回。注意:调用时已经持有sk->sk_lock,睡眠之前释放锁,返回前再次持有锁
        static void __lock_sock(struct sock *sk)
        {
            //定义一个等待队列结点
            DEFINE_WAIT(wait);
        
            //循环,直到sock_owned_by_user()返回0才结束
            for (;;) {
                //将调用进程挂接到锁的等待队列中
                prepare_to_wait_exclusive(&sk->sk_lock.wq, &wait,
                            TASK_UNINTERRUPTIBLE);
                //释放锁并打开下半部
                spin_unlock_bh(&sk->sk_lock.slock);
                //执行一次调度
                schedule();
                //再次被调度到时会回到这里,首先持锁并关闭下半部
                spin_lock_bh(&sk->sk_lock.slock);
                //如果没有进程再次持有该传输控制块,那么返回
                if (!sock_owned_by_user(sk))
                    break;
            }
            finish_wait(&sk->sk_lock.wq, &wait);
        }
    
    void lock_sock_nested(struct sock *sk, int subclass)
    {
        might_sleep();//调用lock_sock()可能会导致休眠---------注意
        spin_lock_bh(&sk->sk_lock.slock);//持有自旋锁并关闭下半部
        //如果owned不为0,说明有进程持有该传输控制块,调用__lock_sock()等待,挂在等待队列上休眠
        if (sk->sk_lock.owned)
            __lock_sock(sk);
        //上面__lock_sock()返回后现场已经被还原,即持有锁并且已经关闭下半部。
        //将owned设置为1,表示本进程现在持有该传输控制块
        sk->sk_lock.owned = 1;
        //释放锁但是没有开启下半部-----还是关闭了 软中断
        spin_unlock(&sk->sk_lock.slock);
        /*
         * The sk_lock has mutex_lock() semantics here:------------这是干啥?

    We express the mutex-alike socket_lock semanticsto the lock validator by explicitly managingthe slock as a lock variant
    (in addition tothe slock itself): ------不懂*/
        mutex_acquire(&sk->sk_lock.dep_map, subclass, 0, _RET_IP_);
        
        local_bh_enable();//开启下半部  软中断
    }

    owned为1之后不再持有自旋锁,也已经开启软中断。-----作用是协议栈的处理并非立刻就能结束,如果只是简单的在开始起持有自旋锁并关闭下半部,在处理结束时释放自旋锁并打开下半部,会降低系统性能,同时长时间关闭软中断,还可能使得网卡接收软中断得不到及时调用,导致丢包

    release_sock()

    进程上下文在结束传输控制块的操作之后,需要调用release_sock()释放传输控制块。释放的核心是将owned设置为0并通知其它等待该传输控制块的进程

    void release_sock(struct sock *sk)
    {
        /*
         * The sk_lock has mutex_unlock() semantics:
         */
        //调试相关,忽略
        mutex_release(&sk->sk_lock.dep_map, 1, _RET_IP_);
    
        //获取自旋锁并关闭下半部
        spin_lock_bh(&sk->sk_lock.slock);
        //如果后备队列不为空,则调用__release_sock()处理后备队列中的数据包,见数据包的接收过程
        if (sk->sk_backlog.tail)
            __release_sock(sk);
        //设置owned为0,表示调用者不再持有该传输控制块
        sk->sk_lock.owned = 0;
        //如果等待队列不为空,则唤醒这些等待的进程
        if (waitqueue_active(&sk->sk_lock.wq))
            wake_up(&sk->sk_lock.wq);
        //释放自旋锁并开启下半部
        spin_unlock_bh(&sk->sk_lock.slock);
    }

    (1)软中断先访问进程后访问

            这时软中断已经获取了自旋锁,进程在获取自旋锁时会等待,软中断释放锁时进程才能成功获取锁。

    (2)进程先访问软中断后访问

            进程获取自旋锁(关软中断,防止被软中断打断)时会将sk->sk_lock.owned设置为1后释放自旋锁并开启软中断,然后执行对socket的访问。这时如果软中断发生,则进程的执行被中止,然后软中断中 将数据放到接收后备队列中

    int tcp_v4_rcv(struct sk_buff *skb)
    {
    ...
    process:
    ...
        //获取sk->sk_lock.slock自旋锁
        bh_lock_sock_nested(sk);
        //如果没有进程锁定该传输控制块,将数据接收到奥prequeue或者receive_queue中
        if (!sock_owned_by_user(sk)) {
            if (!tcp_prequeue(sk, skb))
                ret = tcp_v4_do_rcv(sk, skb);
        } else
            //如果进程已经锁定该传输控制块,那么先将数据接收到后备队列中----赶紧退出 让进程处理 然后在release的时候 处理后备队列
            sk_add_backlog(sk, skb);
        //释放自旋锁
        bh_unlock_sock(sk);
    ...
    
    /* BH context may only use the following locking interface. */
    #define bh_lock_sock(__sk)    spin_lock(&((__sk)->sk_lock.slock))
    #define bh_lock_sock_nested(__sk) 
                    spin_lock_nested(&((__sk)->sk_lock.slock), 
                    SINGLE_DEPTH_NESTING)
    #define bh_unlock_sock(__sk)    spin_unlock(&((__sk)->sk_lock.slock))

    所以这个锁 貌似 规避不了,那么怎么处理呢???

  • 相关阅读:
    C++Primer第5版学习笔记(三)
    C++Primer第5版学习笔记(二)
    C++Primer第5版学习笔记(一)
    A*寻路算法的探寻与改良(三)
    A*寻路算法的探寻与改良(二)
    A*寻路算法的探寻与改良(一)
    html5页面js判断是否安装app,以及判断是否在app内部打开html5页面
    结合prototype和xmlhttprequest封装ajax请求
    前端常见的性能优化和浏览器兼容性问题
    常见的HTTP状态码
  • 原文地址:https://www.cnblogs.com/codestack/p/13572350.html
Copyright © 2020-2023  润新知