最详细的Linux TCP/IP 协议栈源码分析( 二 )


释放sock 。
创建socket同时还创建sock数据空间 , 初始化sock,初始化过程主要做的事情是初始化三个队列 , receive_queue(接收到的数据包sk_buff链表队列),send_queue(需要发送数据包的sk_buff链表队列),backlog_queue(主要用于tcp中三次握手成功的那些数据包,自己猜的),根据family、type参数 , 初始化sock的操作 , 比如对于family为inet类型的 , type为stream类型的 , sock->proto初始化为tcp_prot.其中包括stream类型的协议sock操作对应的入口函数 。
在一端对socket进行write的过程中 , 首先会把要write的字符串缓冲区整理成msghdr的数据结构形式(参见linux内核2.4版源代码分析大全),然后调用sock_sendmsg把msghdr的数据传送至inet层 , 对于msghdr结构中数据区中的每个数据包 , 创建sk_buff结构 , 填充数据 , 挂至发送队列 。 一层层往下层协议传递 。 一下每层协议不再对数据进行拷贝 。 而是对sk_buff结构进行操作 。
inet套接字及以下层 数据存放在sk_buff这样的数据结构里:
路由:
在linux的路由系统主要保存了三种与路由相关的数据 , 第一种是在物理上和本机相连接的主机地址信息表 , 第二种是保存了在网络访问中判断一个网络地址应该走什么路由的数据表;第三种是最新使用过的查询路由地址的缓存地址数据表 。
1.neighbour结构 neighbour_table{ }是一个包含和本机所连接的所有邻元素的信息的数据结构 。 该结构中有个元素是neighbour结构的数组 , 数组的每一个元素都是一个对应于邻机的neighbour结构 , 系统中由于协议的不同 , 会有不同的判断邻居的方式 , 每种都有neighbour_table{}类型的实例 , 这些实例是通过neighbour_table{}中的指针next串联起来的 。 在neighbour结构中 , 包含有与该邻居相连的网络接口设备net_device的指针 , 网络接口的硬件地址 , 邻居的硬件地址 , 包含有neigh_ops{}指针 , 这些函数指针是直接用来连接传输数据的 , 包含有queue_xmit(struct * sk_buff)函数入口地址 , 这个函数可能会调用硬件驱动程序的发送函数 。
2.FIB结构 在FIB中保存的是最重要的路由规则,通过对FIB数据的查找和换算 , 一定能够获得路由一个地址的方法 。 系统中路由一般采取的手段是:先到路由缓存中查找表项 , 如果能够找到 , 直接对应的一项作为路由的规则;如果不能找到 , 那么就到FIB中根据规则换算传算出来 , 并且增加一项新的 , 在路由缓存中将项目添加进去 。
3.route结构(即路由缓存中的结构)
数据链路层:
net_device{}结构 , 对应于每一个网络接口设备 。 这个结构中包含很多可以直接获取网卡信息的函数和变量 , 同时包含很多对于网卡操作的函数 , 这些直接指向该网卡驱动程序的许多函数入口 , 包括发送接收数据帧到缓冲区等 。 当这些完成后 , 比如数据接收到缓冲区后便由netif_rx(在net/core/dev.c各种设备驱动程序的上层框架程序)把它们组成sk_buff形式挂到系统接收的backlog队列然后交由上层网络协议处理 。 同样 , 对于上层协议处理下来的那些sk_buff 。 便由dev_queue_xmit函数放入网络缓冲区 , 交给网卡驱动程序的发送程序处理 。
在系统中存在一张链表dev_base将系统中所有的net_device{}结构连在一起 。 对应于内核初始化而言 , 系统启动时便为每个所有可能支持的网络接口设备申请了一个net_device{}空间并串连起来 , 然后对每个接点运行检测过程 , 如果检测成功 , 则在dev_base链表中保留这个接点 , 否则删除 。 对应于模块加载来说 , 则是调用register_netdev()注册net_device,在这个函数中运行检测过程 , 如果成功 , 则加到dev_base链表 。 否则就返回检测不到信息 。 删除同理 , 调用
unregister_netdev 。
2.启动分析
2.1 初始化进程 :start-kernel(main.c)---->do_basic_setup(main.c)---->sock_init(/net/socket.c)---->do_initcalls(main.c)