首頁 > 軟體

Linux使用者態資料傳送和接收

2020-06-16 16:30:59

前言:在前面的文章中介紹了協定無關層和系統呼叫介面層。當使用者態程式呼叫sendto()和recvfrom()來傳送和接收資料時,其中的過程是怎麼樣的呢?又是經過了幾次資料拷貝呢?這篇重點說明這兩個介面,接著上篇來說明資料傳輸的過程。

1. sendto()

在上一篇中,我們知道,當在應用中呼叫sendto()傳送函數時,就會呼叫到系統呼叫sys_sendto,在socket.c檔案中,我們找到了這個系統呼叫的實現。
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
        unsigned, flags, struct sockaddr __user *, addr,
        int, addr_len)

首先根據檔案描述符找到對應的socket結構;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
        goto out;

然後填充要傳送的訊息的訊息頭,包括使用者態資料起始地址,長度等資訊。
iov.iov_base = buff;
iov.iov_len = len;
msg.msg_name = NULL;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_namelen = 0;

之後把相關的地址資訊copy到核心態

最後進行傳送:
err = sock_sendmsg(sock, &msg, len);
          ||
    __sock_sendmsg(&iocb, sock, msg, size);
          ||
        sock->ops->sendmsg(iocb, sock, msg, size);

到這裡,我們看到呼叫到了協定族註冊的傳送函數,如果是DGRAM型別的socket,對應的INET族的傳送函數是--inet_sendmsg()。我們繼續沿著這條線往下走,在INET族中,DGRAM型別對應的就是UDP協定,所以,最終會呼叫到udp_sendmsg()中。

接下來看一下這個函數,在這裡不打算仔細說這個函數(暫時還真說不清。。。太龐大了!)。只關心使用者資料是怎麼拷貝到核心空間,組裝成udp報文的。

關於函數最開始的很多工作,先跳過,直接到標籤do_append_data處:
 首先確認了得到使用者態封包的處理常式,然後就開始新增資料頭。
getfrag  =  is_udplite ?  udplite_getfrag : ip_generic_getfrag;
err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,
        sizeof(struct udphdr), &ipc, &rt,
        corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);

其實,拷貝使用者態的資料就在getfrag函數實現的。
static __inline__ int udplite_getfrag(void *from, char *to, int  offset,
                      int len, int odd, struct sk_buff *skb)
{
    return memcpy_fromiovecend(to, (struct iovec *) from, offset, len);
}

其他的就先不多說了,留到分析傳輸層UDP的時候再細說。sendto也就說到這裡。

2. recvfrom()

第一節,我們看了傳送的過程呼叫,下面看一下接收的過程。在無關層呼叫上,sendto()和recvfrom()的處理過程是一樣的,就是根據socket型別找到對應的處理常式,在INET域,最後就是udp_recvmsg()

這個函數就相對簡單,首先從佇列中取出網絡卡驅動收到的資料:
skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
                  &peeked, &err);
if (!skb)
    goto out;

然後就把收到的收據拷貝到使用者態中:
if (skb_csum_unnecessary(skb))
        err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr),
                          msg->msg_iov, copied);
else {
    err = skb_copy_and_csum_datagram_iovec(skb,
                          sizeof(struct udphdr),
                          msg->msg_iov);

最後打上時間戳和接收到的封包的地址資訊:
sock_recv_timestamp(msg, sk, skb);

/* Copy the address. */
if (sin) {
    sin->sin_family = AF_INET;
    sin->sin_port = udp_hdr(skb)->source;
    sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
    memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
}

基本的過程就做完了,其餘的都是例外處理等流程,可以針對性的看。更詳細的分析,等到說傳輸層UDP的時候再議。


IT145.com E-mail:sddin#qq.com