Linux源码-sys_accept()
来源:互联网 发布:安卓软件打包 编辑:程序博客网 时间:2024/06/14 06:58
(本文部分参考《Linux源代码情景分析》)
操作SYS_ACCEPT接受连接请求,通过sys_accept()实现。
“有连接”模式的插口一旦通过 listen()设置成 server 插口以后,就只能被动地通过 accept()接受来自client 插口的连接请求。进程对 accept()的调用是阻塞性的,就是说如果没有连接请求就会进入睡眠等待,直到有连接请求到来,接受了请求以后(或者超过了预定的等待时间)才会返回。所以,在已经有连接请求的情况下是“接受连接请求”,而在尚无连接请求的情况下是“等待连接请求”。在内核中,accept()是通过 net/socket.c 中的函数 sys_accept()实现的。
函数整个调用过程如下:
下面是完整代码:
函数 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);/* 获得侦听端口的socket */ if (!sock) goto out; err = -ENFILE; if (!(newsock = sock_alloc()))/* 分配一个新的套接口,用来处理与客户端的连接 */ goto out_put; /* 根据侦听套接口来初始化新连接的类型和回调表 */ newsock->type = sock->type; newsock->ops = sock->ops;... /* accept系统调用在套接口层的实现,对TCP来说,是inet_accept */ err = sock->ops->accept(sock, newsock, sock->file->f_flags); if (err < 0) goto out_release; if (upeer_sockaddr) {/* 调用者需要获取对方套接口地址和端口 */ /* *调用传输层回调获得对方的地址和端口*这个函数既可以用来获取对方插口的地址,也可以用来获取本插口的地址,具体由参数 peer 决定。在这里由于调用时将参数的值设成 2,所以取的是对方地址。 */ if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) { err = -ECONNABORTED; goto out_release; } /* 成功后复制到用户态 */ err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen); if (err < 0) goto out_release; } if ((err = sock_map_fd(newsock)) < 0)/* 为新连接分配文件描述符并返回*/ goto out_release;...}
代码中先通过 sockfd_lookup()找到 server 插口的 socket 结构,然后通过 sock_alloc()分配一个新的socket 结构,再通过相应函数指针调用具体的回调函数inet_accept()。
值得注意的是:一个插口经 listen()设置成 server 插口以后永远不会与任何插口建立起连接。因为一旦接受了一个连接请求之后就会创建出另一个插口,连接就建立在这个新的插口与请求连接的那个插口之间,而原先的 server 插口则并无改变,并且还可再次通过 accept()接受下一个连接请求,server 插口永远保持接受新的连接请求的能力,但是其本身从来不成为某个连接的一端。正因为这样, sys_accept()返回的是新插口的打开文件号,同时会把对方(cli)的插口地址从参数中带出来。
inet_accept()代码如下:
/* accept系统调用在套接口层的实现 */int inet_accept(struct socket *sock, struct socket *newsock, int flags){ struct sock *sk1 = sock->sk;/* 根据套口获得传输层控制块 */ int err = -EINVAL; /* 调用传输层的函数tcp_accept获得已经完成的连接的传输控制块,即子传输控制块 */ 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_accept()函数代码如下:
/* accept调用的传输层实现 */struct sock *tcp_accept(struct sock *sk, int flags, int *err){ struct tcp_sock *tp = tcp_sk(sk); struct open_request *req; struct sock *newsk; int error; lock_sock(sk); error = -EINVAL; if (sk->sk_state != TCP_LISTEN)/* 本调用仅仅针对侦听套口,不是此状态的套口则退出 */ goto out; if (!tp->accept_queue) {/* accept队列为空,说明还没有收到新连接 */ long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);/* 如果套口是非阻塞的,或者在一定时间内没有新连接,则返回 *//*标志位 O_NONBLOCK在文件操作中常用,表示如果有报文就接收,但是若没有也得马上返回,而不是睡眠等待,这个标志位也可用于 accept()。*/ error = -EAGAIN; if (!timeo)/* 超时时间到,没有新连接,退出 */ goto out; /* 运行到这里,说明有新连接到来(返回值为0),则等待新的传输控制块 */ 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;}
wait_for_connect()函数代码如下;
static int wait_for_connect(struct sock *sk, long timeo){ struct tcp_sock *tp = tcp_sk(sk); DEFINE_WAIT(wait); int err; for (;;) { prepare_to_wait_exclusive(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE); release_sock(sk); if (!tp->accept_queue) timeo = schedule_timeout(timeo); lock_sock(sk); err = 0; if (tp->accept_queue) break; err = -EINVAL; if (sk->sk_state != TCP_LISTEN) break; err = sock_intr_errno(timeo); if (signal_pending(current)) break; err = -EAGAIN; if (!timeo) break; } finish_wait(sk->sk_sleep, &wait); return err;}void fastcallprepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state){ unsigned long flags; /*参数 WQ_FLAG_EXCLUSIVE 表示如果有多个进程在睡眠等待就只唤醒其中一个*/ wait->flags |= WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&q->lock, flags); if (list_empty(&wait->task_list)) __add_wait_queue_tail(q, wait); if (is_sync_wait(wait)) set_current_state(state); spin_unlock_irqrestore(&q->lock, flags);}
当中一些重要的数据结构的创建过程如下:
注:
1、
server 插口必须有个地址,这样 client 一方才能通过地址来找到 server 插口的数据结构,所以在创建了插口以后一定要调用 bind()将其“捆绑”到一个地址上。可是,对于 client 一方的插口来说,它只能主动地去寻找某个 server 插口,别的插口是不会来寻找它的。所以实际上没有必要让 client 插口也有个地址,当然有也无妨。
2、
在 sys_accept()中已经调用了 sock_alloc(),分配了一个新的 socket 结构(也就是inode 结构)。但 sock_alloc(),它并不分配与 socket 结构配对的 sock结构,从这个意义上讲,这个新的插口还不完整。那么什么时候才分配所需的 sock 数据结构呢?这是由client 一方在 connect()的过程中分配,并且将其指针通过用作连接请求报文的 sk_buff 结构带过来的,通过 sock_graft()将 client 一方提供的 sock结构 与 server 一方提供的 newsock(socket结构) 挂上钩。
3、
简单介绍一下sock_map_fd()函数(将套接口与文件描述符绑定):
- Linux源码-sys_accept()
- Linux源码
- linux源码
- linux源码
- linux源码
- linux源码
- Linux-----Linux源码安装软件
- 如何阅读Linux源码
- 看Linux内核源码
- linux 核心源码下载
- linux 内核源码结构
- 如何阅读Linux源码
- LINUX 串口通讯源码
- linux源码阅读
- linux内核源码组织
- linux mtd源码分析
- linux 源码学习计划
- Linux源码阅读
- 一步步教你Hadoop多节点集群安装配置
- ntp服务器设置
- 1.1 View与ViewGroup
- WeUI入门知识点
- 1. Two Sum
- Linux源码-sys_accept()
- sc2017新初三膜你赛3 比赛总结
- 2017.8模拟赛4比赛笔记
- spring3.2与jdk1.8不兼容问题
- 安装MySQL 出现故障重新安装的方法
- LeetCode 119 Pascal's Triangle II
- days2
- 知识漏洞
- Android网络框架比较