Chinaunix首页 | 论坛 | 博客
  • 博客访问: 297814
  • 博文数量: 71
  • 博客积分: 30
  • 博客等级: 民兵
  • 技术积分: 217
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-31 15:43
文章分类

全部博文(71)

文章存档

2016年(4)

2015年(2)

2014年(2)

2013年(63)

分类: LINUX

2013-06-14 16:44:09

今天我们继续内核中的TCP/IPsocket的分析,同样按照我们第一节socket初始化(http://blog.chinaunix.net/u2/64681/showart.php?id=1680618 )那节的分析流程图和服务器端的练习代码继续我们的分析过程,我们看到声明了二个用于地址的结构变量,下边我们会看到这个地址数据结构的定义,下边练习程序要执行的路线是对socket地址绑定,我们来分析一下这个过程,我们看到在那节的练习中通过下面的代码来绑定地址给socket

bind(server_fd, (struct sockaddr *)&server_address, server_len);

首先也是进入系统调用的总入口处sys_socketcall()系统调用处

    case SYS_BIND:
        err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
        break;

很明显我们要进入sys_bind()函数去执行绑定,朋友们注意我们分析的内核版本是2.6.26

 

sys_socketcall()-->sys_bind()

asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
    struct socket *sock;
    char address[MAX_SOCK_ADDR];
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, address);
        if (err >= 0) {
            err = security_socket_bind(sock,
                         (struct sockaddr *)address,
                         addrlen);
            if (!err)
                err = sock->ops->bind(sock,
                         (struct sockaddr *)
                         address, addrlen);
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}

这个函数的开头要找到上一节我们创建的socket。这是通过sockfd_lookup_light()来找到的,我们分析一下这个函数

 

sys_socketcall()-->sys_bind()-->sockfd_lookup_light()

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
    struct file *file;
    struct socket *sock;

    *err = -EBADF;
    file = fget_light(fd, fput_needed);
    if (file) {
        sock = sock_from_file(file, err);
        if (sock)
            return sock;
        fput_light(file, *fput_needed);
    }
    return NULL;
}

这里我们看到参数fd这实际上是从应用程序中传递过来的server_sockfd,那是从c库到系统调用一路传过来的,而server_sockfd是我们创建socket时的文件标识号,所以这里调用fget_light()从当前进程的files_struct结构中找到我们创建socket时的file文件指针并增加他的使用计数,关于找到file的指针,请朋友们参考相关的文件系统资料,我们这里重点围绕着socket的分析,我们看到接着调用sock_from_file函数,从名称上可以看出是根据file找到已经创建的socket

 

sys_socketcall()-->sys_bind()-->sockfd_lookup_light()-->sock_from_file()

static struct socket *sock_from_file(struct file *file, int *err)
{
    if (file->f_op == &socket_file_ops)
        return file->private_data;    /* set in sock_map_fd */

    *err = -ENOTSOCK;
    return NULL;
}

这里我们看一下上一节socket的创建(http://blog.chinaunix.net/u2/64681/showart.php?id=1685664 )文章的最后我们提到了非常重要的是file结构中的private_data指向了服务器端的socket,既然这里要对socket进行地址绑定所以首先是重新获取服务器端创建的socket

file->private_data = sock;

这样上面的找到socket的函数非常容易理解了,就是找到file是最关键的,而file结构中有指针指向我们创建的socket,可能有朋友被上面这里的名称sock所迷惑,这个结构变量是socket不是sock,内核中声名的这里sock的结构变量名为sk。回到sys_bind函数中,我们找到了socket下边继续往下看,看到执行了move_addr_to_kernel函数

 

sys_socketcall()-->sys_bind()-->move_addr_to_kernel()

int move_addr_to_kernel(void __user *uaddr, int ulen, void *kaddr)
{
    if (ulen < 0 || ulen > MAX_SOCK_ADDR)
        return -EINVAL;
    if (ulen == 0)
        return 0;
    if (copy_from_user(kaddr, uaddr, ulen))
        return -EFAULT;
    return audit_sockaddr(ulen, kaddr);
}

我们又看到了copy_from_user函数,并且kaddr就是kernel address的意思,同理,uaddr就是user address的意思,ulen就是user length的意思,这样copy_from_user的函数的作用我就不用说了,这里函数就是将我们在应用程序中创建的(struct sockaddr *)&server_address结构复制到内核空间里来,有朋友可能对内核空间和用户空间的概念模糊,请查看有关操作系统理论,这里我们看到我们在用户空间中声明的地址转换成了sockaddr结构指针,我们的server_address初始声明为struct sockaddr_in 类型,我们需要看一下相关的结构

struct sockaddr {
    sa_family_t sa_family; /* address family, AF_xxx qinjiana0786*/
    char sa_data[14]; /* 14 bytes of protocol address */
};

上面的过程把我们练习中的地址结构变量通过move_addr_to_kernel()拷贝到局部数组变量char address[MAX_SOCK_ADDR]中。MAX_SOCK_ADDR定义为128我们看一下练习中的声明的struct sockaddr_in server_address

/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__    16        /* sizeof(struct sockaddr)    */
struct sockaddr_in {
  sa_family_t        sin_family;    /* Address family        */
  __be16        sin_port;    /* Port number            */
  struct in_addr    sin_addr;    /* Internet address        */

  /* Pad to size of `struct sockaddr'. wumingxiaozu*/
  unsigned char        __pad[__SOCK_SIZE__ - sizeof(short int) -
            sizeof(unsigned short int) - sizeof(struct in_addr)];
}

typedef unsigned short  sa_family_t;

这个结构正象注释中说明的那样用于IPsocket地址使用。而在练习中我们看到

server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("192.168.1.1");
    server_address.sin_port = 9266;

对照上面的结构我们发现还有一个__pad数组我们没看到对他的赋值和操作,这个数组主要作用填充保障我们的地址数据结构与通用的sockaddr数据结构保持相同的大小。此后我们在应用程序中设置的地址结构的数据都复制到了sys_bind()函数中的数组变量address中了,接着我们看到会执行

sock->ops->bind(sock,(struct sockaddr *) address, addrlen);

在这里应该明白了上面__pad的作用了,确实是为了将我们的数据能够正确对应到sockaddr结构的大小起填充作用的。我们回忆一下昨天讲到的socket的创建过程中对ops钩子结构的操作,在那里将socketops通过answer结构变量转接入了inet_stream_ops,所以这里会跳入这个钩子结构去执行,我们先看一下这个结构

const struct proto_ops inet_stream_ops = {
    .family         = PF_INET,
    .owner         = THIS_MODULE,
    .release     = inet_release,
    .bind         = inet_bind,
    .connect     = inet_stream_connect,
    .socketpair     = sock_no_socketpair,
    .accept         = inet_accept,
    .getname     = inet_getname,
    .poll         = tcp_poll,
    .ioctl         = inet_ioctl,
    .listen         = inet_listen,
    .shutdown     = inet_shutdown,
    .setsockopt     = sock_common_setsockopt,
    .getsockopt     = sock_common_getsockopt,
    .sendmsg     = tcp_sendmsg,
    .recvmsg     = sock_common_recvmsg,
    .mmap         = sock_no_mmap,
    .sendpage     = tcp_sendpage,
    .splice_read     = tcp_splice_read,
#ifdef CONFIG_COMPAT
    .compat_setsockopt = compat_sock_common_setsockopt,
    .compat_getsockopt = compat_sock_common_getsockopt,
#endif
};

结构中其他的部分我们暂且不要关心,只注意.bind= inet_bind这一句,也就是说会执行钩子函数inet_bind。篇幅所限,接下一篇

阅读(1459) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~