参考资料

<<linux内核网络栈源代码情景分析>>

socket常用函数概述

根据socket提供的常用的库函数,socket,read,write等函数,

执行的过程

inet协议的注册流程

在上文中分析过了在设备的初始化过程中,会调用sock_init函数初始化,在该函数中又会调用proto_init函数进行初始化,在proto_init函数中会调用inet对应的初始化函数inet_proto_init,该函数如下;

void inet_proto_init(struct net_proto *pro)
{struct inet_protocol *p;int i;printk("Swansea University Computer Society TCP/IP for NET3.019\n");/**    Tell SOCKET that we are alive... *  ÏòSOCKETÄ£¿é×¢²áЭÒé´Ø*/(void) sock_register(inet_proto_ops.family, &inet_proto_ops);       // 注册tcp/ip协议对应的协议操作函数seq_offset = CURRENT_TIME*250;/**    Add all the protocols. */for(i = 0; i < SOCK_ARRAY_SIZE; i++) {tcp_prot.sock_array[i] = NULL;udp_prot.sock_array[i] = NULL;raw_prot.sock_array[i] = NULL;}tcp_prot.inuse = 0;tcp_prot.highestinuse = 0;udp_prot.inuse = 0;udp_prot.highestinuse = 0;raw_prot.inuse = 0;raw_prot.highestinuse = 0;printk("IP Protocols: ");for(p = inet_protocol_base; p != NULL;)                            // 添加协议到链表尾部{struct inet_protocol *tmp = (struct inet_protocol *) p->next;inet_add_protocol(p);printk("%s%s",p->name,tmp?", ":"\n");p = tmp;}/**  Set the ARP module up*/arp_init();                                                  // arp模块初始化/**  Set the IP module up*/ip_init();                                                        // ip模块初始化
}

首先会调用sock_register函数注册协议到列表pops中,

int sock_register(int family, struct proto_ops *ops)
{int i;cli();                                   // 禁止中断for(i = 0; i < NPROTO; i++)            // 遍历最大的协议包含数量{if (pops[i] != NULL)                // 如果该位置内容不为空则下一个continue;pops[i] = ops;                       // 找到为空的位置,讲该函数操作方法设置进去pops[i]->family = family;             // 设置该协议的familysti();                               // 打开中断并返回return(i);}sti();                                     // 打开中断return(-ENOMEM);                         // 返回出错
}

此时我们查看传入的inet_proto_ops操作集合;

static struct proto_ops inet_proto_ops = {AF_INET,inet_create,              // 创建inet_dup,inet_release,                 // 释放inet_bind,                     // 绑定inet_connect,              // 连接inet_socketpair,inet_accept,               // 接受inet_getname, inet_read,                   // 读数据inet_write,               // 写数据inet_select,              // 检查套接字inet_ioctl,inet_listen,                 // 监听队列inet_send,                   // 发送数据inet_recv,                   // 接受数据inet_sendto,inet_recvfrom,inet_shutdown,                 inet_setsockopt,            // 设置socket配置参数inet_getsockopt,             // 获取socket配置参数inet_fcntl,
};
socket函数的调用过程

一般socket的编程过程,会出现socket服务端与客户端,python的实例代码如下;

    import socketsock = socket.socket()                  # 初始化sock.bind(("127.0.0.1", 9999))          # 监听ip与端口sock.listen(10)                         # 监听队列while True:conn, addr = sock.accept()          # 等待接受请求print(conn, addr)data = conn.recv(1024)              # 接受数据print("recv data : ", data) conn.send(data)                     # 发送数据conn.close()                        # 关闭连接

服务端代码如上所示,主要初始化socket,设置监听端口,设置监听队列长度,等待接受客户端请求,接受客户端的数据与发送客户端的数据。客户端的代码如下;

    sock = socket.socket()                  # 初始化连接sock.connect(("127.0.0.1", 9999))       # 连接远端服务器sock.send(b"hello world")               # 发送数据data = sock.recv(1024)                  # 接受数据print("client recv data: ", data)sock.close()                            # 关闭连接

客户端主要就是初始化socket,连接远端端口与ip,发送数据给服务端,然后等待接受服务端返回的数据,最后关闭连接。

sock_socket初始化函数
/**  Perform the socket system call. we locate the appropriate*  family, then create a fresh socket.*/static int sock_socket(int family, int type, int protocol)
{int i, fd;struct socket *sock;struct proto_ops *ops;/* Locate the correct protocol family. */for (i = 0; i < NPROTO; ++i) {if (pops[i] == NULL) continue;          // 查找注册进来的协议if (pops[i]->family == family)         // 判断传入的协议号与传入的一致break;}if (i == NPROTO)                              // 等于注册协议号的最大长度则表示没有找到对应的协议号{return -EINVAL;                        // 返回出错}ops = pops[i];                                 // 获取找到的协议号/**  Check that this is a type that we know how to manipulate and*   the protocol makes sense here. The family can still reject the* protocol later.*/if ((type != SOCK_STREAM && type != SOCK_DGRAM &&type != SOCK_SEQPACKET && type != SOCK_RAW &&type != SOCK_PACKET) || protocol < 0)        // 判断类型是否符合要求return(-EINVAL);/**    Allocate the socket and allow the family to set things up. if*  the protocol is 0, the family is instructed to select an appropriate*   default.*/if (!(sock = sock_alloc()))                      // 申请内存{printk("NET: sock_socket: no more sockets\n");return(-ENOSR); /* Was: EAGAIN, but we are out ofsystem resources! */           // 如果申请内存失败则报错}sock->type = type;                               // 设置类型sock->ops = ops;                                 // 设置协议操作的ops操作集合if ((i = sock->ops->create(sock, protocol)) < 0)  // 调用协议ops的创建sock函数{sock_release(sock);                          // 如果创建失败则释放刚刚申请的内存return(i);                                       // 返回报错编号}if ((fd = get_fd(SOCK_INODE(sock))) < 0)         // 获取文件描述符{sock_release(sock);                          // 如果文件描述符获取失败则释放sock内存return(-EINVAL);                             // 返回错误编码}return(fd);                                   // 返回文件描述符
}

sock的初始化函数,根据传入的family参数来初始化不同的ops的函数集,如inet_proto_ops操作集合,然后再分配inode结构合socket结构并设置socket结构对应的ops字段,再通过sock->ops->create函数来创建sock结构,最后通过get_fd来分配一个文件描述符并返回。

/**  Obtains the first available file descriptor and sets it up for use. */static int get_fd(struct inode *inode)
{int fd;struct file *file;/**   Find a file descriptor suitable for return to the user. */file = get_empty_filp();                     // 获取一个空闲的文件结构if (!file)                                    // 如果没有找到则返回-1return(-1);for (fd = 0; fd < NR_OPEN; ++fd)             // 遍历文件描述符列表if (!current->files->fd[fd])              // 找到一个没有被使用的文件描述符break;if (fd == NR_OPEN) {file->f_count = 0;return(-1);}FD_CLR(fd, &current->files->close_on_exec);current->files->fd[fd] = file;            // 设置当前文件描述符对应的file/* ¶ÔsocketµÄÆÕͨÎļþ²Ù×÷¼¯ºÏ */file->f_op = &socket_file_ops;              // 设置操作函数集为socket_file_opsfile->f_mode = 3;file->f_flags = O_RDWR;                  // 设置模式file->f_count = 1;file->f_inode = inode;                     // 设置inodeif (inode) inode->i_count++;file->f_pos = 0;return(fd);                              // 返回文件描述符
}

sock->ops对应的操作集函数就是位于af_inet.c中的操作集,在af_inet.c中对应的操作集底层调用的操作集对应位于tcp.c中的tcp/ip协议的操作集。

sock_bind函数的执行过程
static int sock_bind(int fd, struct sockaddr *umyaddr, int addrlen)
{struct socket *sock;int i;char address[MAX_SOCK_ADDR];int err;if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL)                // 检查传入的文件描述符是否符合规则return(-EBADF);if (!(sock = sockfd_lookup(fd, NULL)))                                   // 查找对应的文件描述符的sockreturn(-ENOTSOCK);if((err=move_addr_to_kernel(umyaddr,addrlen,address))<0)                    // 讲输入传入内核return err;if ((i = sock->ops->bind(sock, (struct sockaddr *)address, addrlen)) < 0)   // 调用sock的绑定bind函数进行监听{return(i);                                                                // 如果出错就返回错误值}return(0);
}

该函数主要就是检查文件描述符是否正确,然后讲数据传入内核地址中,然后调用sock->ops->bind函数进行调用,该函数也是位于af_inet.c中,主要工作就是完成端口的检查设置监听的地址与端口。

sock_listen函数的执行过程
static int sock_listen(int fd, int backlog)
{struct socket *sock;if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL)          // 检查文件描述符是否合法return(-EBADF);if (!(sock = sockfd_lookup(fd, NULL)))                                // 查找对应的文件描述符的sockreturn(-ENOTSOCK);/* ÒªÇósocketµÄ״̬ÊÇSS_UNCONNECTED£¬¸Õ½¨Á¢×´Ì¬ */if (sock->state != SS_UNCONNECTED)                                    // 检查文件描述符的状态如果不为未连接状态则报错{return(-EINVAL);}if (sock->ops && sock->ops->listen)                                     // 检查是否有操作函数listensock->ops->listen(sock, backlog);                                   // 调用该函数sock->flags |= SO_ACCEPTCON;                                            // sock的标志添加接受请求的标志return(0);
}

主要检查了文件描述符的合法性,然后查找对应的文件描述符的sock,检查该sock的状态,然后调用sock->ops->listen函数,如果是tcp协议则调用了位于af_inet.c中的inet_listen函数;

/**  Move a socket into listening state.*/static int inet_listen(struct socket *sock, int backlog)
{struct sock *sk = (struct sock *) sock->data;                      // 获取sock结构if(inet_autobind(sk)!=0)                                            // 检查是否有未绑定的端口号,设置source,设置端口号return -EAGAIN;/* We might as well re use these. */ /** note that the backlog is "unsigned char", so truncate it* somewhere. We might as well truncate it to what everybody* else does..*/if ((unsigned) backlog > 128)                                        // 如果设置的队列长度大于128则设置为128backlog = 128;sk->max_ack_backlog = backlog;                                       // 设置最大队列数if (sk->state != TCP_LISTEN)                                      // 如果sock的状态不是TCP_LISTEN改为TCP_LISTEN{sk->ack_backlog = 0;                                           // ack_backlog设置为0sk->state = TCP_LISTEN;                                       // 设置为TCP_LISTEN状态}return(0);
}

大致的监听过程如上所示,检查backlog的长度大小,检查状态并重置sk的状态。

sock_send发送数据与sock_recv接受数据
static int sock_send(int fd, void * buff, int len, unsigned flags)
{struct socket *sock;struct file *file;int err;if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))            // 检查文件描述符是否合法return(-EBADF);if (!(sock = sockfd_lookup(fd, NULL)))                                            // 查找fd对应的sockreturn(-ENOTSOCK);if(len<0)                                                                            return -EINVAL;err=verify_area(VERIFY_READ, buff, len);                                            // 检查发送数据的缓冲区长度if(err)return err;return(sock->ops->send(sock, buff, len, (file->f_flags & O_NONBLOCK), flags));        // 发送数据并添加文件的描述符的发送状态
}

该函数检查了文件描述符后就直接调用了底层的ops的send方法将数据发送出去,该详细内容待后文详细分析。

static int sock_recv(int fd, void * buff, int len, unsigned flags)
{struct socket *sock;struct file *file;int err;if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))            // 检查文件描述符是否合法return(-EBADF);if (!(sock = sockfd_lookup(fd, NULL)))                                            // 查找对应的sockreturn(-ENOTSOCK);if(len<0)return -EINVAL;if(len==0)return 0;err=verify_area(VERIFY_WRITE, buff, len);                                            // 检查是否合法if(err)return err;return(sock->ops->recv(sock, buff, len,(file->f_flags & O_NONBLOCK), flags));       // 接受数据
}

同样是检查了文件描述符是否合法,然后获取对应的sock,调用该sock的操作集将接受数据。

sock_connect连接远端
/**  Attempt to connect to a socket with the server address.  The address*   is in user space so we verify it is OK and move it to kernel space.*/static int sock_connect(int fd, struct sockaddr *uservaddr, int addrlen)
{struct socket *sock;struct file *file;int i;char address[MAX_SOCK_ADDR];int err;if (fd < 0 || fd >= NR_OPEN || (file=current->files->fd[fd]) == NULL)              // 检查文件描述符的合法return(-EBADF);if (!(sock = sockfd_lookup(fd, &file)))                                            // 查找对应文件描述符的sockreturn(-ENOTSOCK);if((err=move_addr_to_kernel(uservaddr,addrlen,address))<0)                           // 拷贝数据到内核return err;switch(sock->state)                                                                 // 检查sock的状态{case SS_UNCONNECTED:                                                           // 为连接则继续/* This is ok... continue with connect */break;case SS_CONNECTED:                                                              // 已经连接过则检查是否为SOCK_DGRAM类型如果不是则返回/* Socket is already connected */if(sock->type == SOCK_DGRAM) /* Hack for now - move this all into the protocol */break;return -EISCONN;case SS_CONNECTING:/* Not yet connected... we will check this. *//**  FIXME:  for all protocols what happens if you start*    an async connect fork and both children connect. Clean* this up in the protocols!*/break;default:return(-EINVAL);}// µ÷ÓÃinet_connectº¯Êýʱ£¬socketµÄ״̬¿ÉÄÜÊÇ£º// 1. SS_UNCONNECTED// 2. SS_CONNECTED״̬µÄÊý¾Ý±¨Ì×½Ó×Ö(È磺udp)// 3. SS_CONNECTINGi = sock->ops->connect(sock, (struct sockaddr *)address, addrlen, file->f_flags);        // 调用操作函数集来连接if (i < 0) {return(i);}return(0);
}

先将远端的地址从用户缓冲区复制到内核缓冲区,之后检查套接字的状态,如果有效则调用sock->ops->connect进一步操作,继续完成网络数据传输。详细的连接内容待后文分析。

总结

本文主要讲述了用户态的常见的socket的调用的方式,然后根据示例代码查看了socket.c对应的函数的处理的大致流程,并未进入详细的函数的处理过程,大部分的函数的工作都是进行参数的检查之后就调用更底层的函数进一步去处理,后续会继续分析。由于本人才疏学浅,如有错误请批评指正。

Linux内核网络栈1.2.13-socket.c函数概述相关推荐

  1. Linux内核网络栈1.2.13-af_inet.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> socket常用函数继续调用分析 根据socket提供的常用库函数,socket.read和write等函数,继续往下一层 ...

  2. linux内核网络初始化,Linux内核--网络栈实现分析

    本文分析基于内核Linux Kernel 1.2.13 以后的系列博文将深入分析Linux内核的网络栈实现原理,这里看到曹桂平博士的分析后,也决定选择Linux内核1.2.13版本进行分析. 原因如下 ...

  3. Linux内核网络栈1.2.13-有关tcp/ip协议的基础入门

    参考资料 <<linux内核网络栈源代码情景分析>> Linux内核网络栈的基础内容 主要分析tcp/ip相关的基本构成,概述了socket的系统调用进入内核的一个流程,并了解 ...

  4. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的&qu ...

  5. Linux内核--网络栈实现分析(三)--驱动程序层+链路层(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7497260 更多请看专栏,地址 ...

  6. Linux内核--网络栈实现分析(一)--网络栈初始化--转

    转载地址 http://blog.csdn.net/yming0221/article/details/7488828 作者:闫明 本文分析基于内核Linux Kernel 1.2.13 以后的系列博 ...

  7. Linux内核网络栈1.2.13-route.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> route路由表概述 在IP协议的实现中,只要发送数据包都要查询路由表,选择合适的路由选项,确定下一站的地址,并构造MAC ...

  8. Linux内核网络栈1.2.13-tcp.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> af_inet.c文件中调用函数在协议层的实现 本文主要根据在af_inet.c文件中根据初始化不同的协议,来调用不同的协 ...

  9. 显示驱动包含在Linux内核层,驱动程序层(上) - Linux内核--网络栈实现分析_Linux编程_Linux公社-Linux系统门户网站...

    经过前面两篇博文的分析,已经对Linux的内核网络栈的结构有了一个模糊的认识,这里我们开始从底层开始详细分析Linux内核网络栈的实现.由于这是早期版本,代码的层次隔离做的还不是很好,这里说是从底层分 ...

最新文章

  1. 剑指 Offer 24. 反转链表(C语言)
  2. 服务端的第五次课程:安全,认证,授权
  3. idea 编译Java heap space 内存溢出
  4. 【数据库系统】数据库体系结构
  5. 数据中心智能化运维之路
  6. 基于amoeba实现mysql数据库的读写分离/负载均衡
  7. Resharper上手指南转
  8. ogg启动报错libnnz11.so: cannot open shared object file
  9. php打出等边三角形,CSS 如何进行单一div的正多边形变换
  10. TensorFlow函数(四)tf.trainable_variable() 和 tf.all_variable()
  11. python工程师简历项目经验怎么写_班长项目经验简历范文
  12. iOS 苹果登录(第三方登录)
  13. uniapp微信登陆
  14. oracle中的userenv,Oracle 中的userenv()
  15. 基于Android的sina微博分享功能
  16. 谷歌图像爬虫方法总结与教程
  17. ORM框架的简单介绍
  18. uva10410(dbl)
  19. JSP-学生管理系统
  20. 计算机网络协议层次结构图

热门文章

  1. 腾讯提结合ACNet进行细粒度分类,效果达到最新SOTA | CVPR 2020
  2. 免费!这里有一份开发者进阶“宝典”求带走
  3. wxPython:Python首选的GUI库 | CSDN博文精选
  4. 优化思路千万种,基于下界函数的最优化效率如何?
  5. 腾讯裁撤中层干部,拥抱年轻人
  6. 弃Java、Swift于不顾,为何选Python?
  7. 如何将三万行代码从Flow移植到TypeScript?
  8. 精选Python开源项目Top10!
  9. 有了这款可视化工具,Java 应用性能调优 so easy。。。
  10. Spring Cloud第三篇:服务消费者Feign