Socket并不是TCP/IP协议的一部份,从广义上来讲,socket是Unix/Linux抽像的进程间通讯的一种方法
网络socket通讯仅仅是其若干协议中的一类,而tcp/ip又是网络协议各类中的一种
从tcp/ip的角度看socket,它更多地体现了用户API与协议栈的一个中间层接口层
用户通过调用socket API将报文递交给协议栈,或者从协议栈中接收报文
系统总入口Linux内核为所有的与socket有关操作的API,提供了一个统一的系统调用入口,其代码在net/socket.c中
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
...
/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
a0=a[0];
a1=a[1];
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;
}
首先调用 copy_from_user将用户态参数拷贝至数组a,但是问题在于每个被调用的 API 的参数不尽相同,
那么每次拷贝的字节在小如何判断.来看其第三个参数 nargs[call],其中 call 是操作码,后面有个大大的
switch...case 就是判断它。对应的操作码定义在 include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define
SYS_BIND 2 /*
sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
而数组nargs则根据操作码的不同,计算对应的参数的空间大小:
/* Argument list sizes for sys_socketcall */
#define AL(x) ((x) * sizeof(unsigned long))
static unsigned char nargs[18]={AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)};
#undef AL
当拷贝完成参数后,就进入一个switch...case...判断操作码,跳转至对应的系统接口.
sys_socket函数 操作码 SYS_SOCKET 是由 sys_socket()实现的:
1239 asmlinkage long sys_socket(int family, int type, int protocol)
1240 {
1241 int retval;
1242 struct socket *sock;
1243
1244 retval = sock_create(family, type, protocol, &sock);
1245 if (retval < 0)
1246 goto out;
1247
1248 retval = sock_map_fd(sock);
1249 if (retval < 0)
1250 goto out_release;
1251
1252 out:
1253 /* It may be already another descriptor 8) Not kernel problem. */
1254 return retval;
1255
1256 out_release:
1257 sock_release(sock);
1258 return retval;
1259 }
在分析这段代码之前, 首先来看创建一个Socket, 对内核而言,究竟意味着什么?究竟需要内核干什么事?
当用户空间要创建一个 socke 接口时,会调用 API 函数
int socket(int domain, int type, int protocol)
其三个参数分别表示协议族,协议类型(面向连接或无连接)以及协议
对于用户态而言, 一个Socket, 就是一个特殊的已经打开的文件,为了对socket抽像出文件的概念,
内核中为socket定义了一个专门的文件系统类型sockfs.
344 static struct vfsmount *sock_mnt __read_mostly;
345
346 static struct file_system_type sock_fs_type = {
347 .name = "sockfs",
348 .get_sb = sockfs_get_sb,
349 .kill_sb = kill_anon_super,
350 };
在模块初始化的时候,安装该文件系统:
void __init sock_init(void)
{
……
register_filesystem(&sock_fs_type);
sock_mnt = kern_mount(&sock_fs_type);
}
稍后还要回来继续分析安装中的一点细节
有了文件系统后,对内核而言,创建一个socket,就是在sockfs文件系统中创建一个文件节点(inode),并建立起为了实现
socket功能所需的一整套数据结构,包括struct inode和struct socket结构.
struct socket结构在内核中,就代表了一个"Socket",当一个struct socket数据结构被分配空间后,再将其与一个已打开
的文件“建立映射关系”.这样,用户态就可以用抽像的文件的概念来操作socket了
——当然由于网络的特殊性,至少就目前而言,这种抽像,并不如其它模块的抽像那么完美.
文件系统struct vfsmount中有一个成员指针mnt_sb指向该文件系统的超级块,而超级块结构struct super_lock
有一个重要的成员s_op指向了超级块的操作函数表,其中有函数指针alloc_inode()即为在给定的超级块下创建并初始化
一个新的索引节点对像. 也就是调用:
sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);
当然,连同相关的处理细节一起,这一操作被层层封装至一个上层函数new_inode()
那如何分配一个struct socket结构呢?
如前所述,一个socket总是与一个inode 密切相关的.当然,在 inode 中设置一个socket成员是完全可行的,
但是这貌似浪费了空间——毕竟更多的文件系统没有socket这个东东.
所以,内核引入了另一个socket_alloc结构
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
显而易见,该结构实现了inode和socket的封装.已知一个inode可以通过宏SOCKET_I来获取
与之对应的 socket:
sock = SOCKET_I(inode);
static inline struct socket *SOCKET_I(struct inode *inode)
{
return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}
但是这样做也同时意味着在分配一个inode后,必须再分配一个socket_alloc结构,并实现对应的封装.
否则container_of又能到哪儿去找到socket呢?
现在来简要地看一个这个流程——这是文件系统安装中的一个重要步骤
881 struct vfsmount *kern_mount(struct file_system_type *type)
882 {
883 return vfs_kern_mount(type, 0, type->name, NULL);
884 }
817 struct vfsmount *
818 vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
819 {
820 struct vfsmount *mnt;
821 char *secdata = NULL;
822 int error;
....
828 mnt = alloc_vfsmnt(name);
829 if (!mnt)
830 goto out;
841 ....
842 error = type->get_sb(type, flags, name, data, mnt);
843 if (error < 0)
844 goto out_free_secdata;
849
850 mnt->mnt_mountpoint = mnt->mnt_root;
851 mnt->mnt_parent = mnt;
852 up_write(&mnt->mnt_sb->s_umount);
853 free_secdata(secdata);
854 return mnt;
855 .....
865 }
申请文件系统mnt结构, 调用之前注册的sock_fs_type的get_sb成员函数指针, 获取相应的超级块sb.
并将mnt->mnt_sb指向sock_fs_type中的超级块
337 static int sockfs_get_sb(struct file_system_type *fs_type,
338 int flags, const char *dev_name, void *data, struct vfsmount *mnt)
339 {
340 return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC,
341 mnt);
342 }
注意其第三个参数 sockfs_ops,它封装了 sockfs 的功能函数表
331 static struct super_operations sockfs_ops = {
332 .alloc_inode = sock_alloc_inode,
333 .destroy_inode =sock_destroy_inode,
334 .statfs = simple_statfs,
335 };
struct super_block *
get_sb_pseudo(struct file_system_type *fs_type, char *name,
struct super_operations *ops, unsigned long magic)
{
struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
……
s->s_op = ops ? ops : &default_ops;
}
这里就是先获取/分配一个超级块,然后初始化超级块的各成员,包括s_op,它封装了对应的功能函数表.
s_op自然就指向了sockfs_ops,那前面提到的new_inode()函数分配inode时调用的
sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb);
这个alloc_inode函数指针也就是sockfs_ops的sock_alloc_inode()函数——转了一大圈,终于指到它了.
来看看sock_alloc_inode是如何分配一个inode节点的
283 static kmem_cache_t * sock_inode_cachep __read_mostly;
284
285 static struct inode *sock_alloc_inode(struct super_block *sb)
286 {
287 struct socket_alloc *ei;
288 ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL);
289 if (!ei)
290 return NULL;
291 init_waitqueue_head(&ei->socket.wait);
292
293 ei->socket.fasync_list = NULL;
294 ei->socket.state = SS_UNCONNECTED;
295 ei->socket.flags = 0;
296 ei->socket.ops = NULL;
297 ei->socket.sk = NULL;
298 ei->socket.file = NULL;
299 ei->socket.flags = 0;
300
301 return &ei->vfs_inode;
302 }
函数先分配了一个用于封装socket和inode的 ei,然后在高速缓存中为之申请了一块空间.
这样inode和socket就同时都被分配了,接下来初始化socket的各个成员,这些成员在后面都会一一提到
96 /**
97 * struct socket - general BSD socket
98 * @state: socket state (%SS_CONNECTED, etc)
99 * @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
100 * @ops: protocol specific socket operations
101 * @fasync_list: Asynchronous wake up list
102 * @file: File back pointer for gc
103 * @sk: internal networking protocol agnostic socket representation
104 * @wait: wait queue for several uses
105 * @type: socket type (%SOCK_STREAM, etc)
106 */
107 struct socket {
108 socket_state state;
109 unsigned long flags;
110 const struct proto_ops *ops;
111 struct fasync_struct *fasync_list;
112 struct file *file;
113 struct sock *sk;
114 wait_queue_head_t wait;
115 short type;
116 };
至目前为止,分配inode,socket以及两者如何关联,都已一一分析了.
最后一个关键问题,就是如何把socket与一个已打开的文件,建立映射关系.
在内核中,用struct file结构描述一个已经打开的文件,指向该结构的指针内核中通常用file或filp来描述.
我们知道,内核中可以通过全局项current来获得当前进程,它是一个struct task_struct类型的指针.
tastk_struct有一个成员:
struct files_struct *files;指向一个已打开的文件.
当然,由于一个进程可能打开多个文件,所以,struct files_struct 结构有
struct file *fd_array[NR_OPEN_DEFAULT]成员,
这是个数组,以文件描述符为下标,即current->files->fd[fd],可以找到与当前进程指定文件描述符的文件
有了这些基础,如果要把一个socket与一个已打开的文件建立映射,首先要做的就是为socket分配一个struct file,
并申请分配一个相应的文件描述符fd. 因为socket并不支持open方法,所以不能期望用户界面通过调用open() API
来分配一个struct file,而是通过调用get_empty_filp来获取
struct file *file = get_empty_filp()
fd = get_unused_fd();获取一个空间的文件描述符
然后让current的files指针的fd数组的fd索引项指向该file
void fastcall fd_install(unsigned int fd, struct file * file)
{
struct files_struct *files = current->files;
spin_lock(&files->file_lock);
if (unlikely(files->fd[fd] != NULL))
BUG();
files->fd[fd] = file;
spin_unlock(&files->file_lock);
}
做到这一步,有了一个文件描述符fd和一个打开的文件file,它们与当前进程相连,但是好像与创建的socket并无任何瓜葛.
要做的映射还是没有进展,struct file或者文件描述述fd或current都没有任何能够与 inode或者是socket相关的东东
这需要一个中间的桥梁,目录项:struct dentry结构
因为一个文件都有与其对应的目录项:
struct file {
struct list_head f_list;
struct dentry *f_dentry;
……
而一个目录项:
struct dentry {
……
struct inode *d_inode; /* Where the name belongs to - NULL is negative */
d_inode 成员指向了与之对应的 inode节点
之前已经创建了一个inode节点和与之对应的 socket. 所以现在要做的就是:
“先为当前文件分配一个对应的目录项,再将已创建的 inode节点安装至该目录项”
这样一个完成的映射关系:
进程,文件描述符,打开文件,目录项,inode节点,socket就完整地串起来了
基本要分析的一些前导的东东都一一罗列了,虽然已尽量避免陷入文件系统的细节分析,但是还是不可避免地进入其中,
因为它们关系实现太紧密了,现在可以来看套接字的创建过程了
1239 asmlinkage long sys_socket(int family, int type, int protocol)
1240 {
1241 int retval;
1242 struct socket *sock;
1243
1244 retval = sock_create(family, type, protocol, &sock);
1245 if (retval < 0)
1246 goto out;
1247
1248 retval = sock_map_fd(sock);
1249 if (retval < 0)
1250 goto out_release;
1251
1252 out:
1253 /* It may be already another descriptor 8) Not kernel problem. */
1254 return retval;
1255
1256 out_release:
1257 sock_release(sock);
1258 return retval;
1259 }
1229 int sock_create(int family, int type, int protocol, struct socket **res)
1230 {
1231 return __sock_create(family, type, protocol, res, 0);
1232 }
AF_INET协议簇的协议封装 接下来,函数调用之前已经注册的inet_family_ops的函数指针create,也就是inet_create()函数.
前面可以说一个通用的socket已经创建好了,这里要完成与协议本身相关的一些创建socket的工作.
这一部份的工作比较复杂,还是先来看看af_inet.c中的模块初始化时候,做了哪些与此相关的工作.
要引入的第一个数据结构是struct inet_protosw,它封装了一个协议类型(如 SOCK_STREAM,SOCK_DGRAM等)
与IP协议中对应的传输层协议.
68 /* This is used to register socket interfaces for IP protocols. */
69 struct inet_protosw {
70 struct list_head list;
71
72 /* These two fields form the lookup key. */
73 unsigned short type; /* This is the 2nd argument to socket(2). */
74 int protocol; /* This is the L4 protocol number. */
75
76 struct proto *prot;
77 const struct proto_ops *ops;
78
79 int capability; /* Which (if any) capability do we need to use this socket interface*/
83 char no_check; /* checksum on rcv/xmit/none? */
84 unsigned char flags; /* See INET_PROTOSW_* below. */
85 };
type是协议类型,对于 ipv4 而言就是SOCK_STREAM,SOCK_DGRAM或者是SOCK_RAW之一.
protocol是传输层的协议号,prot用于描述一个具体的传输层协议,而ops指向对应的当前协议类型的操作函数集
针对不同的协议类型,定义了不同的 ops:
791 const struct proto_ops inet_stream_ops = {
792 .family = PF_INET,
793 .owner = THIS_MODULE,
794 .release = inet_release,
795 .bind = inet_bind,
796 .connect = inet_stream_connect,
797 .socketpair = sock_no_socketpair,
798 .accept = inet_accept,
799 .getname = inet_getname,
800 .poll = tcp_poll,
801 .ioctl = inet_ioctl,
802 .listen = inet_listen,
803 .shutdown = inet_shutdown,
804 .setsockopt = sock_common_setsockopt,
805 .getsockopt = sock_common_getsockopt,
806 .sendmsg = inet_sendmsg,
807 .recvmsg = sock_common_recvmsg,
808 .mmap = sock_no_mmap,
809 .sendpage = tcp_sendpage,
810 #ifdef CONFIG_COMPAT
811 .compat_setsockopt = compat_sock_common_setsockopt,
812 .compat_getsockopt = compat_sock_common_getsockopt,
813 #endif
814 };
815
816 const struct proto_ops inet_dgram_ops = {
817 .family = PF_INET,
818 .owner = THIS_MODULE,
819 .release = inet_release,
820 .bind = inet_bind,
821 .connect = inet_dgram_connect,
822 .socketpair = sock_no_socketpair,
823 .accept = sock_no_accept,
824 .getname = inet_getname,
825 .poll = udp_poll,
826 .ioctl = inet_ioctl,
827 .listen = sock_no_listen,
828 .shutdown = inet_shutdown,
829 .setsockopt = sock_common_setsockopt,
830 .getsockopt = sock_common_getsockopt,
831 .sendmsg = inet_sendmsg,
832 .recvmsg = sock_common_recvmsg,
833 .mmap = sock_no_mmap,
834 .sendpage = inet_sendpage,
835 #ifdef CONFIG_COMPAT
836 .compat_setsockopt = compat_sock_common_setsockopt,
837 .compat_getsockopt = compat_sock_common_getsockopt,
838 #endif
839 };
840
841 /*
842 * For SOCK_RAW sockets; should be the same as inet_dgram_ops but without
843 * udp_poll
844 */
845 static const struct proto_ops inet_sockraw_ops = {
846 .family = PF_INET,
847 .owner = THIS_MODULE,
848 .release = inet_release,
849 .bind = inet_bind,
850 .connect = inet_dgram_connect,
851 .socketpair = sock_no_socketpair,
852 .accept = sock_no_accept,
853 .getname = inet_getname,
854 .poll = datagram_poll,
855 .ioctl = inet_ioctl,
856 .listen = sock_no_listen,
857 .shutdown = inet_shutdown,
858 .setsockopt = sock_common_setsockopt,
859 .getsockopt = sock_common_getsockopt,
860 .sendmsg = inet_sendmsg,
861 .recvmsg = sock_common_recvmsg,
862 .mmap = sock_no_mmap,
863 .sendpage = inet_sendpage,
864 #ifdef CONFIG_COMPAT
865 .compat_setsockopt = compat_sock_common_setsockopt,
866 .compat_getsockopt = compat_sock_common_getsockopt,
867 #endif
868 };
从各个函数指针的名称,我们就可以大约知道它们是做什么事的了.进一步进以看到,
它们的函数指针指向的函数差不多都是相同的.除了一些细节上的区别,例如后面两种协议类型并不支持listen.
socket()API第二个参数是协议类型,第三个参数是该协议类型下的协议——不过对于ipv4而言,
它们都是一一对应的,但是从抽像封装的角度看,数据结构的设计本身应该满足一个协议类型下边可能存在多个不同的协议,
即一对多的情况.而一一对应,仅是它们的特例:
876 /* Upon startup we insert all the elements in inetsw_array[] into
877 * the linked list inetsw.
878 */
879 static struct inet_protosw inetsw_array[] =
880 {
881 {
882 .type = SOCK_STREAM,
883 .protocol = IPPROTO_TCP,
884 .prot = &tcp_prot,
885 .ops = &inet_stream_ops,
886 .capability = -1,
887 .no_check = 0,
888 .flags = INET_PROTOSW_PERMANENT |
889 INET_PROTOSW_ICSK,
890 },
892 {
893 .type = SOCK_DGRAM,
894 .protocol = IPPROTO_UDP,
895 .prot = &udp_prot,
896 .ops = &inet_dgram_ops,
897 .capability = -1,
898 .no_check = UDP_CSUM_DEFAULT,
899 .flags = INET_PROTOSW_PERMANENT,
900 },
903 {
904 .type = SOCK_RAW,
905 .protocol = IPPROTO_IP, /* wild card */
906 .prot = &raw_prot,
907 .ops = &inet_sockraw_ops,
908 .capability = CAP_NET_RAW,
909 .no_check = UDP_CSUM_DEFAULT,
910 .flags = INET_PROTOSW_REUSE,
911 }
912 };
数组的每一个元素,就是支持的一种协议名称,例如IPOROTO_TCP,但是由于IPV4本身协议类型跟协议是一一对应的,
所以没有更多的.type= SOCK_xxx 了.这样数组实现了对PF_INET协议族下支持的协议类型,
以及协议类型下边的协议进行了封装,虽然事实上它们是一一对应的关系,不过理论上完全可能存在一对多的可能.
数组内,封装的一个具体的协议,由 struct proto 结构来描述
以 TCP协议为例,TCP协议的 sokcet 操作函数都被封装在这里了。
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = tcp_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.sendmsg = tcp_sendmsg,
.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v4_do_rcv,
.hash = tcp_v4_hash,
.unhash = tcp_unhash,
.get_port = tcp_v4_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = &tcp_sockets_allocated,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
}
分配struct sock 看完了PF_INET的协议簇,协议类型和协议(也就是socket调用的三个参数)的封装关系,它们通过了两个数据结构
inet_protosw,struct proto来描述,被一个数组inetsw_array所封装.接下来看它的初始化工作:
static struct list_head inetsw[SOCK_MAX];
static int __init inet_init(void)
{
……
/* Register the socket-side information for inet_create. */
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);
……
}
inetsw是一个数组,其每一个元素都是一个链表首部,前面一个循环初始化之.后一个循环就值得注意了,
也就是函数
916 void inet_register_protosw(struct inet_protosw *p)
917 {
918 struct list_head *lh;
919 struct inet_protosw *answer;
920 int protocol = p->protocol;
921 struct list_head *last_perm;
922
923 spin_lock_bh(&inetsw_lock);
925 if (p->type >= SOCK_MAX)
926 goto out_illegal;
928 /* If we are trying to override a permanent protocol, bail. */
929 answer = NULL;
930 last_perm = &inetsw[p->type];
931 list_for_each(lh, &inetsw[p->type]) {
932 answer = list_entry(lh, struct inet_protosw, list);
934 /* Check only the non-wild match. */
935 if (INET_PROTOSW_PERMANENT & answer->flags) {
936 if (protocol == answer->protocol)
937 break;
938 last_perm = lh;
939 }
941 answer = NULL;
942 }
943 if (answer)
944 goto out_permanent;
943 if (answer)
944 goto out_permanent;
945
946 /* Add the new entry after the last permanent entry if any, so that
947 * the new entry does not override a permanent entry when matched with
948 * a wild-card protocol. But it is allowed to override any existing
949 * non-permanent entry. This means that when we remove this entry, the
950 * system automatically returns to the old behavior.
951 */
952 list_add_rcu(&p->list, last_perm);
953 out:
954 spin_unlock_bh(&inetsw_lock);
955
956 synchronize_net();
957
958 return;
.....................
这个函数完成的工作就是把inetsw_array数组中相同的协议类型下边的协议,
加入到inetsw对应的协议类型的链表中去,因为事实上一对一的关系,所以这个函数要简单得多
因为不存在其它成员,所以每一次list_entry都为空值,所以不存在覆盖和追加的情况,直接调用
list_add_rcu(&p->list, last_perm);
把协议类型节点(struct inet_protosw类型的数组的某个元素)添加到链表(链表首部本身是一个数组,
数组索引是协议对应的协议类型的值)的第一个成员.
OK,绕了这么大一圈子,了解了协议的封装及链表的注册. 现在回到inet_create中来
220 /*
221 * Create an inet socket.
222 */
223
224 static int inet_create(struct socket *sock, int protocol)
225 {
226 struct sock *sk;
227 struct list_head *p;
228 struct inet_protosw *answer;
229 struct inet_sock *inet;
230 struct proto *answer_prot;
231 unsigned char answer_flags;
232 char answer_no_check;
233 int try_loading_module = 0;
234 int err;
235
236 sock->state = SS_UNCONNECTED;
socket的初始状态设置为“未连接”,这意味着面向连接的协议类型,如 tcp,在使用之前必须建立连接, 修改状态位.
237
238 /* Look for the requested type/protocol pair. */
239 answer = NULL;
240 lookup_protocol:
241 err = -ESOCKTNOSUPPORT;
242 rcu_read_lock();
243 list_for_each_rcu(p, &inetsw[sock->type]) {
244 answer = list_entry(p, struct inet_protosw, list);
245
246 /* Check the non-wild match. */
247 if (protocol == answer->protocol) {
248 if (protocol != IPPROTO_IP)
249 break;
250 } else {
251 /* Check for the two wild cases. */
252 if (IPPROTO_IP == protocol) {
253 protocol = answer->protocol;
254 break;
255 }
256 if (IPPROTO_IP == answer->protocol)
257 break;
258 }
259 err = -EPROTONOSUPPORT;
260 answer = NULL;
261 }
这个循环根据socket(2)调用的protocol把之前在链表中注册的协议节点找出来.
一个问题是,因为一一对应关系的存在,用户态调用socket(2)的时候,常常第三个参数直接就置 0 了.
也就是这里protocol为 0.那内核又如何处理这一默认值呢?
也就是protocol != answer->protocol,而是被if (IPPROTO_IP == protocol) 所匹配了.
这样将protocol置为链表中第一个协议,而当循环结束时,answer自然也是指向这个链表中的第一个注册节点.
假设SOCK_STREAM下同时注册了TCP和123,那么这里默认就取TCP了.当然如果把123在inetsw_array数组中的
位置调前,那么就 默认取123了.
将创建的socket的ops函数指针集指向具体协议类型的.例如创建的是SOCK_STREAM,
那么就指向了inet_stream_ops.
289 sock->ops = answer->ops;
answer_prot指针指向当前要创建的socket的协议类型下边的协议,如上例它就是IPPROTO_TCP的tcp_prot结构
290 answer_prot = answer->prot;
291 answer_no_check = answer->no_check;
292 answer_flags = answer->flags;
293 rcu_read_unlock();
294
295 BUG_TRAP(answer_prot->slab != NULL);
接下来一个重要的工作,就是为 socket 分配一个sock,并初始化它
298 sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
299 if (sk == NULL)
300 goto out;
301
302 err = 0;
303 sk->sk_no_check = answer_no_check;
304 if (INET_PROTOSW_REUSE & answer_flags)
305 sk->sk_reuse = 1;
306
307 inet = inet_sk(sk);
308 inet->is_icsk = INET_PROTOSW_ICSK & answer_flags;
309
310 if (SOCK_RAW == sock->type) {
311 inet->num = protocol;
312 if (IPPROTO_RAW == protocol)
313 inet->hdrincl = 1;
314 }
315
316 if (ipv4_config.no_pmtu_disc)
317 inet->pmtudisc = IP_PMTUDISC_DONT;
318 else
319 inet->pmtudisc = IP_PMTUDISC_WANT;
321 inet->id = 0;
322
323 sock_init_data(sock, sk);
324
325 sk->sk_destruct = inet_sock_destruct;
326 sk->sk_family = PF_INET;
327 sk->sk_protocol = protocol;
328 sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
329
330 inet->uc_ttl = -1;
331 inet->mc_loop = 1;
332 inet->mc_ttl = 1;
333 inet->mc_index = 0;
334 inet->mc_list = NULL;
335
336 sk_refcnt_debug_inc(sk);
337
338 if (inet->num) {
339 /* It assumes that any protocol which allows
340 * the user to assign a number at socket
341 * creation time automatically
342 * shares.
343 */
344 inet->sport = htons(inet->num);
345 /* Add to protocol hash chains. */
346 sk->sk_prot->hash(sk);
347 }
348
349 if (sk->sk_prot->init) {
350 err = sk->sk_prot->init(sk);
351 if (err)
352 sk_common_release(sk);
353 }
354 out:
355 return err;
356 out_rcu_unlock:
357 rcu_read_unlock();
358 goto out;
359 }
虽然create的代码就到这儿了,不过要说清楚sk的分配,还得费上大力气.每一个 Socket 套接字,
都有一个对应的struct socket结构来描述(内核中一般使用名称为sock),但是同时又有一个
struct sock结构(内核中一般使用名称为 sk).两者之间是一一对应的关系.在后面的sock_init_data函数中可以看到
sk->sk_socket=sock;
sock->sk=sk;
这样的代码.
socket结构和sock结构实际上是同一个事物的两个方面.不妨说socket结构是面向进程和系统调用界面的侧面,
而sock结构则是面向底层驱动程序的侧面.设计者把socket套接字中与文件系统关系比较密切的那一部份放在
socket结构中而把与通信关系比较密切的那一部份,则单独成为一个数结结构,那就是sock结构.
由于这两部份逻辑上本来就是一体的,所以要通过指针互相指向对方形成一对一的关系.
再暂时回到inet_init中来,初始化工作中有如下代码:
1262 rc = proto_register(&tcp_prot, 1);
1263 if (rc)
1264 goto out;
1265
1266 rc = proto_register(&udp_prot, 1);
1267 if (rc)
1268 goto out_unregister_tcp_proto;
1269
1270 rc = proto_register(&raw_prot, 1);
1271 if (rc)
1272 goto out_unregister_udp_proto;
这里为每个protocol都调用了proto_register函数,其重要功能之一就是根据协议的obj_size成员的大小,
为协议创建高速缓存.
1701 static DEFINE_RWLOCK(proto_list_lock);
1702 static LIST_HEAD(proto_list);
1703
1704 int proto_register(struct proto *prot, int alloc_slab)
1705 {
1706 char *request_sock_slab_name = NULL;
1707 char *timewait_sock_slab_name;
1708 int rc = -ENOBUFS;
1709
1710 if (alloc_slab) {
可以看到函数最重要的功能就是根据prot的obj_size成员的大小为协议创建高速缓存
1711 prot->slab = kmem_cache_create(prot->name, prot->obj_size, 0,
1712 SLAB_HWCACHE_ALIGN, NULL, NULL);
1713
1714 if (prot->slab == NULL) {
1715 printk(KERN_CRIT "%s: Can't create sock SLAB cache!\n",
1716 prot->name);
1717 goto out;
1718 }
1719
顺便看到它的另一个重要的功能是维护一个以proto_list为首的链表
1758 write_lock(&proto_list_lock);
1759 list_add(&prot->node, &proto_list);
1760 write_unlock(&proto_list_lock);
这里要注意的是prot->obj_size的大小,它它非仅仅是一个sk的大小,以 TCP为例:
.obj_size = sizeof(struct tcp_sock)。稍后再来分析这个东东
回到inet_create()函数中来,其调用sk_alloc()分配一个sk
sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, 1);
840 struct sock *sk_alloc(int family, gfp_t priority,
841 struct proto *prot, int zero_it)
842 {
843 struct sock *sk = NULL;
844 kmem_cache_t *slab = prot->slab;
845
846 if (slab != NULL)
847 sk = kmem_cache_alloc(slab, priority);
848 else
849 sk = kmalloc(prot->obj_size, priority);
850
851 if (sk) {
852 if (zero_it) {
853 memset(sk, 0, prot->obj_size);
854 sk->sk_family = family;
855 /*
856 * See comment in struct sock definition to understand
857 * why we need sk_prot_creator -acme
858 */
859 sk->sk_prot = sk->sk_prot_creator = prot;
860 sock_lock_init(sk);
861 }
862
863 if (security_sk_alloc(sk, family, priority))
864 goto out_free;
865
866 if (!try_module_get(prot->owner))
867 goto out_free;
868 }
869 return sk;
870
871 out_free:
872 if (slab != NULL)
873 kmem_cache_free(slab, sk);
874 else
875 kfree(sk);
876 return NULL;
877 }
在之前创建的高速缓存中申请分配一个slab缓存项并清零,然后设置协议族,并把sk中的sk_prot与对应的协议关联起来
初始化sk 分配完成sk后另一个重要的功能就是初始化它,sk的成员相当复杂,其主要的初始化工作是在函数sock_init_data()
中完成的.
1477 void sock_init_data(struct socket *sock, struct sock *sk)
1478 {
1479 skb_queue_head_init(&sk->sk_receive_queue);
1480 skb_queue_head_init(&sk->sk_write_queue);
1481 skb_queue_head_init(&sk->sk_error_queue);
sock结构中有三个重要的双向队列分别是sk_receive_queue,sk_write_queue和sk_error_queue
从它们的名字就可以看出来其作用了
队列并非采用通用的list_head来维护而是使用skb_buffer队列
// 109 struct sk_buff_head {
// 110 /* These two members must be first. */
// 111 struct sk_buff *next;
// 112 struct sk_buff *prev;
// 113
// 114 __u32 qlen;
// 115 spinlock_t lock;
// 116 };
这样队列中指向的每一个skb_buffer就是一个数据包,分别是接收、发送和投递错误
剩余的就是初始化其它成员变量了,后面再来专门分析这些成员的作用
1482 #ifdef CONFIG_NET_DMA
1483 skb_queue_head_init(&sk->sk_async_wait_queue);
1484 #endif
1485
1486 sk->sk_send_head = NULL;
1487
1488 init_timer(&sk->sk_timer);
1489
1490 sk->sk_allocation = GFP_KERNEL;
1491 sk->sk_rcvbuf = sysctl_rmem_default;
1492 sk->sk_sndbuf = sysctl_wmem_default;
1493 sk->sk_state = TCP_CLOSE;
1494 sk->sk_socket = sock;
1495
1496 sock_set_flag(sk, SOCK_ZAPPED);
1497
1498 if(sock)
1499 {
1500 sk->sk_type = sock->type;
1501 sk->sk_sleep = &sock->wait;
1502 sock->sk = sk;
1503 } else
1504 sk->sk_sleep = NULL;
1505
1506 rwlock_init(&sk->sk_dst_lock);
1507 rwlock_init(&sk->sk_callback_lock);
1508 lockdep_set_class(&sk->sk_callback_lock,
1509 af_callback_keys + sk->sk_family);
1510
1511 sk->sk_state_change = sock_def_wakeup;
1512 sk->sk_data_ready = sock_def_readable;
1513 sk->sk_write_space = sock_def_write_space;
1514 sk->sk_error_report = sock_def_error_report;
1515 sk->sk_destruct = sock_def_destruct;
1516
1517 sk->sk_sndmsg_page = NULL;
1518 sk->sk_sndmsg_off = 0;
1519
1520 sk->sk_peercred.pid = 0;
1521 sk->sk_peercred.uid = -1;
1522 sk->sk_peercred.gid = -1;
1523 sk->sk_write_pending = 0;
1524 sk->sk_rcvlowat = 1;
1525 sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
1526 sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
1527
1528 sk->sk_stamp.tv_sec = -1L;
1529 sk->sk_stamp.tv_usec = -1L;
1530
1531 atomic_set(&sk->sk_refcnt, 1);
1532 }
inet_create函数中除了初始化sk成员的值还有一部份代码是初始化一个inet的东东
307 inet = inet_sk(sk);
308 inet->is_icsk = INET_PROTOSW_ICSK & answer_flags;
inet是一个struct inet_sock结构类型来看它的定义
struct inet_sock {
/* sk and pinet6 has to be the first two members of inet_sock */
struct sock sk;
……
}
我们说sock是面向用户态调用而sk是面向内核驱动调用的,那sk是如何与协议栈交互的呢?
对于每一个类型的协议,为了与 sk 联系起来都定义了一个struct XXX_sock结构XXX是协议名
struct tcp_sock {
/* inet_sock has to be the first member of tcp_sock */
struct inet_sock inet;
int tcp_header_len; /* Bytes of tcp header to send */
……
}
struct udp_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
int pending; /* Any pending frames ? */
unsigned int corkflag; /* Cork is required */
__u16 encap_type; /* Is this an Encapsulation socket? */
/*
* Following member retains the infomation to create a UDP header
* when the socket is uncorked.
*/
__u16 len; /* total length of pending frames */
};
struct raw_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
struct icmp_filter filter;
};
很明显它们的结构定义是“af_inet一般属性+自己的私有属性”, 因为它们的第一个成员总是inet
现在回头来找一下起初在af_inet.c中封装协议注册的时候size成员, 对于 tcp 而言:
.obj_size = sizeof(struct tcp_sock),
其它协议类似.
以obj_size来确定每个slab缓存项分配的大小,所以我们就可说每次申请分配的实际上是一个struct XXX_sock
结构大小的结构.因为都是定义于上层结构的第一个成员,可以使用强制类型转换来使用这块分配的内存空间.
例如inet = inet_sk(sk);
static inline struct inet_sock *inet_sk(const struct sock *sk)
{
return (struct inet_sock *)sk;
}
struct tcp_sock *tp = tcp_sk(sk);
static inline struct tcp_sock *tcp_sk(const struct sock *sk)
{
return (struct tcp_sock *)sk;
}
OK,inet_create()运行完,一个socket套接字基本上就创建完毕了,剩下的就是与文件系统挂钩,回到最初的sys_socket()
函数中来,它在调用完sock_create()后,紧接着调用sock_map_fd()函数
422 int sock_map_fd(struct socket *sock)
423 {
424 struct file *newfile;
425 int fd = sock_alloc_fd(&newfile);
426
427 if (likely(fd >= 0)) {
428 int err = sock_attach_fd(sock, newfile);
429
430 if (unlikely(err < 0)) {
431 put_filp(newfile);
432 put_unused_fd(fd);
433 return err;
434 }
435 fd_install(fd, newfile);
436 }
437 return fd;
438 }
这个函数的核心思想在一开始就已经分析过了.从进程的角度来讲一个socket套接字就是一个特殊的已打开的文件.
前面分配好一个socket 后,这里要做的就是将它与文件系统拉上亲戚关系.
首先获取一个空闲的文件描述符号和file结构,然后在文件系统中分配一个目录项(d_alloc),使其指向已经分配的inode节
点(d_add),然后把其目录项挂在sockfs文件系统的根目录之下,并且把目录项的指针d_op设置成
指向sockfs_dentry_operati,这个数据结构通过函数指针提供他与文件路径有关的操作.
static struct dentry_operations sockfs_dentry_operations = {
.d_delete = sockfs_delete_dentry,
};
最后一步就是将file结构中的f_op和sock结构中的i_fop都指向socket_file_ops,它是一个函数指针集,
指向了socket面向文件系统的用户态调用的一些接口函数.
static struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.readv = sock_readv,
.writev = sock_writev,
.sendpage = sock_sendpage
};
OK,到这里整个socket套接字的创建工作就宣告完成了
写到这里,可以为 socket 的创建下一个小结了:
- 所谓创建socket,对内核而言最重要的工作就是分配sock与sk
- sock面向上层系统调用,主要是与文件系统交互.通过进程的current指针的files,结合创建socket
时返回的文件描符述,可以找到内 核中对应的struct file,再根据file的f_dentry可以找到对应的目
录项,而目录项struct dentry中,有d_inode指针,指向与sock封装在一起的inode.sock又与
sk指针互指一一对应.在这串结构中有两个重要的函数集指针,一个是文件系统struct file中的
f_op指针,它指向了对应的用户态调用的read,write等操调用,但不支持open,
另一个是struct socket结构,即sock的ops指针,它在inet_create()中被置为
sock->ops = answer->ops指向具体协议类型的ops
例如inet_stream_ops,inet_dgram_ops或者是inet_sockraw_ops等等
它用来支持上层的socket的其它API调用
- sk面向内核协议栈,协议栈与它的接口数据结构是struct protoname_sock,该结构中包含了一般性
的inet结构和自己的私有成员,struct inet_sock的第一个成员就是一个 sk 指针,而分配的sk实
际上空间大小是struct protoname_sock,所以这三者可以通过强制类型转换来获取需要的指针
- 由于水平有限,文件系统的一些细节被我跳过了,sk 中的大多数成员变量的作用,也被我跳出过了.
呵呵,还好,终于还是把这块流程给初步分析出来了.另外当时写的时候,没有想到会写这么长,
大大超出了每贴的字限制。所以,每个小节内容跟标题可能会有点对不上号。