• Linux内核RPC请求过程


    这篇文章讲讲server端RPC报文的处理流程。server端RPC报文的处理函数是svc_process,这个函数位于net/sunrpc/svc.c中。这个函数须要一个svc_rqst结构的指针作为參数,svc_rqst是与RPC请求相关的一个数据结构,这里包括了接收到的RPC消息,RPC消息的解析结果也放在这个数据结构中,RPC消息的处理结果也放在这个消息中了。这个数据结构的定义在include/linux/sunrpc/svc.h。因为我主要想解说NFS,所以非常多RPC的知识就略过不讲了。

    1. /* 
    2.  * Process the RPC request. 
    3.  */  
    4. int  
    5. svc_process(struct svc_rqst *rqstp)  
    6. {  
    7.         // 这是一块缓存,server从网卡中接收到RPC消息后存放在这里(已经去掉了IP头,TCP或UDP报文头)  
    8.         // argv指向了缓存的起始地址,从这里開始就是RPC报文头了。  
    9.         struct kvec             *argv = &rqstp->rq_arg.head[0];  
    10.         // 这是一块缓存,这块缓存用来存放RPC应答消息,当今还沒有分配内存。  
    11.         struct kvec             *resv = &rqstp->rq_res.head[0];  
    12.         struct svc_serv         *serv = rqstp->rq_服务器;  
    13.         u32                     dir;  
    14.   
    15.         /* 
    16.          * Setup response xdr_buf. 
    17.          * Initially it has just one page 
    18.          */  
    19.         rqstp->rq_resused = 1;  
    20.         // 为RPC应答消息分配内存  
    21.         resv->iov_base = page_address(rqstp->rq_respages[0]);  
    22.         resv->iov_len = 0;   
    23.         rqstp->rq_res.pages = rqstp->rq_respages + 1;  
    24.         rqstp->rq_res.len = 0;  
    25.         rqstp->rq_res.page_base = 0;  
    26.         rqstp->rq_res.page_len = 0;  
    27.         rqstp->rq_res.buflen = PAGE_SIZE;  
    28.         rqstp->rq_res.tail[0].iov_base = NULL;  
    29.         rqstp->rq_res.tail[0].iov_len = 0;  
    30.   
    31.         rqstp->rq_xid = svc_getu32(argv);   // 这里開始解析RPC报文了,依照RPC报文格式,这是RPC消息的XID。  
    32.   
    33.         dir  = svc_getnl(argv);   // 这个函数解析出了RPC保本中的第二个字段(Message Type)  
    34.         if (dir != 0) {           // RPC协议规定,RPC应答消息中Message Type字段必须为0.假设不是0就不处理了。  
    35.                 /* direction != CALL */  
    36.                 svc_printk(rqstp, "bad direction %d, dropping request ", dir);  
    37.                 serv->sv_stats->rpcbadfmt++;  
    38.                 svc_drop(rqstp);   // 丢弃这个RPC报文。  
    39.                 return 0;  
    40.         }  
    41.   
    42.         // 当今能够确定这是一个RPC请求报文了,调用svc_process_common()进行处理,这是RPC请求的主处理函数,而且会填充RPC应答报文。  
    43.         // 返回值1表示处理过程正常,已经正常填充了RPC应答消息,能够发送给client了。  
    44.         // 返回值0表示RPC请求报文格式异常,直接丢弃这个报文。  
    45.         /* Returns 1 for send, 0 for drop */  
    46.         if (svc_process_common(rqstp, argv, resv))  
    47.                 return svc_send(rqstp);    // 这是发送RPC应答消息的报文,不会深入分析这个函数了。  
    48.         else {  
    49.                 svc_drop(rqstp);    // 丢弃RPC报文。  
    50.                 return 0;  
    51.         }  
    52. }  

    svc_process_common是基本的处理函数,这个函数的定义例如以下:

    static int svc_process_common(struct svc_rqst *rqstp, struct kvec *argv, struct kvec *resv)

    參数rqstp 表示一个RPC请求。

    參数argv是一块缓存,这块缓存中保存了接收到的RPC请求报文。

    參数resv是一块缓存,这块缓存用来保存组装后的RPC应答报文。

    这个函数的处理流程就比較复杂了,基本上包括以下五个处理步骤:

    (1)组装RPC报文头基本信息

    (2)解析RPC服务信息

    (3)对用户身份进行验证

    (4)检查server端能否够处理这个RPC请求

    (5)处理RPC请求

    以下具体解说各个步骤

    (1)组装RPC报文头基本信息

    svc_process()中解析了RPC请求报文头的前两个字段(XID和Message Type),这里解析解析请求报文中的第3个字段,组装了应答报文中的前三个字段。处理程序例如以下:

    1. svc_putu32(resv, rqstp->rq_xid);        // 组装RPC应答报文中的第一个字段XID  
    2. vers = svc_getnl(argv);         // 解析RPC请求报文中的第三个字段 RPC Version  
    3.   
    4. /* First words of reply: */  
    5. svc_putnl(resv, 1);             // 组装RPC应答报文中第二个字段Message Type,RPC应答消息中这个字段固定为1.  
    6.   
    7. // 眼下通用的RPC版本号是2,server端仅仅支持RPC版本号2。假设不是版本号2,则不处理了。  
    8. if (vers != 2)          /* RPC version number */  
    9.         goto err_bad_rpc;  
    10.   
    11. // 组装RPC应答消息的第三个字段 Reply State,这里临时将这个字段设置为0了,表示正常。  
    12. // 但这不过一个临时值,假设后面用户验证过程出错了,会改动这个字段。  
    13. // reply_statp就是指向了这个字段的位置,当出错后能够依据reply_statp指针找到这个字段。  
    14. reply_statp = resv->iov_base + resv->iov_len;   // 写到这个位置了  
    15. svc_putnl(resv, 0);             /* ACCEPT */  


    (2)解析RPC服务信息

    1. // 解析RPC请求消息中第4个字段Program,这个字段是RPC服务程序(如NFS服务)的编号  
    2. rqstp->rq_prog = prog = svc_getnl(argv);        /* program number */  
    3. // 解析RPC请求消息中第5个字段Version,这个字段是RPC服务程序的版本   
    4. rqstp->rq_vers = vers = svc_getnl(argv);        /* version number */  
    5. // 解析RPC请求消息中第6个字段Procedure,这个字段是RPC服务例程的编号  
    6. rqstp->rq_proc = proc = svc_getnl(argv);        /* procedure number */  

    也非常easy,直接从RPC请求消息中提取数据就能够了。

    (3)对用户身份进行验证

    1. auth_res = svc_authenticate(rqstp, &auth_stat);  
    2. /* Also give the program a chance to reject this call: */  
    3. if (auth_res == SVC_OK && progp) {  
    4.         auth_stat = rpc_autherr_badcred;  
    5.         auth_res = progp->pg_authenticate(rqstp);  
    6. }  

    这里包括了两个函数svc_authenticate和progp->pg_authenticate。svc_authenticate的作用是解析RPC请求报文中的认证信息, progp->pg_authenticate的作用是依据解析出的信息对用户身份进行验证。这两个函数都和採用的认证方式有关,我们这里仅仅简介,下一篇文章中将以UNIX认证为例具体解说解析和认证过程。

    解析RPC消息中用户信息的函数是svc_authenticate()。rqstp是输入參数,表示一个RPC请求;auth_stat是输出參数,表示解析结果。这个函数的代码例如以下:

    1. int  
    2. svc_authenticate(struct svc_rqst *rqstp, __be32 *authp)  
    3. {  
    4.         rpc_authflavor_t        flavor;  
    5.         struct auth_ops         *aops;  
    6.   
    7.         *authp = rpc_auth_ok;  
    8.   
    9.         // 前面的函数已经解析出了RPC请求报文中前6个字段,以下该解析第7个字段Credential了。  
    10.         // Credential中第一个字段是Flavor,表示认证方式。  
    11.         flavor = svc_getnl(&rqstp->rq_arg.head[0]);  
    12.   
    13.         dprintk("svc: svc_authenticate (%d) ", flavor);  
    14.   
    15.         spin_lock(&authtab_lock);  
    16.         // 推断server端是否支持这样的认证方式  
    17.         // authtab[flavor]是这样的认证方式的操作函数集合  
    18.         if (flavor >= RPC_AUTH_MAXFLAVOR || !(aops = authtab[flavor]) ||  
    19.             !try_module_get(aops->owner)) {  
    20.                 spin_unlock(&authtab_lock);  
    21.                 *authp = rpc_autherr_badcred;   // 不支持这样的认证方式,这是错误码  
    22.                 return SVC_DENIED;  
    23.         }  
    24.         spin_unlock(&authtab_lock);  
    25.   
    26.         rqstp->rq_authop = aops;    // 认证方式的操作函数集合  
    27.         // 调用详细认证方式中的accept()函数解析RPC报文中的认证信息。  
    28.         // 每种认证方式都有自己的处理函数。  
    29.         return aops->accept(rqstp, authp);   
    30. }  

    这个函数的主要作用是解析RPC请求消息中的Credential字段和Verifier字段,然后填充RPC应答消息中的Verifier字段。这个函数仅仅解析了Credential中的第一个字段,这个字段表示认证类型,然后就调用对应认证方式中的函数进行处理了。眼下Linux中支持下列认证方式

    1. enum rpc_auth_flavors {  
    2.     RPC_AUTH_NULL  = 0,  
    3.     RPC_AUTH_UNIX  = 1,  
    4.     RPC_AUTH_SHORT = 2,  
    5.     RPC_AUTH_DES   = 3,  
    6.     RPC_AUTH_KRB   = 4,  
    7.     RPC_AUTH_GSS   = 6,  
    8.     RPC_AUTH_MAXFLAVOR = 8,  
    9.     /* pseudoflavors: */  
    10.     RPC_AUTH_GSS_KRB5  = 390003,  
    11.     RPC_AUTH_GSS_KRB5I = 390004,  
    12.     RPC_AUTH_GSS_KRB5P = 390005,  
    13.     RPC_AUTH_GSS_LKEY  = 390006,  
    14.     RPC_AUTH_GSS_LKEYI = 390007,  
    15.     RPC_AUTH_GSS_LKEYP = 390008,  
    16.     RPC_AUTH_GSS_SPKM  = 390009,  
    17.     RPC_AUTH_GSS_SPKMI = 390010,  
    18.     RPC_AUTH_GSS_SPKMP = 390011,  
    19. };  

    RPC_AUTH_MAXFLAVOR表示认证方式种类,以下的认证方式所有属于RPC_AUTH_GSS认证的子类。每种认证方式都须要实现以下的函数

    1. struct auth_ops {  
    2.         char *  name;  
    3.         struct module *owner;  
    4.         int     flavour;  
    5.         int     (*accept)(struct svc_rqst *rq, __be32 *authp);  
    6.         int     (*release)(struct svc_rqst *rq);  
    7.         void    (*domain_release)(struct auth_domain *);  
    8.         int     (*set_客户端)(struct svc_rqst *rq);  
    9. };  

    下篇文章中我们会具体介绍UNIX认证的操作过程,这里就不深入解说了。

    (4)检查server端能否够处理这个RPC请求

    1. progp = serv->sv_program;       // 取出这个端口中注冊的RPC服务处理程序  
    2.   
    3. // 每一个端口上能够注冊多种RPC服务,这些RPC服务的处理程序构成了一个链表,  
    4. // 遍历链表中的每一种处理程序,检查是否支持接收到的RPC请求。  
    5. for (progp = serv->sv_program; progp; progp = progp->pg_next)  
    6.         if (prog == progp->pg_prog)     // 比較程序编号,假设编号相等就表示找到处理程序了。  
    7.                 break;    
    8.   
    9. // progp就是找到的处理程序。假设遍历到链表结尾也沒有找到编号相等的处理程序,  
    10. // 则表示server不能处理这个请求。这样的情况下,因为已经遍历到链表结尾,progp就是NULL。  
    11. if (progp == NULL)      // 假设沒有处理例程,退出。  
    12.         goto err_bad_prog;  
    13.   
    14. // 对照完RPC程序编号后还须要对照程序版本,假设版本不相等也不能处理。  
    15. if (vers >= progp->pg_nvers ||  
    16.   !(versp = progp->pg_vers[vers]))  
    17.         goto err_bad_vers;  
    18.   
    19. // versp是RPC处理程序中的一个版本号,每一个版本号的处理程序中包括多个处理例程,  
    20. // procp依据例程编号找到了处理例程,须要检查server端是否实现了这个处理例程。  
    21. // procp->pc_func就是这个处理例程的处理函数了,假设server端沒有实现这个例程,  
    22. // 也直接退出。  
    23. procp = versp->vs_proc + proc;      // 取出处理例程  
    24. if (proc >= versp->vs_nproc || !procp->pc_func)  
    25.         goto err_bad_proc;  
    26. rqstp->rq_procinfo = procp;     // 设置处理程序  
    27.   
    28. /* Syntactic check complete */  
    29. serv->sv_stats->rpccnt++;  
    30.   
    31. /* Build the reply header. */  
    32. statp = resv->iov_base +resv->iov_len;  
    33. // 这是RPC应答消息的第5个字段Accept State,先初始化为RPC_SUCCESS。  
    34. // 假设这个RPC的处理过程出错了,会改动这个字段的值,改动为对应的错误码。  
    35. svc_putnl(resv, RPC_SUCCESS);       // 认证通过  

    该解释的内容都写在凝视中了,这里说明一下Linux中保存RPC处理程序的数据结构。首先是RPC例程的数据结构。

    1. struct svc_procedure {  
    2.         // 这是RPC请求的处理函数  
    3.         svc_procfunc            pc_func;        /* process the request */  
    4.         // 这是RPC请求的解码函数,RPC报文的内容是pc_func的參数,  
    5.         // 这个函数负责解析这些内容  
    6.         kxdrproc_t              pc_decode;      /* XDR decode args */  
    7.         // 这是RPC请求的编码函数,server端须要将pc_func的处理结果封装到  
    8.         // RPC应答报文中,这就是封装函数  
    9.         kxdrproc_t              pc_encode;      /* XDR encode result */  
    10.         // 这是释放内存的一个函数,由于pc_func可能须要分配额外的内存  
    11.         kxdrproc_t              pc_release;     /* XDR free result */  
    12.         // 这是RPC请求报文中数据的长度  
    13.         unsigned int            pc_argsize;     /* argument struct size */  
    14.         // 这是RPC应答报文中数据的长度  
    15.         unsigned int            pc_ressize;     /* result struct size */  
    16.         // 这是这个例程的调用次数,就是一个统计量  
    17.         unsigned int            pc_count;       /* call count */  
    18.         // 这是缓存类型,NFS中某些请求能够缓存处理结果。当再次接收到同样的请求后,  
    19.         // 就不处理了,直接将缓存中的数据返回给client就能够了。  
    20.         unsigned int            pc_cachetype;   /* cache info (NFS) */  
    21.         // 这是调整RPC应答消息缓存的一个数据量  
    22.         unsigned int            pc_xdrressize;  /* maximum size of XDR reply */  
    23. };  

    以下是RPC版本号的数据结构,一个版本号中包括多个例程。

    1. struct svc_version {  
    2.         // 版本号编号  
    3.         u32                     vs_vers;        /* version number */  
    4.         // 这个版本号中RPC例程的数量  
    5.         u32                     vs_nproc;       /* number of procedures */  
    6.         // 这里包括了各个RPC例程的处理函数,这里不是一个例程,  
    7.         // 这个版本号中全部例程的处理函数都在这里,各个例程按顺序排列。  
    8.         struct svc_procedure *  vs_proc;        /* per-procedure info */  
    9.         // 这也是从组装应答消息相关的缓存的一个长度  
    10.         u32                     vs_xdrsize;     /* xdrsize needed for this version */     
    11.   
    12.         // 假设这个值为1,就说明尽管定义了这个版本号的处理例程,可是不正确外提供服务  
    13.         unsigned int            vs_hidden : 1;  /* Don't register with 端口mapper. 
    14.                                                  * Only used for nfsacl so far. */  
    15.   
    16.         /* Override dispatch function (e.g. when caching replies). 
    17.          * A return value of 0 means drop the request. 
    18.          * vs_dispatch == NULL means use default dispatcher. 
    19.          */  
    20.         // 这是RPC请求的处理函数,简单来说就是依次调用svc_procedure中的pc_decode、pc_func、pc_encode函数.  
    21.         // NFS中这个函数是nfsd_dispatch().  
    22.         int                     (*vs_dispatch)(struct svc_rqst *, __be32 *);  
    23. };  

    最后是RPC服务程序的数据结构,每一个RPC服务程序包括多个版本号。

    1. struct svc_program {  
    2.         // 指向了下一套处理程序,能够将多套处理程序注冊在同一个端口上  
    3.         struct svc_program *    pg_next;        /* other programs (same xprt) */  
    4.         // RPC程序编号  
    5.         u32                     pg_prog;        /* program number */  
    6.         // 这是最低版本号  
    7.         unsigned int            pg_lovers;      /* lowest version */  
    8.         // 这是最高版本号  
    9.         unsigned int            pg_hivers;      /* lowest version */  
    10.         // 服务程序中版本号的数量  
    11.         unsigned int            pg_nvers;       /* number of versions */  
    12.         // 这是各个版本号处理程序的指针  
    13.         struct svc_version **   pg_vers;        /* version array */  
    14.         // RPC服务名称  
    15.         char *                  pg_name;        /* service name */  
    16.         // 属于某个类别,同类别的RPC服务共享同样的认证方式  
    17.         char *                  pg_class;       /* class name: services sharing authentication */  
    18.         // 这里包括了一些统计信息  
    19.         struct svc_stat *       pg_stats;       /* rpc statistics */  
    20.         // 这是RPC处理程序中验证用户信息的函数.    
    21.         int                     (*pg_authenticate)(struct svc_rqst *);  
    22. };  

    对于NFS服务来说,NFS服务相应的数据结构是svc_program。NFS眼下包括3个不同的版本号(NFSV2、NFSV3、NFSV4),每一个版本号相应一个svc_version结构。每一个版本号中包括多个处理例程,每一个处理例程相应一个svc_procedure结构。

    另一点须要注意,svc_program中包括一个函数pg_authenticate,须要注意这个函数和前面提到的认证方式中accept的差别。accept的作用是解析RPC报文中的认证信息,不过解析数据,可是不正确用户进行认证。pg_authenticate才是真正的认证函数。

    (5)处理RPC请求

    1. /* Call the function that processes the request. */  
    2. if (!versp->vs_dispatch) {  
    3.         /* Decode arguments */  
    4.         xdr = procp->pc_decode;         // 这是RPC请求的解码函数  
    5.         // 開始解码了,解析RPC报文中的数据  
    6.         if (xdr && !xdr(rqstp, argv->iov_base, rqstp->rq_argp))  
    7.                 goto err_garbage;  
    8.   
    9.         // 处理请求  
    10.         *statp = procp->pc_func(rqstp, rqstp->rq_argp, rqstp->rq_resp);  
    11.   
    12.         /* Encode reply */  
    13.         if (rqstp->rq_dropme) {  
    14.                 if (procp->pc_release)  
    15.                         procp->pc_release(rqstp, NULL, rqstp->rq_resp);  
    16.                 goto dropit;   
    17.         }         
    18.         // 编码处理结果,将处理结果封装到RPC应答消息中  
    19.         if (*statp == rpc_success &&  
    20.             (xdr = procp->pc_encode) &&  
    21.             !xdr(rqstp, resv->iov_base+resv->iov_len, rqstp->rq_resp)) {  
    22.                 dprintk("svc: failed to encode reply ");  
    23.                 /* serv->sv_stats->rpcsystemerr++; */  
    24.                 *statp = rpc_system_err;  
    25.         }         
    26. else {  
    27.         dprintk("svc: calling dispatcher ");  
    28.         if (!versp->vs_dispatch(rqstp, statp)) {    // 这个函数负责处理请求  
    29.                 /* Release reply info */   
    30.                 if (procp->pc_release)  
    31.                         procp->pc_release(rqstp, NULL, rqstp->rq_resp);  
    32.                 goto dropit;   
    33.         }         
    34. }  

    经过步骤1--步骤4的处理,我们已经解析了RPC请求报头的数据,找到了RPC请求的处理函数,最后一步就是開始处理这个请求了。处理一个RPC请求的函数是svc_version结构中的vs_dispatch函数。假设RPC程序未定义这个函数,就依照标准的流程进行处理。在标准的流程中,首先调用svc_procedure结构中的pc_decode函数,这个函数的内容是解析RPC报文的净荷,对于NFS服务来说,这个函数的作用就是解析RPC报文中的NFS数据,这些数据就是处理函数的參数。真正的处理函数是svc_procedure结构中的pc_func函数,每一个例程都须要定义自己的处理函数。处理完毕后,须要将处理结果封装在RPC应答报文中返回给client。比方对于READ操作,我们须要将读取的数据封装在RPC报文中返回,这个封装过程是由svc_procedure结构中的pc_encode函数实现的。

    NFS服务定义了自己的vs_dispatch函数,NFSV2、NFSV3、NFSV4使用了同一个vs_dispatch函数,这个函数的定义是nfsd_dispatch,这个函数定义在fs/nfsd/nfssvc.c中,处理流程基本上和上面讲的流程同样,就不解说了。

  • 相关阅读:
    Ant的实现原理
    单例模式
    Ant常用代码段
    [转]大象吃香蕉问题
    i++和++i探秘
    带滚动条的table
    公钥系统/数字签名/数字证书工作原理入门 [转]
    动态创建WPF 控件,并绑定指定Style
    C#如何使用帮助及如何关联到F1键
    Umbraco安装记录
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/3714316.html
Copyright © 2020-2023  润新知