一、Linux 4.19内核recvmsg系统调用代码中文注释
Linux内核中处理接收消息的系统调用的实现代码,对代码关键部分添加中文注释:
// 内核辅助函数,用于执行接收消息的操作
static int ___sys_recvmsg(struct socket *sock, struct user_msghdr __user *msg,
struct msghdr *msg_sys, unsigned int flags, int nosec)
{
// 声明一些局部变量
struct compat_msghdr __user *msg_compat = (struct compat_msghdr __user *)msg;
// 为IO提供一个快速的栈上局部iovec数组
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov = iovstack;
unsigned long cmsg_ptr;
int len;
ssize_t err;
// 本地socket地址存储结构体
struct sockaddr_storage addr;
// 用户模式的地址指针
struct sockaddr __user *uaddr;
int __user *uaddr_len = COMPAT_NAMELEN(msg);
msg_sys->msg_name = &addr;
// 根据flags判断是否使用兼容模式的消息头
if (MSG_CMSG_COMPAT & flags)
err = get_compat_msghdr(msg_sys, msg_compat, &uaddr, &iov);
else
err = copy_msghdr_from_user(msg_sys, msg, &uaddr, &iov);
if (err < 0)
return err;
cmsg_ptr = (unsigned long)msg_sys->msg_control;
msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
// 假设所有内核代码都知道sockaddr_storage的大小
msg_sys->msg_namelen = 0;
// 检查文件标志是否为非阻塞,并设置相应的标志
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
// 接收消息,nosec标志决定是否跳过安全检查
err = (nosec ? sock_recvmsg_nosec : sock_recvmsg)(sock, msg_sys, flags);
if (err < 0)
goto out_freeiov;
len = err;
// 如果有地址信息,将它从内核空间复制回用户空间
if (uaddr != NULL) {
err = move_addr_to_user(&addr, msg_sys->msg_namelen, uaddr, uaddr_len);
if (err < 0)
goto out_freeiov;
}
// 更新用户提供的msg_flags
err = __put_user((msg_sys->msg_flags & ~MSG_CMSG_COMPAT), COMPAT_FLAGS(msg));
if (err)
goto out_freeiov;
// 更新控制信息的长度
if (MSG_CMSG_COMPAT & flags)
err = __put_user((unsigned long)msg_sys->msg_control - cmsg_ptr,
&msg_compat->msg_controllen);
else
err = __put_user((unsigned long)msg_sys->msg_control - cmsg_ptr,
&msg->msg_controllen);
if (err)
goto out_freeiov;
err = len;
out_freeiov:
// 释放分配的iovec,如果它不是使用栈分配的
kfree(iov);
return err;
}
// 通过系统调用接口的recvmsg函数实现
long __sys_recvmsg(int fd, struct user_msghdr __user *msg, unsigned int flags,
bool forbid_cmsg_compat)
{
// 声明变量用于文件描述符引用计数和错误处理
int fput_needed, err;
struct msghdr msg_sys;
struct socket *sock;
// 如果禁止了兼容模式,但flags指定了兼容模式,则返回错误
if (forbid_cmsg_compat && (flags & MSG_CMSG_COMPAT))
return -EINVAL;
// 通过文件描述符查找对应的socket对象
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
// 调用内核辅助函数执行真正的接收操作
err = ___sys_recvmsg(sock, msg, &msg_sys, flags, 0);
// 减少文件描述符引用计数
fput_light(sock->file, fput_needed);
out:
return err;
}
// 定义recvmsg系统调用的接口函数
SYSCALL_DEFINE3(recvmsg, int, fd, struct user_msghdr __user *, msg,
unsigned int, flags)
{
// 调用内部接口函数处理接收信息的请求,并返回结果
return __sys_recvmsg(fd, msg, flags, true);
}
这段代码主要包含两个部分:`___sys_recvmsg()` 是接收消息的内部实现函数,它负责从用户空间复制消息头到内核空间,接收消息,然后将结果传回到用户空间。这个函数主要被 __sys_recvmsg() 调用,后者是适配系统调用接口的函数。
`__sys_recvmsg()` 函数是对 ___sys_recvmsg() 的简单封装,多了一些检查与文件描述符处理,最终通过 SYSCALL_DEFINE3 宏定义了 recvmsg 系统调用的接口函数,该函数将调用 __sys_recvmsg() 同时要求 flags 参数不能包含兼容模式 MSG_CMSG_COMPAT。当用户空间的程序调用 recvmsg 时,它就会进入内核空间执行这段代码,完成消息的接收。
二、代码讲解:
这段代码是Linux内核中处理接收消息(`recvmsg`系统调用)的C语言代码。该代码主要用于socket通信,以接收从指定socket中来的消息。
接下来,将逐步解释代码的主要功能和组件:
___sys_recvmsg 函数
这是一个静态辅助函数,用来从用户空间接收消息头(`user_msghdr`),将其复制到内核空间并接收消息。
参数解释:
- struct socket *sock: 指向与文件描述符相关联的socket结构的指针。
- struct user_msghdr __user *msg: 用户空间提供的消息头指针,包含了消息的各种控制信息。
- struct msghdr *msg_sys: 内核空间的消息头结构,它将被填充接收到的数据。
- unsigned int flags: 指定接收行为的标志位,如 MSG_DONTWAIT,`MSG_CMSG_CLOEXEC` 等。
- int nosec: 这是一个布尔标识,表示是否绕过安全模块检查。
函数的主要步骤:
1. 分配 iovec 结构,用于描述接收缓冲区的位置和大小。
2. 检查 flags 是否指定了兼容模式(`MSG_CMSG_COMPAT`),并从用户空间拷贝消息头到 msg_sys 结构。
3. 设定消息标志位和地址空间大小。
4. 根据socket文件的非阻塞标志位设置相应的标志位。
5. 调用 sock_recvmsg 或 sock_recvmsg_nosec 来从socket实际接收数据。
6. 若有地址信息,将其拷贝回用户空间。
7. 更新用户空间的消息相关控制信息。
8. 释放分配的iovec结构并返回接收的长度或错误码。
__sys_recvmsg 函数
这个函数是BSD风格的接收消息的接口。
参数解释:
- int fd: 文件描述符,指向需要接收消息的socket。
- struct user_msghdr __user *msg: 用户空间的消息头指针。
- unsigned int flags: 同上。
- bool forbid_cmsg_compat: 这是一个布尔标志,表示是否禁止使用兼容模式消息控制头。
函数的主要步骤:
1. 检查参数是否要禁止兼容模式。
2. 通过文件描述符获取对应的socket结构。
3. 使用`___sys_recvmsg`函数从上一步获取的socket中接收消息。
4. 确保调用结束后释放socket的引用。
5. 返回操作的结果。
SYSCALL_DEFINE3(recvmsg, ...)
这是recvmsg系统调用的定义,它是系统调用接口的一个宏,它创建了一个接收三个参数的系统调用。
函数的作用是简单地调用`__sys_recvmsg`函数,传递文件描述符、消息头指针和标志位,并指定禁止兼容模式消息控制头。
注:这里的 "兼容模式" 通常是指32位程序在64位系统上运行时,内核数据结构与用户空间数据结构大小不一致的情况。
代码中涉及到用户空间和内核空间的交互,以及对临界资源的操作,在实际执行过程中需要考虑数据的同步和安全性。需要确保所有从用户空间复制的数据都经过了正确的验证和处理,防止潜在的安全漏洞或内核崩溃。
这段代码的主要应用场景在于Linux网络编程,特别是涉及到基于socket的TCP/IP或UDP/IP通信。用户空间的程序通过文件描述符来引用打开的socket,当应用程序需要从网络上读取数据时,它会调用`recvmsg`系统调用。此系统调用会将用户空间提供的参数传递到内核空间,内核空间负责从网络接口卡读取数据,处理它们,并将结果复制回用户空间。
关键点是,`recvmsg`系统调用提供了一种灵活的消息接收机制,允许用户程序获取关于接收到的数据的详细信息,比如发送者的地址等。这在开发网络服务器和客户端程序时是非常有用的。