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_addr和addrlen這兩個引數將被忽略。可見,
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家族又是怎麼和他們匯合的呢,請看下回分解。