Chinaunix首页 | 论坛 | 博客
  • 博客访问: 53773
  • 博文数量: 7
  • 博客积分: 170
  • 博客等级: 入伍新兵
  • 技术积分: 212
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-24 15:57
文章分类

全部博文(7)

文章存档

2011年(7)

分类: LINUX

2011-06-25 12:36:05

1、示例及函数入口:
1) 示例代码如下:

  1. int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);  

2) 入口:
net/Socket.c:sys_socketcall(),根据子系统调用号,创建socket会执行sys_socket()函数;

2、分配socket结构:
1) 调用链:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->sock_alloc();

2) 在socket文件系统中创建i节点:

  1. inode = new_inode(sock_mnt->mnt_sb);  
其中,sock_mnt为socket文件系统的根节点,是在内核初始化安装socket文件系统时赋值的,mnt_sb是该文件系统安装点的超级块对象的指针;
这里,new_inode函数是文件系统的通用函数,其作用是在相应的文件系统中创建一个inode;其主要代码如下(fs/Inode.c):

  1. struct inode *new_inode(struct super_block *sb) {  
  2.     struct inode * inode;  
  3.     inode = alloc_inode(sb);  
  4.     …...  
  5.     return inode;  
  6. }  
这里调用了alloc_inode函数分配inode结构(fs/Inode.c):

  1. static struct inode *alloc_inode(struct super_block *sb) {  
  2.     struct inode *inode;  
  3.   
  4.     if (sb->s_op->alloc_inode)  
  5.         inode = sb->s_op->alloc_inode(sb);  
  6.     else  
  7.         inode = (struct inode *) kmem_cache_alloc(inode_cachep, GFP_KERNEL);  
  8.     …...  
  9. }  
上面有个条件判断:if (sb->s_op->alloc_inode),意思是说如果当前文件系统的超级块有自己分配inode的操作函数,则调用它自己的函数分配inode,否则从公用的高速缓存区中分配一块inode;

3) 创建socket专用inode:
socket文件系统注册一文中后面提到,在安装socket文件系统时,会初始化该文件系统的超级块,此时会初始化超级块的操作指针s_op为sockfs_ops结构;因此此时分配inode会调用sock_alloc_inode函数来完成:

  1. static struct inode *sock_alloc_inode(struct super_block *sb) {  
  2.     struct socket_alloc *ei;  
  3.     ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);  
  4.     …...  
  5.     return &ei->vfs_inode;  
  6. }  
从这里可以看到,实际上分配了一个socket_alloc结构体,该结构体包含socket和inode:

  1. struct socket_alloc {  
  2.     struct socket socket;  
  3.     struct inode vfs_inode;  
  4. };  
但最终返回的是该结构体中的inode成员;至此,socket结构和inode结构均分配完毕;分配inode后,应用程序便可以通过文件描述符对socket进行read()/write()之类的操作,这个是由虚拟文件系统(VFS)来完成的。

3、根据inode取得socket对象:
由于创建inode是文件系统的通用逻辑,因此其返回值是inode对象的指针;但这里在创建socket的inode后,需要根据inode得到socket对象;内联函数SOCKET_I由此而来:

  1. static inline struct socket *SOCKET_I(struct inode *inode)  
  2. {  
  3.     return &container_of(inode, struct socket_alloc, vfs_inode)->socket;  
  4. }  
再看看container_of宏(include/linux/Kernel.h):

  1. #define container_of(ptr, type, member) ({          \  
  2.     const typeof( ((type *)0)->member ) *__mptr = (ptr); \  
  3.     (type *)( (char *)__mptr - offsetof(type,member) );})  
和offsetof宏(include/linux/Stddef.h):

  1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  

1) offerset(TYPE, MEMBER)宏的作用:返回MEMBER成员在结构体TYPE中的偏移量;
先看一下例子,假设有个结构体A如下:

  1. struct struct_A {  
  2.     char a;  
  3.     int b;  
  4. }  
其中,成员a相对于结构的偏移量为0,成员b相对于结构体的偏移量为1;结构体struct_A的变量m在内存中地址结构如下:

我们再来看offset宏:

  1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  
可以这样来理解,把0地址强制转化为TYPE结构的指针,然后再拿到MEMBER成员的地址,该地址正好等于MEMBER成员在结构体TYPE中的偏移量;
还是拿上面的例子来说吧,如下图,offset(struct_A, b)的值为1,正好等于其偏移量;
如下图所示:

2) container_of(ptr, type, member)宏的作用:返回ptr指针所在的结构体;其中ptr为结体体type的变量中member成员的指针;
再来看看它的实现:

  1. #define container_of(ptr, type, member) ({          \  
  2.     const typeof( ((type *)0)->member ) *__mptr = (ptr); \  
  3.     (type *)( (char *)__mptr - offsetof(type,member) );})  
将ptr指针转化为char *,然后减去其在结构体中的偏移量,得到的是ptr所在的结构体的地址,最后强制转换成type *;

回到sock_alloc函数,SOCKET_I根据inode取得socket变量后,记录当前进程的一些信息,如fsuid, fsgid,并增加sockets_in_use的值(该变量表示创建socket的个数);创建后socket变量后,在__sock_create()函数中设置其type为应用程序传递下来的type,上面的例子中即为SOCK_STREAM;

4、使用协议族来初始化socket:
1) 协议族的概念:
协议族是由多个协议组成的一个通信协议栈, 如我们最熟悉的TCP/IP(AF_INET因特网协议族)包括TCP,IP,ICMP,ARP等协议;

2) Linux支持的协议族:

Linux2.6.26中支持33个协议域,在net/Socket.c中定义全局变量:


  1. static const struct net_proto_family *net_families[NPROTO] __read_mostly;  
在/include/linux/socket.h中定义了每个协议域的宏,每个协议域占用该数组的一项,如AF_INET占用net_families[2],如果net_families[2]=null,则说明当前内核没有注册AF_INET模块;

3) 注册AF_INET协议域:

 

socket文件系统注册中提到系统初始化的工作,AF_INET的注册也正是通过这个来完成的;

初始化入口net/ipv4/Af_inet.c:


  1. fs_initcall(inet_init);  
  2. static int __init inet_init(void) {  
  3.     …...  
  4.     // 为不同的套接字分配高速缓冲区  
  5.     rc = proto_register(&tcp_prot, 1);  
  6.     rc = proto_register(&udp_prot, 1);  
  7.     rc = proto_register(&raw_prot, 1);  
  8.     …...  
  9.     (void)sock_register(&inet_family_ops);  
  10.     …...  
  11.     /* 将所有的socket类型按type通过inetsw管理起来 */  
  12.     for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)  
  13.         INIT_LIST_HEAD(r);  
  14.   
  15.     for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)  
  16.         inet_register_protosw(q);  
  17.     …...  
  18. }  

 

这里调用sock_register函数来完成注册:

 

  1. int sock_register(const struct net_proto_family *ops) {  
  2.     int err;  
  3.     …...  
  4.     if (net_families[ops->family])  
  5.         err = -EEXIST;  
  6.     else {  
  7.         net_families[ops->family] = ops;  
  8.         err = 0;  
  9.     }  
  10.     …...  
  11. }  
根据family将AF_INET协议域inet_family_ops注册到内核中的net_families数组中;下面是其定义:

  1. static struct net_proto_family inet_family_ops = {  
  2.     .family = PF_INET,   
  3.     .create = inet_create,  
  4.     .owner  = THIS_MODULE,  
  5. };  
其中,family指定协议域的类型,create指向相应协议域的socket的创建函数;

4) 套接字类型

在相同的协议域下,可能会存在多个套接字类型;如AF_INET域下存在流套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),在这三种类型的套接字上建立的协议分别是TCP, UDP,ICMP/IGMP等。

在Linux内核中,结构体struct proto表示域中的一个套接字类型,它提供该类型套接字上的所有操作及相关数据(在内核初始化时会分配相应的高速缓冲区,见上面提到的inet_init函数)。

AF_IENT域的这三种套接字类型定义用结构体inet_protosw(net/ipv4/Af_inet.c)来表示,如下:


  1. static struct inet_protosw inetsw_array[] =  
  2. {  
  3.     {  
  4.         .type =       SOCK_STREAM,  
  5.         .protocol =   IPPROTO_TCP,  
  6.         .prot =       &tcp_prot,  
  7.         .ops =        &inet_stream_ops,  
  8.         .capability = -1,  
  9.         .no_check =   0,  
  10.         .flags =      INET_PROTOSW_PERMANENT |  
  11.                   INET_PROTOSW_ICSK,  
  12.     },  
  13.   
  14.     {  
  15.         .type =       SOCK_DGRAM,  
  16.         .protocol =   IPPROTO_UDP,  
  17.         .prot =       &udp_prot,  
  18.         .ops =        &inet_dgram_ops,  
  19.         .capability = -1,  
  20.         .no_check =   UDP_CSUM_DEFAULT,  
  21.         .flags =      INET_PROTOSW_PERMANENT,  
  22.        },  
  23.   
  24.   
  25.        {  
  26.            .type =       SOCK_RAW,  
  27.            .protocol =   IPPROTO_IP,    /* wild card */  
  28.            .prot =       &raw_prot,  
  29.            .ops =        &inet_sockraw_ops,  
  30.            .capability = CAP_NET_RAW,  
  31.            .no_check =   UDP_CSUM_DEFAULT,  
  32.            .flags =      INET_PROTOSW_REUSE,  
  33.        }  
  34. };  
其中,tcp_prot(net/ipv4/Tcp_ipv4.c)、udp_prot(net/ipv4/Udp.c)、raw_prot(net/ipv4/Raw.c)分别表示三种类型的套接字,分别表示相应套接字的操作和相关数据;ops成员提供该协议域的全部操作集合,针对三种不同的套接字类型,有三种不同的域操作inet_stream_ops、inet_dgram_ops、inet_sockraw_ops,其定义均位于net/ipv4/Af_inet.c下;

内核初始化时,在inet_init中,会将不同的套接字存放到全局变量inetsw中统一管理;inetsw是一个链表数组,每一项都是一个struct inet_protosw结构体的链表,总共有SOCK_MAX项,在inet_init函数对AF_INET域进行初始化的时候,调用函数inet_register_protosw把数组inetsw_array中定义的套接字类型全部注册到inetsw数组中;其中相同套接字类型,不同协议类型的套接字通过链表存放在到inetsw数组中,以套接字类型为索引,在系统实际使用的时候,只使用inetsw,而不使用inetsw_array;


5) 使用协议域来初始化socket

 

了解了上面的知识后,我们再回到net/Socket.c:sys_socket()->sock_create()->__sock_create()中:


  1. const struct net_proto_family *pf;  
  2. …...  
  3. pf = rcu_dereference(net_families[family]);  
  4. err = pf->create(net, sock, protocol);  
上面的代码中,找到内核初始化时注册的协议域,然后调用其create方法;

5、分配sock结构:

本文中的例子会调用inet_family_ops.create方法即inet_create方法完成socket的创建工作;其调用链如下:

net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create();

inet_create()主要完成以下几个工作:

1) 设置socket的状态为SS_UNCONNECTED;


  1. sock->state = SS_UNCONNECTED;  

 


2) 根据socket的type找到对应的套接字类型:


  1. list_for_each_rcu(p, &inetsw[sock->type]) {  
  2.     answer = list_entry(p, struct inet_protosw, list);  
  3.   
  4.     /* Check the non-wild match. */  
  5.     if (protocol == answer->protocol) {  
  6.         if (protocol != IPPROTO_IP)  
  7.             break;  
  8.     } else {  
  9.         /* Check for the two wild cases. */  
  10.         if (IPPROTO_IP == protocol) {  
  11.             protocol = answer->protocol;  
  12.             break;  
  13.         }  
  14.         if (IPPROTO_IP == answer->protocol)  
  15.             break;  
  16.     }  
  17.     err = -EPROTONOSUPPORT;  
  18.     answer = NULL;  
  19. }  
由于同一type不同protocol的套接字保存在inetsw中的同一链表中,因此需要遍历链表来查找;在上面的例子中,会将protocol重新赋值为answer->protocol,即IPPROTO_TCP,其值为6;

3) 使用匹配的协议族操作集初始化socket;

  1. sock->ops = answer->ops;  
  2. answer_prot = answer->prot;// 供后面使用  
结合例子,sock变量的ops指向inet_stream_ops结构体变量;

4) 分配sock结构体变量net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sk_alloc():

  1. sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);  
其中,answer_prot指向tcp_prot结构体变量;

  1. struct sock *sk_alloc(struct net *net, int family, gfp_t priority, struct proto *prot) {  
  2.     struct sock *sk;  
  3.   
  4.     sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);  
  5.     if (sk) {  
  6.         sk->sk_family = family;  
  7.   
  8.         sk->sk_prot = sk->sk_prot_creator = prot;  
  9.         sock_lock_init(sk);  
  10.         sock_net_set(sk, get_net(net));  
  11.     }  
  12.   
  13.     return sk;  
  14. }  
其中,sk_prot_alloc分配sock结构体变量;由于在inet_init中为不同的套接字分配了高速缓冲区,因此该sock结构体变量会在该缓冲区中分配空间;分配完成后,对其做一些初始化工作:
i) 初始化sk变量的sk_prot和sk_prot_creator;
ii) 初始化sk变量的等待队列;
iii) 设置net空间结构,并增加引用计数;

6、建立socket结构与sock结构的关系:
1) socket, sock, inet_sock, tcp_sock的关系
创建完sk变量后,回到inet_create函数中:

  1. inet = inet_sk(sk);  
  2. static inline struct inet_sock *inet_sk(const struct sock *sk)  
  3. {  
  4.     return (struct inet_sock *)sk;  
  5. }  
这里是根据sk变量得到inet_sock变量的地址;细心的同学可能会问到:inet_sock是什么?之前分配的是sock变量,与inet_sock有什么关系啊?
a. struct socket:这个是基本的BSD socket,应用程序通过系统调用开始创建的socket都是该结构体,它是基于虚拟文件系统创建出来的;
类型主要有三种,即流式、数据报、原始套接字协议;
其状态比较粗粒度,如下:

  1. typedef enum {  
  2.     SS_FREE = 0,            /* not allocated        */  
  3.     SS_UNCONNECTED,         /* unconnected to any socket    */  
  4.     SS_CONNECTING,          /* in process of connecting */  
  5.     SS_CONNECTED,           /* connected to socket      */  
  6.     SS_DISCONNECTING        /* in process of disconnecting  */  
  7. } socket_state;  
b. struct sock:它是网络层的socket;对应有TCP、UDP、RAW三种;
其状态相比socket结构更精细:

  1. enum {  
  2.     TCP_ESTABLISHED = 1,  
  3.     TCP_SYN_SENT,  
  4.     TCP_SYN_RECV,  
  5.     TCP_FIN_WAIT1,  
  6.     TCP_FIN_WAIT2,  
  7.     TCP_TIME_WAIT,  
  8.     TCP_CLOSE,  
  9.     TCP_CLOSE_WAIT,  
  10.     TCP_LAST_ACK,  
  11.     TCP_LISTEN,  
  12.     TCP_CLOSING,    /* Now a valid state */  
  13.   
  14.     TCP_MAX_STATES  /* Leave at the end! */  
  15. };  
c. struct inet_sock:它是INET域的socket表示,是对struct sock的一个扩展,提供INET域的一些属性,如TTL,组播列表,IP地址,端口等;
d. struct raw_socket:它是RAW协议的一个socket表示,是对struct inet_sock的扩展,它要处理与ICMP相关的内容;
e. sturct udp_sock:它是UDP协议的socket表示,是对struct inet_sock的扩展;
f. struct inet_connection_sock:它是所有面向连接的socket表示,是对struct inet_sock的扩展;
g. struct tcp_sock:它是TCP协议的socket表示,是对struct inet_connection_sock的扩展,主要增加滑动窗口,拥塞控制一些TCP专用属性;
h. struct inet_timewait_sock:它是网络层用于超时控制的socket表示;
i. struct tcp_timewait_sock:它是TCP协议用于超时控制的socket表示;

上面简单介绍了一下内核中不同的socket相关的结构体的作用;回到inet_create函数中:

  1. inet = inet_sk(sk);  
这里为什么能直接将sock结构体变量强制转化为inet_sock结构体变量呢?只有一种可能,那就是在分配sock结构体变量时,真正分配的是inet_sock或是其他结构体;

我们回到分配sock结构体的那块代码(参考前面的5.4小节:net/core/Sock.c):

  1. static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority, int family) {  
  2.     struct sock *sk;  
  3.     struct kmem_cache *slab;  
  4.   
  5.     slab = prot->slab;  
  6.     if (slab != NULL)  
  7.         sk = kmem_cache_alloc(slab, priority);  
  8.     else  
  9.         sk = kmalloc(prot->obj_size, priority);  
  10.   
  11.     return sk;  
  12. }  
上面的代码在分配sock结构体时,有两种途径,一是从tcp专用高速缓存中分配;二是从内存直接分配;前者在初始化高速缓存时,指定了结构体大小为prot->obj_size;后者也有指定大小为prot->obj_size,
根据这点,我们看下tcp_prot变量中的obj_size(net/ipv4/Tcp_ipv4.c):

  1. .obj_size       = sizeof(struct tcp_sock),  
也就是说,分配的真实结构体是tcp_sock;由于tcp_sock、inet_connection_sock、inet_sock、sock之间均为0处偏移量,因此可以直接将tcp_sock直接强制转化为inet_sock;这几个结构体间的关系如下:

2) 建立socket, sock的关系
创建完sock变量之后,便是初始化sock结构体,并建立sock与socket之间的引用关系;调用链如下:
net/Socket.c:sys_socket()->sock_create()->__sock_create()->net/ipv4/Af_inet.c:inet_create()->net/core/Sock.c:sock_init_data():
该函数主要工作是:
a. 初始化sock结构的缓冲区、队列等;
b. 初始化sock结构的状态为TCP_CLOSE;
c. 建立socket与sock结构的相互引用关系;

7、使用tcp协议初始化sock:
inet_create()函数最后,通过相应的协议来初始化sock结构:

  1. if (sk->sk_prot->init) {  
  2.     err = sk->sk_prot->init(sk);  
  3.     if (err)  
  4.         sk_common_release(sk);  
  5. }
例子中,这里调用的是tcp_prot的init钩子函数net/ipv4/Tcp_ipv4.c:tcp_v4_init_sock(),它主要是对tcp_sock和inet_connection_sock进行一些初始化;

8、socket与文件系统关联:
回到net/Socket.c:sys_socket()函数:

  1. asmlinkage long sys_socket(int family, int type, int protocol)  
  2. {  
  3.     int retval;  
  4.     struct socket *sock;  
  5.   
  6.     retval = sock_create(family, type, protocol, &sock);  
  7.     if (retval < 0)  
  8.         goto out;  
  9.   
  10.     retval = sock_map_fd(sock);  
  11.     if (retval < 0)  
  12.         goto out_release;  
  13.   
  14. out:  
  15.     /* It may be already another descriptor 8) Not kernel problem. */  
  16.     return retval;  
  17.   
  18. out_release:  
  19.     sock_release(sock);  
  20.     return retval;  
  21. }  
创建好与socket相关的结构后,需要与文件系统关联,详见sock_map_fd()函数:
1) 申请文件描述符,并分配file结构和目录项结构;
2) 关联socket相关的文件操作函数表和目录项操作函数表;
3) 将file->private_date指向socket;

socket与文件系统关联后,以后便可以通过文件系统read/write对socket进行操作了;

小结:
1、socket库函数通过内核创建socket,并初始化其状态为TCP_CLOSE;
2、创建完后,与文件系统关联,其文件一般位于/proc/$pid/fd/目录下;
3、应用程序可以通过文件对socket进行操作;
阅读(3987) | 评论(4) | 转发(3) |
给主人留下些什么吧!~~

zhuyoong2011-08-17 16:44:58

challengezcy: 谢谢分享.....
大家共同学习。

challengezcy2011-08-16 16:09:50

谢谢分享

zhuyoong2011-06-30 11:35:44

crazyhadoop: 这写一次要花费不少时间吧?.....
一边学习,一边总结,所以花在写的上面的时间并不多。

如果是学完后,再回头来写总结,就稍微多一些了。

crazyhadoop2011-06-30 10:45:34

这写一次要花费不少时间吧?