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节点:
- inode = new_inode_pseudo(sock_mnt->mnt_sb);
这里,new_inode函数是文件系统的通用函数,其作用是在相应的文件系统中创建一个inode;其主要代码如下(fs/Inode.c):
- struct inode *new_inode_pseudo(struct super_block *sb) {
- struct inode * inode;
- inode = alloc_inode(sb);
- …...
- return inode;
- }
- static struct inode *alloc_inode(struct super_block *sb) {
- struct inode *inode;
- if (sb->s_op->alloc_inode)
- inode = sb->s_op->alloc_inode(sb);//sock_alloc_inode();
- else
- inode = (struct inode *) kmem_cache_alloc(inode_cachep, GFP_KERNEL);
- …...
- }
3) 创建socket专用inode:
在 “socket文件系统注册” 一文中后面提到,在安装socket文件系统时,会初始化该文件系统的超级块,此时会初始化超级块的操作指针s_op为sockfs_ops结构;因此此时分配inode会调用sock_alloc_inode函数来完成:
- static struct inode *sock_alloc_inode(struct super_block *sb) {
- struct socket_alloc *ei;
- ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);
- …...
- return &ei->vfs_inode;
- }
- struct socket_alloc {
- struct socket socket;
- struct inode vfs_inode;
- };
3、根据inode取得socket对象:
由于创建inode是文件系统的通用逻辑,因此其返回值是inode对象的指针;但这里在创建socket的inode后,需要根据inode得到socket对象;内联函数SOCKET_I由此而来:
- static inline struct socket *SOCKET_I(struct inode *inode)
- {
- return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
- }
- #define container_of(ptr, type, member) ({ \
- const typeof( ((type *)0)->member ) *__mptr = (ptr); \
- (type *)( (char *)__mptr - offsetof(type,member) );})
- #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
1) offerset(TYPE, MEMBER)宏的作用:返回MEMBER成员在结构体TYPE中的偏移量;
先看一下例子,假设有个结构体A如下:
- struct struct_A {
- char a;
- int b;
- }
我们再来看offset宏:
- #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
还是拿上面的例子来说吧,如下图,offset(struct_A, b)的值为1,正好等于其偏移量;
如下图所示:
2) container_of(ptr, type, member)宏的作用:返回ptr指针所在的结构体;其中ptr为结体体type的变量中member成员的指针;
再来看看它的实现:
- #define container_of(ptr, type, member) ({ \
- const typeof( ((type *)0)->member ) *__mptr = (ptr); \
- (type *)( (char *)__mptr - offsetof(type,member) );})
回到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中定义全局变量:
- static const struct net_proto_family *net_families[NPROTO] __read_mostly;
3) 注册AF_INET协议域:
在“socket文件系统注册”中提到系统初始化的工作,AF_INET的注册也正是通过这个来完成的;
初始化入口net/ipv4/Af_inet.c:
- fs_initcall(inet_init);
- static int __init inet_init(void) {
- …...
- // 为不同的套接字分配高速缓冲区
- rc = proto_register(&tcp_prot, 1);
- rc = proto_register(&udp_prot, 1);
- rc = proto_register(&raw_prot, 1);
- …...
- (void)sock_register(&inet_family_ops);
- …...
- /* 将所有的socket类型按type通过inetsw管理起来 */
- for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
- INIT_LIST_HEAD(r);
- for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
- inet_register_protosw(q);
- …...
- }
这里调用sock_register函数来完成注册:
- int sock_register(const struct net_proto_family *ops) {
- int err;
- …...
- if (net_families[ops->family])
- err = -EEXIST;
- else {
- net_families[ops->family] = ops;
- err = 0;
- }
- …...
- }
- static struct net_proto_family inet_family_ops = {
- .family = PF_INET,
- .create = inet_create,
- .owner = THIS_MODULE,
- };
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)来表示,如下:
- 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,
- }
- };
内核初始化时,在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()中:
- const struct net_proto_family *pf;
- …...
- pf = rcu_dereference(net_families[family]);
- err = pf->create(net, sock, protocol);