Linux源码-sys

xiaoxiao2021-02-28  95

(本文部分参考《Linux内核源代码情景分析》) 本文主要介绍函数sys_socket()在内核中的实现。 1、系统调用总入口: sys_socketcall() Linux 内核为所有与 socket 有关的操作提供了一个统一的系统调用入口,但是在用户程序界面上则通过 C 语言程序库 c.lib 提供诸多库函数,看起来好像都是独立的系统调用一样。内核中为 socket 设置的总入口为 sys_socketcall(),其代码在 net/socket.c :

/*与socket相关的系统调用总入口。 */ /* *函数的第一个参数 call 即为具体的操作码,而参数 args 为指向一个数组的指针,可以根据具体操作码的不同,确定从用户空间复制参数的数量; */ asmlinkage long sys_socketcall(int call, unsigned long __user *args) { unsigned long a[6]; unsigned long a0,a1; int err; if(call<1||call>SYS_RECVMSG) return -EINVAL; /* 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; }

2、函数sys_socket()——创建插口 操作 SYS_SOCKET 是由 sys_socket()实现的,其代码在 net/socket.c 中:

asmlinkage long sys_socket(int family, int type, int protocol) { int retval; struct socket *sock; /* 根据协议族、套口类型、传输层协议创建套口 */ retval = sock_create(family, type, protocol, &sock); if (retval < 0) goto out; /* 为创建的套接口分配一个文件描述符并进行绑定 */ retval = sock_map_fd(sock); if (retval < 0) goto out_release; out: return retval; out_release: sock_release(sock); return retval; }

sys_socket()函数中主体函数由两个函数实现:sock_create()和sock_map_fd(),接下来分别介绍。 2.1 sock_create()只是起了中转作用,它调用了函数__sock_create():

int sock_create(int family, int type, int protocol, struct socket **res) { /* 传入0表示是用户态进程创建套接口 */ return __sock_create(family, type, protocol, res, 0); }

函数__sock_create()代码如下:

/** * 创建一个套接口 * family: 套接口协议族 * type: 套接口类型 * protocol: 传输层协议 * res: 输出参数,创建成功的套接口指针 * kern: 由内核还是应用程序创建。 */ static int __sock_create(int family, int type, int protocol, struct socket **res, int kern) { int err; struct socket *sock; if (family < 0 || family >= NPROTO)/* 参数合法性检测 */ return -EAFNOSUPPORT; if (type < 0 || type >= SOCK_MAX) return -EINVAL; /** * IPV4协议族的SOCK_PACKET类型套接口已经不被支持 * 为兼容旧程序,转换为PF_PACKET */ if (family == PF_INET && type == SOCK_PACKET) { static int warned; if (!warned) { warned = 1; printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm); } family = PF_PACKET; } /* 由安全模块对创建过程进行审计 */ err = security_socket_create(family, type, protocol, kern); if (err) return err; #if defined(CONFIG_KMOD) if (net_families[family]==NULL)/* 相应的协议族在内核中尚不存在,加载模块以支持该协议族 */ { request_module("net-pf-%d",family); } #endif /* 等待,直到锁被释放 */ net_family_read_lock(); if (net_families[family] == NULL) {/* 如果协议族仍然不存在,说明不支持此协议族 */ err = -EAFNOSUPPORT; goto out; } if (!(sock = sock_alloc())) {/* 分配与inode关联的套接口 */ printk(KERN_WARNING "socket: no more sockets\n"); err = -ENFILE; goto out; } sock->type = type;/* 设置套接口类型。 */ err = -EAFNOSUPPORT; if (!try_module_get(net_families[family]->owner))/* 增加对协议族模块的引用,如果失败则退出 */ goto out_release; /* 调用协议族的创建方法,对IPV4来说,调用的是inet_create */ if ((err = net_families[family]->create(sock, protocol)) < 0) goto out_module_put; if (!try_module_get(sock->ops->owner)) {/* 增加传输层模块的引用计数 */ sock->ops = NULL; goto out_module_put; } /* 增加了传输层模块的引用计数后,可以释放协议族的模块引用计数 */ module_put(net_families[family]->owner); *res = sock; /* 通知安全模块,对创建过程进行检查。 */ security_socket_post_create(sock, family, type, protocol, kern); out: net_family_read_unlock(); return err; out_module_put: module_put(net_families[family]->owner); out_release: sock_release(sock); goto out; }

本函数中sock_alloc()函数和inet_create()函数最为关键。

2.1.1 函数 sock_alloc()分配一个 socket 数据结构并进行一些初始化:

static struct socket *sock_alloc(void) { struct inode * inode; struct socket * sock; inode = new_inode(sock_mnt->mnt_sb); if (!inode) return NULL; sock = SOCKET_I(inode); /* *在 inode 结构中还要将 i_mode 里的 S_IFSOCK 标志位设成 1,并将 i_sock 也设成 1,以示这个 inode 结构所代表的并不是磁盘文件,而是一个插口。 */ inode->i_mode = S_IFSOCK|S_IRWXUGO; inode->i_sock = 1; inode->i_uid = current->fsuid; inode->i_gid = current->fsgid; get_cpu_var(sockets_in_use)++; put_cpu_var(sockets_in_use); return sock; }

由上述代码可知,取得一个 inode 结构是取得一个 socket 结构的必要条件。不仅如此, socket 结构其实只是 inode结构中的一部分。具体数据结构参看文末。 实际上,本函数主体功能又分担给两个函数new_inode()和sock = SOCKET_I(); 2.1.1.1

struct inode *new_inode(struct super_block *sb) { ... inode = alloc_inode(sb); if (inode) { ... } return inode; }

在本函数中,首先通过函数alloc_inode(sb)创建一个inode节点,然后对节点进行一些设置。 alloc_inode()代码如下:

static struct inode *alloc_inode(struct super_block *sb) { /* *如果当前文件系统超级块有自己分配inode节点的操作函数就调用自己的,否则从公用高速缓冲区分配一块inode */ if (sb->s_op->alloc_inode) inode = sb->s_op->alloc_inode(sb); else inode = (struct inode *) kmem_cache_alloc(inode_cachep, SLAB_KERNEL); ... return inode; }

2.1.1.2 在 inode 结构中有一个关键性的成分 u。这是一个 union,要按具体的文件类型和格式而解释成不同的数据结构。目前 Linux 支持 20 多种不同的文件系统,因此对这个 union 有 20 多种不同的解释,而 socket 结构正是其中之一。sock = SOCKET_I(inode);只是将 inode 结构中的这个 union 解释为 socket 结构而已:

static inline struct socket *SOCKET_I(struct inode *inode) { /* * container_of(ptr, type, member)宏的作用: *返回ptr指针所在的结构体;其中ptr为结体体type的变量中member成员的指针; *将ptr指针转化为char *,然后减去其在结构体中的偏移量, *得到的是ptr所在的结构体的地址,最后强制转换成type *; */ return &container_of(inode, struct socket_alloc, vfs_inode)->socket; }

2.1.2 调用协议族的创建方法创建一个套接口,对IPV4来说,就是调用inet_create:

/** * 创建一个IPV4的socket * sock: 已经创建的套接口 * protocol: 套接口的协议号 */ static int inet_create(struct socket *sock, int protocol) { struct sock *sk; struct list_head *p; struct inet_protosw *answer; struct inet_sock *inet; struct proto *answer_prot; unsigned char answer_flags; char answer_no_check; int err; /* * 初始化套接口状态 * 插口的初始状态设置成SS_UNCONNECTED,“有连接”模式,既类型为 SOCK_STREAM 的插口必须在建立了连接以后才能使用。 */ sock->state = SS_UNCONNECTED; answer = NULL; rcu_read_lock(); list_for_each_rcu(p, &inetsw[sock->type]) {/* 根据套接口类型遍历IPV4链表 */ answer = list_entry(p, struct inet_protosw, list); if (protocol == answer->protocol) {/* 比较传输层协议 */ if (protocol != IPPROTO_IP) break; } else { if (IPPROTO_IP == protocol) { protocol = answer->protocol; break; } if (IPPROTO_IP == answer->protocol) break; } answer = NULL; } err = -ESOCKTNOSUPPORT; if (!answer)/* 找不到对应的传输层协议 */ goto out_rcu_unlock; err = -EPERM; /* 创建该类型的套接口需要特定能力,而当前进程没有这种能力,则退出 */ if (answer->capability > 0 && !capable(answer->capability)) goto out_rcu_unlock; err = -EPROTONOSUPPORT; if (!protocol) goto out_rcu_unlock; /* 设置套接口层的接口 */ sock->ops = answer->ops; answer_prot = answer->prot; answer_no_check = answer->no_check; answer_flags = answer->flags; rcu_read_unlock(); BUG_TRAP(answer_prot->slab != NULL); err = -ENOBUFS; /* 根据协议族等参数分配传输控制块。 */ sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot->slab_obj_size, answer_prot->slab); if (sk == NULL) goto out; err = 0; sk->sk_prot = answer_prot; /* 设置是否需要校验和 */ sk->sk_no_check = answer_no_check; /* 是否可以重用地址和端口 */ if (INET_PROTOSW_REUSE & answer_flags) sk->sk_reuse = 1; inet = inet_sk(sk); if (SOCK_RAW == sock->type) {/* 是原始套接口 */ inet->num = protocol;/* 设置本地端口为协议号 */ if (IPPROTO_RAW == protocol)/* 如果是RAW协议,则需要自己构建IP首部 */ inet->hdrincl = 1; } if (ipv4_config.no_pmtu_disc)/* 是否支持PMTU */ inet->pmtudisc = IP_PMTUDISC_DONT; else inet->pmtudisc = IP_PMTUDISC_WANT; inet->id = 0; sock_init_data(sock, sk);/* 初始化传输控制块 */ sk_set_owner(sk, sk->sk_prot->owner); /* 在套接口被释放时,进行一些清理工作。 */ sk->sk_destruct = inet_sock_destruct; /* 设置协议族和协议号 */ sk->sk_family = PF_INET; sk->sk_protocol = protocol; /* 设置后备队列接收函数。 */ sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv; inet->uc_ttl = -1; inet->mc_loop = 1; inet->mc_ttl = 1; inet->mc_index = 0; inet->mc_list = NULL; #ifdef INET_REFCNT_DEBUG atomic_inc(&inet_sock_nr); #endif if (inet->num) {/* 如果设置了本地端口号 */ inet->sport = htons(inet->num);/* 设置网络序的本地端口号 */ sk->sk_prot->hash(sk);/* 将传输控制块加入到静列表中 */ } if (sk->sk_prot->init) {/* 调用传输层的初始化回调,对TCP来说,就是tcp_v4_init_sock。UDP没有设置回调 */ err = sk->sk_prot->init(sk); if (err) sk_common_release(sk); } out: return err; out_rcu_unlock: rcu_read_unlock(); goto out; }

2.1.2.1分配传输控制块sk = sk_alloc()

struct sock *sk_alloc(int family, int priority, int zero_it, kmem_cache_t *slab) { struct sock *sk = NULL; if (!slab)/* 如果传输层没有指定缓存分配区,则默认使用sk_cachep */ slab = sk_cachep; /* 在指定的分配区中,用指定的分配参数分配传输控制块 */ sk = kmem_cache_alloc(slab, priority); if (sk) { if (zero_it) {/* 需要初始化它 */ memset(sk, 0, zero_it == 1 ? sizeof(struct sock) : zero_it); sk->sk_family = family; sock_lock_init(sk); } /* 释放时需要使用 */ sk->sk_slab = slab; if (security_sk_alloc(sk, family, priority)) {/* 安全审计,如果失败则释放传输层接口 */ kmem_cache_free(slab, sk); sk = NULL; } } return sk; }

2.1.2.2 inet = inet_sk(sk) 将sock强转为inet_sock,之所以可以强转是因为在分配sock结构体变量时,分配的真实结构体是tcp_sock,而sock、inet_sock、tcp_sock之间均为0处偏移。

2.1.2.3 初始化传输控制块sock_init_data(sock, sk) 本函数主要完成的工作是: a.初始化sock结构的缓冲区和队列 b.初始化sock结构的一些状态 c.建立socket和sock结构的相互引用关系

2.2 sock_map_fd()代码如下:

/*将套接口与文件描述符绑定。 */ int sock_map_fd(struct socket *sock) { int fd; struct qstr this; char name[32]; /* 获得空闲的文件描述符。 */ fd = get_unused_fd(); if (fd >= 0) {/* 成功分配文件描述符 */ struct file *file = get_empty_filp(); if (!file) { put_unused_fd(fd); fd = -ENFILE; goto out; } sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino); this.name = name; this.len = strlen(name); this.hash = SOCK_INODE(sock)->i_ino; /*分配文件目录项。 */ file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this); if (!file->f_dentry) { put_filp(file); put_unused_fd(fd); fd = -ENOMEM; goto out; } file->f_dentry->d_op = &sockfs_dentry_operations; d_add(file->f_dentry, SOCK_INODE(sock)); file->f_vfsmnt = mntget(sock_mnt); file->f_mapping = file->f_dentry->d_inode->i_mapping; sock->file = file; file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops; file->f_mode = FMODE_READ | FMODE_WRITE; file->f_flags = O_RDWR; file->f_pos = 0; /* 将文件描述符实例增加到已经打开的文件列表中,完成文件与进程的绑定 */ fd_install(fd, file); } out: return fd; }

其中fd_install函数如下,只是做了相应指针的改变:

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); }

整体流程图: 重要数据结构:

转载请注明原文地址: https://www.6miu.com/read-66901.html

最新回复(0)