今天分析服务器端的socket接收客户的连接函数accept(),我们从应用程序界面
accept(server_sockfd, (struct sockaddr *)&client_address, client_len);
|
还是以前的分析方法,这里要注意第二个参数,client_address,它是在我们的测试程序中另外声明用于保存客户端socket地址的数据结构变量。其他二个参数无需多说。还是按照以前的方式我们直接看sys_socketcall()函数的代码部分
case SYS_ACCEPT: err = sys_accept(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break;
|
asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen) { struct socket *sock, *newsock; struct file *newfile; int err, len, newfd, fput_needed; char address[MAX_SOCK_ADDR];
sock = sockfd_lookup_light(fd, &err, &fput_needed); if (!sock) goto out;
err = -ENFILE; if (!(newsock = sock_alloc())) goto out_put;
newsock->type = sock->type; newsock->ops = sock->ops;
/* * We don't need try_module_get here, as the listening socket (sock) * has the protocol module (sock->ops->owner) held. */ __module_get(newsock->ops->owner);
newfd = sock_alloc_fd(&newfile); if (unlikely(newfd < 0)) { err = newfd; sock_release(newsock); goto out_put; }
err = sock_attach_fd(newsock, newfile); if (err < 0) goto out_fd_simple;
err = security_socket_accept(sock, newsock); if (err) goto out_fd;
err = sock->ops->accept(sock, newsock, sock->file->f_flags); if (err < 0) goto out_fd;
if (upeer_sockaddr) { if (newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2) < 0) { err = -ECONNABORTED; goto out_fd; } err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen); if (err < 0) goto out_fd; }
/* File flags are not inherited via accept() unlike another OSes. */
fd_install(newfd, newfile); err = newfd;
security_socket_post_accept(sock, newsock);
out_put: fput_light(sock->file, fput_needed); out: return err; out_fd_simple: sock_release(newsock); put_filp(newfile); put_unused_fd(newfd); goto out_put; out_fd: fput(newfile); put_unused_fd(newfd); goto out_put; }
|
这个函数总的作用就是使服务端的socket能够创建与客户端连接的“子连接”,也就是会利用服务器端的socket创建一个新的能与客户端建立连接的socket,而且会把新连接的socket的id号,返回到我们测试程序中的client_sockfd,同时也把客户端的socket地址保存在client_address中,函数中首先会进入sockfd_lookup_light()中找到我们服务器端的socket,这个函数前面章节中用到多次了不再进入细细分析了,接着函数中调用sock_alloc()函数创建一个新的socket,此后为这个新创建的socket分配一个可用的文件号,然后能过sock_attach_fd使其与文件号挂钩。最重要的当属这句代码
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
|
正如前面一节中提到的我们会进入
static const struct proto_ops unix_stream_ops结构中的钩子函数.accept = unix_accept,这个函数在2.6.26内核源代码目录下/net/unix/Af_unix.c中的1202行处
static int unix_accept(struct socket *sock, struct socket *newsock, int flags) { struct sock *sk = sock->sk; struct sock *tsk; struct sk_buff *skb; int err;
err = -EOPNOTSUPP; if (sock->type!=SOCK_STREAM && sock->type!=SOCK_SEQPACKET) goto out;
err = -EINVAL; if (sk->sk_state != TCP_LISTEN) goto out;
/* If socket state is TCP_LISTEN it cannot change (for now...), * so that no locks are necessary. */
skb = skb_recv_datagram(sk, 0, flags&O_NONBLOCK, &err); if (!skb) { /* This means receive shutdown. */ if (err == 0) err = -EINVAL; goto out; }
tsk = skb->sk; skb_free_datagram(sk, skb); wake_up_interruptible(&unix_sk(sk)->peer_wait);
/* attach accepted sock to socket */ unix_state_lock(tsk); newsock->state = SS_CONNECTED; sock_graft(tsk, newsock); unix_state_unlock(tsk); return 0;
out: return err; }
|
这里首先还是判断一下socket所使用的连接类型是否为有连接即tcp协议的连接,如果我们的服务器端的socket还没有处于监听状态,那么就不能再继续往下执行了,再接着要进入
struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err) { int peeked;
return __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0), &peeked, err); }
|
转接到
struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags, int *peeked, int *err) { struct sk_buff *skb; long timeo; /* * Caller is allowed not to check sk->sk_err before skb_recv_datagram() */ int error = sock_error(sk);
if (error) goto no_packet;
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
do { /* Again only user level code calls this function, so nothing * interrupt level will suddenly eat the receive_queue. * * Look at current nfs client by the way... * However, this function was corrent in any case. 8) */ unsigned long cpu_flags;
spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags); skb = skb_peek(&sk->sk_receive_queue); if (skb) { *peeked = skb->peeked; if (flags & MSG_PEEK) { skb->peeked = 1; atomic_inc(&skb->users); } else __skb_unlink(skb, &sk->sk_receive_queue); } spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
if (skb) return skb;
/* User doesn't want to wait */ error = -EAGAIN; if (!timeo) goto no_packet;
} while (!wait_for_packet(sk, err, &timeo));
return NULL;
no_packet: *err = error; return NULL; }
|
这里可以又遇到我们在以前提到的关于网络传输协议中的数据包的结构
struct sk_buff ,我们这里要回忆一上前边我们曾经讲到socket有接收队列和发送队列,在sock结构中sk_receive_queue队列中有到达的数据包,所以上面这个函数就是主要接收数据包使用的,函数循环在接收队列中检查struct sk_buff ,然后如果有数据包就要把这个结构从队列中摘下来,因为我们提到过数据包就是用struct sk_buff来表示的,我们分析一下上面的代码,函数首先是进入sock_error()
static inline int sock_error(struct sock *sk) { int err; if (likely(!sk->sk_err)) return 0; err = xchg(&sk->sk_err, 0); return -err; }
|
这里首先是要检查一下是否sock有错误出现了,并将错误码清0,xchg()我们将在以后谈到。这里是通过一条汇编指令将二个值直接对换。我们继续看到进入
static inline long sock_rcvtimeo(const struct sock *sk, int noblock) { return noblock ? 0 : sk->sk_rcvtimeo; }
|
这个函数也就是说如果我们没有设置noblock值的话,就会返回设置的定时时间,如果设置了就说明不要停留直接返回了。我们可以看到下边使用了锁对队列进行了“安全保护”防止其他进程并发操作。这样保证了我们数据的完整。下边就是将找到的数据包结构摘链了,能过__skb_unlink()实现操作
static inline void __skb_unlink(struct sk_buff *skb, struct sk_buff_head *list) { struct sk_buff *next, *prev;
list->qlen--; next = skb->next; prev = skb->prev; skb->next = skb->prev = NULL; next->prev = prev; prev->next = next; }
|
我们看到这些操作都是在一个大的while循环中,而循环的条件就是
while (!wait_for_packet(sk, err, &timeo));
|
我们读一下这个函数
static int wait_for_packet(struct sock *sk, int *err, long *timeo_p) { int error; DEFINE_WAIT(wait);
prepare_to_wait_exclusive(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
/* Socket errors? */ error = sock_error(sk); if (error) goto out_err;
if (!skb_queue_empty(&sk->sk_receive_queue)) goto out;
/* Socket shut down? */ if (sk->sk_shutdown & RCV_SHUTDOWN) goto out_noerr;
/* Sequenced packets can come disconnected. * If so we report the problem */ error = -ENOTCONN; if (connection_based(sk) && !(sk->sk_state == TCP_ESTABLISHED || sk->sk_state == TCP_LISTEN)) goto out_err;
/* handle signals */ if (signal_pending(current)) goto interrupted;
error = 0; *timeo_p = schedule_timeout(*timeo_p); out: finish_wait(sk->sk_sleep, &wait); return error; interrupted: error = sock_intr_errno(*timeo_p); out_err: *err = error; goto out; out_noerr: *err = 0; error = 1; goto out; }
|
首先是声明了一个等待队列
#define DEFINE_WAIT(name) \ wait_queue_t name = { \ .private = current, \ .func = autoremove_wake_function, \ .task_list = LIST_HEAD_INIT((name).task_list), \ }
|
我们看到在函数中声明了一个名为wait的等待队列,等待队列以后我们会专门讲起,这里我们看函数
prepare_to_wait_exclusive(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
|
接着要把这里创建的wait,即当前进程的这里的等待队列挂入sk中的sk_sleep队列,这样我们可以理解到多个进程都可以对一个socket并发的连接,如果我们程序运行到schedule_timeout()处会进入睡眠状态,存在几种情形的“唤醒”,一是等待的时间到了,二是接收到了信号,三是接收到了客户端的连接请求。同时“醒来”以后,timeo_p保存着等待时间剩余。 关于定时函数我们以后会在相关章节中详述。我们回到unix_accept()函数中,skb指针已经指向我们接收到的sk_buff结构即数据包
接着我们看到从sk_buff结构中取得它载运着的sock指针,可能熟悉进程的朋友会把tsk的理解成task,按照资料上介绍他应该是target sock的简称,即目标sock。我们以前说过,socket结构在具体应用上分为二部分,另一部分是这里的sock结构,因为sock是与具体的协议即以前所说的规程的相关,所以变化比较大,而socket比较通用,所以我们上面通过socket_alloc()只是分配了通用部分的socket结构,并没有建立对应协议的sock结构,那么我们分配的新的socket的所需要的sock是从哪里来的呢,我们可以在代码中看到他是从客户端传递过来的sock,与我们新建的socket挂入的,看一下这个关键的函数
static inline void sock_graft(struct sock *sk, struct socket *parent) { write_lock_bh(&sk->sk_callback_lock); sk->sk_sleep = &parent->wait; parent->sk = sk; sk->sk_socket = parent; security_sock_graft(sk, parent); write_unlock_bh(&sk->sk_callback_lock); }
|
上面传递的参数是
sock_graft(tsk, newsock);
|
tsk是我们客户端的sock,newsock是我们服务器端的新socket,可以看出上面的sock_graft,graft是嫁接的意思,从函数面上就可以理解了,然后其内部就是将服务器端新建的socket与客户端的sock“挂钩了”,从此以后,这个socket就是服务器端与客户端通讯的桥梁了。这样回到上面的主线函数时,我们看到将newsock->state = SS_CONNECTED;也就是状态改变成了连接状态,而以前的服务器的socket并没有任何的状态改变,那个socket继续覆行他的使命“孵化”新的socket。看到这里有些朋友可能会迷糊,怎么知道数据包的,为什么没有对数据包的对比或者状态检测就确定队列中有到来的sk_buff就是客户端发送来的,这个问题我们要结合客户端的发送来看,这里朋友们只是理解到接收队列中存在的sk_buff肯定是客户端发送来的数据包。同时,我们在unix_accept()也看到当服务器端的socket接收到数据包后就要释放客户端的sock和数据包sk_buff结构,以前我们也看到如果接收队列中到达了上限,那么之后请求连接的客户端会进入睡眠状态,所以既然接收了一个请求就可以空出一个队列位置,就唤醒等待连接的客户端
wake_up_interruptible(&unix_sk(sk)->peer_wait);
|
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
|
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags;
spin_lock_irqsave(&q->lock, flags); __wake_up_common(q, mode, nr_exclusive, 0, key); spin_unlock_irqrestore(&q->lock, flags); }
|
看似函数调用非常的深层,linux内核就是这样的感觉,其实,如果大家阅读的次数多了以后,自己阅读相关的代码不成问题。回到我们的sys_accept()函数中下面接着调用
newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)
|
我们追踪这个钩子函数
static const struct proto_ops unix_stream_ops = {
。。。。。。 .getname = unix_getname, 。。。。。。 };
|
只列出相关的钩子函数,可以看到执行的是unix_getname()
static int unix_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer) { struct sock *sk = sock->sk; struct unix_sock *u; struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr; int err = 0;
if (peer) { sk = unix_peer_get(sk);
err = -ENOTCONN; if (!sk) goto out; err = 0; } else { sock_hold(sk); }
u = unix_sk(sk); unix_state_lock(sk); if (!u->addr) { sunaddr->sun_family = AF_UNIX; sunaddr->sun_path[0] = 0; *uaddr_len = sizeof(short); } else { struct unix_address *addr = u->addr;
*uaddr_len = addr->len; memcpy(sunaddr, addr->name, *uaddr_len); } unix_state_unlock(sk); sock_put(sk); out: return err; }
|
这里函数根据peer,也就是否获取对方socket地址,来获得本socket还是客户端socket的地址。我们看到sys_accept()传递过来的参数是2,所以看一下unix_peer_get()
static struct sock *unix_peer_get(struct sock *s) { struct sock *peer;
unix_state_lock(s); peer = unix_peer(s); if (peer) sock_hold(peer); unix_state_unlock(s); return peer; }
|
#define unix_peer(sk) (unix_sk(sk)->peer)
|
也就是说我们新创建的socket中的sock指针指向了对方的sock。接着sock_hold()增加了sock的使用计数了。
static inline void sock_hold(struct sock *sk) { atomic_inc(&sk->sk_refcnt); }
|
atomic_inc()是原子操作,是用一条汇编指令对计数字加1。我们看到取得客户端的socket地址是通过这句代码来完成的
struct unix_address *addr = u->addr;
*uaddr_len = addr->len; memcpy(sunaddr, addr->name, *uaddr_len);
|
然后在sys_accept()中使用
err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);
|
将客户端的socket地址复制给我们的应用程序界面。这样我们程序界面上
client_address就得到了客户端的socket地址。接着我们看到函数执行了fd_install()函数,即为新创建的socket分配一个文件号和file结构,这函数我们说过将在文件系统中重点分析。
阅读(3303) | 评论(0) | 转发(1) |