Linux Kernel 2.6.9源碼分析 -- accept

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發過來的連接請求.
SOCKET2

發佈了26 篇原創文章 · 獲贊 0 · 訪問量 2858
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章