socket创建-内核中

来源:互联网 发布:华硕笔记本优化 编辑:程序博客网 时间:2024/06/01 10:11
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;}static int __sock_create(int family,int type,int protocol,struct socket**res,int kern){    int err;    struct socket*sock;        const struct net_proto_family*pf;    if(family<0||family>=NRROTO)return -EAFNOSUPPROT;    if(type<0||type>=SOCK_MAX)return -EINVAL;    .....;    sock=sock_alloc(); /*此函数不止是生成socket,还有相关的i节点*/    sock->type=type;    ...;        pf=rcu_dererence(net_families[family]);        err=pf->create(sock,protocol);    ...;    *res=sock;    return 0;    ...;}static struct file_system_type sockfs_type={    .name="sockfs",    .get_sb=sockfs_get_sb,    .kill_sb=kill_anon_super,}; /*      每一种文件系统都是用file_systme_type结构表示,      这里的get_sb函数是从磁盘中读取该文件系统的超级块,然后进行处理      kill_sb即销毁sb      注意:一个超级块就代表一个文件系统      */static struct super_operations sockfs_ops={    .alloc_inode=sock_alloc_inode,    .destroy_inode=sock_destroy_inode,    .statfs=simple_statfs,}; /*对于每个文件系统,都要有操作超级块的函数集,这个就是,     其中alloc_inode函数用于:根据给定的超级块,分配i节点,     即如果在socket的文件系统中,要生成一个i节点,则需要调用这里     的alloc_inode函数,即sock_alloc_inode     下面会用到     */static struct socket* sock_alloc(void){    struct inode *inode;    struct socket *sock;    inode=new_inode(sock_mnt->mnt_sb);    /*在此中,不止是生成i节点,还生成了socket*/    /*这个涉及到套接子的文件系统,这里说明一下*/    if(!inode)return NULL;    sock=SOCKET_I(inode);    /*这是个根据inode节点来找到socket结构的内联函数     * 在普通的i节点中,只用i节点这么一个结构,     * 但在socket文件系统中,由于socket和inode的紧密关系,     * 把他们放入到了一个结构中:struct socket_alloc     * struct socket_alloc{     *   struct socket socket;     *   struct inode  vfs_inode;     *   }     *   这里的new_inode就是根据其超级块的类型来找到合适的alloc_inode函数     *   故下面主要是socket文件系统中的alloc_inode函数,即sock_allo_inode     *   它就是来生成socket文件系统中的“inode节点”,即socket_alloc结构     */    ....;    return sock;}static struct inode *sock_alloc_inode(struct super_block*sb){    struct socket_alloc*ei;    ei=kmem_cache_alloc(sock_inode_cachep,GFP_KERNEL);/*slab知识*/    init_waitqueue_head(&ei->socket.wait);    ei->socket.fasyn_list=NULL;    ...;    ei->socket.sk=NULL;    ei->socket.ops=NULL;    ei->socket.file=NULL;    ...;    return &ei->vfs_inode;}/*到此,已经完成__sock_create()中的sock_alloc部分, * 下面是_sock_create()中的pf->create部分, * 这部分主要是来完成socket中sock结构的生成,以及一些初始化的工作 *  * 在描述pf->create之前,还要熟知的知识,设计到协议栈的构建: * * pf是net_family_ops结构,,这个结构就代表了一个协议族,那先看看 * ipv4协议族即AF_INET是怎样的 * *  static struct net_proto_family inet_family_ops={ *  .family=AF_INET; *  .create=inet_create; *  .woner=THIS_MODULE; *  } * * 在上文中的函数,它按照family的值去 * 搜索net_families数组, pf=rcu_dererence(net_families[family]); * * 而net_families是个全局数组,放置的是协议族,通过链表将各个协议族 * 连接起来。 * * 故从net_families数组中根据family的值找到了ipv4的协议族,那么 * pf->create函数就是inet_create 函数辣。。 */static int inet_create(struct socket*sock,int protocol){    struct sock*sk;/*前面说过,这个函数的主要目的是生成结构sock*/    struct list_head*p;    struct inet_protosw*answer;    struct inet_sock*inet; /*sock结构是通用结构,而inet_sock是专用sock结构*/       struct proto *anwer_prot;    ...;    list_for_each_rcu(p,&inetsw[sock->type]){answer=list_entry(p,struct inet_protosw,list);if(protocol==answer->protocol){    if(protocol!=IPPROTO_IP)break;}else{    if(IPPOROTO_IP==protocol) {protocol=answer->protocol;break;    }    if(IPPROTO_IP==anwswer->protocol)break;}err=-EPROTONOSUPPROT;answer=NULL;    }    ....;    sock->ops=answer->ops; /*把proto_ops操作集赋予个sock->ops结构*/    .....;    sk=sk_alloc(PF_INET,GFP_KERNEL,answer_proto,1);/*生成sock结构*/    ...;    inet=inet_sk(sk);/*把sock结构转为专用的inet_sock结构*/    ...;    sk->sk_prot->hash(sk); /*把prot操作集赋予sock->sk_prot??*/    ....;} /*  上面的函数有些补充知识  * *  在套接口层与传输层结合的地方,有一个重要的数据结构proto_ops *  它当中是一组与套接口系统调用想对应的传输层函数指针,因此整个proto_ops *  可以看作是一张套接口调用到传输层的跳转表(在这些函数中,可能会进一步的 *  通过proto跳转,下面说),进入到具体的传输层或网络层的处理。 *  struct proto_ops{ *  int family; *  struct module *owner; *  int (*release) (struct socket *sock); *  int (*bind) (struct socket *sock,struct sockaddr*myaddr, *  int sockaddr_len); *  int (*connect)(struct socket*sock,struct sockaddr*vaddr, *  int sockaddr_len); *  ............; * *  一些列的函数指针 *  ............; *  } *   既然proto_ops结构完成从协议无关层的套接口层到协议相关的传输层的转接 *   proto结构(这里还没说,暂且放在这)完成从传输层到网络层的转接,那么可 *   知,每个传输层的协议都需要定义一个特定的protos_ops结构实例和proto结构 *   实例。 * *   在ipv4协议族中,一个传输层协议对应一个inet_protosw结构,而这个结构中 *   就包含了proto_ops和proto结构。 * *   协议族中的所有inet_protosw结构都定义在全局数组inetsw_array[]中, * *   inetsw_array[]={ *    { type=SOCK_STREAM; *      protocol=IPPROTO_TCP; *      prot=&tcp_prot; 这里就是proto结构了 *      ops=&inet_stream_ops; 这里就proto_ops结构了 *      } ,这就是一个inet_protosw结构了 *    { type=SOCK_DGRAM; *      protocol=IPPROTO_UDP; *      prot=&udp_prot; *      ops=&inet_dgram_ops; *      } , *      ..... *    } * *    当创建socket的时候,就在这个数组中搜寻匹配的inet_protosw结构 , *    然后将其proto_ops结构存储在socket的ops中,将proto结构放在 *    socket的sock结构的sk_prot中。 * * *    这样inet_create 函数创建了sock结构,且 *    把socket的proto_ops和sock的prot都赋予了各自的操作集 * *    因为以后所有针对socket的函数都需要调用这些操作集函数,所以 *    在生成socket的时候,就要一切都准备好* *//*前面讲述了sys_socket的sock_create部分,即socket和inode节点的生成以及一些 * 初始化工作,那么socket和文件描述符的一一对应关系则是在 * sock_map_fd(socket)  */int sock_map_fd(struct socket*sock){    /*无论何时,变量的地址是不会变的,变的     * 只是变量中存放的值,,若想改变这个变量的值     * 则只需传递变量的地址即可了,指针变量也是一样     */    struct file*newfile;    int fd=sock_alloc_fd(&newfile); /*传递指针的地址可以改变指针的指向      若只是传递指针,则不会改变指针的指向      */    if(fd>=0){int err=sock_attack_fd(sock,newfile);if(err<0){    put_filp(newfile);    put_unused_fd(fd);}fd_install(fd,newfile);/*将文件描述符结构实例file增加到已经打开的 文件列表中,完成进程和文件的关联(fs/open.c)*/    }}    static int sock_alloc_fd(struct file**filep){    int fd;    fd=get_unused_fd();    if(likely(fd>=0)){struct file*file=get_empty_filp();*filep=file;if(unlikely(!file)){    put_unused_fd(fd);    return -ENFILE;}    }    else*filep=NULL;    return fd;}     int sock_attack_fd(struct socket*sock,struct file*file){    .....;    sock->file=file;    file->f_op=&socket_file_ops;    /*每个文件对象file都有其操作集,这里既然是socket文件系统的文件对象     * 那么它也有操作集,初始化为socket_file_ops,     * 这给socket提供了另一种读取文件的方法     */    SOCK_INODE(sock)->i_fops=&socket_file_ops;/*和上面一样,只是把inode的 操作集也初始化成一样 */    file->private=sock;    ...;    return 0;}/*至此,socket的创建就完成了,总结来看,它完成了这么几个事情* * * 1,生成了socket结构和sock结构 * 2,初始化了socket结构的操作集proto_ops和sock的操作集proto * 3,建立了socket和文件描述符的关系(通过inode节点). * 4, 建立了和socket关联的文件描述符的操作集f_ops。 *//*这里只是分析到套接口层,而套接口层继续调用额传输层的功能完成各种 */asmlinkage long sys_sendto(int fd,void __user *buff,size_t len,    unsigned flags,struct sockaddr __user*addr,int addr_len){    /*在socket的io中,最后都利用了sock_sendmsg函数*/    struct socket *sock;    char address[MAX_SOCK_ADDR];    int err;    struct msghdr msg;    /*在io中,都是利用这个结构来传递用户太和内核太的消息的,     * 这个结构能够描述更多完整的信息,包括数据块、控制块的信息     *  struct msghdr     *  {     *     void *msg_name; //socket name      *     int msg_namelne; // length of name     *     struct iovec*msg_iov;//Data blocks     *     *      struct iovec{     *          void __user*iov_base;数据块的基址     *          __kernel_size_t iov_len;一个数据块的长度     *          }     *     //这个结构就是存储消息的数据结构,     *     通常在构造msghdr的时候,都是先构造出iovec结构     *     __kernel_size_t msg_iovlen;//Number of blocks     *     *     void *msg_control;     *     __kernel_size_t msg_controllen;     *     unsigned msg_flags;     *  }     */    struct iovec iov;    struct file*sock_file;    int fput_needed;    sock_file=fget_light(fd,&fput_needed);//fd-->file;        if(!sock_file)    return -EBADF;    sock=sock_from_file(sock_file,&err);    if(!sock)    goto out_put;    iov.iov_base=buff;/*先构造iovec结构,这里把用户空间的buff            地址赋值给iovec结构的基址            */    iov.iov_len=len;    msg.msg_name=NULL;    msg.msg_iov=&iov;/*这里只生成了一个数据块*/    msg.msg_iovlen=1;    msg.msg_control=NULL;    msg.msg_controllen=0;    msg.msg_namelen=0;    if(addr){//判断这个就是为了是send调用的时候可以兼容,下面一个函数    err=move_addr_to_kernel(addr,addr_len,address);    /*如果目的地址有效,则需要复制到内核中,组装完成msg结构*/    if(err<0)        goto out_put;    msg.msg_name=address;/*为何不直接把addr赋值到这里呢?                   因为直接从用户空间赋值到内核空间是不安全的,                   恶意的用户可能会是内核creash,所以                   move_addr_to_kernel这个函数进行了严格的                   审查                   */    msg.msg_namelen=addr_len;    }    if(sock->file->f_flags& O_NONBLOCK)    flags|=MSG_DONTWAIT;    /*如果套接子设置了不阻塞选项(从中可以看出设置的选项都是作用到     *                   其关联的file文件的flags上面)     * ,那么也给相应的msg设置flags,不过需要把文件系统的标志(O_NONBLOCK)     * 转为网络系统的标志(MSG_DONTWAIT)     */    msg.msg_flags=flags;    err=sock_sendmsg(sock,&msg,len); //最后调用sock_sendmsg发送到                             //内核缓冲区,注意不是发送到                     //对方,这里只是把数据复制到                     //内核,具体的发送到对方还需要传输层层                     //和网络层以及链路曾的参与                     //                     //故需要追踪到传输层    out_put:       fput_light(sock_file,fput_needed);       return err;}asmlinkage long sys_send(int fd,void __user*buff,size_t len,unsigned flags){    return sys_sendto(fd,buff,len,flags,NULL,0);    /*send只是简单地调用上面的函数,区别在于他们有指明目的地址,     * 不过,发送出去的数据还是要指明目的地址的,还是要设置msg的目的地址的     * 那么这个地址在哪里呢?connect已经把它完成了,看看connect。     */    asmlinkage long sys_connect(int fd,struct sockaddr __user* uservaddr,        int addrlen)    {    struct socket *sock;    char address[MAX_SOCK_ADDR];    int err,fput_needed;    sock=sockfd_lookup_light(fd,&err,&fput_needed);    if(!sock)        goto out;    err=move_addr_to_kernel(uservaddr,addrlen,address);    if(err<0)        goto out_put;        //安全检查        err=sock->ops->connect(sock,(struct sockaddr*)address,addrlen,        sock->file->f_flags);    /*最终调用了ops操作集*/out_put:    fput_light(sock->file,fput_needed);out:    return err;    }}/*下面看看ops中的connect,这里假设sock是stream,则ops->connect为 * inet_stream_connect */int inet_stream_connect(struct socket*sock,struct sockaddr*uaddr,    int addr_len,int flags){    struct sock*sk=sock->sk;    int err;    long timeo;    lock_sock(sk);    ...;    switch(sock->state){    /*在创建socket的时候,把它的state设置为了SS_UNCONNECTED*/    case SS_UNCONNECTED:        err=-EISONN;        if(sk->sk_state!=TCP_CLOSE)        goto out;        err=sk->sk_prot->connect(sk,uaddr,addr_len);        /*最后调用了传输层sock的操作集connect*/        /*而传输层的操作集中connect操作是tcp_v4_connect*/        /*而这个函数调用了很多路由方面的函数,先放放吧*/        if(err<0)        goto out;        sock->state=SS_CONNCTING;                err=-EINPROGRESS;        break;        .....;    }    timeo=sock    ....;}


0 0