分享
 
 
 

BSDsocket

王朝other·作者佚名  2008-05-21
窄屏简体版  字體: |||超大  

NOTE: 这篇文章假定读者熟悉 UNIX 下的 socket 和网络编程. 对用户级编程知识, 本文不作叙述. socket 机制在系统中的地位

socket 机制是作为一个通用的, 跨机器, 跨平台的进程间通讯机制出现在系统中的. 我们通常把它作为网络通讯之用. 它使用了与文件系统相同的接口, 提供了协议无关的进程间通讯功能.

因此, 我们可以想到. 一个 socket 的机构应该被连接在 file 结构的 f_data 域中, 这样才能与上层的 VFS 相连, 并且应该逐个实现 fileops 中的函数接口 , 连在 file 结构的 f_ops 之中, 这样我们才可以如我们实际所做的那样, 用文件系统的 read(2), write(2) 函数进行 socket 通信. 同时, socket 的机构也应该包含一个域, 它是一个函数跳转表, 以跳转到底层协议, 如 TCP 的实现函数中. 在这个角度上看, socket 的作用也跟 VFS 是类似的. 但它与 VFS 大部分操作直接转接不同, 它还有大量需要自己处理的工作. 数据结构内核结构

socket 的基本结构在 sys/socketvar.h 中, 包括 struct socket 以及描述 socket 缓冲区的结构 struct sockbuf. 上文所说的 socket 应有的机构都可以得到确认.

在 sys/protosw.h 中, 我们可以看到由 socket 结构 so_proto 域指向的 protosw 结构. 这就是 socket 与底层具体协议实现的接口. 我们需要大概记忆接口函数.

sys/socketvar.h 的 145 行是 socket 的状态, 需牢记. 用户接口数据结构

XXX. mbuf

在 sockbuf 结构中, 我们可以看到, 一个 socket 的缓冲是一个 mbuf 链 (sb_mb 域). mbuf 是 socket 及以下的协议实现的基本数据封装, 传输和控制单位. 其结构定义在 sys/mbuf.h. 其中 m_hdr 是各种 mbuf 共同的头部信息, 最为重要.

我们首先来看一个注释, 弄明白一些重要的 size 问题. 我们使用 i386 的标准 . 即 MSIZE = 256, MCLBYTES = 2048. /** Mbufs are of a single size, MSIZE (machine/param.h), which* includes overhead.

An mbuf may add a single "mbuf cluster" of size* MCLBYTES (also in machine/param.h), which has no additional overhead* and is used instead of the internal data area; this is done when* at least MINCLSIZE of data must be stored.*/

也就是说, 一个 mbuf 有 256 字节大, 如果它使用扩展空间, 可以用到 2048 个字节. 又, mbuf 携带的数据分两种: 正常数据和报文头部. 那么, 如果不使用扩展空间 (m_ext), mbuf 在携带正常数据的时候应可以使用 256 - sizeof(m_hdr) 的空间, 而携带报文头部时这个大小应该是 256 - sizeof(m_hdr) - sizeof(pkthdr).

定义 MBUF 结构在 269 行, 真正的结构描述在 225 行. 可以看到, 我们最终得出的是, 在不使用扩展空间时. 一个 mbuf 能携带 256 - 30 = 226bytes 的普通数据 和 256 - 30 - 20 = 206 bytes 的协议头.

我们要对 m_hdr 结构很熟悉. 也应该看看 kern/uipc_mbuf.c 的函数接口. 连接建立socket(2)

sys_socket() 在 uipc_syscalls.c 中. 它要完成两个工作: 建立文件结构和相应的接口; 调用 socreate 完成真正的 socket 建立工作. 其中第一项工作是我们熟悉的, 注意 89 行起三行填写参数的代码, 还要注意 socketops 的内容, 这是我们对 socket 调用 read(2) 等调用时将要进入的地方. 我们现在来看 socreate 是怎么完成第二项工作的.

socreate 在 uipc_socket.c 中. 在这里, 我们首先要利用 pffindproto() /pffindtype() 根据参数找到相应的 protosw 结构, 即到底层协议实现的接口 . 然后申请 socket 结构的内存空间, 填写参数. 上层机构建立完毕后, 我们要通知协议实现加入这个 socket, 并真正创建协议相关的通讯机构. 我们使用其 pr_usrreq 的 PRU_ATTACH 操作完成这一点.

至此, 从顶层的文件接口, 到底层的协议实现机构, 整个 socket 创建完成. bind(2)

在 sys_bind 中我们可以看到 socket 系统的一种通用的处理模式. 它首先调用 getsock(), 根据 fd 得到相应的 socket 结构. 用 sockargs 把参数按格式复制并封装到一个 mbuf 里, 然后传到底层的 soxxx 函数进行处理. 而 sobind() 的工作非常简单, 只是直接向下调用即可. listen(2)

listen(2) 的执行过程道理同上. 它只是通知协议实现要进入 listen 状态而已. accept(2)

accept(2) 的实现比较复杂. 它等待并获取连接队列上的第一个连接, 为它建立 socket 和 fd, 真正建立一条点对点的通信线路. 我们首先要根据 socket 结构描述中的一段注释弄明白连接队列上的连接是怎么来的, 怎么放的. /** Variables for connection queueing.* Socket where accepts occur is so_head in all subsidiary sockets.* If so_head is 0, socket is not related to an accept.* For head socket so_q0 queues partially completed connections,* while so_q is a queue of connections ready to be accepted.* If a connection is aborted and it has so_head set, then* it has to be pulled out of either so_q0 or so_q.* We allow connections to queue up based on current queue lengths* and limit on number of queued connections for this socket.*/

sys_accept() 照样使用 getsock 取回 socket, 至 199 行为止, 都是检查参数的过程. 200 行 的 while scope 完成了最重要的等待连接到来的过程. 为新的 socket 申请文件描述符后, 我们要使用 soqremque 将头部的连接 socket 从队列中取下, 然后才能安全正确地组建好新 socket 的上层结构.

接着, 我们要调用 soaccept(). 这里, 我们要通过底层协议实现, 取得对端地址返回给用户, 以备后用. connect(2)

sys_connect() 的工作是明显的. 取得 socket 后, 它首先要判断这个 socket 是否已经连接上了以防止重复连接; 接着, 它调用 soconnect() 使自己进入请求连接状态; 现在, 它就可以安心地等待 connect 的完成, 因为这时候已经是 connect 的对端的事了. 最后, 我们处理错误, 退出.

soconnect 直接调用底层协议实现. 忽略. 数据传输sosend()

无论是 send(2), 还是对 socket fd 的 write(2), 最终都是调用 uipc_socket.c: sosend() 来完成.

在函数开头至 757 行止, 我们获取了一些重要参数, 如是否允许分段发送 (atomic), 要传送的字节数 (resid), 是否使用路由选择 (dontroute) 等.

接着, 我们首先对缓冲区加锁. 然后在 while 循环中试图发送所有数据. 我们每次发送之前都应该先检查状态, 比如端口是否关闭, socket 是否还在连接等, 这就是 line 764 - 779 的代码. 接着, 直到 799 行, 我们要检查 socket 的发送缓存的空间是否足够, 有条件时等待空间 (下层协议实现正使用部分空间, 他们完成释放后就能凑够空间). 如果是 atomic 而空间不够的话, 我们在这里就要挂了.

接下来的 do 循环, 我们要将数据装入 mbuf 然后发送出去. line 801 - 868 是将 (一部分) 待发数据装入 mbuf 的过程, 如果 uio == NULL, 则数据已经分块装好在 top 中, 我们就什么都不用干, 否则, 我们就把数据装进一个 mbuf 链中, 每次填充的 mbuf 是 m, 然后通过 mp 将每个 mbuf 的 next 域连成一条链, 然后, 我们在 879 行, 把数据交给协议实现去传送.

从函数整体来看, 函数有三重 do 循环, 第一层要解决把一个大于 sockbuf 容量的消息分块发送 (分成不同的报文) 的问题, 第二层是要解决把读到的数据分成一个个 mbuf 存放的问题, 最里一层是要处理从 uio 多次读数据才能填充好一个 mbuf 问题. 通过这种方法解决了各层机构容量不匹配的问题. soreceive()

无论是 recv(2), 还是对 socket fd 的 read(2), 最终都是调用 uipc_socket.c: soreceive() 来完成.

首先读读前面的注释, 我们再来看它的实现. 我们首先要以 PEEK 方式 (读数据而不取下 mbuf) 读取带外数据, 然后, 若缓冲区中的数据不够我们所要求的, 我们将等待 (line 989 - 1036).

运行到达 dontblock 标记处时, 我们要开始真正的读缓冲过程. 这是 m 指向我们要读取的 mbuf 链. 注意现在缓冲区中的 mbuf 可能分数几个不同的 packet, 这由 mbuf 头部的 m_nextpkt 提供, 在这里开始由 nextrecord 指向. 根据协议要求, 在真正读数据之前, 我们可能首先要读出地址信息 (line 1051 - 1074), 和控制信息 (1075 - 1101). 如果除此以外我们还有数据, 我们把数据的起始作为报头, 提取信息, 然后在 line 1137 的 while 处开始真正的读数据过程.

获取本次需要读取的长度后, 我们在 1165 真正把数据从 mbuf 中读出, 在 1187 处, 如果我们已经把一个 mbuf 中的数据读完, 就删掉这个 mbuf, 并专向下一个 --- 此时, 如果我们已经把一个 packet 读完, 我们会转向下一个 packet, 即下一个 mbuf 链. 接下来, 如果还没读够数据, 我们可能要通知底层实现读入更多数据 (line 1269) 并等待数据的到来 (line 1277) 数据读取的过程便这样循环完成.

最后是收尾工作. XXX. 其实没设呢么好注意的. 连接关闭soclose()

当对一个连向 socket 的 fd 进行 close(2) 操作时, 会调用 soclose() 例程, 关闭与撤消 socket 相关的机制.

首先, 如果这是一个 server 端的 socket, 它就会有等待 accept 的 socket, 我们要把它关掉. 因为这些连接在 server socket 被关闭后我们不可能通过 accept(2) 或其他任何手段来获取和使用它们. 接着, 我们需要用 sodisconnect() 去关闭对端连接, 我们可能还要确认对端关闭, 即在 583 行处等待连接真正被关闭.

完成关闭工作后, 我们要把相关的数据结构撤消. 方法是首先用 PRU_DETACH 操作通知底层协议实现取消 socket, 然后用 sofree() 将 struct socket 撤消.

我们再来看 sodisconnect(), 这是一个简单的函数. 在检验 socket 状态后, 它向底层协议实现调用 disconnect 操作. 然后使用 sodopendfree 撤消相应的 mbuf 链. shutdown(2)

shutdown(2) 能够实现 socket 的半关闭功能. sys_shutdown() 直接调用了 soshutdown(). 在这里, 如果我们关闭的是读一端, 它就要调用 sorflush() 在这里, socket 被标记为不再接受数据, 并清空其接收端的 sockbuf, 这之中的 socantrcvmore() 除了标记状态之外, 还要将等待读的进程唤醒, 因为它们不可能再因读到数据而被唤醒了. 另一方面, 如果是关闭写一端, 就要通过协议实现作相应的处理, 通知对方等. BSD 本地进程间通讯: 本地 socket

socket 是作为一种进程间通讯机制而非网络通讯机制出现在 BSD UNIX 中的 . 所以, 我们选择了一个底层协议实现: 本地 socket, (AF_UNIX, PF_LOCAL, etc.) 来分析, 作为本章的结束. 我们选择了它以 STREAM 方式运行的环境进行分析. 常用网络协议中与之对应的是 TCP.

该协议实现的入口在 uipc_usrreq.c: uipc_usrreq(), 在这里请求经过初步处理后, 在根据要求的操作进入到 unp_xxx() 函数进行处理. 分析它们, 最好先回去看 socket 部分相应操作的实现, 了解协议实现中相应部分的地位和任务 . 其操作就很好懂了.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有