(本文部分参考《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);
}
整体流程图:
重要数据结构: