11.3 TCP 커 널 동기 화

11.3.1 자물쇠 의 구조        2.1 Socket 시스템 에서 호출 되 었 습 니 다. TCP socket 은 커 널 에 데이터 구 조 를 가지 고 있 습 니 다. 이 데이터 구 조 는 두 개 이상 의 사용자 가 동시에 방문 할 수 없습니다. 그렇지 않 으 면 데이터 가 일치 하지 않 아 심각 한 문제 가 발생 할 수 있 습 니 다.Linux 에서 TCP socket 의 사용 자 는 두 가지 가 있 습 니 다. 프로 세 스 (스 레 드) 와 소프트 인 터 럽 트 입 니 다.같은 시간 에 두 개의 프로 세 스 (스 레 드) 가 있 거나 서로 다른 CPU 에 있 는 두 개의 소프트 인 터 럽 트 가 있 거나 프로 세 스 (스 레 드) 와 소프트 인 터 럽 트 가 같은 socket 에 접근 할 수 있 습 니 다.socket 이 같은 시각 에 한 사용자 만 접근 할 수 있 는 이상 상호 배척 체 제 는 어떻게 실현 되 었 습 니까?자물쇠 로 완성 되 었 습 니 다.프로 세 스 (스 레 드) 는 socket 에 접근 하기 전에 자 물 쇠 를 신청 하고 접근 이 끝 날 때 자 물 쇠 를 풀 어 줍 니 다.소프트 인 터 럽 트 도 마찬가지 지만 소프트 인 터 럽 트 가 신청 한 잠 금 은 프로 세 스 (스 레 드) 와 다 릅 니 다.TCP 의 커 널 동기 화 는 자물쇠 로 이 루어 진다.TCP 는 프로 세 스 와 스 레 드 를 구분 하지 않 기 때문에 아래 프로 세 스 와 스 레 드 는 모두 프로 세 스 로 대체 합 니 다.
        프로 세 스 용 locksock 신청 잠 금:
1459 static inline void lock_sock(struct sock *sk)
1460 {   
1461     lock_sock_nested(sk, 0);
1462 }
        lock_sock_nested:
2284 void lock_sock_nested(struct sock *sk, int subclass)
2285 {
2286     might_sleep();    //             
2287     spin_lock_bh(&sk->sk_lock.slock);    //             
2288     if (sk->sk_lock.owned)        //         
2289         __lock_sock(sk);
2290     sk->sk_lock.owned = 1;    //        、  
2291     spin_unlock(&sk->sk_lock.slock);    //     (         )
2292     /*
2293      * The sk_lock has mutex_lock() semantics here:
2294      */
2295     mutex_acquire(&sk->sk_lock.dep_map, subclass, 0, _RET_IP_);
2296     local_bh_enable();    //     ,       
2297 }
        잠 금 해제 시 release 사용sock:
2300 void release_sock(struct sock *sk)
2301 {   
2302     /*
2303      * The sk_lock has mutex_unlock() semantics:
2304      */
2305     mutex_release(&sk->sk_lock.dep_map, 1, _RET_IP_);
2306 
2307     spin_lock_bh(&sk->sk_lock.slock);
2308     if (sk->sk_backlog.tail)    //backlog   skb
2309         __release_sock(sk);    //  backlog    skb
2310 
2311     if (sk->sk_prot->release_cb)    
2312         sk->sk_prot->release_cb(sk);    //       socket          
2313 
2314     sk->sk_lock.owned = 0;  //       
2315     if (waitqueue_active(&sk->sk_lock.wq))    //         
2316         wake_up(&sk->sk_lock.wq);    //    
2317     spin_unlock_bh(&sk->sk_lock.slock);
2318 }
        소프트 인 터 럽 트 사용 bhlock_sock_nested 자전거 자물쇠 신청, bh 사용unlock_sock 자 물 쇠 를 방출 합 니 다.
        TCP 가 어떻게 서로 다른 유형의 사용자 들 사이 에서 데이터 동기 화 를 실현 하 는 지 분석 해 보 자.
11.3.2 프로 세 스 간
        프로 세 스 T1 먼저 lock 호출sock_nested 함수 잠 금 획득, sk - > sk 설정lock. owned = 1 후 socket 에 접근 하기;프로 세 스 T2 호출 locksock_nested 함수 시 호출lock_sock 함수:
1832 static void __lock_sock(struct sock *sk)
1833     __releases(&sk->sk_lock.slock)
1834     __acquires(&sk->sk_lock.slock)
1835 {
1836     DEFINE_WAIT(wait);
1837 
1838     for (;;) {
1839         prepare_to_wait_exclusive(&sk->sk_lock.wq, &wait,
1840                     TASK_UNINTERRUPTIBLE);  //       TASK_UNINTERRUPTIBLE,    CPU         ,       
1841         spin_unlock_bh(&sk->sk_lock.slock);
1842         schedule();    //  CPU
1843         spin_lock_bh(&sk->sk_lock.slock);
1844         if (!sock_owned_by_user(sk))
1845             break;
1846     }
1847     finish_wait(&sk->sk_lock.wq, &wait);
1848 }
        DEFINE_WAIT 는 수면 이 벤트 를 정의 합 니 다.
889 #define DEFINE_WAIT_FUNC(name, function)                \
890     wait_queue_t name = {                       \
891         .private    = current,              \
892         .func       = function,             \
893         .task_list  = LIST_HEAD_INIT((name).task_list), \
894     }
895 
896 #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

        T2 는 1842 줄 의 schedule 을 실행 한 후 수면 상태 로 들 어 갑 니 다. prepare 에 있 기 때 문 입 니 다.to_wait_exclusive 함수 에 프로 세 스 상태 설정:
 81 void     
 82 prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
 83 {        
 84     unsigned long flags;
 85 
 86     wait->flags |= WQ_FLAG_EXCLUSIVE;
 87     spin_lock_irqsave(&q->lock, flags);
 88     if (list_empty(&wait->task_list))
 89         __add_wait_queue_tail(q, wait);     //      wait   sk->sk_lock.wq.task_list 
 90     set_current_state(state);    //      
 91     spin_unlock_irqrestore(&q->lock, flags);
 92 }
        T1 실행 releasesock 잠 금 해제 시 wake 실행up 깨 우기 T2, wakeup 은 봉 인 했 습 니 다wake_up 함수 의 매크로,wake_up 깨 우기 동작 을 수행 합 니 다:
3159 static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
3160             int nr_exclusive, int wake_flags, void *key)
3161 {
3162     wait_queue_t *curr, *next;
3163 
3164     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {    //        
3165         unsigned flags = curr->flags;
3166 
3167         if (curr->func(curr, mode, wake_flags, key) &&    //curr->func  DEFINE_WAIT        autoremove_wake_function
3168                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
3169             break;
3170     }
3171 }
...
3183 void __wake_up(wait_queue_head_t *q, unsigned int mode,
3184             int nr_exclusive, void *key)
3185 {
3186     unsigned long flags;
3187 
3188     spin_lock_irqsave(&q->lock, flags);
3189     __wake_up_common(q, mode, nr_exclusive, 0, key);
3190     spin_unlock_irqrestore(&q->lock, flags);
3191 }
        autoremove_wake_function 호출 defaultwake_function 함수, defaultwake_function 함수 호출 tryto_wake_up 깨 우기 T2:
1484 static int
1485 try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
1486 {
...
1514     p->state = TASK_WAKING;    //       CPU  
...
        즉, 두 프로 세 스 가 같은 socket 을 연이어 방문 한 후에 방문 하면 잠 을 자고 먼저 방문 한 사람 이 자 물 쇠 를 풀 어야 깨 어 나 방문 할 수 있 는 기회 가 생 긴 다 는 것 이다.깨 우 는 순 서 는 줄 을 서 는 순서 다.
11.3.3 소프트 인 터 럽 트 사이
        하나의 CPU 는 같은 시간 에 하나의 소프트 인 터 럽 트 만 실행 할 수 있 기 때문에 소프트 인 터 럽 트 간 의 병렬 접근 은 서로 다른 CPU 사이 에서 만 가능 합 니 다.소프트 인 터 럽 트 에 사용 되 는 자 물 쇠 는 자전 자물쇠 이 고 두 번 째 소프트 인 터 럽 트 가 이 자 물 쇠 를 신청 할 때 자물쇠 소유자 가 자 물 쇠 를 풀 때 까지 팽팽 한 순환 을 수행 합 니 다.CPU 가 소프트 인 터 럽 트 컨 텍스트 에 오래 머 물 수 없 기 때문에 (그렇지 않 으 면 CPU 의 다른 작업 을 수행 할 수 없습니다) 이 자 물 쇠 를 사용 하면 가장 빠 른 속도 로 자 물 쇠 를 얻 을 수 있 습 니 다.자 물 쇠 를 얻 은 후의 방문 도 시간 이 너무 길 어 서 는 안 되 며, 특히 잠 을 잘 수 없다.두 개 이상 의 소프트 인 터 럽 트 가 동시에 하나의 socket 에 접근 하 는 경우: 패키지 소프트 인 터 럽 트 와 타이머 시간 초과 가 동시에 발생 하고, irqloadbalance 를 열 때 같은 네트워크 카드 로 받 은 패 키 지 는 서로 다른 CPU 에서 동시에 처리 되 며, 서로 다른 네트워크 카드 로 도착 하 는 요청 은 같은 listen scoket 에 접근 합 니 다.
11.3.4 프로 세 스 와 소프트 인 터 럽 트 사이
        소프트 인 터 럽 트 의 실행 우선 순위 가 높 습 니 다. 프로 세 스 가 실행 되 는 임의의 시간 에 소프트 인 터 럽 트 에 의 해 중 단 될 수 있 습 니 다. (소프트 인 터 럽 트 를 닫 지 않 는 한)방문 의 선후 순서에 따라 두 가지 상황 이 있다.
(1) 소프트 인 터 럽 트 프로 세 스에 먼저 접근 한 후 접근
        이 때 소프트 인 터 럽 트 는 자 물 쇠 를 가 져 왔 습 니 다. 프로 세 스 는 자 물 쇠 를 가 져 올 때 기 다 립 니 다. 소프트 인 터 럽 트 가 자 물 쇠 를 풀 때 프로 세 스 가 자 물 쇠 를 성공 적 으로 가 져 올 수 있 습 니 다.
(2) 프로 세 스 가 소프트 에 먼저 접근 하고 끊 긴 후에 접근 합 니 다.
        프로 세 스 가 자동 잠 금 (소프트 인 터 럽 트, 소프트 인 터 럽 트 중단 방지) 을 가 져 올 때 sk - > sklock. owned 는 1 로 설정 한 후 자 물 쇠 를 풀 고 소프트 인 터 럽 트 를 열 고 socket 에 대한 접근 을 실행 합 니 다.이 때 소프트 인 터 럽 트 가 발생 하면 프로 세 스 의 실행 이 중 단 됩 니 다.소프트 인 터 럽 트 TCP 입구 함수 tcpv4_rcv 시:
1961 int tcp_v4_rcv(struct sk_buff *skb)
1962 {
...
2024     bh_lock_sock_nested(sk);    //     
2025     ret = 0;
2026     if (!sock_owned_by_user(sk)) {    sk->sk_lock.owned 1     
...
2039     } else if (unlikely(sk_add_backlog(sk, skb,
2040                        sk->sk_rcvbuf + sk->sk_sndbuf))) {
2041         bh_unlock_sock(sk);    //     
2042         NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP);
2043         goto discard_and_relse;
2044     }
2045     bh_unlock_sock(sk);     //     
        프로 세 스 가 socket 을 잠 근 상태 에서 skb 는 skadd_backlog 함수 처리:
 777 static inline __must_check int sk_add_backlog(struct sock *sk, struct sk_buff *skb,
 778                           unsigned int limit)
 779 {
 780     if (sk_rcvqueues_full(sk, skb, limit))
 781         return -ENOBUFS;
 782 
 783     __sk_add_backlog(sk, skb);
 784     sk->sk_backlog.len += skb->truesize;
 785     return 0;
 786 }
        에서sk_add_backlog 함 수 는 skb 를 backlog 대기 열 에 넣 습 니 다:
 749 static inline void __sk_add_backlog(struct sock *sk, struct sk_buff *skb)
 750 {
 751     /* dont let skb dst not refcounted, we are going to leave rcu lock */
 752     skb_dst_force(skb);
 753 
 754     if (!sk->sk_backlog.tail)
 755         sk->sk_backlog.head = skb;
 756     else
 757         sk->sk_backlog.tail->next = skb;
 758 
 759     sk->sk_backlog.tail = skb;
 760     skb->next = NULL;
 761 }
       skb 를 backlog 대기 열 에 넣 으 면 소프트 인 터 럽 트 가 되 돌아 오고 프로 세 스 가 실 행 될 수 있 습 니 다.프로 세 스 가 잠 금 을 풀 기 전에 모든 소프트 인 터 럽 트 는 skb 를 backlog 대기 열 에 넣 습 니 다.프로 세 스 가 release 를 호출 할 때sock 잠 금 해제 시 backlog 대기 열 이 비어 있 지 않 으 면release_sock:
1850 static void __release_sock(struct sock *sk)
1851     __releases(&sk->sk_lock.slock)
1852     __acquires(&sk->sk_lock.slock)
1853 {
1854     struct sk_buff *skb = sk->sk_backlog.head;
1855 
1856     do {
1857         sk->sk_backlog.head = sk->sk_backlog.tail = NULL;
1858         bh_unlock_sock(sk);    //     ,       
1859 
1860         do {
1861             struct sk_buff *next = skb->next;
1862 
1863             prefetch(next);
1864             WARN_ON_ONCE(skb_dst_is_noref(skb));
1865             skb->next = NULL;
1866             sk_backlog_rcv(sk, skb);  //    skb
1867 
1868             /*
1869              * We are in process context here with softirqs
1870              * disabled, use cond_resched_softirq() to preempt.
1871              * This is safe to do because we've taken the backlog
1872              * queue private:
1873              */
1874             cond_resched_softirq();    //        CPU,        ;            
1875 
1876             skb = next;
1877         } while (skb != NULL);
1878 
1879         bh_lock_sock(sk);
1880     } while ((skb = sk->sk_backlog.head) != NULL);
1881 
1882     /*
1883      * Doing the zeroing here guarantee we can not loop forever
1884      * while a wild producer attempts to flood us.
1885      */
1886     sk->sk_backlog.len = 0;
1887 }
        __release_sock 에서 backlog 대기 열 을 처리 하 는 방법 은 먼저 backlog 대기 열 에 있 는 모든 skb 를 개인 대기 열 로 옮 긴 다음 자전 자 물 쇠 를 풀 고 소프트 인 터 럽 트 를 닫 는 조건 에서 sk 를 호출 하 는 것 입 니 다.backlog_rcv 함수 처리 skb.skb 를 처리 할 때마다 CPU 를 한 번 씩 포기 합 니 다. 대기 열 에 skb 가 너무 많아 서 소프트 인 터 럽 트 가 너무 오래 걸 리 지 않도록 합 니 다.처리 하 는 동안 소프트 인 터 럽 트 가 발생 하면 skb 는 원리 적 인 backlog 대기 열 에 들 어가 현재 처리 하 는 대기 열 과 관계 가 없습니다.sk_backlog_rcv 는 skb 를 TCP 에 넣 어 처리 합 니 다:
 790 static inline int sk_backlog_rcv(struct sock *sk, struct sk_buff *skb)
 791 {
 792     if (sk_memalloc_socks() && skb_pfmemalloc(skb))
 793         return __sk_backlog_rcv(sk, skb);  //  sk->sk_backlog_rcv
 794 
 795     return sk->sk_backlog_rcv(sk, skb); //  tcp_v4_do_rcv  
 796 }   
        결국, backlog 대기 열 에 있 는 skb 는 tcpv4_do_rcv 함수 처리.
        한 마디 로 하면 프로 세 스 가 socket 을 먼저 잠 글 때 소프트 인 터 럽 트 는 skb 를 backlog 대기 열 에 넣 고 돌아 갈 수 있 을 뿐 socket 에 접근 할 수 없습니다.프로 세 스 가 socket 을 풀 때 backlog 대기 열 에 있 는 skb 를 처리 합 니 다.프로 세 스 가 socket 을 가지 고 있 는 시간 이 길 수록 backlog 대기 열 이 커지 고 너무 크 면 가방 을 잃 어 버 릴 수 있 습 니 다 (실제로 거의 발생 하지 않 습 니 다).이러한 병발 방식 을 사용 하면 socket 이 프로 세 스 와 소프트 인 터 럽 트 사이 의 병발 보 호 를 실현 할 뿐만 아니 라 소프트 인 터 럽 트 의 운행 에 도 영향 을 주지 않 습 니 다.프로 세 스 가 socket 에 접근 할 때 잠시 수면 을 취 합 니 다 (예 를 들 어 사용자 상태 와 커 널 사이 에서 데 이 터 를 전달 할 때). 심각 한 결 과 를 일 으 키 지 는 않 지만 장시간 수면 을 취 할 때 socket 을 방출 해 야 합 니 다 (예 를 들 어 메모 리 를 기다 릴 때).

좋은 웹페이지 즐겨찾기