前边二节我们看了关于socket的一些主要的实践练习,这些练习是对于我们进入内核追踪内核的socket过程非常重要,这节我们从应用程序的实践开始进入内核看是如何实现的,首先我们把前边二节的几个应用程序界面函数重新放在这里供大家参照,我们强调一点,有可能朋友们对我所说的应用程序界面的说法有点模糊,其实就是说我们在实践练习中所使用的函数,这些函数是通过C库间接运行系统调用的,表面上看起来能过C库的c.lib提供的诸多库函数进行工作的,其实他们最终都统一归结到内核的sys_socketcall()代码上,下面是前面几节我们看到的应用程序界面 socket(AF_UNIX, SOCK_STREAM, 0); connect(sockfd, (struct sockaddr *)&address, len); bind(server_sockfd, (struct sockaddr *)&server_address, server_len); listen(server_sockfd, 5); accept(server_sockfd,(struct sockaddr *)&client_address, client_len); 前面练习中我们用到上面这些函数,这些函数全部要通过sys_socketcall()系统调用,关于系统调用以前说过我们将会在关于中断的练习中详细论述,这里只要知道已经到达了系统调用函数处,这个函数在net/socket.c的2007行,注意本文针对的内核版本是2.6.26,我们再把内核代码贴在这里 /* * System call vectors. * * Argument checking cleaned up. Saved 20% in size. * This function doesn't need to set the kernel lock because * it is set by the callees. */ 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; err = audit_socketcall(nargs[call] / sizeof(unsigned long), a); if (err) return 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 = 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; } 这个函数是内核socket的总系统调用入口,参数call是具体的操作码,参数args是一个数组指针。另外我们需要明确从用户空间复制的参数数量,这是根据nargs[]来决定的,以call为下标将会从该数组中找到参数的个数,依据个数来把args处的参数从用户空间即我们的应用程序复制过来 /* Argument list sizes for sys_socketcall */ #define AL(x) ((x) * sizeof(unsigned long)) static const 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 可以看到上面的nargs数组中可以看出是规定了参数的个数,根据AL的宏解释,我们还可以看到在include/linux/net.h中规定了call的详细数字 #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) */ 这里就可以确定上面数组中call具体数字了。有可能朋友们会对注释中的(2)有疑惑,其的含义喻为这里是系统调用号。好了,我们将围绕sys_socketcall()系统调用函数展开具体的追踪过程,这里我们先看应用程序界面 socket(AF_UNIX, SOCK_STREAM, 0); 尽管在我们第二个练习中有还有另一个界面函数 socket(AF_INET, SOCK_STREAM, 0); 可以对比二者,也就是我们二个练习的socket类型不同之处一个是AF_UNIX另一个则是AF_INET,我们在前面二节中的题目中也加以区分了一个是unix本机内部的socket另一个则是以IP的socket,这二种都会统一进入系统调用sys_socketcall(),我们看一下上面的系统调用部分 case SYS_SOCKET: err = sys_socket(a0, a1, a[2]); 有朋友可能会被我贴的代码所吓倒,会问“这里只需要一个函数sys_socket()”那贴出全代码做什么,我们上面贴出sys_socketcall()的整个函数代码是为了让大家先对其有个整体的了解,今后我们只提到追踪到其中的一部分,即在此函数中的走向,所以希望用到时不了解的朋友可以查看这里的函数代码,这个假设是在你不想一边看这里的文章一边看你手中的源码时。这里应用程序界面函数会能过C函数库到达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; } 这个函数从名称可以看出是为了修建一个插口,对照应用程序界面传递过来的三个参数,第一个参数family应用程序传递过来的值为AF_UNIX或者AF_INET,这里我们声明本节我们重要的针对第一种情况AF_UNIX展开论述。第二个参数是SOCK_STREAM,第三个参数是0。我们从上面的函数中可以看到是进入了sock_create()函数去创建socket。另外在2.6.26内核的net/socket.c处的300行我们可以看到 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, }; 这里如果看过前边共享内存了的第一节的朋友可以看到那里我们也有一个file_system_type,但是这里安装过程与共享内存的文件系统不同,我们说过详细的关于文件系统的学习我会专门有文章进行介绍,这里我们要知道linux内核在系统初始化时会调用一个do_kern_mount()函数,我们暂且贴出这个函数的代码供朋友们浏览,它在fs/super.c的961行处 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 vfsmount *mnt; if (!type) return ERR_PTR(-ENODEV); mnt = vfs_kern_mount(type, flags, name, data); if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) && !mnt->mnt_sb->s_subtype) mnt = fs_set_subtype(mnt, fstype); put_filesystem(type); return mnt; } 我们这里的sock_fs_type正是通过这个函数安装的,安装完毕后,做为安装点的sock_mnt保存有文件系统的连接插件。我们暂且介绍这么多,现在关键是看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()函数处理,这个函数代码量较大,我分需要分段来看 static 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 */ if (family 0 || family >= NPROTO) return -EAFNOSUPPORT; if (type 0 || type >= SOCK_MAX) return -EINVAL; /* Compatibility. This uglymoron is moved from INET layer to here to avoid deadlock in module load. */ 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; 这个函数是检测规则域的,因为我们目前讲到过程是根据上面提到的AF_UNIX所以,这段代码只是检测一下传递过来的参数是否在规定的范围,而security_socket_create我们可以看到是空的只是返回一个0。下一篇我们继续重点讲解关于创建socket. |
|小黑屋|最新主题|手机版|微赢网络技术论坛 ( 苏ICP备08020429号 )
GMT+8, 2024-9-30 13:16 , Processed in 0.239688 second(s), 12 queries , Gzip On, MemCache On.
Powered by Discuz! X3.5
© 2001-2023 Discuz! Team.