原文地址:http://blog.csdn.net/zhaqiwen/article/details/7725430
内核版本:2.6.12
一、系统总入口
Linux 内核为所有的与 socket 有关的操作的 API,提供了一个统一的系统调用入口,其代码在net/socket.c中:
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; }
首先调用 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) */[/code]
而数组 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()实现的: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: /* It may be already another descriptor 8) Not kernel problem. */ return retval; out_release: sock_release(sock); return retval; }
在分析这段代码之间,首先来看,创建一个 Socket,对内核而言,究竟意味着什么?究竟需要内核干什么事?
static struct vfsmount *sock_mnt; static struct file_system_type sock_fs_type = { .name = "sockfs", .get_sb = sockfs_get_sb, .kill_sb = kill_anon_super, };
文件系统 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_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 呢?现在来简要地看一个这个流程——这是文件系统安装中的一个重要步骤:
struct vfsmount *kern_mount(struct file_system_type *type) { return do_kern_mount(type->name, 0, type->name, NULL); } struct vfsmount * do_kern_mount(const char *fstype, int flags, const char *name, void *data) { struct file_system_type *type = get_fs_type(fstype); struct super_block *sb = ERR_PTR(-ENOMEM); …… sb = type->get_sb(type, flags, name, data); …… mnt->mnt_sb = sb; …… }
static struct super_block *sockfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC); }注意其第三个参数 sockfs_ops,它封装了 sockfs 的功能函数表:
static struct super_operations sockfs_ops = { .alloc_inode = sock_alloc_inode, .destroy_inode =sock_destroy_inode, .statfs = simple_statfs, }; 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 节点的:
static struct inode *sock_alloc_inode(struct super_block *sb) { struct socket_alloc *ei; ei = (struct socket_alloc *)kmem_cache_alloc(sock_inode_cachep, SLAB_KERNEL); if (!ei) return NULL; init_waitqueue_head(&ei->socket.wait); ei->socket.fasync_list = NULL; ei->socket.state = SS_UNCONNECTED; ei->socket.flags = 0; ei->socket.ops = NULL; ei->socket.sk = NULL; ei->socket.file = NULL; ei->socket.flags = 0; return &ei->vfs_inode; }
函数先分配了一个用于封装 socket 和 inode 的 ei,然后在高速缓存中为之申请了一块空间。这样,inode 和 socket 就同时都被分配了。接下来初始化 socket 的各个成员,这些成员,在后面都会一一提到。
/* * struct socket - general BSD socket * @state: socket state (%SS_CONNECTED, 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 * @type: socket type (%SOCK_STREAM, etc) */ struct socket { socket_state state; unsigned long flags; struct proto_ops *ops; struct fasync_struct *fasync_list; struct file *file; struct sock *sk; wait_queue_head_t wait; short type; };
在内核中,用 struct file结构描述一个已经打开的文件,指向该结构的指针内核中通常用 file或 filp来描述。我们知道,内核中,可以通过全局项 current 来获得当 前进程,它是一个 struct task_struct类型的指针。tastk_struct 有一个成员:
struct files_struct *files;
有了这些基础,如果要把一个 socket 与一个已打开的文件建立映射,首先要做的就是为 socket分配一个struct file,并申请分配一个相应的文件描述符fd。因为socket并不支持open方法(前面说socket的文件界面的抽像并不完美,这应该是一个佐证 吧?),所以不能期望用户界面通过调用 open() API来分配一个 struct file,而是通过调用get_empty_filp 来获取:
struct file *file = get_empty_filp();
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); }
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就完整地串起来了。
现在可以来看套接字的创建过程了:
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: /* It may be already another descriptor 8) Not kernel problem. */ return retval; out_release: sock_release(sock); return retval; } int sock_create(int family, int type, int protocol, struct socket **res) { return __sock_create(family, type, protocol, res, 0); }