深度剖析网络协议栈中的 socket 函数,可以说是把前面介绍的串联起来,将网络协议栈各层关联起来。
1、应用层——socket 函数
为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型。该函数只是作为一个简单的接口函数供用户调用,调用该函数后将进入内核栈进行系统调用sock_socket 函数。
#include <sys/socket.h> int socket(int family, int type, int protocol); /*返回:非负描述字——成功, -1——出错 其中family参数指明协议族,type参数指明套接口类型,后面protocol通常设为0,以选择所给定family 和 type组合的系统缺省值*/2、BSD Socket 层——sock_socket 函数
从应用层进入该函数是通过一个共同的入口函数 sys_socket
/* * System call vectors. Since I (RIB) want to rewrite sockets as streams, * we have this level of indirection. Not a lot of overhead, since more of * the work is done via read/write/select directly. * * I'm now expanding this up to a higher level to separate the assorted * kernel/user space manipulations and global assumptions from the protocol * layers proper - AC. */ //本函数是网络栈专用操作函数集的总入口函数,主要是将请求分配,调用具体的底层函数进行处理 asmlinkage int sys_socketcall(int call, unsigned long *args) { int er; switch(call) { case SYS_SOCKET://socket函数 er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_socket(get_fs_long(args+0), get_fs_long(args+1),//返回地址上的值 get_fs_long(args+2)));//调用sock_socket函数 …… }下面就是sock_socket 函数主体
/* * 系统调用,创建套接字socket。涉及到sock结构的创建. */ static int sock_socket(int family, int type, int protocol) { int i, fd; struct socket *sock; struct proto_ops *ops; /* 匹配应用程序调用socket()函数时指定的协议 */ for (i = 0; i < NPROTO; ++i) { if (pops[i] == NULL) continue; if (pops[i]->family == family) //设置域 break; } //没有匹配的协议,则出错退出 if (i == NPROTO) { return -EINVAL; } //根据family输入参数决定域操作函数集用于ops字段的赋值 //操作函数集是跟域相关的,不同的域对应不同的操作函数集 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. */ //分配socket套接字结构 if (!(sock = sock_alloc())) { printk("NET: sock_socket: no more sockets\n"); return(-ENOSR); /* Was: EAGAIN, but we are out of system resources! */ } //指定对应类型,协议,以及操作函数集 sock->type = type; sock->ops = ops; //分配下层sock结构,sock结构是比socket结构更底层的表示一个套接字的结构 //前面博文有说明:http://blog.csdn.net/wenqian1991/article/details/21740945 //socket是通用的套接字结构体,而sock与具体使用的协议相关 if ((i = sock->ops->create(sock, protocol)) < 0) //这里调用下层函数 create { sock_release(sock);//出错回滚销毁处理 return(i); } //分配一个文件描述符并在后面返回给应用层序作为以后的操作句柄 if ((fd = get_fd(SOCK_INODE(sock))) < 0) { sock_release(sock); return(-EINVAL); } return(fd);//这个就是我们应用系统使用的套接字描述符 }该要介绍的注释里,已经说明白了,可以看到,该函数又将调用下一层函数 create。(网络栈就是这样,上层调用下层函数)
sock_socket 函数内部还调用了一个函数 sock_alloc(),该函数主要是分配一个 socket 套接字结构(实际上找到一个空闲的inode结构,socket结构已经包含在inode结构中)
/* * 分配一个socket结构 */ struct socket *sock_alloc(void) { struct inode * inode; struct socket * sock; inode = get_empty_inode();//分配一个inode对象 if (!inode) return NULL; //获得的inode结构的初始化 inode->i_mode = S_IFSOCK; inode->i_sock = 1; inode->i_uid = current->uid; inode->i_gid = current->gid; //可以看出socket结构体的实体空间,就已经存在了inode结构中的union类型中, //所以无需单独的开辟空间分配一个socket 结构 sock = &inode->u.socket_i;//这里把inode的union结构中的socket变量地址传给sock sock->state = SS_UNCONNECTED; sock->flags = 0; sock->ops = NULL; sock->data = NULL; sock->conn = NULL; sock->iconn = NULL; sock->next = NULL; sock->wait = &inode->i_wait; sock->inode = inode;//回绑 sock->fasync_list = NULL; sockets_in_use++;//系统当前使用的套接字数量加1 return sock; }
3、INET Socket 层——inet_create 函数
/* * Create an inet socket. * * FIXME: Gcc would generate much better code if we set the parameters * up in in-memory structure order. Gcc68K even more so */ //该函数被上层sock_socket函数调用,用于创建一个socket套接字对应的sock结构并对其进行初始化 //socket是通用结构,sock是具体到某种协议的结构 //代码是一大串,功能就是建立套接字对应的sock结构并对其进行初始化 static int inet_create(struct socket *sock, int protocol) { struct sock *sk; struct proto *prot; int err; //分配一个sock结构,内存分配一个实体 sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL); if (sk == NULL) return(-ENOBUFS); sk->num = 0;//本地端口号 sk->reuse = 0; //根据类型进行相关字段的赋值 //关于哪种类型与协议的对应关系,请参考<UNP 卷1>,有些类型就只能和某种协议对应 switch(sock->type) { case SOCK_STREAM: case SOCK_SEQPACKET: if (protocol && protocol != IPPROTO_TCP) { kfree_s((void *)sk, sizeof(*sk)); return(-EPROTONOSUPPORT); } protocol = IPPROTO_TCP;//tcp协议 sk->no_check = TCP_NO_CHECK; //这个prot变量表明了套接字使用的是何种协议 //然后使用的则是对应协议的操作函数 prot = &tcp_prot; break; case SOCK_DGRAM: if (protocol && protocol != IPPROTO_UDP) { kfree_s((void *)sk, sizeof(*sk)); return(-EPROTONOSUPPORT); } protocol = IPPROTO_UDP;//udp协议 sk->no_check = UDP_NO_CHECK;//不使用校验 prot=&udp_prot; break; case SOCK_RAW: if (!suser()) //超级用户才能处理 { kfree_s((void *)sk, sizeof(*sk)); return(-EPERM); } if (!protocol)// 原始套接字类型,这里表示端口号 { kfree_s((void *)sk, sizeof(*sk)); return(-EPROTONOSUPPORT); } prot = &raw_prot; sk->reuse = 1; sk->no_check = 0; /* * Doesn't matter no checksum is * performed anyway. */ sk->num = protocol;//本地端口号 break; case SOCK_PACKET: if (!suser()) { kfree_s((void *)sk, sizeof(*sk)); return(-EPERM); } if (!protocol) { kfree_s((void *)sk, sizeof(*sk)); return(-EPROTONOSUPPORT); } prot = &packet_prot; sk->reuse = 1; sk->no_check = 0; /* Doesn't matter no checksum is * performed anyway. */ sk->num = protocol; break; default://不符合以上任何类型,则返回 kfree_s((void *)sk, sizeof(*sk)); return(-ESOCKTNOSUPPORT); } sk->socket = sock;//建立与其对应的socket之间的关系 #ifdef CONFIG_TCP_NAGLE_OFF sk->nonagle = 1;//如果定义了Nagle算法 #else sk->nonagle = 0; #endif //各种初始化 //这里是sock结构 sk->type = sock->type; sk->stamp.tv_sec=0; sk->protocol = protocol; sk->wmem_alloc = 0; sk->rmem_alloc = 0; sk->sndbuf = SK_WMEM_MAX; sk->rcvbuf = SK_RMEM_MAX; sk->pair = NULL; sk->opt = NULL; sk->write_seq = 0; sk->acked_seq = 0; sk->copied_seq = 0; sk->fin_seq = 0; sk->urg_seq = 0; sk->urg_data = 0; sk->proc = 0; sk->rtt = 0; /*TCP_WRITE_TIME << 3;*/ sk->rto = TCP_TIMEOUT_INIT; /*TCP_WRITE_TIME*/ sk->mdev = 0; sk->backoff = 0; sk->packets_out = 0; sk->cong_window = 1; /* start with only sending one packet at a time. */ sk->cong_count = 0; sk->ssthresh = 0; sk->max_window = 0; sk->urginline = 0; sk->intr = 0; sk->linger = 0; sk->destroy = 0; sk->priority = 1; sk->shutdown = 0; sk->keepopen = 0; sk->zapped = 0; sk->done = 0; sk->ack_backlog = 0; sk->window = 0; sk->bytes_rcv = 0; sk->state = TCP_CLOSE; sk->dead = 0; sk->ack_timed = 0; sk->partial = NULL; sk->user_mss = 0; sk->debug = 0; /* this is how many unacked bytes we will accept for this socket. */ sk->max_unacked = 2048; /* needs to be at most 2 full packets. */ /* how many packets we should send before forcing an ack. if this is set to zero it is the same as sk->delay_acks = 0 */ sk->max_ack_backlog = 0; sk->inuse = 0; sk->delay_acks = 0; skb_queue_head_init(&sk->write_queue); skb_queue_head_init(&sk->receive_queue); sk->mtu = 576;//最大传输单元 sk->prot = prot; sk->sleep = sock->wait; sk->daddr = 0;//远端地址 sk->saddr = 0 /* 本地地址 */; sk->err = 0; sk->next = NULL; sk->pair = NULL; sk->send_tail = NULL; sk->send_head = NULL; sk->timeout = 0; sk->broadcast = 0; sk->localroute = 0; init_timer(&sk->timer); init_timer(&sk->retransmit_timer); sk->timer.data = (unsigned long)sk; sk->timer.function = &net_timer; skb_queue_head_init(&sk->back_log); sk->blog = 0; sock->data =(void *) sk; //下面是sock结构中tcp首部初始化 sk->dummy_th.doff = sizeof(sk->dummy_th)/4; sk->dummy_th.res1=0; sk->dummy_th.res2=0; sk->dummy_th.urg_ptr = 0; sk->dummy_th.fin = 0; sk->dummy_th.syn = 0; sk->dummy_th.rst = 0; sk->dummy_th.psh = 0; sk->dummy_th.ack = 0; sk->dummy_th.urg = 0; sk->dummy_th.dest = 0; //ip部分 sk->ip_tos=0; sk->ip_ttl=64; #ifdef CONFIG_IP_MULTICAST sk->ip_mc_loop=1; sk->ip_mc_ttl=1; *sk->ip_mc_name=0; sk->ip_mc_list=NULL; #endif sk->state_change = def_callback1; sk->data_ready = def_callback2; sk->write_space = def_callback3; sk->error_report = def_callback1; if (sk->num) //如果分配了本地端口号 { /* * It assumes that any protocol which allows * the user to assign a number at socket * creation time automatically * shares. */ //将具有确定端口号的新sock结构加入到sock_array数组表示的sock结构链表中 put_sock(sk->num, sk);//实际上这里确定的端口号一般为初始化0 sk->dummy_th.source = ntohs(sk->num);//tcp首部源端地址,就是端口号 //这里需要进行字节序转换,网络字节序转主机字节序 } if (sk->prot->init) //根据不同协议类型,调用对应init函数 { err = sk->prot->init(sk);//调用相对应4层协议的初始化函数 if (err != 0) { destroy_sock(sk);//出错了,就销毁 return(err); } } return(0); }到这里一个 socket 套接字就创建完成了
我们简单的总结一下这几个函数的功能:
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地址和端口号),也不知道对端的地址信息。