linux socket-recvfrom系统调用

Posted osnet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux socket-recvfrom系统调用相关的知识,希望对你有一定的参考价值。

文章目录

recvfrom库函数

应用程序调用recv或recvfrom库函数接收数据。recv和recvfrom主要区别是是否设置socket地址。

__socketcall ssize_t recvfrom(int, void*, size_t, int, const struct sockaddr*, socklen_t*);
ssize_t recv(int, void*, size_t, int);

ssize_t recv(int socket, void *buf, size_t len, int flags) 
  return recvfrom(socket, buf, len, flags, NULL, 0); //对于recv,指定后两个参数为空


armv8系统调用:
ENTRY(recvfrom)
    mov     x8, __NR_recvfrom
    svc     #0

    cmn     x0, #(MAX_ERRNO + 1)
    cneg    x0, x0, hi
    b.hi    __set_errno_internal

    ret
END(recvfrom)

recvfrom系统调用

先看数据结构

struct msghdr

struct msghdr为socket层和协议层进行通信的消息形式。
struct iovec存储用户空间传递的buf地址和长度

struct iovec

	void __user *iov_base;	/* BSD uses caddr_t (1003.1g requires void *) */
	__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
;

/*
 *	As we do 4.4BSD message passing we use a 4.4BSD message passing
 *	system, not 4.3. Thus msg_accrights(len) are now missing. They
 *	belong in an obscure libc emulation or the bin.
 */
 
struct msghdr 
	void	*	msg_name;	/* Socket name			*/ //包括socket地址等
	int		msg_namelen;	/* Length of name		*/
	struct iovec *	msg_iov;	/* Data blocks			*/  //用户空间buf block,可以是数组
	__kernel_size_t	msg_iovlen;	/* Number of blocks		*/ //用户空间buf block个数,数组长度
	void 	*	msg_control;	/* Per protocol magic (eg BSD file descriptor passing) */ //控制消息
	__kernel_size_t	msg_controllen;	/* Length of cmsg list */
	unsigned int	msg_flags;
;

struct kiocb

每个IO请求都对应一个kiocb结构体,ki_ctx指示是否为同步IO还是异步IO,默认NULL为同步IO
ki_users指示引用数量。tsk存储当前进程。

struct kiocb 
	atomic_t		ki_users;

	struct file		*ki_filp;
	struct kioctx		*ki_ctx;	/* NULL for sync ops */
	kiocb_cancel_fn		*ki_cancel;
	void			(*ki_dtor)(struct kiocb *);

	union 
		void __user		*user;
		struct task_struct	*tsk;
	 ki_obj;

	__u64			ki_user_data;	/* user's data for completion */
	loff_t			ki_pos;

	void			*private;
	/* State that we remember to be able to restart/retry  */
	unsigned short		ki_opcode;
	size_t			ki_nbytes; 	/* copy of iocb->aio_nbytes */
	char 			__user *ki_buf;	/* remaining iocb->aio_buf */
	size_t			ki_left; 	/* remaining bytes */
	struct iovec		ki_inline_vec;	/* inline vector */
 	struct iovec		*ki_iovec;
 	unsigned long		ki_nr_segs;
 	unsigned long		ki_cur_seg;

	struct list_head	ki_list;	/* the aio core uses this
						 * for cancellation */

	/*
	 * If the aio_resfd field of the userspace iocb is not zero,
	 * this is the underlying eventfd context to deliver events to.
	 */
	struct eventfd_ctx	*ki_eventfd;
;


struct sock_iocb

struct sock_iocb为sock 类型io 请求控制块。struct kiocb 中private指向sock_iocb类型。

/* sock_iocb: used to kick off async processing of socket ios */
struct sock_iocb 
	struct list_head	list;

	int			flags;
	int			size;
	struct socket		*sock; //指向socket
	struct sock		*sk;//指向socket中sock
	struct scm_cookie	*scm;
	struct msghdr		*msg, async_msg; //指向msghdr消息
	struct kiocb		*kiocb; 
;

recvfrom

/*
 *	Receive a frame from the socket and optionally record the address of the
 *	sender. We verify the buffers are writable and if needed move the
 *	sender address from kernel to user space.
 */

SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
		unsigned int, flags, struct sockaddr __user *, addr,
		int __user *, addr_len)

	struct socket *sock;
	struct iovec iov;
	struct msghdr msg;
	struct sockaddr_storage address;
	int err, err2;
	int fput_needed;


	//根据文件描述符fd找到对应的socekt结构指针
	sock = sockfd_lookup_light(fd, &err, &fput_needed);

    //构造struct msghdr,用于socket和协议栈层交互
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_iovlen = 1;
	msg.msg_iov = &iov; //指向struct iovec,存储了用户空间buf
	iov.iov_len = size;
	iov.iov_base = ubuf;//存储了用户空间buf
	/* Save some cycles and don't copy the address if not needed */
	msg.msg_name = addr ? (struct sockaddr *)&address : NULL;//用于存储发送端的地址
	/* We assume all kernel code knows the size of sockaddr_storage */
	msg.msg_namelen = 0;
	if (sock->file->f_flags & O_NONBLOCK)
		flags |= MSG_DONTWAIT; //标志是否为阻塞io
	//调用sock接口
	err = sock_recvmsg(sock, &msg, size, flags);

	if (err >= 0 && addr != NULL)  //保存发送端地址到用户空间 地址buf
		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;



int sock_recvmsg(struct socket *sock, struct msghdr *msg,
		 size_t size, int flags)

	struct kiocb iocb;
	struct sock_iocb siocb;
	int ret;
    //构造一个kiocb ,并且iocb.private存储siocb;用于io请求
	init_sync_kiocb(&iocb, NULL);
	iocb.private = &siocb;
	ret = __sock_recvmsg(&iocb, sock, msg, size, flags);
	if (-EIOCBQUEUED == ret)
		ret = wait_on_sync_kiocb(&iocb);
	return ret;


__sock_recvmsg---》__sock_recvmsg_nosec:
static inline int __sock_recvmsg_nosec(struct kiocb *iocb, struct socket *sock,
				       struct msghdr *msg, size_t size, int flags)

	struct sock_iocb *si = kiocb_to_siocb(iocb);
    //填充上面构造的sock_iocb 
	si->sock = sock;
	si->scm = NULL;
	si->msg = msg;  //指向了msghdr
	si->size = size;
	si->flags = flags;

    //调用具体socket type的socket接口,比如SOCK_STREAM,SOCK_DGRAM,或者SOCK_RAW等
    //这里io请求控制块从kiocb 转换为了sock_iocb ,也就是从通用socekt层到具体socket type的socket 层面,使用具体的对应iocb
	return sock->ops->recvmsg(iocb, sock, msg, size, flags);


实际看SOCK_STREAM,SOCK_DGRAM,或者SOCK_RAW 类型socket的socket管理面recvmsg 接口都为inet_recvmsg

int inet_recvmsg(struct kiocb *iocb, 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);
    
    //调用sock中 sk_prot-》recvmsg,也就是调用socekt类型下,某种具体协议的recvmsg函数,从socekt层面转到协议栈层面
    //比如SOCK_STREAM类型socekt下的tcp,SOCK_DGRAM类型socekt下的udp
	err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
				   flags & ~MSG_DONTWAIT, &addr_len);
	if (err >= 0)
		msg->msg_namelen = addr_len;
	return err;


以上是关于linux socket-recvfrom系统调用的主要内容,如果未能解决你的问题,请参考以下文章

怎样查询linux系统调用函数

Linux 系统调用

在Linux操作系统中如何截获系统调用

如何在Linux内核里增加一个系统调用?

1.linux系统调用和库函数调用的区别

[Linux]系统调用理解