ソケットバックログ
Rev.2を表示中。最新版はこちら。
クライアントがconnectすると、そのソケットは、サーバのsock->sk_receive_queueをヘッドとするリストに登録され、acceptすると、このリストのソケットと接続し、sock->sk_receive_queueから削除されます。(MSG_PEEKなら削除されません。)バックログは、sock->sk_receive_queueのリスト可能な数値で、listシステムコールで設定します。本件はunixドメインソケットの実装に基づくものです。バックログを超えたconnectは、フラグによりsock->peer_waitにリストされ、所定の時間待機して、再度connectを試みます。
バックログのデフォルトの最大値は/proc/sys/net/core/somaxconnの128です。
[root@localhost kitamura]# cat /proc/sys/net/core/somaxconn 128 SYSCALL_DEFINE2(listen, int, fd, int, backlog) { struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; if ((unsigned)backlog > somaxconn) backlog = somaxconn; err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed); } return err; }unixドメインソケットのlistenコールバックで、sk->sk_max_ack_backlog = backlogとし、connectできる数を設定します。sk->sk_state = TCP_LISTENは、ソケットがlistenされた属性で、本設定がされてないとaccesシステムコールでエラーとなります。
backlog > sk->sk_max_ack_backlogなら、u->peer_waitでウエイトしているソケットを起床させ(起床すると再度conectを試みます。)、backlogを拡張時に、ウエイトしていたソケットが接続できるようにいたします。backlog < sk->sk_max_ack_backlogなら再接続できないので起床する必要がありません。
static int unix_listen(struct socket *sock, int backlog) { int err; struct sock *sk = sock->sk; struct unix_sock *u = unix_sk(sk); struct pid *old_pid = NULL; const struct cred *old_cred = NULL; err = -EOPNOTSUPP; if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET) goto out; /* Only stream/seqpacket sockets accept */ err = -EINVAL; if (!u->addr) goto out; /* No listens on an unbound socket */ unix_state_lock(sk); if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN) goto out_unlock; if (backlog > sk->sk_max_ack_backlog) wake_up_interruptible_all(&u->peer_wait); sk->sk_max_ack_backlog = backlog; sk->sk_state = TCP_LISTEN; init_peercred(sk); err = 0; out_unlock: unix_state_unlock(sk); put_pid(old_pid); if (old_cred) put_cred(old_cred); out: return err; }connectシステムコールからstruct proto_opsの.connectコールバックとしてコールされます。skb->sk = skとするクライアントソケットとの両ソケットの共有バッファを取得し、 サーバ側ソケットのother->sk_receive_queueにリストされ、バックログを超えているなら、unix_wait_for_peer()でother->->peer_waitにリストしプロセスとして待機します。(flagがO_NONBLOCKならリストされません。) なおこのプロセスが起床すると、goto restartでother->sk_receive_queueへの登録を試みます。
static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags) { struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr; struct sock *sk = sock->sk; struct unix_sock *u = unix_sk(sk), *newu, *otheru; struct sock *newsk = NULL; struct sock *other = NULL; struct sk_buff *skb = NULL; unsigned hash; int st; int err; long timeo; err = unix_mkname(sunaddr, addr_len, &hash); if (err < 0) goto out; addr_len = err; if (sock->passcred && !u->addr && (err = unix_autobind(sock)) != 0) goto out; timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); err = -ENOMEM; newsk = unix_create1(NULL); if (newsk == NULL) goto out; skb = sock_wmalloc(newsk, 1, 0, GFP_KERNEL); if (skb == NULL) goto out; restart: other = unix_find_other(sunaddr, addr_len, sk->sk_type, hash, &err); if (!other) goto out; unix_state_rlock(other); if (sock_flag(other, SOCK_DEAD)) { unix_state_runlock(other); sock_put(other); goto restart; } err = -ECONNREFUSED; if (other->sk_state != TCP_LISTEN) goto out_unlock; if (skb_queue_len(&other->sk_receive_queue) > other->sk_max_ack_backlog) { err = -EAGAIN; if (!timeo) goto out_unlock; timeo = unix_wait_for_peer(other, timeo); err = sock_intr_errno(timeo); if (signal_pending(current)) goto out; sock_put(other); goto restart; } st = sk->sk_state; switch (st) { case TCP_CLOSE: break; case TCP_ESTABLISHED: /* Socket is already connected */ err = -EISCONN; goto out_unlock; default: err = -EINVAL; goto out_unlock; } unix_state_wlock(sk); if (sk->sk_state != st) { unix_state_wunlock(sk); unix_state_runlock(other); sock_put(other); goto restart; } err = security_unix_stream_connect(sock, other->sk_socket, newsk); if (err) { unix_state_wunlock(sk); goto out_unlock; } sock_hold(sk); unix_peer(newsk) = sk; newsk->sk_state = TCP_ESTABLISHED; newsk->sk_type = SOCK_STREAM; newsk->sk_peercred.pid = current->tgid; newsk->sk_peercred.uid = current->euid; newsk->sk_peercred.gid = current->egid; newu = unix_sk(newsk); newsk->sk_sleep = &newu->peer_wait; otheru = unix_sk(other); if (otheru->addr) { atomic_inc(&otheru->addr->refcnt); newu->addr = otheru->addr; } if (otheru->dentry) { newu->dentry = dget(otheru->dentry); newu->mnt = mntget(otheru->mnt); } sk->sk_peercred = other->sk_peercred; sock_hold(newsk); unix_peer(sk) = newsk; sock->state = SS_CONNECTED; sk->sk_state = TCP_ESTABLISHED; unix_state_wunlock(sk); spin_lock(&other->sk_receive_queue.lock); __skb_queue_tail(&other->sk_receive_queue, skb); atomic_inc(&newu->inflight); spin_unlock(&other->sk_receive_queue.lock); unix_state_runlock(other); other->sk_data_ready(other, 0); sock_put(other); return 0; out_unlock: if (other) unix_state_runlock(other); out: if (skb) kfree_skb(skb); if (newsk) unix_release_sock(newsk, 0); if (other) sock_put(other); return err; }リストはtop->prev=tail tail->next=topとなっており、top->prev=newsk/newsk->next=list/newsk->prev=前のtailとする事でlistの末尾にnewskを登録します。
static inline __u32 skb_queue_len(const struct sk_buff_head *list_) { return list_->qlen; } static inline void __skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk) { struct sk_buff *prev, *next; newsk->list = list; list->qlen++; next = (struct sk_buff *)list; prev = next->prev; newsk->next = next; newsk->prev = prev; next->prev = prev->next = newsk; }acceptシステムコールからstruct proto_opsの.acceptコールバックです。skb_peek(&sk->sk_receive_queue);で、sk->sk_receive_queueの先頭にリストされているソケットを取得し、__skb_unlink()でsk->sk_receive_queueのリストから削除します。なお、flagがMSG_PEEKでなら、削除されず、再度acceptすると同じクライアントとのやり取りとなります。
static int unix_accept(struct socket *sock, struct socket *newsock, int flags) { struct sock *sk = sock->sk; struct sock *tsk; struct sk_buff *skb; int err; err = -EOPNOTSUPP; if (sock->type!=SOCK_STREAM) goto out; err = -EINVAL; if (sk->sk_state != TCP_LISTEN) goto out; skb = skb_recv_datagram(sk, 0, flags&O_NONBLOCK, &err); if (!skb) { /* This means receive shutdown. */ if (err == 0) err = -EINVAL; goto out; } tsk = skb->sk; skb_free_datagram(sk, skb); wake_up_interruptible(&unix_sk(sk)->peer_wait); unix_state_wlock(tsk); newsock->state = SS_CONNECTED; sock_graft(tsk, newsock); unix_state_wunlock(tsk); return 0; out: return err; } struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err) { int peeked; return __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0), &peeked, err); } struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags, int *peeked, int *err) { struct sk_buff *skb; long timeo; int error = sock_error(sk); if (error) goto no_packet; timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); do { unsigned long cpu_flags; spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags); skb = skb_peek(&sk->sk_receive_queue); if (skb) { *peeked = skb->peeked; if (flags & MSG_PEEK) { skb->peeked = 1; atomic_inc(&skb->users); } else __skb_unlink(skb, &sk->sk_receive_queue); } spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags); if (skb) return skb; error = -EAGAIN; if (!timeo) goto no_packet; } while (!wait_for_packet(sk, err, &timeo)); return NULL; no_packet: *err = error; return NULL; } static inline struct sk_buff *skb_peek(const struct sk_buff_head *list_) { struct sk_buff *list = ((const struct sk_buff *)list_)->next; if (list == (struct sk_buff *)list_) list = NULL; return list; } static inline void __skb_unlink(struct sk_buff *skb, struct sk_buff_head *list) { struct sk_buff *next, *prev; list->qlen--; next = skb->next; prev = skb->prev; skb->next = skb->prev = NULL; next->prev = prev; prev->next = next; }skb_peek()でconnectによりsk->sk_receive_queueに先頭にリストされているソケットバッファを取得し、skb_dequeue()でそのソケットをリストから削除します。ただしMSG_PEEKなら、削除されません。
struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err) { struct sk_buff *skb; long timeo; int error = sock_error(sk); if (error) goto no_packet; timeo = sock_rcvtimeo(sk, noblock); do { if (flags & MSG_PEEK) { unsigned long cpu_flags; spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags); skb = skb_peek(&sk->sk_receive_queue); if (skb) atomic_inc(&skb->users); spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags); } else skb = skb_dequeue(&sk->sk_receive_queue); if (skb) return skb; error = -EAGAIN; if (!timeo) goto no_packet; } while (!wait_for_packet(sk, err, &timeo)); return NULL; no_packet: *err = error; return NULL; } static inline struct sk_buff *skb_dequeue(struct sk_buff_head *list) { unsigned long flags; struct sk_buff *result; spin_lock_irqsave(&list->lock, flags); result = __skb_dequeue(list); spin_unlock_irqrestore(&list->lock, flags); return result; } static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list) { struct sk_buff *next, *prev, *result; prev = (struct sk_buff *) list; next = prev->next; result = NULL; if (next != prev) { result = next; next = next->next; list->qlen--; next->prev = prev; prev->next = next; result->next = result->prev = NULL; result->list = NULL; } return result; }
備考
バックログを超えてのsock->peer_waitのウエイト時間は、sk->sk_sndtimeo=((long)(~0UL>>1))で、setsockoptシステムコールで、 引数optnameをSO_SNDTIMEOで設定できます。データグラムのソケットコールバックは、ソケット間の直接の接続を行わないため、accept/listenはエラーとなります。
struct proto_ops unix_dgram_ops = { .family = PF_UNIX, .owner = THIS_MODULE, .release = unix_release, .bind = unix_bind, .connect = unix_dgram_connect, .socketpair = unix_socketpair, .accept = sock_no_accept, .getname = unix_getname, .poll = datagram_poll, .ioctl = unix_ioctl, .listen = sock_no_listen, .shutdown = unix_shutdown, .setsockopt = sock_no_setsockopt, .getsockopt = sock_no_getsockopt, .sendmsg = unix_dgram_sendmsg, .recvmsg = unix_dgram_recvmsg, .mmap = sock_no_mmap, .sendpage = sock_no_sendpage, }; int sock_no_accept(struct socket *sock, struct socket *newsock, int flags) { return -EOPNOTSUPP; } int sock_no_listen(struct socket *sock, int backlog) { return -EOPNOTSUPP; }