Socket与系统调用深度分析
一、什么是系统调用
系统态和用户态
系统态(也称为管态或核心态),操作系统在系统态运行
在实际运行过程中,处理机会在系统态和用户态间切换。相应地,现代多数操作系统将 CPU 的指令集分为特权指令和非特权指令两类。
1) 特权指令——在系统态时运行的指令
对内存空间的访问范围基本不受限制,不仅能访问用户存储空间,也能访问系统存储空间,
一般应用程序所使用的都是非特权指令,它只能完成一般性的操作和任务,不能对系统中的硬件和软件直接进行访问,其对内存的访问范围也局限于用户空间。
当应用程序中需要操作系统提供服务时,如请求 I/O 资源或执行 I/O 操作,应用程序必须使用系统调用命令。由操作系统捕获到该命令后,便将 CPU 的状态从用户态转换到系统态,然后执行操作系统中相应的子程序(例程),完成所需的功能。执行完成后,系统又将CPU 状态从系统态转换到用户态,再继续执行应用程序。
系统调用和一般调用的区别:
系统调用不允许由调用过程直接转向被调用过程。
通常都是通过软中断机制,先由用户态转换为系统态,经核心分析后,才能转向相应的系统调用处理子程序。
中断机制
系统调用是通过中断机制实现的,并且一个操作系统的所有系统调用都通过同一个中断入口来实现。对于拥有保护机制的操作系统来说,中断机制本身也是受保护的。
系统调用的类型
对于一般通用的 OS 而言,可将其所提供的系统调用分为:进程控制、文件操纵、通信管理和系统维护等几大类。
系统调用的实现
系统调用的实现与一般过程调用的实现相比,两者间有很大差异。对于系统调用,控制是由原来的用户态转换为系统态,这是借助于中断和陷入机制来完成的,在该机制中包括中断和陷入硬件机构及中断与陷入处理程序两部分。当应用程序使用 OS 的系统调用时,产生一条相应的指令,CPU 在执行这条指令时发生中断,并将有关信号送给中断和陷入硬件机构,该机构收到信号后,启动相关的中断与陷入处理程序进行处理,实现该系统调用所需要的功能。
二、Socket系统调用过程



socket()函数系统调用过程
在sys_socketcall()函数中可以看到,socket系统调用最终调用的是sys_socket()函数
sys_socket()函数声明如下:
asmlinkage long sys_socket(int, int, int);
同样的,sys_socket()函数实现为:
sys_socket()
参数kern:表示由应用程序还是内核创建该套接口,一般为0(表示应用程序),或者1(表示内核)。
sock_create()函数
这个函数是对__socket_create函数的封装,直接调用__sock_create()函数。
int sock_create(int family, int type, int protocol, struct socket **res) { return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0); }
__sock_create()函数
创建socket及inode
int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern) { int err; struct socket *sock; const struct net_proto_family *pf; /* * Check protocol is in range */ /*family和type字段范围检查*/ if (family < 0 || family >= NPROTO) return -EAFNOSUPPORT; if (type < 0 || type >= SOCK_MAX) return -EINVAL; /*兼容性考虑,IPv4协议族的SOCK_PACKET已经废弃,当family ==F_INET && type == SOCK_PACKET时, 强制把family改为PF_PACKET。*/ if (family == PF_INET && type == SOCK_PACKET) { static int warned; if (!warned) { warned = 1; pr_info("%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm); } family = PF_PACKET; } /*安全模块对套接口的创建做检查,安全模块不是网络中必需的组成部门,不做讨论。*/ // 检查权限,并考虑协议集、类型、协议,以及 socket 是在内核中创建还是在用户空间中创建 err = security_socket_create(family, type, protocol, kern); if (err) return err; /* * 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. */ /*调用sock_alloc()在sock_inode_cache缓存中分配与套接口关联的i结点和套接口,同时 初始化i结点和套接口,失败则直接返回错误码。*/ sock = sock_alloc(); if (!sock) { net_warn_ratelimited("socket: no more sockets\n"); return -ENFILE; /* Not exactly a match, but its the closest posix thing */ } sock->type = type; /*如果协议族支持内核模块动态加载,但在创建此协议族类型的套接字时,内核模块并未被加载,则调用 request_module()进行内核模块的动态加载。*/ #ifdef CONFIG_MODULES /* Attempt to load a protocol module if the find failed. * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user * requested real, full-featured networking support upon configuration. * Otherwise module support will break! */ if (rcu_access_pointer(net_families[family]) == NULL) request_module("net-pf-%d", family); #endif rcu_read_lock(); /*获取对应协议的net_proto_family指针*/ pf = rcu_dereference(net_families[family]); err = -EAFNOSUPPORT; if (!pf) goto out_release; /* * We will call the ->create function, that possibly is in a loadable * module, so we have to bump that loadable module refcnt first. */ /*如果对应协议族模块是动态加载到内核中去的,则对此内核模块的应用计数+1,以防 在创建过程中,该模块被卸载,造成严重的后果。*/ if (!try_module_get(pf->owner)) goto out_release; /* Now protected by module ref count */ rcu_read_unlock(); /*在IPv4协议族中调用inet_create()对已创建的socket继续进行初始化,同时创建网络层socket。*/ err = pf->create(net, sock, protocol, kern); if (err < 0) goto out_module_put; /* * Now to bump the refcnt of the [loadable] module that owns this * socket at sock_release time we decrement its refcnt. */ /*如果proto_ops结构实例所在模块以内核模块方式动态加载进内核, 则增加该模块的引用计数,在sock_release时,减小该计数。*/ if (!try_module_get(sock->ops->owner)) goto out_module_busy; /* * Now that we're done with the ->create function, the [loadable] * module can have its refcnt decremented */ /*调用完inet_create函数后,对此模块的引用计数减一。*/ module_put(pf->owner); /*安全模块对创建后的socket做安全检查,不做讨论。*/ err = security_socket_post_create(sock, family, type, protocol, kern); if (err) goto out_sock_release; *res = sock; return 0; out_module_busy: err = -EAFNOSUPPORT; out_module_put: sock->ops = NULL; module_put(pf->owner); out_sock_release: sock_release(sock); return err; out_release: rcu_read_unlock(); goto out_sock_release; }
sock_alloc()函数
sock_alloc()函数,创建i结点和socket,i结点和socket是绑定在一起的,放在结构体socket_alloc中,并且进行相关初始化。
static struct socket *sock_alloc(void) { struct inode *inode; struct socket *sock; /*创建i借点和socket*/ inode = new_inode_pseudo(sock_mnt->mnt_sb); if (!inode) return NULL; /*返回创建的socket指针*/ sock = SOCKET_I(inode); kmemcheck_annotate_bitfield(sock, type); /*inode相关初始化*/ inode->i_ino = get_next_ino(); inode->i_mode = S_IFSOCK | S_IRWXUGO; inode->i_uid = current_fsuid();//用户id inode->i_gid = current_fsgid();//组id inode->i_op = &sockfs_inode_ops;//inode的操作函数指针指向sockfs_inode_ops this_cpu_add(sockets_in_use, 1); return sock; }
sock_alloc_inode()函数
这个函数很重要,给socket_alloc结构体分配内存,初始化socket基本参数。
static struct inode *sock_alloc_inode(struct super_block *sb) { struct socket_alloc *ei; struct socket_wq *wq; /*从sock_inode_cachep缓存中分配socket_alloc类型大小的内存来存放i结点和socket。*/ ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL); if (!ei) return NULL; /*创建等待队列*/ wq = kmalloc(sizeof(*wq), GFP_KERNEL); if (!wq) { kmem_cache_free(sock_inode_cachep, ei); return NULL; } /*初始化等待队列*/ init_waitqueue_head(&wq->wait); wq->fasync_list = NULL; // 将socket_alloc中socket的等待队列指向wq RCU_INIT_POINTER(ei->socket.wq, wq); /*套接口状态设置为SS_UNCONNECTED*/ ei->socket.state = SS_UNCONNECTED; ei->socket.flags = 0; ei->socket.ops = NULL; ei->socket.sk = NULL; ei->socket.file = NULL; return &ei->vfs_inode; }
inet_create()函数
这个函数很重要。创建网络层socket,包括具体的tcp_socket啥的,将相关结构关联起来。
/* * Create an inet socket. */ static int inet_create(struct net *net, struct socket *sock, int protocol,int kern) { struct sock *sk; struct inet_protosw *answer; struct inet_sock *inet; struct proto *answer_prot; unsigned char answer_flags; int try_loading_module = 0; int err; /*协议值正确性判断*/ if (protocol < 0 || protocol >= IPPROTO_MAX) return -EINVAL; /*socket的状态初始化为SS_UNCONNECTED*/ sock->state = SS_UNCONNECTED; /* Look for the requested type/protocol pair. */ lookup_protocol: err = -ESOCKTNOSUPPORT; rcu_read_lock(); /*遍历inetsw[sock->type]链表*/ list_for_each_entry_rcu(answer, &inetsw[sock->type], list) { err = 0; /* Check the non-wild match. */ /*感觉这里选择protocol有点问题,还是不要设为0了,用具体的的协议。例如IPPROTO_UDP*/ 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; } /*如果在inetsw中未能找到匹配的inet_protosw结构的实例,则需加载对应的模块,再返回到lookup_protocol进行处理。 尝试加载次数不超过两次,第一次要求加载的模块和PF_INET, protocol, sock->type这三个参数完全对应, 第二次要求加载的模块和PF_INET, protocol两个参数对应即可。*/ if (unlikely(err)) { if (try_loading_module < 2) { rcu_read_unlock(); /* * Be more specific, e.g. net-pf-2-proto-132-type-1 * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM) */ if (++try_loading_module == 1) request_module("net-pf-%d-proto-%d-type-%d", PF_INET, protocol, sock->type); /* * Fall back to generic, e.g. net-pf-2-proto-132 * (net-pf-PF_INET-proto-IPPROTO_SCTP) */ else request_module("net-pf-%d-proto-%d", PF_INET, protocol); goto lookup_protocol; } else goto out_rcu_unlock; } err = -EPERM; /*检测用户是否用创建原始套接字的权利,只有root用户才能创建RAW SOCKET。*/ if (sock->type == SOCK_RAW && !kern && !ns_capable(net->user_ns, CAP_NET_RAW)) goto out_rcu_unlock; /*socket的ops指向对应的proto_ops结构*/ sock->ops = answer->ops; answer_prot = answer->prot; answer_flags = answer->flags; rcu_read_unlock(); WARN_ON(!answer_prot->slab); err = -ENOBUFS; /*创建网络层socket,这里其实分配的大小为tcp_sock或者udp_sock内存大小,不单单是sock大小。*/ sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot); if (!sk) goto out; err = 0; /*设置是否可以重用地址和端口?*/ if (INET_PROTOSW_REUSE & answer_flags) sk->sk_reuse = SK_CAN_REUSE; /*取inet_sock头*/ inet = inet_sk(sk); /*表示是否为基于连接的传输控制块,即是否是基于inet_connection_sock结构的传输控制块,例如TCP的传输控制块*/ inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0; inet->nodefrag = 0; /*如果套接字类型为原始套接字,则设置本地端口为协议号,并且如果协议为RAW协议, 则设置inet_sock中的hdrincl,表示需要自己构建IP首部。*/ if (SOCK_RAW == sock->type) { inet->inet_num = protocol; if (IPPROTO_RAW == protocol) inet->hdrincl = 1; } /*根据系统参数sysctl_ip_no_pmtu_disc设置创建的传输控制块是否支持PMTU。*/ if (net->ipv4.sysctl_ip_no_pmtu_disc) inet->pmtudisc = IP_PMTUDISC_DONT; else inet->pmtudisc = IP_PMTUDISC_WANT; inet->inet_id = 0; /*初始化sk参数。*/ sock_init_data(sock, sk); /*初始化析构函数*/ sk->sk_destruct = inet_sock_destruct; /*设置协议类型*/ sk->sk_protocol = protocol; /*设置传输控制块中的sk_backlog_rcv后备队列接收函数。*/ sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv; inet->uc_ttl = -1; inet->mc_loop = 1; inet->mc_ttl = 1; inet->mc_all = 1; inet->mc_index = 0; inet->mc_list = NULL; inet->rcv_tos = 0; sk_refcnt_debug_inc(sk); /*如果传输控制块中的num设置了本地端口号,则设置inet的inet_sport参数*/ if (inet->inet_num) { inet->inet_sport = htons(inet->inet_num); /* Add to protocol hash chains. */ /*调用传输层接口hash,把传输控制块加入到散列表中。*/ sk->sk_prot->hash(sk); } /*如果init指针已被设置,则调用init函数进行具体传输控制块的初始化,TCP为tcp_v4_init_sock(), 而UDP中则没有对应的实现。此时,套接口及传输控制块的创建过程全部结束。*/ if (sk->sk_prot->init) { err = sk->sk_prot->init(sk); if (err) sk_common_release(sk); } out: return err; out_rcu_unlock: rcu_read_unlock(); goto out; }
三、内核跟踪
在Menuos中输入replyhi命令,观察其系统调用



再输入hello命令,观察系统系统调用


浙公网安备 33010602011771号