- 浏览: 1401308 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
首先来看整个与socket相关的操作提供了一个统一的接口sys_socketcall.
下面就是它的代码片段:
可以看到代码比较简单,就是通过传递进来的call类型,来调用相应的socket相关的函数.
这里你可能注意到了,那就是一般文件句柄相关的操作,比如write,read,aio,poll这些并没有看到(也就是file_operations).这是因为socket上面其实还有一层vfs层,内核把socket当做一个文件系统来处理,并实现了相应的vfs方法.因此下面我们先来了解下vfs.然后会描述下进程如何通过vfs存取句柄.
vfs其实就相当于对下层的文件系统和上层应用之间的粘合层,它定义了文件系统需要实现的相关的操作,然后下层的文件系统只需要实现这些方法就可以了,也就是说在内核其他部分和上层应用看来,所有的文件系统没有任何区别.
下面的这张图就是从用户空间调用write的大体流程:
vfs中有4种主要的数据结构:
1 超级块对象,代表一个已安装的文件系统.super_block
2 索引节点对象,代表一个文件.inode
3 目录项对象,代表一个目录项.dentry
4 文件对象,表示一个被进程打开的文件.file
其中每种对象都包含一个操作对象.依次为super_operations,inode_operations,dentry_operations以及file_operations.各自操作不同的层次.然后我们的文件系统只需要实现这些方法,然后注册到内核就可以了.
接下来我们来看和vfs相应的结构:
第一个就是file_system_type结构,这个结构表示了一个文件系统:
然后是vfsmount结构,它表示了一个安装点,换句话说也就是一个文件系统实例.
第三个是files_struct结构,它主要是为每个进程来维护它所打开的句柄.这里只需要注意一个就是fd_array和fstable中的fd的区别.当进程数比较少也就是小于NR_OPEN_DEFAULT(32)时,句柄就会存放在fd_array中,而当句柄数超过32则就会重新分配数组,然后将fd指针指向它(然后我们通过fd就可以取得相应的file结构).
而且files_struct是每个进程只有一个的.
还有两个一个是fs_struct,一个是namespace也都是进程相关的.这里就不一一介绍了.
我这里vfs介绍只是个大概,需要详细了解的,可以去看ulk的vfs相关章节和linux内核设计与实现的相关章节.
因此下面的图表示了进程和socket的关系:
上面的这张图有些老了,新的内核中的inode节点中已经没有u这个联合体了,对应的是会有一个包含socket和inode的一个结构体,然后我们通过inode,而inode中专门有个i_mode域来判断相应的inode类型,比如socket就是 S_IFSOCK.就可以直接计算出相应的socket的地址,然后就可以存取socket了.后面我们会介绍.
内核中标售socket有两个数据结构,一个是socket,另一个是sock,其中socket是一个general BSD socket, 它也就是应用程序和4层协议之间的一个接口,屏蔽掉了相关的4层协议部分.而在内核中,socket所需要使用的相关的4层协议的信息全部是保存在sock结构当中的,而socket和sock这两个结构都有保存对方的指针,因此可以很容易的存取对方.
还有一个就是ops域,这个域保存了所有的相关的4层协议的操作函数..
而在sock中有一个sk_common保存了一个skc_prot域,这个域保存的是相应的协议簇的操作函数的集合.
后面介绍到socket创建的时候,我们会分析proto_ops和proto的区别.其实proto相当于对proto_ops的一层封装,最终会在proto中调用proto_ops.
然后我们来看sock_init的实现,在这个函数中,将socket注册为一个伪文件系统,并安装相应的mount点:
我们知道每次创建一个socket,都是要依赖于当前的protocol family类型的(后面会分析sys_socket的源码的时候会看到).而在内核中,每种类型的protocol family都会有一个相对应的net_proto_family结构,然后将这个结构注册到内核的net_families数组中,这样我们创建socket的时候,就可以调用这个数组来创建socket.
我们先来看sock_register的源码,也就是如何将一个net_proto_family注册到相应的数组:
我们知道每个协议簇和相应的套接口都对应有好多种组合,因此在协议簇的实现中保存了一个相应的结构来保存这些组合,然后后面就首先通过family然后确定到某个结构,再根据套接口的类型来得到这个结构,并赋值给sock.
这里要注意我们只分析af_inet的实现,其他的协议簇都差不多:
我们来看这个的实现:
接下来来分析inet_init的源码.
接下来我们来通过分析创建socket的函数sys_socket,来更加好的理解socket的实现.
sock_create的具体流程我们就不分析了,我们只需要知道最终他会通过传递进来的family的值,来取得相应的family中注册的creat函数.然后会调用这个函数来完成socket的创建.而在上面的代码分析中,我们知道在af_inet中,注册的create函数是inet_create函数,因此我们来看这个函数的实现:
这里举个例子,来看一下tcp_v4_init_sock的实现,也就是tcp的初始化函数.
上面我们看到有两个新的结构inet_connection_sock以及tcp_sock.我们接下来就来看这两个结构.
inet_connection_sock也就是所有面向连接的协议的socket的相关信息.它的第一个域是inet_sock,因此我们可以很方便的进行转换.而tcp_sock 相当与inet_connection_sock得一个子类,保存有所有tcp相关的socket的信息.它的第一个域就是inet_connection_sock.
可以看到其实tcp_socket类似于inet_sock(前面的blog有介绍),都是保存了本层的相关的信息.
这里就不列出这两个结构了,内核中这两个结构的注释都是很详细的..
在看sock_map_fd实现之前,我们先来看内核中socket类型的inode节点的实现:
这里看到,我们只要拥有了inode节点,通过containof宏我们就可以计算出socket的地址,从而就可以得到整个socket的信息了.
而inode节点的赋值是在sock_alloc中实现的,而这个函数是在__sock_create中被调用的,也就是在init_cteate被调用之前.
然后我们来看sock_map_fd的实现.我们首先要知道,socket是没有open函数的,因此要通过vfs层的调用,必须要在create的时候,映射一个file结构,从而将句柄与这个file关联起来.
sock_alloc_fd实现比较简单,这里就不分析了.
就来看下sock_attach_fd的实现.:
这里要注意,内核通过把socket指针赋值给file的private_data,这样就可以通过句柄,在fdtable中得到file对象,然后轻松取得socket对象.
下面就是sys_socket的流程图:
最终来总结一下.内核中,socket是作为一个伪文件系统来实现的,它在初始化时注册到内核,而每个进程的files_struct域保存了所有的句柄,包括socket的.一般的文件操作的话,内核直接调用vfs层的方法,然后会自动调用socket实现的相关方法.内核通过inode结构的imode域就可以知道当前的句柄所关联的是不是socket类型,这时遇到socket独有的操作,就通过containof方法,来计算出socket的地址,从而就可以进行相关的操作.
最后我们要注意的是,内核在调用相关操作都是直接调用socket的ops域,然后在ops域中调用相应的sock结构体中的sock_common域的skc_prot的操作集中的相对应的函数.
举个例子,假设现在我们使用tcp协议然后调用bind方法,内核会先调用sys_bind方法:
可以看到它调用的是ops域的bind方法.而这时我们的ops域是inet_stream_ops,来看它的bind方法:
它最终调用的是sock结构的sk_prot域(也就是sock_common的skc_prot域)的bind方法,而此时我们的skc_prot的值是tcp_prot,因此最终会调用tcp_prot的bind方法.
下面就是示意图:
PS:随便抱怨下,linux kernel的socket实现也太复杂了..不知道其他的操作系统的socket实现的怎么样..
我也想知道
找到了,是《TCP/IP ARCHITECTURE, DESIGN, AND IMPLEMENTATION IN LINUX》
我也想知道
下面就是它的代码片段:
asmlinkage long sys_socketcall(int call, unsigned long __user *args) { unsigned long a[6]; unsigned long a0, a1; int err; .......................................... 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 = do_accept(a0, (struct sockaddr __user *)a1, (int __user *)a[2], 0); break; case SYS_GETSOCKNAME: err = sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]); break; ..................................... return err; }
可以看到代码比较简单,就是通过传递进来的call类型,来调用相应的socket相关的函数.
这里你可能注意到了,那就是一般文件句柄相关的操作,比如write,read,aio,poll这些并没有看到(也就是file_operations).这是因为socket上面其实还有一层vfs层,内核把socket当做一个文件系统来处理,并实现了相应的vfs方法.因此下面我们先来了解下vfs.然后会描述下进程如何通过vfs存取句柄.
vfs其实就相当于对下层的文件系统和上层应用之间的粘合层,它定义了文件系统需要实现的相关的操作,然后下层的文件系统只需要实现这些方法就可以了,也就是说在内核其他部分和上层应用看来,所有的文件系统没有任何区别.
下面的这张图就是从用户空间调用write的大体流程:
vfs中有4种主要的数据结构:
1 超级块对象,代表一个已安装的文件系统.super_block
2 索引节点对象,代表一个文件.inode
3 目录项对象,代表一个目录项.dentry
4 文件对象,表示一个被进程打开的文件.file
其中每种对象都包含一个操作对象.依次为super_operations,inode_operations,dentry_operations以及file_operations.各自操作不同的层次.然后我们的文件系统只需要实现这些方法,然后注册到内核就可以了.
接下来我们来看和vfs相应的结构:
第一个就是file_system_type结构,这个结构表示了一个文件系统:
struct file_system_type { const char *name; int fs_flags; ///最关键的函数,得到文件系统的超级块. int (*get_sb) (struct file_system_type *, int, const char *, void *, struct vfsmount *); void (*kill_sb) (struct super_block *); ........................................... };
然后是vfsmount结构,它表示了一个安装点,换句话说也就是一个文件系统实例.
第三个是files_struct结构,它主要是为每个进程来维护它所打开的句柄.这里只需要注意一个就是fd_array和fstable中的fd的区别.当进程数比较少也就是小于NR_OPEN_DEFAULT(32)时,句柄就会存放在fd_array中,而当句柄数超过32则就会重新分配数组,然后将fd指针指向它(然后我们通过fd就可以取得相应的file结构).
而且files_struct是每个进程只有一个的.
struct files_struct { /* * read mostly part */ atomic_t count; struct fdtable *fdt; struct fdtable fdtab; /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; int next_fd; struct embedded_fd_set close_on_exec_init; struct embedded_fd_set open_fds_init; ///所打开的所有文件 struct file * fd_array[NR_OPEN_DEFAULT]; }; struct fdtable { unsigned int max_fds; struct file ** fd; /* current fd array */ fd_set *close_on_exec; fd_set *open_fds; struct rcu_head rcu; struct fdtable *next; };
还有两个一个是fs_struct,一个是namespace也都是进程相关的.这里就不一一介绍了.
我这里vfs介绍只是个大概,需要详细了解的,可以去看ulk的vfs相关章节和linux内核设计与实现的相关章节.
因此下面的图表示了进程和socket的关系:
上面的这张图有些老了,新的内核中的inode节点中已经没有u这个联合体了,对应的是会有一个包含socket和inode的一个结构体,然后我们通过inode,而inode中专门有个i_mode域来判断相应的inode类型,比如socket就是 S_IFSOCK.就可以直接计算出相应的socket的地址,然后就可以存取socket了.后面我们会介绍.
内核中标售socket有两个数据结构,一个是socket,另一个是sock,其中socket是一个general BSD socket, 它也就是应用程序和4层协议之间的一个接口,屏蔽掉了相关的4层协议部分.而在内核中,socket所需要使用的相关的4层协议的信息全部是保存在sock结构当中的,而socket和sock这两个结构都有保存对方的指针,因此可以很容易的存取对方.
还有一个就是ops域,这个域保存了所有的相关的4层协议的操作函数..
而在sock中有一个sk_common保存了一个skc_prot域,这个域保存的是相应的协议簇的操作函数的集合.
后面介绍到socket创建的时候,我们会分析proto_ops和proto的区别.其实proto相当于对proto_ops的一层封装,最终会在proto中调用proto_ops.
/** * struct socket - general BSD socket * @state: socket state (%SS_CONNECTED, etc) * @type: socket type (%SOCK_STREAM, etc) * @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc) * @ops: protocol specific socket operations * @fasync_list: Asynchronous wake up list * @file: File back pointer for gc * @sk: internal networking protocol agnostic socket representation * @wait: wait queue for several uses */ struct socket { socket_state state; short type; unsigned long flags; const struct proto_ops *ops; struct fasync_struct *fasync_list; struct file *file; struct sock *sk; wait_queue_head_t wait; }; struct sock_common { unsigned short skc_family; volatile unsigned char skc_state; unsigned char skc_reuse; int skc_bound_dev_if; struct hlist_node skc_node; struct hlist_node skc_bind_node; atomic_t skc_refcnt; unsigned int skc_hash; struct proto *skc_prot; #ifdef CONFIG_NET_NS struct net *skc_net; #endif }; struct proto_ops { int family; struct module *owner; int (*release) (struct socket *sock); int (*bind) (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len); int (*connect) (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags); ................................................... };
然后我们来看sock_init的实现,在这个函数中,将socket注册为一个伪文件系统,并安装相应的mount点:
///相应的mount对象 static struct vfsmount *sock_mnt __read_mostly; ///文件系统对象. static struct file_system_type sock_fs_type = { .name = "sockfs", .get_sb = sockfs_get_sb, .kill_sb = kill_anon_super, }; static int __init sock_init(void) { /* * Initialize sock SLAB cache. */ sk_init(); /* * Initialize skbuff SLAB cache */ skb_init(); ///初始化一个inodecache. init_inodecache(); ///注册文件系统到内核. register_filesystem(&sock_fs_type); ///安装mount点. sock_mnt = kern_mount(&sock_fs_type); #ifdef CONFIG_NETFILTER netfilter_init(); #endif return 0; }
我们知道每次创建一个socket,都是要依赖于当前的protocol family类型的(后面会分析sys_socket的源码的时候会看到).而在内核中,每种类型的protocol family都会有一个相对应的net_proto_family结构,然后将这个结构注册到内核的net_families数组中,这样我们创建socket的时候,就可以调用这个数组来创建socket.
我们先来看sock_register的源码,也就是如何将一个net_proto_family注册到相应的数组:
static const struct net_proto_family *net_families[NPROTO] __read_mostly; int sock_register(const struct net_proto_family *ops) { int err; if (ops->family >= NPROTO) { printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family, NPROTO); return -ENOBUFS; } spin_lock(&net_family_lock); ///代码非常简单,就是根据类型,然后放到相应的位置. if (net_families[ops->family]) err = -EEXIST; else { net_families[ops->family] = ops; err = 0; } spin_unlock(&net_family_lock); printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family); return err; }
我们知道每个协议簇和相应的套接口都对应有好多种组合,因此在协议簇的实现中保存了一个相应的结构来保存这些组合,然后后面就首先通过family然后确定到某个结构,再根据套接口的类型来得到这个结构,并赋值给sock.
这里要注意我们只分析af_inet的实现,其他的协议簇都差不多:
我们来看这个的实现:
///可以看到这是一个数组,每个元素都是一个链表,也就是每种类型的socket就是一个链表.而这个链表所包含的是不同4层协议的inetsw.可是在inet中,现在每种类型的socket只对应一个4层协议.这里只是为了以后扩展. static struct list_head inetsw[SOCK_MAX]; ///相应的socket的对应的信息的结构. struct inet_protosw { struct list_head list; ///需要这两个key才能定位一个inet_protosw. unsigned short type; /* This is the 2nd argument to socket(2). */ unsigned short protocol; /* This is the L4 protocol number. */ ///相应的基于ipv4的4层协议的操作集合. struct proto *prot; ///相应的协议簇的操作信息. const struct proto_ops *ops; int capability; /* Which (if any) capability do * we need to use this socket * interface? */ char no_check; /* checksum on rcv/xmit/none? */ unsigned char flags; /* See INET_PROTOSW_* below. */ }; void inet_register_protosw(struct inet_protosw *p) { struct list_head *lh; struct inet_protosw *answer; int protocol = p->protocol; struct list_head *last_perm; ............................................. answer = NULL; last_perm = &inetsw[p->type]; ///这个操作也很简单,就是将inet_protosw根据套接口类型插入到全局链表数组. list_for_each(lh, &inetsw[p->type]) { answer = list_entry(lh, struct inet_protosw, list); /* Check only the non-wild match. */ if (INET_PROTOSW_PERMANENT & answer->flags) { if (protocol == answer->protocol) break; last_perm = lh; } answer = NULL; } if (answer) goto out_permanent; ///插入链表. list_add_rcu(&p->list, last_perm); ..............................
接下来来分析inet_init的源码.
///表示了所有的可能的当前协议簇和套接口类型的组合. 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, } }; ///协议簇的创建函数. static struct net_proto_family inet_family_ops = { .family = PF_INET, .create = inet_create, .owner = THIS_MODULE, }; static int __init inet_init(void) { ............................................. ///注册相应的proto到全局链表中. rc = proto_register(&tcp_prot, 1); if (rc) goto out; rc = proto_register(&udp_prot, 1); if (rc) goto out_unregister_tcp_proto; rc = proto_register(&raw_prot, 1); if (rc) goto out_unregister_udp_proto; ///注册协议簇的操作函数(后面socket创建的时候会用到). (void)sock_register(&inet_family_ops); ............................................. /* Register the socket-side information for inet_create. */ for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r) INIT_LIST_HEAD(r); ///将inetsw_array插入到相应的数组链表. for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) inet_register_protosw(q); ........................................... }
接下来我们来通过分析创建socket的函数sys_socket,来更加好的理解socket的实现.
asmlinkage long sys_socket(int family, int type, int protocol) { ............................................... ///主要是两个函数,一个是创建socket retval = sock_create(family, type, protocol, &sock); if (retval < 0) goto out; ///这个是相应的文件系统的操作. retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); .................................... }
sock_create的具体流程我们就不分析了,我们只需要知道最终他会通过传递进来的family的值,来取得相应的family中注册的creat函数.然后会调用这个函数来完成socket的创建.而在上面的代码分析中,我们知道在af_inet中,注册的create函数是inet_create函数,因此我们来看这个函数的实现:
static int inet_create(struct net *net, struct socket *sock, int protocol) { struct sock *sk; struct inet_protosw *answer; struct inet_sock *inet; struct proto *answer_prot; unsigned char answer_flags; char answer_no_check; int try_loading_module = 0; int err; ........................................................... ///首先给socket状态赋值. sock->state = SS_UNCONNECTED; /* Look for the requested type/protocol pair. */ lookup_protocol: err = -ESOCKTNOSUPPORT; rcu_read_lock(); ///通过type和protocl的值,来查找到相应的inet_protosw结构. list_for_each_entry_rcu(answer, &inetsw[sock->type], list) { err = 0; /* Check the non-wild match. */ if (protocol == answer->protocol) { if (protocol != IPPROTO_IP) break; } else { /* Check for the two wild cases. */ if (IPPROTO_IP == protocol) { protocol = answer->protocol; break; } if (IPPROTO_IP == answer->protocol) break; } err = -EPROTONOSUPPORT; } .......................................... ///开始给socket赋值.这里我们可以看到最终socket的ops域所得到的值就是相应的协议簇的操作集合(比如inet_stream_ops这些).. sock->ops = answer->ops; answer_prot = answer->prot; answer_no_check = answer->no_check; answer_flags = answer->flags; rcu_read_unlock(); WARN_ON(answer_prot->slab == NULL); err = -ENOBUFS; ///alloc一个sock结构,其中将刚才取得的inet_protosw中的pro 域赋值给sock的sk_prot域和sk_prot_creator.以及family域也被相应的赋值. sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot); if (sk == NULL) goto out; .................................................................... ///这个函数中会初始化相应的socket中的写队列,读队列以及错误队列.并将sk指针和sock连接起来.而且还将初始化相应的定时器. sock_init_data(sock, sk); .......................................... ///调用相应的初始化. if (sk->sk_prot->init) { ///其实也就是相对应4层协议的初始化函数,它会初始化一些协议相关的东西. err = sk->sk_prot->init(sk); if (err) sk_common_release(sk); } out: return err; out_rcu_unlock: rcu_read_unlock(); goto out; }
这里举个例子,来看一下tcp_v4_init_sock的实现,也就是tcp的初始化函数.
static int tcp_v4_init_sock(struct sock *sk) { struct inet_connection_sock *icsk = inet_csk(sk); struct tcp_sock *tp = tcp_sk(sk); skb_queue_head_init(&tp->out_of_order_queue); ///初始化定时器,也就是tcp的那3个定时器,write,delay以及keepalive定时器. tcp_init_xmit_timers(sk); tcp_prequeue_init(tp); ................................... ///状态赋值.初始状态. sk->sk_state = TCP_CLOSE; .................................. return 0; }
上面我们看到有两个新的结构inet_connection_sock以及tcp_sock.我们接下来就来看这两个结构.
inet_connection_sock也就是所有面向连接的协议的socket的相关信息.它的第一个域是inet_sock,因此我们可以很方便的进行转换.而tcp_sock 相当与inet_connection_sock得一个子类,保存有所有tcp相关的socket的信息.它的第一个域就是inet_connection_sock.
可以看到其实tcp_socket类似于inet_sock(前面的blog有介绍),都是保存了本层的相关的信息.
这里就不列出这两个结构了,内核中这两个结构的注释都是很详细的..
在看sock_map_fd实现之前,我们先来看内核中socket类型的inode节点的实现:
这里看到,我们只要拥有了inode节点,通过containof宏我们就可以计算出socket的地址,从而就可以得到整个socket的信息了.
struct socket_alloc { struct socket socket; struct inode vfs_inode; };
而inode节点的赋值是在sock_alloc中实现的,而这个函数是在__sock_create中被调用的,也就是在init_cteate被调用之前.
static struct socket *sock_alloc(void) { struct inode *inode; struct socket *sock; ///新建一个inode,sock_mnt就是sock_init中被安装的mount点. inode = new_inode(sock_mnt->mnt_sb); if (!inode) return NULL; ///然后组合inode和socket结构. sock = SOCKET_I(inode); ///设置inode类型. inode->i_mode = S_IFSOCK | S_IRWXUGO; inode->i_uid = current->fsuid; inode->i_gid = current->fsgid; ///将sockets_in_use(也就是当前创建的socket)加一. get_cpu_var(sockets_in_use)++; put_cpu_var(sockets_in_use); return sock; }
然后我们来看sock_map_fd的实现.我们首先要知道,socket是没有open函数的,因此要通过vfs层的调用,必须要在create的时候,映射一个file结构,从而将句柄与这个file关联起来.
int sock_map_fd(struct socket *sock, int flags) { struct file *newfile; ///找到一个可用的fd,并找到一个可用的file结构并返回. int fd = sock_alloc_fd(&newfile, flags); if (likely(fd >= 0)) { ///初始化这个file结构. int err = sock_attach_fd(sock, newfile, flags); if (unlikely(err < 0)) { put_filp(newfile); put_unused_fd(fd); return err; } ///将句柄和文件指针关联起来. fd_install(fd, newfile); } return fd; }
sock_alloc_fd实现比较简单,这里就不分析了.
就来看下sock_attach_fd的实现.:
这里要注意,内核通过把socket指针赋值给file的private_data,这样就可以通过句柄,在fdtable中得到file对象,然后轻松取得socket对象.
///目录项的操作集合 static struct dentry_operations sockfs_dentry_operations = { .d_delete = sockfs_delete_dentry, .d_dname = sockfs_dname, }; ///文件的操作集合.这些函数最终调用的还是socket的ops域中的函数.而我们上面已经提过最终他们调用sock域的proto中的函数. static const 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, #ifdef CONFIG_COMPAT .compat_ioctl = compat_sock_ioctl, #endif .mmap = sock_mmap, .open = sock_no_open, /* special open code to disallow open via /proc */ .release = sock_close, .fasync = sock_fasync, .sendpage = sock_sendpage, .splice_write = generic_splice_sendpage, .splice_read = sock_splice_read, }; static int sock_attach_fd(struct socket *sock, struct file *file, int flags) { struct dentry *dentry; struct qstr name = { .name = "" }; ///根据装载点的mnt_sb(super block)的root域来创建一个目录项. dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name); if (unlikely(!dentry)) return -ENOMEM; ///将sockfs的目录项操作集合赋值. dentry->d_op = &sockfs_dentry_operations; /* * We dont want to push this dentry into global dentry hash table. * We pretend dentry is already hashed, by unsetting DCACHE_UNHASHED * This permits a working /proc/$pid/fd/XXX on sockets */ dentry->d_flags &= ~DCACHE_UNHASHED; ///将inode和目录项关联起来. d_instantiate(dentry, SOCK_INODE(sock)); sock->file = file; ///初始化文件对象,主要就是将socket_file_ops赋值给file结构的f_op域. init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE, &socket_file_ops); SOCK_INODE(sock)->i_fop = &socket_file_ops; file->f_flags = O_RDWR | (flags & O_NONBLOCK); file->f_pos = 0; ///将sock赋值给private_data,这样我们就能通过file轻松获得socket结构(在后面会用到). file->private_data = sock; return 0; }
下面就是sys_socket的流程图:
最终来总结一下.内核中,socket是作为一个伪文件系统来实现的,它在初始化时注册到内核,而每个进程的files_struct域保存了所有的句柄,包括socket的.一般的文件操作的话,内核直接调用vfs层的方法,然后会自动调用socket实现的相关方法.内核通过inode结构的imode域就可以知道当前的句柄所关联的是不是socket类型,这时遇到socket独有的操作,就通过containof方法,来计算出socket的地址,从而就可以进行相关的操作.
最后我们要注意的是,内核在调用相关操作都是直接调用socket的ops域,然后在ops域中调用相应的sock结构体中的sock_common域的skc_prot的操作集中的相对应的函数.
举个例子,假设现在我们使用tcp协议然后调用bind方法,内核会先调用sys_bind方法:
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) { ................................................ if (!err) err = sock->ops->bind(sock, (struct sockaddr *) &address, addrlen); ................................................... }
可以看到它调用的是ops域的bind方法.而这时我们的ops域是inet_stream_ops,来看它的bind方法:
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { .............................................. /* If the socket has its own bind function then use it. (RAW) */ if (sk->sk_prot->bind) { err = sk->sk_prot->bind(sk, uaddr, addr_len); goto out; } ................................................ }
它最终调用的是sock结构的sk_prot域(也就是sock_common的skc_prot域)的bind方法,而此时我们的skc_prot的值是tcp_prot,因此最终会调用tcp_prot的bind方法.
下面就是示意图:
PS:随便抱怨下,linux kernel的socket实现也太复杂了..不知道其他的操作系统的socket实现的怎么样..
评论
4 楼
luyc
2010-08-27
luyc 写道
我也想知道
找到了,是《TCP/IP ARCHITECTURE, DESIGN, AND IMPLEMENTATION IN LINUX》
3 楼
luyc
2010-08-24
sunzixun 写道
哥们,你好
你最后一张图 3.4 是来自哪一本书啊。谢谢
你最后一张图 3.4 是来自哪一本书啊。谢谢
我也想知道
2 楼
sunzixun
2010-08-20
哥们,你好
你最后一张图 3.4 是来自哪一本书啊。谢谢
你最后一张图 3.4 是来自哪一本书啊。谢谢
1 楼
mryufeng
2009-08-31
bsd的更复杂
发表评论
-
Receive packet steering patch详解
2010-07-25 16:46 12126Receive packet steering简称rp ... -
内核中拥塞窗口初始值对http性能的影响分析
2010-07-11 00:20 9696这个是google的人提出的 ... -
linux 内核tcp拥塞处理(一)
2010-03-12 16:17 9572这次我们来分析tcp的拥塞控制,我们要知道协议栈都是很保守的, ... -
内核tcp协议栈SACK的处理
2010-01-24 21:13 12154上一篇处理ack的blog中我 ... -
内核tcp的ack的处理
2010-01-17 03:06 11155我们来看tcp输入对于ack,段的处理。 先是ack的处理, ... -
内核处理time_wait状态详解
2010-01-10 17:39 6802这次来详细看内核的time_wait状态的实现,在前面介绍定时 ... -
tcp协议栈处理各种事件的分析
2009-12-30 01:29 13622首先我们来看socket如何将一些状态的变化通知给对应的进程, ... -
linux内核sk_buff的结构分析
2009-12-25 00:42 47904我看的内核版本是2.6.32. 在内核中sk_buff表示一 ... -
tcp的输入段的处理
2009-12-18 00:56 8351tcp是全双工的协议,因此每一端都会有流控。一个tcp段有可能 ... -
内核协议栈tcp层的内存管理
2009-11-28 17:13 12061我们先来看tcp内存管理相关的几个内核参数,这些都能通过pro ... -
linux内核定时器的实现
2009-10-31 01:44 10191由于linux还不是一个实时的操作系统,因此如果需要更高精度, ... -
linux内核中tcp连接的断开处理
2009-10-25 21:47 10319我们这次主要来分析相关的两个断开函数close和shotdow ... -
linux内核tcp的定时器管理(二)
2009-10-05 20:52 5414这次我们来看后面的3个定时器; 首先是keep alive定 ... -
linux内核tcp的定时器管理(一)
2009-10-04 23:29 9824在内核中tcp协议栈有6种 ... -
linux 内核tcp接收数据的实现
2009-09-26 20:24 14510相比于发送数据,接收数据更复杂一些。接收数据这里和3层的接口是 ... -
linux 内核tcp数据发送的实现
2009-09-10 01:41 19767在分析之前先来看下SO_RCVTIMEO和SO_SNDTIME ... -
tcp connection setup的实现(三)
2009-09-03 00:34 5188先来看下accept的实现. 其实accept的作用很简单, ... -
tcp connection setup的实现(二)
2009-09-01 00:46 8428首先来看下内核如何处理3次握手的半连接队列和accept队列( ... -
tcp connection setup的实现(一)
2009-08-23 04:10 5810bind的实现: 先来介绍几个地址结构. struct ... -
ip层和4层的接口实现分析
2009-08-08 03:50 6199首先来看一下基于3层的ipv4以及ipv6实现的一些4层的协议 ...
相关推荐
在书中,作者详细介绍了Linux内核的设计原则和实现机制,涵盖了从进程管理、内存管理到设备驱动等多个关键领域。以下是基于标题和描述中涉及的知识点的详细解析: 1. **进程管理**:Linux内核中的进程管理包括进程...
《Linux网络体系结构:Linux内核中网络协议的设计与实现》这本书深入探讨了Linux操作系统内核中的网络架构和协议栈的实现。Linux网络体系结构是理解操作系统如何处理网络通信的关键,它涉及到网络协议的各个层次,...
Linux内核中的sock和socket数据结构是网络编程的核心组成部分,它们是实现网络通信的基础构件。在Linux操作系统中,网络通信的实现依赖于BSD套接字接口,而这一接口在内核中是通过sock和socket数据结构来实现的。 ...
《Linux内核设计与实现》第三版是陈莉君翻译的经典之作,这本书深入浅出地讲解了Linux操作系统内核的工作原理和实现细节。对于想要深入理解操作系统,特别是Linux内核的人来说,这是一本不可多得的参考书。本书涵盖...
6. **网络协议栈**:Linux内核实现了完整的TCP/IP协议栈,书中详细介绍了网络接口层、数据链路层、网络层、传输层以及应用层的协议处理,包括socket编程接口。 7. **系统调用**:系统调用是用户程序与内核交互的...
根据提供的文件标题、描述、标签以及部分内容,我们可以推断出这份文档主要关注的是Linux内核中的TCP/IP协议栈实现分析。接下来将详细阐述这一主题下的关键知识点。 ### 一、Linux内核源码剖析概述 #### 1. Linux...
7. **网络子系统**:Linux内核中的网络部分是另一个重要话题,可能涵盖网络协议栈的层次结构、TCP/IP协议族、网络设备驱动和socket编程接口。 通过阅读和学习这些章节,读者不仅可以理解Linux内核的工作原理,还能...
1. **Linux内核概述**:首先,书中会介绍Linux内核的基本架构,包括内核的模块化设计、进程管理、内存管理和I/O处理机制等,这些都是理解内核工作流程的基础。 2. **进程管理**:深入讲解进程的创建、调度、同步和...
除了核心代码之外,Linux内核中还包含了BSD Socket层和Inet层的非核心代码分析。在BSD Socket层中,例如msg_flag、数据报类型、SockCheckSum、SKStream等,这些都是为了支持更高层次的socket编程接口。而在Inet层中...
《Linux内核源码剖析 TCP/IP实现》是樊东东和莫澜合著的一本深入解析Linux内核网络协议栈的书籍,主要关注TCP/IP协议的实现细节。这本书上册的内容,将引领读者深入理解Linux操作系统如何处理网络通信,特别是TCP/IP...
在《Linux内核设计与实现》中,作者首先概述了Linux内核的历史和发展,介绍了内核的基本结构和工作原理,让读者对Linux内核有一个宏观的认识。接着,深入到内核的各个组成部分,如进程调度、内存管理、虚拟文件系统...
6. **网络协议栈**:书中详细介绍了Linux内核中的网络子系统,包括网络协议的分层结构、TCP/IP协议的实现、socket接口以及网络设备驱动。这部分对于理解网络数据传输和网络安全至关重要。 7. **系统调用**:系统...
### Linux 内核 Socket 相关数据结构介绍 ...通过对以上数据结构的学习和理解,开发者能够更准确地把握 Socket 在 Linux 内核中的实现细节,这对于进一步探索 Linux 内核网络子系统的其他方面也具有重要的指导意义。
本文将深入探讨Linux内核的主要组成部分、工作原理及其在系统中的作用。 1. 内核架构 Linux内核采用微内核架构,主要由以下几个核心模块组成: - 进程管理:负责进程的创建、调度、同步和通信。Linux内核支持多任务...
Linux内核在TCP监听socket(LISTEN状态)中维护了一个open_request数组,用于存储半开连接信息。当达到最大连接数时,新的SYN请求会替换旧的请求,但SYN Cookie机制确保了合法连接不会因资源耗尽而丢失。 Linux内核...
《Linux内核设计与实现》是一本深入探讨Linux操作系统内核的重要著作,它详细阐述了Linux内核的设计原理和实现机制。这本书对于理解操作系统的核心概念、掌握Linux内核的工作方式以及进行系统级编程具有极高的价值。...
《LINUX内核设计与实现》是一本深入探讨Linux操作系统内核的重要著作。它涵盖了Linux内核的基础架构、设计原理以及实现细节,是理解和研究Linux技术的宝贵资源。以下是基于这个主题的一些关键知识点: 1. **Linux...
时钟是操作系统调度任务的基础,Linux内核使用时钟中断来实现时间片调度,确保进程的公平执行。 #### 二、软件基础 **2.1 计算机编程语言** - **2.1.1 汇编语言**:底层编程语言,直接对应CPU指令集,用于编写高...
Linux内核是Linux操作系统的核心部分,它负责管理系统的硬件资源,调度进程,实现硬件接口,以及提供系统调用来供应用程序使用。手册通常会涵盖以下几个关键领域: 1. **内核架构**:这部分会介绍Linux内核的整体...