深度剖析网络协议栈中的 socket 函数,可以说是把前面介绍的串联起来,将网络协议栈各层关联起来。

应用层

FTP

SMTP

HTTP

...

传输层

TCP

UDP

网络层

IP ICMP

ARP

链路层

以太网

令牌环

FDDI

...

 

1、应用层——socket 函数

为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型。该函数只是作为一个简单的接口函数供用户调用,调用该函数后将进入内核栈进行系统调用sock_socket 函数。

  1. #include <sys/socket.h>
  2. int socket(int family, int type, int protocol);
  3. /*返回:非负描述字——成功, -1——出错
  4. 其中family参数指明协议族,type参数指明套接口类型,后面protocol通常设为0,以选择所给定family 和 type组合的系统缺省值*/

2、BSD Socket 层——sock_socket 函数

从应用层进入该函数是通过一个共同的入口函数 sys_socket

  1. /*
  2. *  System call vectors. Since I (RIB) want to rewrite sockets as streams,
  3. *  we have this level of indirection. Not a lot of overhead, since more of
  4. *  the work is done via read/write/select directly.
  5. *
  6. *  I'm now expanding this up to a higher level to separate the assorted
  7. *  kernel/user space manipulations and global assumptions from the protocol
  8. *  layers proper - AC.
  9. */
  10. //本函数是网络栈专用操作函数集的总入口函数,主要是将请求分配,调用具体的底层函数进行处理
  11. asmlinkage int sys_socketcall(int call, unsigned long *args)
  12. {
  13. int er;
  14. switch(call)
  15. {
  16. case SYS_SOCKET://socket函数
  17. er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
  18. if(er)
  19. return er;
  20. return(sock_socket(get_fs_long(args+0),
  21. get_fs_long(args+1),//返回地址上的值
  22. get_fs_long(args+2)));//调用sock_socket函数
  23. ……
  24. }

下面就是sock_socket 函数主体

  1. /*
  2. *  系统调用,创建套接字socket。涉及到sock结构的创建.
  3. */
  4. static int sock_socket(int family, int type, int protocol)
  5. {
  6. int i, fd;
  7. struct socket *sock;
  8. struct proto_ops *ops;
  9. /* 匹配应用程序调用socket()函数时指定的协议 */
  10. for (i = 0; i < NPROTO; ++i)
  11. {
  12. if (pops[i] == NULL) continue;
  13. if (pops[i]->family == family) //设置域
  14. break;
  15. }
  16. //没有匹配的协议,则出错退出
  17. if (i == NPROTO)
  18. {
  19. return -EINVAL;
  20. }
  21. //根据family输入参数决定域操作函数集用于ops字段的赋值
  22. //操作函数集是跟域相关的,不同的域对应不同的操作函数集
  23. ops = pops[i];
  24. /*
  25. *  Check that this is a type that we know how to manipulate and
  26. *  the protocol makes sense here. The family can still reject the
  27. *  protocol later.
  28. */
  29. //套接字类型检查
  30. if ((type != SOCK_STREAM && type != SOCK_DGRAM &&
  31. type != SOCK_SEQPACKET && type != SOCK_RAW &&
  32. type != SOCK_PACKET) || protocol < 0)
  33. return(-EINVAL);
  34. /*
  35. *  Allocate the socket and allow the family to set things up. if
  36. *  the protocol is 0, the family is instructed to select an appropriate
  37. *  default.
  38. */
  39. //分配socket套接字结构
  40. if (!(sock = sock_alloc()))
  41. {
  42. printk("NET: sock_socket: no more sockets\n");
  43. return(-ENOSR); /* Was: EAGAIN, but we are out of
  44. system resources! */
  45. }
  46. //指定对应类型,协议,以及操作函数集
  47. sock->type = type;
  48. sock->ops = ops;
  49. //分配下层sock结构,sock结构是比socket结构更底层的表示一个套接字的结构
  50. //前面博文有说明:http://blog.csdn.net/wenqian1991/article/details/21740945
  51. //socket是通用的套接字结构体,而sock与具体使用的协议相关
  52. if ((i = sock->ops->create(sock, protocol)) < 0) //这里调用下层函数 create
  53. {
  54. sock_release(sock);//出错回滚销毁处理
  55. return(i);
  56. }
  57. //分配一个文件描述符并在后面返回给应用层序作为以后的操作句柄
  58. if ((fd = get_fd(SOCK_INODE(sock))) < 0)
  59. {
  60. sock_release(sock);
  61. return(-EINVAL);
  62. }
  63. return(fd);//这个就是我们应用系统使用的套接字描述符
  64. }

该要介绍的注释里,已经说明白了,可以看到,该函数又将调用下一层函数 create。(网络栈就是这样,上层调用下层函数)

sock_socket 函数内部还调用了一个函数 sock_alloc(),该函数主要是分配一个 socket 套接字结构(实际上找到一个空闲的inode结构,socket结构已经包含在inode结构中)

  1. /*
  2. *  分配一个socket结构
  3. */
  4. struct socket *sock_alloc(void)
  5. {
  6. struct inode * inode;
  7. struct socket * sock;
  8. inode = get_empty_inode();//分配一个inode对象
  9. if (!inode)
  10. return NULL;
  11. //获得的inode结构的初始化
  12. inode->i_mode = S_IFSOCK;
  13. inode->i_sock = 1;
  14. inode->i_uid = current->uid;
  15. inode->i_gid = current->gid;
  16. //可以看出socket结构体的实体空间,就已经存在了inode结构中的union类型中,
  17. //所以无需单独的开辟空间分配一个socket 结构
  18. sock = &inode->u.socket_i;//这里把inode的union结构中的socket变量地址传给sock
  19. sock->state = SS_UNCONNECTED;
  20. sock->flags = 0;
  21. sock->ops = NULL;
  22. sock->data = NULL;
  23. sock->conn = NULL;
  24. sock->iconn = NULL;
  25. sock->next = NULL;
  26. sock->wait = &inode->i_wait;
  27. sock->inode = inode;//回绑
  28. sock->fasync_list = NULL;
  29. sockets_in_use++;//系统当前使用的套接字数量加1
  30. return sock;
  31. }

3、INET Socket 层——inet_create 函数

  1. /*
  2. *  Create an inet socket.
  3. *
  4. *  FIXME: Gcc would generate much better code if we set the parameters
  5. *  up in in-memory structure order. Gcc68K even more so
  6. */
  7. //该函数被上层sock_socket函数调用,用于创建一个socket套接字对应的sock结构并对其进行初始化
  8. //socket是通用结构,sock是具体到某种协议的结构
  9. //代码是一大串,功能就是建立套接字对应的sock结构并对其进行初始化
  10. static int inet_create(struct socket *sock, int protocol)
  11. {
  12. struct sock *sk;
  13. struct proto *prot;
  14. int err;
  15. //分配一个sock结构,内存分配一个实体
  16. sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);
  17. if (sk == NULL)
  18. return(-ENOBUFS);
  19. sk->num = 0;//本地端口号
  20. sk->reuse = 0;
  21. //根据类型进行相关字段的赋值
  22. //关于哪种类型与协议的对应关系,请参考<UNP 卷1>,有些类型就只能和某种协议对应
  23. switch(sock->type)
  24. {
  25. case SOCK_STREAM:
  26. case SOCK_SEQPACKET:
  27. if (protocol && protocol != IPPROTO_TCP)
  28. {
  29. kfree_s((void *)sk, sizeof(*sk));
  30. return(-EPROTONOSUPPORT);
  31. }
  32. protocol = IPPROTO_TCP;//tcp协议
  33. sk->no_check = TCP_NO_CHECK;
  34. //这个prot变量表明了套接字使用的是何种协议
  35. //然后使用的则是对应协议的操作函数
  36. prot = &tcp_prot;
  37. break;
  38. case SOCK_DGRAM:
  39. if (protocol && protocol != IPPROTO_UDP)
  40. {
  41. kfree_s((void *)sk, sizeof(*sk));
  42. return(-EPROTONOSUPPORT);
  43. }
  44. protocol = IPPROTO_UDP;//udp协议
  45. sk->no_check = UDP_NO_CHECK;//不使用校验
  46. prot=&udp_prot;
  47. break;
  48. case SOCK_RAW:
  49. if (!suser()) //超级用户才能处理
  50. {
  51. kfree_s((void *)sk, sizeof(*sk));
  52. return(-EPERM);
  53. }
  54. if (!protocol)// 原始套接字类型,这里表示端口号
  55. {
  56. kfree_s((void *)sk, sizeof(*sk));
  57. return(-EPROTONOSUPPORT);
  58. }
  59. prot = &raw_prot;
  60. sk->reuse = 1;
  61. sk->no_check = 0;    /*
  62. * Doesn't matter no checksum is
  63. * performed anyway.
  64. */
  65. sk->num = protocol;//本地端口号
  66. break;
  67. case SOCK_PACKET:
  68. if (!suser())
  69. {
  70. kfree_s((void *)sk, sizeof(*sk));
  71. return(-EPERM);
  72. }
  73. if (!protocol)
  74. {
  75. kfree_s((void *)sk, sizeof(*sk));
  76. return(-EPROTONOSUPPORT);
  77. }
  78. prot = &packet_prot;
  79. sk->reuse = 1;
  80. sk->no_check = 0;    /* Doesn't matter no checksum is
  81. * performed anyway.
  82. */
  83. sk->num = protocol;
  84. break;
  85. default://不符合以上任何类型,则返回
  86. kfree_s((void *)sk, sizeof(*sk));
  87. return(-ESOCKTNOSUPPORT);
  88. }
  89. sk->socket = sock;//建立与其对应的socket之间的关系
  90. #ifdef CONFIG_TCP_NAGLE_OFF
  91. sk->nonagle = 1;//如果定义了Nagle算法
  92. #else
  93. sk->nonagle = 0;
  94. #endif
  95. //各种初始化
  96. //这里是sock结构
  97. sk->type = sock->type;
  98. sk->stamp.tv_sec=0;
  99. sk->protocol = protocol;
  100. sk->wmem_alloc = 0;
  101. sk->rmem_alloc = 0;
  102. sk->sndbuf = SK_WMEM_MAX;
  103. sk->rcvbuf = SK_RMEM_MAX;
  104. sk->pair = NULL;
  105. sk->opt = NULL;
  106. sk->write_seq = 0;
  107. sk->acked_seq = 0;
  108. sk->copied_seq = 0;
  109. sk->fin_seq = 0;
  110. sk->urg_seq = 0;
  111. sk->urg_data = 0;
  112. sk->proc = 0;
  113. sk->rtt = 0;             /*TCP_WRITE_TIME << 3;*/
  114. sk->rto = TCP_TIMEOUT_INIT;      /*TCP_WRITE_TIME*/
  115. sk->mdev = 0;
  116. sk->backoff = 0;
  117. sk->packets_out = 0;
  118. sk->cong_window = 1; /* start with only sending one packet at a time. */
  119. sk->cong_count = 0;
  120. sk->ssthresh = 0;
  121. sk->max_window = 0;
  122. sk->urginline = 0;
  123. sk->intr = 0;
  124. sk->linger = 0;
  125. sk->destroy = 0;
  126. sk->priority = 1;
  127. sk->shutdown = 0;
  128. sk->keepopen = 0;
  129. sk->zapped = 0;
  130. sk->done = 0;
  131. sk->ack_backlog = 0;
  132. sk->window = 0;
  133. sk->bytes_rcv = 0;
  134. sk->state = TCP_CLOSE;
  135. sk->dead = 0;
  136. sk->ack_timed = 0;
  137. sk->partial = NULL;
  138. sk->user_mss = 0;
  139. sk->debug = 0;
  140. /* this is how many unacked bytes we will accept for this socket.  */
  141. sk->max_unacked = 2048; /* needs to be at most 2 full packets. */
  142. /* how many packets we should send before forcing an ack.
  143. if this is set to zero it is the same as sk->delay_acks = 0 */
  144. sk->max_ack_backlog = 0;
  145. sk->inuse = 0;
  146. sk->delay_acks = 0;
  147. skb_queue_head_init(&sk->write_queue);
  148. skb_queue_head_init(&sk->receive_queue);
  149. sk->mtu = 576;//最大传输单元
  150. sk->prot = prot;
  151. sk->sleep = sock->wait;
  152. sk->daddr = 0;//远端地址
  153. sk->saddr = 0 /* 本地地址 */;
  154. sk->err = 0;
  155. sk->next = NULL;
  156. sk->pair = NULL;
  157. sk->send_tail = NULL;
  158. sk->send_head = NULL;
  159. sk->timeout = 0;
  160. sk->broadcast = 0;
  161. sk->localroute = 0;
  162. init_timer(&sk->timer);
  163. init_timer(&sk->retransmit_timer);
  164. sk->timer.data = (unsigned long)sk;
  165. sk->timer.function = &net_timer;
  166. skb_queue_head_init(&sk->back_log);
  167. sk->blog = 0;
  168. sock->data =(void *) sk;
  169. //下面是sock结构中tcp首部初始化
  170. sk->dummy_th.doff = sizeof(sk->dummy_th)/4;
  171. sk->dummy_th.res1=0;
  172. sk->dummy_th.res2=0;
  173. sk->dummy_th.urg_ptr = 0;
  174. sk->dummy_th.fin = 0;
  175. sk->dummy_th.syn = 0;
  176. sk->dummy_th.rst = 0;
  177. sk->dummy_th.psh = 0;
  178. sk->dummy_th.ack = 0;
  179. sk->dummy_th.urg = 0;
  180. sk->dummy_th.dest = 0;
  181. //ip部分
  182. sk->ip_tos=0;
  183. sk->ip_ttl=64;
  184. #ifdef CONFIG_IP_MULTICAST
  185. sk->ip_mc_loop=1;
  186. sk->ip_mc_ttl=1;
  187. *sk->ip_mc_name=0;
  188. sk->ip_mc_list=NULL;
  189. #endif
  190. sk->state_change = def_callback1;
  191. sk->data_ready = def_callback2;
  192. sk->write_space = def_callback3;
  193. sk->error_report = def_callback1;
  194. if (sk->num) //如果分配了本地端口号
  195. {
  196. /*
  197. * It assumes that any protocol which allows
  198. * the user to assign a number at socket
  199. * creation time automatically
  200. * shares.
  201. */
  202. //将具有确定端口号的新sock结构加入到sock_array数组表示的sock结构链表中
  203. put_sock(sk->num, sk);//实际上这里确定的端口号一般为初始化0
  204. sk->dummy_th.source = ntohs(sk->num);//tcp首部源端地址,就是端口号
  205. //这里需要进行字节序转换,网络字节序转主机字节序
  206. }
  207. if (sk->prot->init) //根据不同协议类型,调用对应init函数
  208. {
  209. err = sk->prot->init(sk);//调用相对应4层协议的初始化函数
  210. if (err != 0)
  211. {
  212. destroy_sock(sk);//出错了,就销毁
  213. return(err);
  214. }
  215. }
  216. return(0);
  217. }

到这里一个 socket 套接字就创建完成了
可以看出socket 套接字的创建过程为:socket() -> sock_socket() -> inet_create()

我们简单的总结一下这几个函数的功能:

sock_socket() 内部的主要结构是 socket 结构体,其主要负责socket 结构体的创建(sock_alloc())和初始化,以及指定socket套接字的类型和操作函数集,然后分配一个文件描述符作为socket套接字的操作句柄,该描述符就是我们常说的套接字描述符。socket 的创建主要是分配一个inode 对象来说实现的。inode 对面内部有一个 union 类型变量,里面包含了各种类型的结构体,这里采用的 socket 类型,然后二者建立关联,inode中的union采用socket,socket结构中的inode指针指向该inode对象。

inet_create() 内部的主要结构是 sock 结构体,sock 结构体比socket 结构更显复杂,其使用范围也更为广泛,socket 结构体是一个通用的结构,不涉及到具体的协议,而sock 结构则与具体的协议挂钩,属于具体层面上的一个结构。inet_create 函数的主要功能则是创建一个 sock 结构(kmalloc())然后根据上层传值下来的协议(通常是类型与地址族组合成成对应的协议)进行初始化。最后将创建好的 sock 结构插入到 sock 表中。

网络栈的更下层用到的套接字就是 sock 结构体,在inet_create 函数中sock 套接字已经创建且初始化,socket() 至此完成。

有了源码,更清楚的了解到socket 函数的功能:创建套接字(sock struct),指定期望的通信协议类型。

到了这一步,套接字拥有自己的实体部分,指定了通信协议类型,但是既没有绑定本地地址信息(ip地址和端口号),也不知道对端的地址信息。

5.2【Linux 内核网络协议栈源码剖析】socket 函数剖析 ☆☆☆的更多相关文章

  1. 【Linux 内核网络协议栈源码剖析】网络栈主要结构介绍(socket、sock、sk_buff,etc)

    原文:http://blog.csdn.net/wenqian1991/article/details/46700177 通过前面的分析,可以发现,网络协议栈中的数据处理,都是基于各类结构体,所有有关 ...

  2. Linux 内核调度器源码分析 - 初始化

    导语 上篇系列文 混部之殇-论云原生资源隔离技术之CPU隔离(一) 介绍了云原生混部场景中CPU资源隔离核心技术:内核调度器,本系列文章<Linux内核调度器源码分析>将从源码的角度剖析内 ...

  3. Linux内核网络协议栈优化总纲

    本文原创为freas_1990  转载请标明出处:http://blog.csdn.net/freas_1990/article/details/9474121 Jack:淫龙,Linux内核协议栈如 ...

  4. 如何查看跟踪查看LINUX内核中的源码

    我的博客:www.while0.com 最近看LINUX书籍时,根据书中代码找相应的函数或者结构定义相当吃力,根据网上资料按以下方法查找速度较快. 1.安装ctags 在源代码目录下运行 ctags ...

  5. ARMv8 Linux内核head.S源码分析

    ARMv8Linux内核head.S主要工作内容: 1. 从el2特权级退回到el1 2. 确认处理器类型 3. 计算内核镜像的起始物理地址及物理地址与虚拟地址之间的偏移 4. 验证设备树的地址是否有 ...

  6. Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)

    转自:http://blog.csdn.net/shanshanpt/article/details/21024465 在2.6.24之后这个结构体有了较大的变化,此处先说一说2.6.16版本的sk_ ...

  7. TCP/IP协议栈源码图解分析系列10:linux内核协议栈中对于socket相关API的实现

    题记:本系列文章的目的是抛开书本从Linux内核源代码的角度详细分析TCP/IP协议栈内核相关技术 轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.com linu ...

  8. 从linux源码看socket的阻塞和非阻塞

    从linux源码看socket的阻塞和非阻塞 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 大部分高性能网络框架采用的是非阻塞模式.笔者这次就从linux ...

  9. 从linux源码看socket(tcp)的timeout

    从linux源码看socket(tcp)的timeout 前言 网络编程中超时时间是一个重要但又容易被忽略的问题,对其的设置需要仔细斟酌.在经历了数次物理机宕机之后,笔者详细的考察了在网络编程(tcp ...

随机推荐

  1. [WPF自定义控件]使用WindowChrome自定义Window Style

    1. 为什么要自定义Window 对稍微有点规模的桌面软件来说自定义的Window几乎是标配了,一来设计师总是克制不住自己想想软件更个性化,为了UI的和谐修改Window也是必要的:二来多一行的空间可 ...

  2. jmeter给cookie设置sessionId避免其他脚本多次登录

    1.相关知识: http头部可以设置:浏览器显示内容类型,如content-type:text/html http头部可以存放:浏览器的cookie信息——cookie是对用户身份进行判断的内容 ht ...

  3. 1010. Radix (25)(出错较多待改进)

    Given a pair of positive integers, for example, 6 and 110, can this equation 6 = 110 be true? The an ...

  4. Robot Framework中的未解之谜

    今天在写测试用例的时候偶然发现了一个问题: 一.看脚本逻辑上没有问题,但是在引用变量的时候不能成功引用,脚本截图如下: 这个是关键字A的截图,没有参数. 此时在case中引用${phonesign}和 ...

  5. numpy模块

    NumPy简介: NumPy 是高性能科学计算和数据分析的基础包:它是pandas等其他工具的基础. NumPy的主要功能: 1. ndarray,一个多维数组结构,高效且节省空间 (最主要的功能) ...

  6. 骑士精神 (codevs 2449)

    题目描述 Description 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位.在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标 ...

  7. 如何通过AS3加载外部SWF文件,调用外部文件文档类的方法?

    一个Flash中通过AS3代码的Loader对象加载另一个SWF文件,并访问其中的文档类中的方法. 简单示例: 主文件:Main.fla, Main.as 被调用的文件:called.swf, Cal ...

  8. python学习之-- importlib模块

    importlib 模块 Python提供了importlib包作为标准库的一部分.目的就是提供Python中import语句的实现(以及__import__函数).另外,importlib允许程序员 ...

  9. Protobuf 完整解析 - 公司最常用的数据交互协议

    Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关.语言无关.可扩展,可用于通讯协议和数据存储等领域. 数据交互xml.json.pro ...

  10. 前后端分离项目shiro的未登录和权限不足

    在前后端分离的项目中.前端代码和后端代码几乎不在同一个目录下,甚至不是在一台服务器上:我这个项目部署在linux.同一台服务器,不同目录下:所有的页面跳转由前台路由,后台只是提供返回的数据: 干货↓  ...