1. 程式人生 > >Linux socket 資料傳送類函式實現(一)

Linux socket 資料傳送類函式實現(一)

注:本文分析基於3.10.0-693.el7核心版本,即CentOS 7.4

三次握手完成了,下面就該傳送資料了。傳送資料使用的函式有很多,比如send、sendto、sendmsg、sendmmsg,甚至還有write、wirtev等,那這些函式都是怎麼將資料傳送的呢,下面我們就來探個究竟。

send()函式

函式原型

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

其中,
sockfd表示傳送資料的socket檔案描述符。如果是客戶端,即為socket系統呼叫建立的檔案描述符;如果是服務端,即為accept系統呼叫返回的檔案描述符;
buf

表示使用者資料儲存起始地址;
len表示使用者資料長度;
flags用於控制send的具體行為,比如是否阻塞,是否路由等。

核心實現

我們看看核心實現,

SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len,
        unsigned int, flags)
{
    return sys_sendto(fd, buff, len, flags, NULL, 0);
}

可見send最終呼叫的是sendto的核心函式,僅僅是做了一層封裝,增加了最後兩個引數。

sendto()函式

函式原型

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

其中,前面四個引數和send()一致,
dest_addr表示資料傳送的目標地址;
addrlen表示目標地址結構的大小,即為sizeof(struct sockaddr)

sendto常用於無連線狀態下的資料傳送,即UDP報文,如果用於有連線狀態,即TCP報文的傳送,則dest_addraddrlen這兩個引數將被忽略。可見,

send
(sockfd, buf, len, flags);

等效於

sendto(sockfd, buf, len, flags, NULL, 0);

核心實現

SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len,
        unsigned int, flags)
{
    return sys_sendto(fd, buff, len, flags, NULL, 0);
}

SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
        unsigned int, flags, struct sockaddr __user *, addr,
        int, addr_len)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err;
    struct msghdr msg;
    struct iovec iov;
    int fput_needed;

    if (len > INT_MAX)
        len = INT_MAX;
    //一貫作風,根據fd獲取socket結構體
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;
    //緩衝區地址,也就是我們傳送的資料存在iovec結構體中
    iov.iov_base = buff;
    iov.iov_len = len;
    //其實最終傳送類的函式,使用者訊息都是封裝在msghdr結構體中
    msg.msg_name = NULL;
    msg.msg_iov = &iov;//資料儲存區
    msg.msg_iovlen = 1;//資料塊數量
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_namelen = 0;
    if (addr) {
        //如果目標地址不為空,將地址資訊拷貝至核心
        err = move_addr_to_kernel(addr, addr_len, &address);
        if (err < 0)
            goto out_put;
        msg.msg_name = (struct sockaddr *)&address;
        msg.msg_namelen = addr_len;
    }
    if (sock->file->f_flags & O_NONBLOCK)//非阻塞呼叫
        flags |= MSG_DONTWAIT;
    msg.msg_flags = flags;
    //向下呼叫sock_sendmsg
    err = sock_sendmsg(sock, &msg, len);

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}

sock_sendmsg中構造IO請求,然後進入__sock_sendmsg

int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
    struct kiocb iocb;
    struct sock_iocb siocb;
    int ret;
    //構造一個IO請求,用kiocb結構表示
    init_sync_kiocb(&iocb, NULL);
    //將kiocb和sock_iocb關聯
    iocb.private = &siocb;
    //往下呼叫__sock_sendmsg,多數傳送類函式最終都會進入這個函式
    ret = __sock_sendmsg(&iocb, sock, msg, size);
    if (-EIOCBQUEUED == ret)
        ret = wait_on_sync_kiocb(&iocb);
    return ret;
}

到這裡我們先暫停一下,對於類send函式,sock_sendmsg是socket層的一個匯聚點,我們暫且停下腳步,看看其他傳送類系統呼叫是怎麼走到這的。

sendmsg()函式

函式原型

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

其中,
sockfd表示傳送資料的socket檔案描述符;
msg表示使用者傳送資料,封裝在struct msghdr結構中;
flags用於控制sendmsg的具體行為,和上面send、sendto一樣。

訊息頭結構

和send、sendto不同的是,sendmsg將使用者的資料封裝在訊息頭結構struct msghdr中,其結構體定義如下,

struct msghdr {
    void         *msg_name;       /* optional address 目的地址*/
    socklen_t     msg_namelen;    /* size of address 目的地址長度*/
    struct iovec *msg_iov;        /* scatter/gather array 分散的資料塊陣列*/
    size_t        msg_iovlen;     /* # elements in msg_iov 分散的資料塊陣列個數*/
    void         *msg_control;    /* ancillary data, see below 控制資料*/
    size_t        msg_controllen; /* ancillary data buffer len 控制資料長度*/
    int           msg_flags;      /* flags on received message */
};

其中分散的資料塊陣列struct iovec結構體資訊如下

struct iovec
{
    void __user *iov_base;  /* 使用者資料 */
    __kernel_size_t iov_len; /* 使用者資料長度 */
};

核心實現

SYSCALL_DEFINE3(sendmsg, int, fd, struct msghdr __user *, msg, unsigned int, flags)
{
    if (flags & MSG_CMSG_COMPAT)
        return -EINVAL;
    return __sys_sendmsg(fd, msg, flags);
}

簡單的封裝後,進入__sys_sendmsg函式,

long __sys_sendmsg(int fd, struct msghdr __user *msg, unsigned flags)
{
    int fput_needed, err;
    struct msghdr msg_sys;
    struct socket *sock;
    //老套路之由fd獲取對應socket
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;
    //又是一層簡單的封裝
    err = ___sys_sendmsg(sock, msg, &msg_sys, flags, NULL);

    fput_light(sock->file, fput_needed);
out:
    return err;
}

核心總是喜歡封裝各種各樣的函式,現在又來到___sys_sendmsg函式,

static int ___sys_sendmsg(struct socket *sock, struct msghdr __user *msg,
             struct msghdr *msg_sys, unsigned int flags,
             struct used_address *used_address)
{
    struct compat_msghdr __user *msg_compat =
        (struct compat_msghdr __user *)msg;
    struct sockaddr_storage address;
    struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
    unsigned char ctl[sizeof(struct cmsghdr) + 20]
        __attribute__ ((aligned(sizeof(__kernel_size_t))));
    /* 20 is size of ipv6_pktinfo */
    unsigned char *ctl_buf = ctl;
    int err, ctl_len, total_len;

    err = -EFAULT;
    //需要相容32位系統,這裡我們不考慮這種情況
    if (MSG_CMSG_COMPAT & flags) {
        if (get_compat_msghdr(msg_sys, msg_compat))
            return -EFAULT;
    } else {
        //將使用者的訊息頭資料拷貝至核心
        err = copy_msghdr_from_user(msg_sys, msg);
        if (err)
            return err;
    }

    if (msg_sys->msg_iovlen > UIO_FASTIOV) {
        err = -EMSGSIZE;
        //訊息個數最大隻能是1024個
        if (msg_sys->msg_iovlen > UIO_MAXIOV)
            goto out;
        err = -ENOMEM;
        //為iovec,即使用者實際資料分配記憶體
        iov = kmalloc(msg_sys->msg_iovlen * sizeof(struct iovec), GFP_KERNEL);
        if (!iov)
            goto out;
    }

    /* This will also move the address data into kernel space */
    if (MSG_CMSG_COMPAT & flags) {
        err = verify_compat_iovec(msg_sys, iov, &address, VERIFY_READ);
    } else
        //拷貝iovec結構裡的資料到核心
        err = verify_iovec(msg_sys, iov, &address, VERIFY_READ);
    if (err < 0)
        goto out_freeiov;
    total_len = err;

    err = -ENOBUFS;

    if (msg_sys->msg_controllen > INT_MAX)
        goto out_freeiov;
    ctl_len = msg_sys->msg_controllen;
    if ((MSG_CMSG_COMPAT & flags) && ctl_len) {
        err =
            cmsghdr_from_user_compat_to_kern(msg_sys, sock->sk, ctl, sizeof(ctl));
        if (err)
            goto out_freeiov;
        ctl_buf = msg_sys->msg_control;
        ctl_len = msg_sys->msg_controllen;
    } else if (ctl_len) {
        //如果訊息頭裡有控制資訊,也一併拷貝到核心
        if (ctl_len > sizeof(ctl)) {
            ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL);
            if (ctl_buf == NULL)
                goto out_freeiov;
        }
        err = -EFAULT;

        if (copy_from_user(ctl_buf, (void __user __force *)msg_sys->msg_control, ctl_len))
            goto out_freectl;
        msg_sys->msg_control = ctl_buf;
    }
    msg_sys->msg_flags = flags;

    if (sock->file->f_flags & O_NONBLOCK)//非阻塞呼叫
        msg_sys->msg_flags |= MSG_DONTWAIT;

    //在sendmsg呼叫中used_address為NULL
    //在sendmmsg中不為空,傳送多個訊息時,如果此次傳送訊息的名字和地址與上次相同,則直接呼叫sock_sendmsg_nosec
    //其實與sock_sendmsg的差別僅在於是否做security_socket_sendmsg檢查,應該是出於效能考慮
    if (used_address && msg_sys->msg_name &&
        used_address->name_len == msg_sys->msg_namelen &&
        !memcmp(&used_address->name, msg_sys->msg_name, used_address->name_len)) {
        err = sock_sendmsg_nosec(sock, msg_sys, total_len);
        goto out_freectl;
    }
    //看吧,即將進入sock_sendmsg
    err = sock_sendmsg(sock, msg_sys, total_len);

    //在sendmsg呼叫中used_address為NULL
    //在sendmmsg呼叫時,因為傳送多個訊息,可以直接使用上一次的訊息頭資訊,因此儲存下來
    if (used_address && err >= 0) {
        used_address->name_len = msg_sys->msg_namelen;
        if (msg_sys->msg_name)
            memcpy(&used_address->name, msg_sys->msg_name, used_address->name_len);
    }
    ...
}

和sendto一樣,之後就進入sock_sendmsg流程,同樣,我們再回過頭看看sendmmsg()系統呼叫。

sendmmsg()函式

函式原型

int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags);

其中,
sockfd表示傳送資料的socket檔案描述符;
msgvec表示使用者傳送資料,可包含多個訊息,指向struct mmsghdr結構陣列;
vlen表示struct mmsghdr結構陣列長度,即訊息個數;
flags用於控制sendmmsg的具體行為,和上面send、sendto一樣。

struct mmsghdr結構

struct mmsghdr {
    struct msghdr msg_hdr;  /* Message header */
    unsigned int  msg_len;  /* Number of bytes transmitted 傳送資料長度*/
};

核心實現

SYSCALL_DEFINE4(sendmmsg, int, fd, struct mmsghdr __user *, mmsg,
        unsigned int, vlen, unsigned int, flags)
{
    if (flags & MSG_CMSG_COMPAT)
        return -EINVAL;
    return __sys_sendmmsg(fd, mmsg, vlen, flags);
}

和sendmsg一樣的封裝,往下呼叫__sys_sendmmsg

int __sys_sendmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen,
           unsigned int flags)
{
    int fput_needed, err, datagrams;
    struct socket *sock;
    struct mmsghdr __user *entry;
    struct compat_mmsghdr __user *compat_entry;
    struct msghdr msg_sys;
    struct used_address used_address;

    if (vlen > UIO_MAXIOV)
        vlen = UIO_MAXIOV;

    datagrams = 0;
    //是它,就是它,我們的老朋友
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        return err;

    used_address.name_len = UINT_MAX;
    entry = mmsg;
    compat_entry = (struct compat_mmsghdr __user *)mmsg;
    err = 0;
    //既然是傳送多個訊息,那也就是多次呼叫sendmsg
    while (datagrams < vlen) {
        if (MSG_CMSG_COMPAT & flags) {//相容性問題,暫不考慮
            err = ___sys_sendmsg(sock, (struct msghdr __user *)compat_entry,
                         &msg_sys, flags, &used_address);
            if (err < 0)
                break;
            err = __put_user(err, &compat_entry->msg_len);
            ++compat_entry;
        } else {
            //這不,走過你(sendmsg)來時的路,想像著....
            err = ___sys_sendmsg(sock, (struct msghdr __user *)entry,
                         &msg_sys, flags, &used_address);
            if (err < 0)
                break;
            err = put_user(err, &entry->msg_len);
            ++entry;
        }

        if (err)
            break;
        ++datagrams;
    }

    fput_light(sock->file, fput_needed);

    /* We only return an error if no datagrams were able to be sent */
    if (datagrams != 0)
        return datagrams;

    return err;
}

這樣,send、sendto、sendmsg、sendmmsg這四個兄弟總算走到一起,在sock_sendmsg()共襄盛舉。

那write家族又是怎麼和他們匯合的呢,請看下回分解。