NFS Client in Linux Kernel - Readdir

1. Open目录

调用nfs_opendir,主要目的是分配ctx,记录在filp->private_data中。其中包括dir_cookie值等。

struct nfs_open_dir_context {
    struct list_head list;
    struct rpc_cred *cred;
    unsigned long attr_gencount;
    __u64 dir_cookie; //初始值为0
    __u64 dup_cookie;
    signed char duped;
};

将open dir context和file关联起来

filp->private_data = ctx;

2. getdents系统调用

早期使用readdir系统调用,后改用getdents。具体参见man getdents
用户提供buffer,getdents将buffer填充成若干个linux_dirent,可以看出linux_dirent是变长的,长度记录在d_reclen中。

struct linux_dirent {
    long           d_ino;
    off_t          d_off;//其中.是1,..是2
    unsigned short d_reclen; //此结构大小
    char           d_name[];
};

3. pos vs cookie

  • dir_ctx->dir_cookie: 上一次最后一个entry的特征值
  • file->f_pos:已经读了多少个entry,是个序列号

getdents系统调用主要是调用文件系统提供的iterate接口,如NFS的nfs_readdir。每次调用getdents返回的结果和提供buffer大小有关,下次调用会接着读。但它如何知道上次读到什么位置呢?答案是dir_ctx->dir_cookiefile->f_pos
下面是内核的log,每次调用readdir,会触发一次readdir,这里的cookie其实是offset。

[  483.002377] NFS: readdir(/test) starting at cookie 0
...
[  483.026451] NFS: readdir(/test) starting at cookie 122

4. nfs_readdir的分析

  1. 构造一个nfs_readdir_descriptor_t类型的desc,描述要从哪开始找。对于pos是file->f_pos,对于cookie是dir_ctx->dir_cookie
  2. 调用readdir_search_pagecache,试图从page cache中寻找结果。
  3. page cache如果失效,会调用nfs_readdir_filler,进而调用nfs_readdir_xdr_to_array发送READDIR给server。结果会填充到这个失效的page。
  4. 调用nfs_do_filldir将查询到的结果,填充到user space。填充的时候,根据当前的desc,找得相应的page。将这个page对应的nfs_cache_array尽量全部拷贝到user space。重复步骤2,直到目录元素全部搞完。

4.1 nfs_readdir_descriptor_t

typedef struct {
    struct file *file;
    struct page *page;//page cache对应的page
    struct dir_context *ctx;
    unsigned long   page_index;
    u64     *dir_cookie;//next entry的cookie
    u64     last_cookie; //上一个entry的cookie是多少
    loff_t      current_index;//应该从page cache的第几个page里寻找
    decode_dirent_t decode;

    unsigned long   timestamp;
    unsigned long   gencount;
    unsigned int    cache_entry_index;
    unsigned int    plus:1;
    unsigned int    eof:1;
} nfs_readdir_descriptor_t; 

4.2 nfs_readdir_xdr_to_array分析

当出现page cache失效时候,会调用nfs_readdir_xdr_to_array填充这个失效的page。

  1. 调用nfs_readdir_alloc_pages分配8个page的内存, 用来保存READDIR的结果。
  2. 调用nfs_readdir_xdr_filler,发送READDIR给Server,结果存在刚才的8个page中。发之前会告诉server从哪开始读,即cookie。如果cookie是0,代表从头读。
  3. 调用nfs_readdir_page_filler,尽量将entry放入到array中。这个array对应的page是file_inode(desc->file)->i_mapping
  4. 重复步骤2,但每次发给Server的cookie值不一样。直到将array填满(104个),或者Server没有数据返回位置。
    NB: 对于8个page的结果,需要用xdr_decode才能使用,每调用一次decode,会输出一个nfs_entry

4.2.1 为什么array只能放104个元素

struct nfs_cache_array {
    int size;
    int eof_index;
    u64 last_cookie;
    struct nfs_cache_array_entry array[0];
};

nfs_cache_array对应一个page,而page大小是4K,所以这个Array放的entry是104个。所以一次RPC请求最多接收104个结果。
以下是RPC请求在内核的log,请求104个entry,使用8个page

[  483.015861] nfs4_xdr_enc_readdir: inlined page args = (104, ffff88003ade3ce0, 0, 32768)

4.2.2 为什么读一个nfs_cache_array需要用8个page

nfs_cache_array存放的是个精简的结果,而RPC请求里有很多其他的内容传送。

4.3 readdir_search_pagecache分析

可以将目录想象成一个文件,这个文件由若干个page组成,每个page记录这个一个nfs_cache_arrayreaddir_search_pagecache就是一个个遍历这些page,然后遍历array中的entry,寻找它需要的entry。
当找到时,会更新desc中的page_index,描述是在第几个page内。
当然最开始这些page都是失效的,失效时会触发操作系统分配实际page,并调用nfs_readdir_filler填充这个page。

4.4 nfs_do_filldir的分析

这时候的desc已经知道了到第几个page中的第几个offset去寻找,剩下的任务是从这开始填充结果到user space。

5 总结

getdents相当于把目录看成一个文件,文件由若干个page组成。每个page对应一个array,array中可以放104个entry。如果某个page cache失效,需要发送多次READDIR请求到server,将这个array填满。每次RPC请求,是根据last cookie为参数的。每次getdents返回的个数和提供的buffer大小有关,第二次调用getdents时候,能查询到上次查询到哪(pos),还能查询到上次最后一个entry的cookie。

你可能感兴趣的:(NFS Client in Linux Kernel - Readdir)