Linux Kernel 2.6.9源码分析 -- accept

冷暖自知 提交于 2020-01-31 03:20:25

Linux Kernel 2.6.9源码分析 – accept

先来看一下原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
sockfd:这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
addr:这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
len:如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
下面来看看系统调用sys_accept

asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen)
{
	struct socket *sock, *newsock;
	int err, len;
	char address[MAX_SOCK_ADDR];
	sock = sockfd_lookup(fd, &err);
	if (!sock)
		goto out;
	err = -EMFILE;
	if (!(newsock = sock_alloc())) 
		goto out_put;
	newsock->type = sock->type;
	newsock->ops = sock->ops;
	............................
	err = sock->ops->accept(sock, newsock, sock->file->f_flags);
	if (err < 0)
		goto out_release;
    ..................
	if ((err = sock_map_fd(newsock)) < 0)
		goto out_release;
    .............................
}

从上面的代码来看,

  1. 通过用户传入的fd找到之前通过sys_socket创建的struct socket结构
  2. 分配一个新的struct socket结构,然后将旧socket的type 和ops赋值给新socket
  3. sock->ops->accept挂上一个客户端请求对应的struct sock,后面来看详细的实现
  4. 通过sock_map_fd,分配struct file并挂上新socket,然后在挂载在当前进程描述符中.
  5. 通过参数upeer_sockaddr 返回client的地址信息给用户,以及通过返回值返回新socket对应的文件描述符给用户.

sock->ops->accept对应的函数为:

int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
	struct sock *sk1 = sock->sk;
	int err = -EINVAL;
	struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
	if (!sk2)
		goto do_err;
	lock_sock(sk2);
	BUG_TRAP((1 << sk2->sk_state) &
		 (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE));
	sock_graft(sk2, newsock);
	newsock->state = SS_CONNECTED;
	err = 0;
	release_sock(sk2);
do_err:
	return err;
}

TCP类型的socket最终会调用到:

struct sock *tcp_accept(struct sock *sk, int flags, int *err)
{
	struct tcp_opt *tp = tcp_sk(sk);
	struct open_request *req;
	struct sock *newsk;
	int error;
	lock_sock(sk);
	/* We need to make sure that this socket is listening,
	 * and that it has something pending.
	 */
	error = -EINVAL;
	if (sk->sk_state != TCP_LISTEN)
		goto out;
	/* Find already established connection */
	if (!tp->accept_queue) {
		long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
		/* If this is a non blocking socket don't sleep */
		error = -EAGAIN;
		if (!timeo)
			goto out;
		error = wait_for_connect(sk, timeo);
		if (error)
			goto out;
	}
	req = tp->accept_queue;
	if ((tp->accept_queue = req->dl_next) == NULL)
		tp->accept_queue_tail = NULL;
 	newsk = req->sk;
	sk_acceptq_removed(sk);
	tcp_openreq_fastfree(req);
	BUG_TRAP(newsk->sk_state != TCP_SYN_RECV);
	release_sock(sk);
	return newsk;
out:
	release_sock(sk);
	*err = error;
	return NULL;
}

1.如果tp->accept_queue队列为空,就会调用wait_for_connect将当前进程进入睡眠状态,当有连接到来时,当前进程会被唤醒.
2.如果tp->accept_queue队列不为空,取出tp->accept_queue队列头部元素即struct open_request,返回其中的struct sock结构
3. 将上步骤中的struct sock结构绑定到新分配的struct socket结构中.
至于tp->accept_queue队列中的元素是怎么来的,流程相当复杂,后续有时间再详细酌…
综合sys_create/sys_bind/sys_connec/sys_accept可总结出如下简要流程图.
可以看出server主套接字每当接收到一个client的连接请求就会new 一个struct sock到accept_queue中,当用户调用sys_accept的时候回new一个新的socket出来并挂街上队列头部的一个sock,然后再新创建一个struct file,将其fd返回给sys_accept的用户.而主socket继续接收其他client发过来的连接请求.
socket

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!