内核版本:4.1.15
开发板:正点原子IMX6ULL
参考资料:深入理解Linux网络——张彦飞
目录
在同步阻塞IO模型中,先是用户进程发起创建socket的指令,然后切换到内核态完成了内核态的初始化,接下来,Linux在数据包的接收上,是硬中断和ksoftirqd线程在进程处理。当ksoftirq线程处理完以后,在通知相关的用户进程。
1 等待接收消息
用户态使用recv函数接受消息,当用户态执行recv函数后,进入系统调用后,用户态进入内核态,执行一些列的内核协议层函数,然后到socket对象的接收队列中查看是否有数据,没有的话就把自己添加到socket对应的等待队列里,然后让出cpu使用权,操作系统会选择下一个就绪的进程来执行。
// net/socket.c
SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
unsigned int, flags, struct sockaddr __user *, addr,
int __user *, addr_len)
{
/* 定义sock,表示网络套接字,后续通过文件描述符fd查找到对应的套接字后,将其赋值给sock */
struct socket *sock;
/* 用于描述一段内存区域,这里用于表示接收数据的缓冲区信息 */
struct iovec iov;
/* msghdr结构体是消息头结构体,用于存储与消息相关的各种信息,如消息的
控制信息,数据缓冲区 目的地址等 */
struct msghdr msg;
/* sockaddr_storage结构体是一个通用的套接字地址存储结构体,可以容纳
不同类型的套接字地址结构,用于存储发送方的地址信息 */
struct sockaddr_storage address;
/* 存储函数执行过程中的错误码 */
int err, err2;
/* 用于表示是否需要减少文件引用计数 */
int fput_needed;
/* 将用空间缓冲区ubuf导入到内核空间的iov中 */
err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);
if (unlikely(err))
return err;
/* 根据传入的fd找到socket对象,找到之后赋值给sock */
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
msg.msg_control = NULL;
msg.msg_controllen = 0;
/* Save some cycles and don't copy the address if not needed */
/* 将address的地址赋值给msg.msg_name,以便在接收数据时获取发送方的地址
信息 */
msg.msg_name = addr ? (struct sockaddr *)&address : NULL;
/* We assume all kernel code knows the size of sockaddr_storage */
msg.msg_namelen = 0;
/* 检查套接字文件的标志f_flags是否设置了O_NONBLOCK,非阻塞标志。
如果设置了该标志,则将flags标志位或上的MSG_DONTWAIT,表示此次接收
操作将以非阻塞方式进行 */
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
/* 通过sock_recvmsg函数从套接字sock接收数据和消息,并将其存储到msg结构体
中,该函数接受套接字sock、消息头结构体指针msg,数据缓存区的迭代计数以及
flag作为参数 */
err = sock_recvmsg(sock, &msg, iov_iter_count(&msg.msg_iter), flags);
if (err >= 0 && addr != NULL)
{
/* 如果数据接收成功,即err>=0,表示需要将发送方的地址信息复制到
用户空间, */
err2 = move_addr_to_user(&address,
msg.msg_namelen, addr, addr_len);
if (err2 < 0)
err = err2;
}
/* 用来决定是否减少套接字相关文件的引用计数 */
fput_light(sock->file, fput_needed);
out:
return err;
}
接下来sock_recvmsg会调用sock_recvmsg_nosec函数,其中sock->ops->recvmsg指针是在inet_creat函数中注册的。
// net/socket.c
static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
// inet_recvmsg
return sock->ops->recvmsg(sock, msg, size, flags);
}
// net/ipv4/af_inet.c
int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
int flags)
{
struct sock *sk = sock->sk;
int addr_len = 0;
int err;
sock_rps_record_flow(sk);
err = sk->sk_prot->recvmsg(sk, msg, size, flags & MSG_DONTWAIT,
flags & ~MSG_DONTWAIT, &addr_len);
if (err >= 0)
msg->msg_namelen = addr_len;
return err;
}
可以看的sock->ops指向的是inetsw_array数组中的inet_stream_ops指针,而sk->sk_port指针指向的是tcp_port指针。
// inetsw_array是传输层协议
// static struct inet_protosw inetsw_array[] =
// {
// {
/