【C语言】linux内核recvmmsg系统调用代码

一、Linux 4.19内核recvmmsg系统调用代码中文注释

/**     Linux recvmmsg 接口*/
int __sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen,unsigned int flags, struct timespec *timeout)
{int fput_needed, err, datagrams; // 文件引用计数标记、错误码和接收的数据报数struct socket *sock; // 套接字结构struct mmsghdr __user *entry; // 指向用户空间的多消息结构struct compat_mmsghdr __user *compat_entry; // 兼容模式下的多消息结构struct msghdr msg_sys; // 内核空间的消息头结构struct timespec64 end_time; // 定义超时结束时间struct timespec64 timeout64; // 用来存放经过转换的超时时间结构体// 如果有超时设置,并且它不合法(负值或大于最大值),则返回 -EINVALif (timeout &&poll_select_set_timeout(&end_time, timeout->tv_sec,timeout->tv_nsec))return -EINVAL;datagrams = 0; // 初始化接收到的数据报数为0// 根据文件描述符获取对应的socket结构,并进行错误检查sock = sockfd_lookup_light(fd, &err, &fput_needed);if (!sock)return err; // 未找到套接字或出错,返回错误码// 如果不是用于获取错误队列的消息,则检查套接字的错误状态if (likely(!(flags & MSG_ERRQUEUE))) {err = sock_error(sock->sk);if (err) {datagrams = err; // 如果套接字有错误,设置数据报数为错误码goto out_put; // 跳转到释放sock结构的标签}}entry = mmsg; // 将用户传入的mmsg赋给entrycompat_entry = (struct compat_mmsghdr __user *)mmsg; // 兼容模式下也使用mmsg// 循环接收多个数据报,直到达到用户要求的数量vlenwhile (datagrams < vlen) {/** 只有在接收第一个数据报时才不需要询问LSM(Linux安全模块)。*/if (flags & MSG_CMSG_COMPAT) { // 如果是兼容模式// 接收消息,并将结果存在compat_entry中err = ___sys_recvmsg(sock, (struct user_msghdr __user *)compat_entry,&msg_sys, flags & ~MSG_WAITFORONE, datagrams);if (err < 0)break; // 如果出现错误,跳出循环// 把返回的数据长度写回用户空间的msg_len字段err = __put_user(err, &compat_entry->msg_len);++compat_entry; // 移动到下一个消息结构} else {// 非兼容模式下的接收和写回操作err = ___sys_recvmsg(sock, (struct user_msghdr __user *)entry,&msg_sys, flags & ~MSG_WAITFORONE, datagrams);if (err < 0)break;err = put_user(err, &entry->msg_len);++entry;}if (err)break;++datagrams; // 接收到的数据报数加一// 如果设置了MSG_WAITFORONE标志,那么在接收到一个数据报之后就设置MSG_DONTWAIT标志if (flags & MSG_WAITFORONE)flags |= MSG_DONTWAIT;if (timeout) {// 获取当前时间,并计算剩余的超时时间ktime_get_ts64(&timeout64);*timeout = timespec64_to_timespec(timespec64_sub(end_time, timeout64));// 如果超时为0,跳出循环if (timeout->tv_sec < 0) {timeout->tv_sec = timeout->tv_nsec = 0;break;}// 如果超时已经达到,返回已经接收到数量少于vlen的数据报if (timeout->tv_nsec == 0 && timeout->tv_sec == 0)break;}// 如果是带外数据,立即返回if (msg_sys.msg_flags & MSG_OOB)break;cond_resched(); // 让出处理器}if (err == 0)goto out_put;// 如果没有错误,则跳转到 out_putif (datagrams == 0) {datagrams = err; // 如果一个数据报都没接收到,返回错误码goto out_put;}/** 如果函数实际接收到的数据报数量少于要求的数量(vlen),* 可能是因为套接字是非阻塞的并且没有足够的数据报...*/if (err != -EAGAIN) {/** ...或者是因为在接收到一些数据报之后,recvmsg 返回了一个错误。* 在这种情况下,我们将错误记录下来,在下一次调用时返回,* 或者如果应用程序使用 getsockopt(SO_ERROR) 查询的话返回它。*/sock->sk->sk_err = -err;}
out_put:fput_light(sock->file, fput_needed);// 释放之前获取的 socket 文件的引用return datagrams; // 返回接收到的数据报数
}
static int do_sys_recvmmsg(int fd, struct mmsghdr __user *mmsg, unsigned int vlen,unsigned int flags, struct timespec __user *timeout)
{int datagrams;struct timespec timeout_sys;// 同步一下,用于存放从用户空间复制来的超时时间if (flags & MSG_CMSG_COMPAT)return -EINVAL; // 如果设置了 MSG_CMSG_COMPAT 标志,则返回无效参数错误if (!timeout)return __sys_recvmmsg(fd, mmsg, vlen, flags, NULL);// 如果 timeout 为空,直接调用 __sys_recvmmsg 并返回结果if (copy_from_user(&timeout_sys, timeout, sizeof(timeout_sys)))return -EFAULT; // 将用户空间的 timeout 数据拷贝到内核空间失败则返回错误datagrams = __sys_recvmmsg(fd, mmsg, vlen, flags, &timeout_sys);// 调用实际处理接收数据报的内核函数if (datagrams > 0 &&copy_to_user(timeout, &timeout_sys, sizeof(timeout_sys)))datagrams = -EFAULT; // 将内核空间的 timeout 数据拷贝回用户空间失败则返回错误return datagrams; // 返回接收到的数据报数量,或者是错误码
}
SYSCALL_DEFINE5(recvmmsg, int, fd, struct mmsghdr __user *, mmsg, unsigned int, vlen,unsigned int, flags, struct timespec __user *, timeout)
{return do_sys_recvmmsg(fd, mmsg, vlen, flags, timeout); // 进行系统调用的封装
}

二、代码讲解

这段代码是 Linux 内核中 recvmmsg 系统调用的实现,`recvmmsg` 是一个用来接收多个消息的系统调用,它是 recvmsg 的批量版本。下面用中文解释每个函数的功能。
1. __sys_recvmmsg 函数:
这个内核函数执行实际的消息接收工作。它尝试从指定的文件描述符 fd 接收 vlen 个消息,并将结果存储在 mmsg 指向的内存区域中。函数返回接收到的消息数量或错误码。
   - fd 是打开的套接字的文件描述符。
   - mmsg 指向用户空间的多个消息头的数组。
   - vlen 指定 mmsg 数组的长度,即可以接收的最大消息数量。
   - flags 是一组影响接收行为的标志位。
   - timeout 指向指定接收操作的最大等待时间的 timespec 结构。
2. do_sys_recvmmsg 函数:
这个函数是 recvmmsg 系统调用的辅助函数。它检查输入参数并调用 __sys_recvmmsg 函数来处理实际的消息接收。此外,函数还处理与用户空间的时间间隔结构的拷贝。
   - 如果设置了不兼容的标志 MSG_CMSG_COMPAT,则返回错误。
   - 如果提供了 timeout 参数,它将从用户空间复制该值。
   - 调用 __sys_recvmmsg 并将结果返回给用户空间。
3. recvmmsg 系统调用 (SYSCALL_DEFINE5(recvmmsg, ...)):
这是 recvmmsg 系统调用的入口点,它定义了接收此调用所需的参数并调用 `do_sys_recvmmsg 函数来处理用户请求。
让我们深入一些函数细节:
- __sys_recvmmsg 函数内部的操作包括:
  - 如果提供了超时参数 (timeout),它会计算一个结束时间(`end_time`)。
  - 使用 sockfd_lookup_light 函数找到与文件描述符 fd 关联的套接字对象 sock。
  - 检查错误队列,并在有错误时直接返回。
  - 在一个循环中,对每个 mmsghdr 执行接收操作,直到达到用户指定的消息数量 vlen、出现错误、超时或接收到带外数据为止。
  - 在接收消息后,更新用户空间的 mmsghdr 结构(如消息长度)。
  - 如果设置了 MSG_WAITFORONE 标志,则在接收到一个消息后,接下来的调用将不会阻塞。
  - 如果设置了超时并且超时发生,则结束循环,即使没有达到 vlen 指定的数量。
  - 函数返回接收到的消息数量或在没有收到任何消息时的错误码。
- do_sys_recvmmsg 函数:
  - 校验 flags 参数。
  - 如果提供了 timeout 参数,则从用户空间拷贝超时值。
  - 调用 __sys_recvmmsg 函数执行消息接收。
  - 如果接收到消息并且将超时值回拷贝到用户空间时出现错误,则返回错误。
- SYSCALL_DEFINE5 宏用于定义具有五个参数的系统调用。这里它定义了 recvmmsg 系统调用,这个系统调用的参数包括:
  - fd(文件描述符)
  - mmsg(指向用户空间的 mmsghdr 结构数组)
  - vlen(用户希望接收的消息数量)
  - flags(接收行为的标志位)
  - timeout(超时时间的用户空间指针)
整体而言,这段代码是一种在内核级别对多个网络消息进行高效批量接收的一种机制,常用在需要高性能网络通信的场景中。