用户端在使用sendmsg/recvmsg发送或者接收数据时,会使用msghdr来构造消息,其对应的内核结构为user_msghdr;其中msg_iov向量指向了多个数据区,msg_iovlen标识了数据区个数;在通过系统调用进入内核后,该结构中的信息会拷贝给内核的msghdr结构;
1 /* 用户空间的消息头 */ 2 struct user_msghdr { 3 /* 指向地址结构 */ 4 void __user *msg_name; /* ptr to socket address structure */ 5 /* 地址结构长度 */ 6 int msg_namelen; /* size of socket address structure */ 7 /* 数据 */ 8 struct iovec __user *msg_iov; /* scatter/gather array */ 9 /* 数据区个数 */ 10 __kernel_size_t msg_iovlen; /* # elements in msg_iov */ 11 /* 控制信息 */ 12 void __user *msg_control; /* ancillary data */ 13 /* 控制信息缓冲区长度 */ 14 __kernel_size_t msg_controllen; /* ancillary data buffer length */ 15 16 /* 接收信息的标志 */ 17 unsigned int msg_flags; /* flags on received message */ 18 };
在套接字发送接收系统调用流程中,send/recv,sendto/recvfrom,sendmsg/recvmsg最终都会使用内核中的msghdr来组织数据,如下,其中msg_iter为指向数据区域的向量汇总信息,其中数据区指针可能包含一个或者多个数据区,对于send/sendto其只包含了一个数据区;
1 /* 2 * As we do 4.4BSD message passing we use a 4.4BSD message passing 3 * system, not 4.3. Thus msg_accrights(len) are now missing. They 4 * belong in an obscure libc emulation or the bin. 5 */ 6 7 struct msghdr { 8 /* 指向socket地址结构 */ 9 void *msg_name; /* ptr to socket address structure */ 10 /* 地址结构长度 */ 11 int msg_namelen; /* size of socket address structure */ 12 /* 数据 */ 13 struct iov_iter msg_iter; /* data */ 14 /* 控制信息 */ 15 void *msg_control; /* ancillary data */ 16 /* 控制信息缓冲区长度 */ 17 __kernel_size_t msg_controllen; /* ancillary data buffer length */ 18 19 /* 接收信息的标志 */ 20 unsigned int msg_flags; /* flags on received message */ 21 22 /* 异步请求控制块 */ 23 struct kiocb *msg_iocb; /* ptr to iocb for async requests */ 24 };
向量指向的数据通过iov_iter进行汇总信息和调整指向,其中iov为多个数据区的首地址,nr_segs为数据区个数;
1 struct iov_iter { 2 int type; /* 类型,读写方向,以及数据指针类型ITER_XXX */ 3 size_t iov_offset; /* 偏移 */ 4 size_t count; /* 数据总字节数 */ 5 union { 6 /* 数据向量指针 */ 7 const struct iovec *iov; 8 const struct kvec *kvec; 9 const struct bio_vec *bvec; 10 struct pipe_inode_info *pipe; 11 }; 12 union { 13 /* 向量中的数据块数量 */ 14 unsigned long nr_segs; 15 struct { 16 int idx; 17 int start_idx; 18 }; 19 }; 20 };
对于每个数据区,iovec记录了数据区的首地址以及数据长度;
1 /* 一个数据区的信息 */ 2 struct iovec 3 { 4 /* 数据区地址 */ 5 void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ 6 /* 数据区长度 */ 7 __kernel_size_t iov_len; /* Must be size_t (1003.1g) */ 8 };
总的数据组织结构如下:
1 struct msghdr{ 2 iov_iter { 3 type 4 iov_offset 5 count | total_buff_len = buff0_len + buff1_len + buff2_len ? 6 --------- 7 iov_base|------>[buff0] 8 iov_len | buff0_len 9 --------- 10 iov_base|------>[buff1] 11 iov_len | buff1_len 12 --------- 13 iov_base|------>[buff2] 14 iov_len | buff2_len 15 --------- 16 nr_segs | iov_count = 3 17 18 } 19 }