・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でループします。
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;
}