Linux协议栈代码阅读笔记(一)

(基于linux-2.6.21.7)

(一)用户态通过诸如下面的C库函数访问协议栈服务

int socket(int domain, int type, int protocol);

int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);

int connect(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);

……

(二)上述C库函数如何与内核交互

C库代码准备好相应的工作后(例如,设置系统调用号啦、参数构造啦、栈啦、寄存器设置啦),通过系统调用指令,进入内核态。从内核返回后,C库函数再做相应的善后工作,然后将结果返回给用户程序。

这部分代码,不同架构的处理器,有不同的实现。

可以参考Glibc的源码。

下面以X86为例,简要描述一下这个过程。

另外,后续的内容,如无特殊说明,均是针对X86架构。

对于X86架构,一般是通过“int  $0x80”指令进入内核,即触发128号中断。

内核中断向量表的定义如下(源码文件archi386kernel Traps.c):

struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };

函数trap_init(源码文件archi386kernel Traps.c)对此表进行了初始化。

其中,对128号中断的初始化方式为:

set_system_gate(SYSCALL_VECTOR,&system_call);

SYSCALL_VECTOR宏的值为0x80,即128。

因此,128号中断,即对应中断向量表的第128个条目,其中断服务程序为system_call这段代码。

system_call这段代码,是用汇编实现的。

其代码在archi386kernelentry.S中。

这个代码,主要是根据系统调用号,索引系统调用表中的一个条目进行执行。

(三)内核态如何处理用户的网络通讯请求

上一步,C库发起了系统调用,进入了内核128号中断,即系统调用软中断。

128号中断处理程序,根据系统调用号,进入系统调用表的相应表目。系统调用表如下,每个表目是一个函数指针。(源码文件:archi386kernel syscall_table.S)

ENTRY(sys_call_table)

.long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */

.long sys_exit

.long sys_fork

.long sys_read

.long sys_write

.long sys_open  /* 5 */

.long sys_close

.long sys_waitpid

.long sys_creat

.long sys_link

.long sys_unlink /* 10 */

.long sys_ni_syscall /* old lock syscall holder */

.long sys_statfs

.long sys_fstatfs /* 100 */

.long sys_ioperm

.long sys_socketcall

.long sys_syslog

.long sys_tee   /* 315 */

.long sys_vmsplice

.long sys_move_pages

.long sys_getcpu

.long sys_epoll_pwait

对于上述的几个socket库函数,全部对应同一个系统调用,即102号系统调用,即sys_socketcall函数。

sys_socketcall函数如何处理用户的socket请求

所有的socket相关的C库函数,如socket、bind、connect、listen、accept、send、recv、sendto、sendmsg等,全部都属于同一个系统调用(即102号系统调用),全部由这一个函数处理。

此函数的代大致如下(源码文件netSocket.c)

long sys_socketcall(int call, unsigned long __user *args)

{

……

switch (call) {

case SYS_SOCKET:

err = sys_socket(a0, a1, a[2]);

break;

case SYS_BIND:

err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

break;

case SYS_CONNECT:

err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);

break;

case SYS_LISTEN:

err = sys_listen(a0, a1);

break;

case SYS_ACCEPT:

err =

sys_accept(a0, (struct sockaddr __user *)a1,

(int __user *)a[2]);

break;

case SYS_GETSOCKNAME:

err =

sys_getsockname(a0, (struct sockaddr __user *)a1,

(int __user *)a[2]);

break;

case SYS_GETPEERNAME:

err =

sys_getpeername(a0, (struct sockaddr __user *)a1,

(int __user *)a[2]);

break;

case SYS_SOCKETPAIR:

err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]);

break;

case SYS_SEND:

err = sys_send(a0, (void __user *)a1, a[2], a[3]);

break;

case SYS_SENDTO:

err = sys_sendto(a0, (void __user *)a1, a[2], a[3],

(struct sockaddr __user *)a[4], a[5]);

break;

case SYS_RECV:

err = sys_recv(a0, (void __user *)a1, a[2], a[3]);

break;

case SYS_RECVFROM:

err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3],

(struct sockaddr __user *)a[4],

(int __user *)a[5]);

break;

case SYS_SHUTDOWN:

err = sys_shutdown(a0, a1);

break;

case SYS_SETSOCKOPT:

err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]);

break;

case SYS_GETSOCKOPT:

err =

sys_getsockopt(a0, a1, a[2], (char __user *)a[3],

(int __user *)a[4]);

break;

case SYS_SENDMSG:

err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);

break;

case SYS_RECVMSG:

err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]);

break;

default:

err = -EINVAL;

break;

}

return err;

}

(四)socket的创建

使用上述C库函数,第一步当然是使用socket库函数创建一个socket。后续的操作,则都是针对这一步创建出的socket而进行了。因此,我们先来看看socket的创建。

创建socket,主要就是分配一个struct socket结构变量,并适当的初始化。

这个工作由sys_socket 通过如下形式调用sock_create完成。其中的参数family、type、protocol都是用户调用socket库函数时传入的。

sock_create(family, type, protocol, &sock);

sock_create成功返回后,就创建了一个struct socket结构变量。

后续的数据收发,状态维护,差不多都基于这个结构变量了。

struct socket结构如下(源码文件:netSocket.c)

struct socket {

socket_state  state;

unsigned long  flags;

const struct proto_ops *ops;

struct fasync_struct *fasync_list;

struct file  *file;

struct sock  *sk;

wait_queue_head_t wait;

short   type;

};

这个结构的初始化,主要依赖于family, type, protocol这三项信息,查找到相应的协议栈模块,填充struct socket结构中相应的成员。

这个初始化过程的层次比较深,涉及较多细节。在下也没有深入阅读理解。

不过,我们可以简单看看大的数据结构。

内核中的协议栈,也是按family, type, protocol这三项信息进行了组织。

a) 固定的协议信息(netipv4 Af_inet.c)

Socket的创建,需要根据family, type, protocol确定一个协议。

内核中固定的协议信息,都在inetsw_array中进行了登记。

Static  struct  inet_protosw  inetsw_array[] =

{

{

.type =       SOCK_STREAM,

.protocol =   IPPROTO_TCP,

.prot =       &tcp_prot,

.ops =        &inet_stream_ops,

.capability = -1,

.no_check =   0,

.flags =      INET_PROTOSW_PERMANENT |

INET_PROTOSW_ICSK,

},

{

.type =       SOCK_DGRAM,

.protocol =   IPPROTO_UDP,

.prot =       &udp_prot,

.ops =        &inet_dgram_ops,

.capability = -1,

.no_check =   UDP_CSUM_DEFAULT,

.flags =      INET_PROTOSW_PERMANENT,

},

{

.type =       SOCK_RAW,

.protocol =   IPPROTO_IP, /* wild card */

.prot =       &raw_prot,

.ops =        &inet_sockraw_ops,

.capability = CAP_NET_RAW,

.no_check =   UDP_CSUM_DEFAULT,

.flags =      INET_PROTOSW_REUSE,

}

};

数组中的每个元素,对应一个协议。

其中,每个元素的prot成员,指向相应的协议(如TCP、UDP等)提供的proto结构变量,其中含有大量的函数指针,指向相应的函数,这些函数用于实现各种协议的交互、收发、控制等。这样一来,每一个协议,在这个数组中都能查到了,如何操作使用他们,也都有了相应的信息。

每个协议的ops成员,也指向一个proto_ops结构变量,其中也包含大量函数指针。这些操作,可以认为是包装后的,更抽象的操作。是更接近用户的socket操作函数。例如,这些操作函数包括:bind、connect、listen等。

具体包含哪些操作,是由family, type决定的(例如,family=PF_INET,type=SOCK_DGRAM时,则使用sendmsg接收数据)。对于多个协议,即使实现不同,但是如果他们的family, type相同,那么对于用户来说,操作都是一样的。

初始化完成后,最终的情况是:

a)    socket.sk.__sk_common.skc_prot指向具体的协议提供的proto结构变量。内含大量函数,实现具体的协议操作。

b)    socket. ops 指向相应的proto_ops结构变量,实现各种socket操作。

c)   最终的流程是:socket. ops包装了socket操作,但是socket. ops中的函数是利用socket.sk.__sk_common.skc_prot中的函数完成最终的操作。

最后,inetsw_array中的元素(协议),不是遍历查找的。他们被按照type分类组织到中inetsw了。Inetsw包含了PF_INET协议族中的全部协议。

Inetsw是个链表数组,定义如下(源码文件netipv4 Af_inet.c)。

static  struct  list_head  inetsw[SOCK_MAX];

(五)使用socket进行收发

上一步已经完成了相关的初始化工作。

后续的建链、收发、断链等操作,也还都是由socketcall这一个函数完成的。

有兴趣的朋友可以自己研习研习相关的代码了。

在下对这方面也没有深入阅读理解:)

linux 协议栈 位置,[置顶] Linux协议栈代码阅读笔记(一)相关推荐

  1. linux getline参数,[置顶]linux getline()函数

    C 有 fgets(), gets() 函数,也有getline.用于读取一行字符直到换行符,包括换行符.这个和我们前面那篇关于标准输入输出函数 流与缓冲区那篇中内容有联系, printf() sca ...

  2. [置顶] Linux协议栈代码阅读笔记(一)

    Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...

  3. C++ Primer Plus 6th代码阅读笔记

    C++ Primer Plus 6th代码阅读笔记 第一章没什么代码 第二章代码 carrots.cpp : cout 可以拼接输出,cin.get()接受输入 convert.cpp 函数原型放在主 ...

  4. CNN去马赛克代码阅读笔记

    有的博客链接是之前几周写好的草稿,最近整理的时候才发布的 CNN去马赛克论文及代码下载地址 有torch,minimal torch和caffe三种版本 关于minimal torch版所做的努力,以 ...

  5. ORB-SLAM2代码阅读笔记(五):Tracking线程3——Track函数中单目相机初始化

    Table of Contents 1.特征点匹配相关理论简介 2.ORB-SLAM2中特征匹配代码分析 (1)Tracking线程中的状态机 (2)单目相机初始化函数MonocularInitial ...

  6. StyleGAN2代码阅读笔记

    源代码地址:https://github.com/NVlabs/stylegan2-ada-pytorch 这是一篇代码阅读笔记,顾名思义是对代码进行阅读,讲解的笔记.对象是styleGAN2的pyt ...

  7. BNN Pytorch代码阅读笔记

    BNN Pytorch代码阅读笔记 这篇博客来写一下我对BNN(二值化神经网络)pytorch代码的理解,我是第一次阅读项目代码,所以想仔细的自己写一遍,把细节理解透彻,希望也能帮到大家! 论文链接: ...

  8. 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(八)—— 模型训练-训练

    系列目录: 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(一)--数据 菜鸟笔记-DuReader阅读理解基线模型代码阅读笔记(二)-- 介绍及分词 菜鸟笔记-DuReader阅读理解基线模 ...

  9. leveldb代码阅读笔记(一)

    leveldb代码阅读笔记 above all leveldb是一个单机的键值存储的内存数据库,其内部使用了 LSM tree 作为底层存储结构,支持多版本数据控制,代码设计巧妙且简洁高效,十分值得作 ...

最新文章

  1. matlab中tecdem,TopoToolbox: A set of Matlab functions for topographic analysis
  2. Java-J2SE专题复习
  3. freeBSD时区设置与时间设置
  4. 【Chocolatey】查找包
  5. MySQL高级特性之分区表
  6. 不同品牌机中的“Fn”按钮功能定位汇总
  7. 802.1D生成树STP协议
  8. GMM R语言程序 gmm包的使用
  9. CAD工程图纸转jpg格式教程
  10. c++点餐系统(C++、多态、sqlite数据库)
  11. 计算机网络的发展历史
  12. Asc、AscB、AscW
  13. 小白题解 Codeforces 794B Cutting Carrot
  14. 简·雅各布斯(yane jacobs y)在你附近
  15. 字典树(Trie,前缀树)
  16. 使用小程序制作一个电子木鱼,功德+1
  17. 金融科技巨头蚂蚁金服:香港和上海启动IPO程序
  18. 谱聚类Python代码详解
  19. java package 注解,如何添加包级别注释或编辑package-info.java?
  20. 浅谈多链路聚合通讯保障技术原理及公安消防行业应急方案

热门文章

  1. Python 爬虫使用固定代理IP
  2. Django基于正则表达式的URL
  3. java在线作业系统_在线作业系统论文
  4. python pip如何安装wheel文件?.whl(pip install [wheel])
  5. Intel Realsense 深度流向彩色流对齐 color_aligned_to_depth和depth_aligned_to_color
  6. python 字符串前加r和f
  7. 分布式系统——网络监视系统zabbix3.4.2,以及向zabbix中添加被监视主机(一)
  8. mysql 5.7 的组复制
  9. Java DelayQueue延迟队列的使用和源码分析
  10. unity3d技术摄像头跟随_堪比灯厂,新时代智能安全灯光技术,体验大众迈腾GTE IQ.LIGHT...