unixドメインソケット送信
・DGRAM型
データサイズがsk->sk_sndbuf - 32を超えるとエラーです。sock_alloc_send_skb()で送信データサイズのバッファ等を設定したskbを取得し、skb->tailに送信データを複写し、 skb_queue_tail()で送信先ソケットのother->sk_receive_queueにリストします。other->sk_data_ready()は、受信待機しているプロセスを起床させother->sk_receive_queueにリストしたskbを取得し受信します。
other->sk_receive_queueにリストされている数が、other->sk_max_ack_backlogを超えてるなら、unix_wait_for_peer()でother->peer_waitを起床させながら、other->sk_receive_queueにリストできるまでgoto restartでループします。
DGRAM型と異なり、送信データのサイズ制限はありませんが、送信ブロックをソケットのsk->sk_sndbuf/2単位でskbに設定し、送信先のother->sk_receive_queueにリストします。1対1のやり取り故、分割送信での受信側の問題はなく、分割送信する事でタスクスケジュール機能が向上します。なお、other->sk_receive_queueにリストできる制限もありません。
connect/acceptは、accept/connectを介する1対1のやり取りのSTREAMの実装としながら、送信はDGRAMの実装となります。
バッファサイズはアライメントしたサイズに、struct skb_shared_infoの領域を付加したサイズで取得し、そのサイズ値をskb->tail = data/skb->end = data + sizeとします。struct skb_shared_infoはpage単位で取得した領域のアドレスが設定され、struct skb_shared_infoをすべてのsdnに設定することで、STREAMで送信が分割されるケースでも、受信では共有するデータを受信できるような事が可能となります。なおUNIXドメインソケットでは利用されていません。
size = min_t(int, size, skb_tailroom(skb))で、アライメントとstruct skb_shared_infoの領域で、size < skb_tailroom(skb)ではありますが、ソースコメントの説明だと、メモリ獲得時に待機する場合で、しかも分割して送信するケース故・・・、でsize>skb_tailroom(skb)の発生するのって、そのケースが全く分からず、min_t(int, size, skb_tailroom(skb))の必要性で悩んでいます。
skb_is_nonlinear(skb)は、struct skb_shared_infoのバッファのpage数で、UNIXドメインソケットは0です。
データサイズがsk->sk_sndbuf - 32を超えるとエラーです。sock_alloc_send_skb()で送信データサイズのバッファ等を設定したskbを取得し、skb->tailに送信データを複写し、 skb_queue_tail()で送信先ソケットのother->sk_receive_queueにリストします。other->sk_data_ready()は、受信待機しているプロセスを起床させother->sk_receive_queueにリストしたskbを取得し受信します。
other->sk_receive_queueにリストされている数が、other->sk_max_ack_backlogを超えてるなら、unix_wait_for_peer()でother->peer_waitを起床させながら、other->sk_receive_queueにリストできるまでgoto restartでループします。
static int unix_dgram_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, int len) { struct sock_iocb *siocb = kiocb_to_siocb(kiocb); struct sock *sk = sock->sk; struct unix_sock *u = unix_sk(sk); struct sockaddr_un *sunaddr=msg->msg_name; struct sock *other = NULL; int namelen = 0; /* fake GCC */ int err; unsigned hash; struct sk_buff *skb; long timeo; struct scm_cookie tmp_scm; if (NULL == siocb->scm) siocb->scm = &tmp_scm; err = scm_send(sock, msg, siocb->scm); if (err < 0) return err; err = -EOPNOTSUPP; if (msg->msg_flags&MSG_OOB) goto out; if (msg->msg_namelen) { err = unix_mkname(sunaddr, msg->msg_namelen, &hash); if (err < 0) goto out; namelen = err; } else { sunaddr = NULL; err = -ENOTCONN; other = unix_peer_get(sk); if (!other) goto out; } if (sock->passcred && !u->addr && (err = unix_autobind(sock)) != 0) goto out; err = -EMSGSIZE; if ((unsigned)len > sk->sk_sndbuf - 32) goto out; skb = sock_alloc_send_skb(sk, len, msg->msg_flags&MSG_DONTWAIT, &err); if (skb==NULL) goto out; memcpy(UNIXCREDS(skb), &siocb->scm->creds, sizeof(struct ucred)); if (siocb->scm->fp) unix_attach_fds(siocb->scm, skb); skb->h.raw = skb->data; err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len); if (err) goto out_free; timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT); restart: if (!other) { err = -ECONNRESET; if (sunaddr == NULL) goto out_free; other = unix_find_other(sunaddr, namelen, sk->sk_type, hash, &err); if (other==NULL) goto out_free; } unix_state_rlock(other); err = -EPERM; if (!unix_may_send(sk, other)) goto out_unlock; if (sock_flag(other, SOCK_DEAD)) { unix_state_runlock(other); sock_put(other); err = 0; unix_state_wlock(sk); if (unix_peer(sk) == other) { unix_peer(sk)=NULL; unix_state_wunlock(sk); unix_dgram_disconnected(sk, other); sock_put(other); err = -ECONNREFUSED; } else { unix_state_wunlock(sk); } other = NULL; if (err) goto out_free; goto restart; } err = -EPIPE; if (other->sk_shutdown & RCV_SHUTDOWN) goto out_unlock; err = security_unix_may_send(sk->sk_socket, other->sk_socket); if (err) goto out_unlock; if (unix_peer(other) != sk && (skb_queue_len(&other->sk_receive_queue) > other->sk_max_ack_backlog)) { if (!timeo) { err = -EAGAIN; goto out_unlock; } timeo = unix_wait_for_peer(other, timeo); err = sock_intr_errno(timeo); if (signal_pending(current)) goto out_free; goto restart; } skb_queue_tail(&other->sk_receive_queue, skb); unix_state_runlock(other); other->sk_data_ready(other, len); sock_put(other); scm_destroy(siocb->scm); return len; out_unlock: unix_state_runlock(other); out_free: kfree_skb(skb); out: if (other) sock_put(other); scm_destroy(siocb->scm); return err; }・STREAM型
DGRAM型と異なり、送信データのサイズ制限はありませんが、送信ブロックをソケットのsk->sk_sndbuf/2単位でskbに設定し、送信先のother->sk_receive_queueにリストします。1対1のやり取り故、分割送信での受信側の問題はなく、分割送信する事でタスクスケジュール機能が向上します。なお、other->sk_receive_queueにリストできる制限もありません。
static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, int len) { struct sock_iocb *siocb = kiocb_to_siocb(kiocb); struct sock *sk = sock->sk; struct sock *other = NULL; struct sockaddr_un *sunaddr=msg->msg_name; int err,size; struct sk_buff *skb; int sent=0; struct scm_cookie tmp_scm; if (NULL == siocb->scm) siocb->scm = &tmp_scm; err = scm_send(sock, msg, siocb->scm); if (err < 0) return err; err = -EOPNOTSUPP; if (msg->msg_flags&MSG_OOB) goto out_err; if (msg->msg_namelen) { err = sk->sk_state == TCP_ESTABLISHED ? -EISCONN : -EOPNOTSUPP; goto out_err; } else { sunaddr = NULL; err = -ENOTCONN; other = unix_peer_get(sk); if (!other) goto out_err; } if (sk->sk_shutdown & SEND_SHUTDOWN) goto pipe_err; while(sent < len) { size=len-sent; if (size > sk->sk_sndbuf / 2 - 64) size = sk->sk_sndbuf / 2 - 64; if (size > SKB_MAX_ALLOC) size = SKB_MAX_ALLOC; skb=sock_alloc_send_skb(sk,size,msg->msg_flags&MSG_DONTWAIT, &err); if (skb==NULL) goto out_err; /* * If you pass two values to the sock_alloc_send_skb * it tries to grab the large buffer with GFP_NOFS * (which can fail easily), and if it fails grab the * fallback size buffer which is under a page and will * succeed. [Alan] */ size = min_t(int, size, skb_tailroom(skb)); memcpy(UNIXCREDS(skb), &siocb->scm->creds, sizeof(struct ucred)); if (siocb->scm->fp) unix_attach_fds(siocb->scm, skb); if ((err = memcpy_fromiovec(skb_put(skb,size), msg->msg_iov, size)) != 0) { kfree_skb(skb); goto out_err; } unix_state_rlock(other); if (sock_flag(other, SOCK_DEAD) || (other->sk_shutdown & RCV_SHUTDOWN)) goto pipe_err_free; skb_queue_tail(&other->sk_receive_queue, skb); unix_state_runlock(other); other->sk_data_ready(other, size); sent+=size; } sock_put(other); scm_destroy(siocb->scm); siocb->scm = NULL; return sent; pipe_err_free: unix_state_runlock(other); kfree_skb(skb); pipe_err: if (sent==0 && !(msg->msg_flags&MSG_NOSIGNAL)) send_sig(SIGPIPE,current,0); err = -EPIPE; out_err: if (other) sock_put(other); scm_destroy(siocb->scm); siocb->scm = NULL; return sent ? : err; }・SEQPACKET型
connect/acceptは、accept/connectを介する1対1のやり取りのSTREAMの実装としながら、送信はDGRAMの実装となります。
static const struct proto_ops unix_seqpacket_ops = { .family = PF_UNIX, .owner = THIS_MODULE, .release = unix_release, .bind = unix_bind, .connect = unix_stream_connect, .socketpair = unix_socketpair, .accept = unix_accept, .getname = unix_getname, .poll = unix_dgram_poll, .ioctl = unix_ioctl, .listen = unix_listen, .shutdown = unix_shutdown, .setsockopt = sock_no_setsockopt, .getsockopt = sock_no_getsockopt, .sendmsg = unix_seqpacket_sendmsg, .recvmsg = unix_seqpacket_recvmsg, .mmap = sock_no_mmap, .sendpage = sock_no_sendpage, }; static int unix_seqpacket_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len) { int err; struct sock *sk = sock->sk; err = sock_error(sk); if (err) return err; if (sk->sk_state != TCP_ESTABLISHED) return -ENOTCONN; if (msg->msg_namelen) msg->msg_namelen = 0; return unix_dgram_sendmsg(kiocb, sock, msg, len); }
バッファサイズはアライメントしたサイズに、struct skb_shared_infoの領域を付加したサイズで取得し、そのサイズ値をskb->tail = data/skb->end = data + sizeとします。struct skb_shared_infoはpage単位で取得した領域のアドレスが設定され、struct skb_shared_infoをすべてのsdnに設定することで、STREAMで送信が分割されるケースでも、受信では共有するデータを受信できるような事が可能となります。なおUNIXドメインソケットでは利用されていません。
struct sk_buff *alloc_skb(unsigned int size, int gfp_mask) { struct sk_buff *skb; u8 *data; skb = kmem_cache_alloc(skbuff_head_cache, gfp_mask & ~__GFP_DMA); if (!skb) goto out; size = SKB_DATA_ALIGN(size); data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask); if (!data) goto nodata; memset(skb, 0, offsetof(struct sk_buff, truesize)); skb->truesize = size + sizeof(struct sk_buff); atomic_set(&skb->users, 1); skb->head = data; skb->data = data; skb->tail = data; skb->end = data + size; atomic_set(&(skb_shinfo(skb)->dataref), 1); skb_shinfo(skb)->nr_frags = 0; skb_shinfo(skb)->tso_size = 0; skb_shinfo(skb)->tso_segs = 0; skb_shinfo(skb)->frag_list = NULL; out: return skb; nodata: kmem_cache_free(skbuff_head_cache, skb); skb = NULL; goto out; }
補足
ソケット作成時のデフォルトsk->sk_sndbufは、/proc/sys/net/core/wmem_defaultで、setsockoptシステムコールで更新できます。[root@localhost north]# cat /proc/sys/net/core/wmem_default 163840
疑問
if ((unsigned)len > sk->sk_sndbuf - 32)の32は、struct skb_shared_infoを考慮しての事でしょうか?size = min_t(int, size, skb_tailroom(skb))で、アライメントとstruct skb_shared_infoの領域で、size < skb_tailroom(skb)ではありますが、ソースコメントの説明だと、メモリ獲得時に待機する場合で、しかも分割して送信するケース故・・・、でsize>skb_tailroom(skb)の発生するのって、そのケースが全く分からず、min_t(int, size, skb_tailroom(skb))の必要性で悩んでいます。
skb_is_nonlinear(skb)は、struct skb_shared_infoのバッファのpage数で、UNIXドメインソケットは0です。
static inline int skb_tailroom(const struct sk_buff *skb) { return skb_is_nonlinear(skb) ? 0 : skb->end - skb->tail; } static inline int skb_is_nonlinear(const struct sk_buff *skb) { return skb->data_len; }