这篇文章中我们先说说deviceid。什么是deviceid呢?在使用pNFS的情况下,客户端执行I/O操作前需要先获取文件的layout。同时客户端需要获取DS的详细信息,比如DS的IP地址,否则客户端还是没办法发起I/O请求。deviceid就包含了DS的基本信息。客户端中deviceid用数据结构struct nfs4_deviceid表示:
#define NFS4_DEVICEID4_SIZE 16
struct nfs4_deviceid {
char data[NFS4_DEVICEID4_SIZE];
};
deviceid通过LAYOUTGET请求返回给客户端,但是好像从这个数据结构中我们看不到DS的信息。这是因为DS的信息比较多,全部信息放在这个数据结构中操作不方便。因此MDS在LAYOUTGET请求中只提供了一些基本信息,客户端不必(也没办法)解析deviceid。
客户端可能存在多个deviceid,为了便于管理,客户端定义了一个数据结构struct nfs4_deviceid_node,每个nfs4_deviceid_node结构表示一个deviceid。
struct nfs4_deviceid_node {
struct hlist_node node; // 链接到全局hash表中nfs4_deviceid_cache
struct hlist_node tmpnode; // 链接到一个临时链表中.
const struct pnfs_layoutdriver_type *ld; // layout类型
const struct nfs_client *nfs_client; // NFS客户端
unsigned long flags; // 一些标志位
unsigned long timestamp_unavailable; // 这是一个时间戳
struct nfs4_deviceid deviceid; // deviceid
atomic_t ref; // 引用计数
};
nfs4_deviceid_node中包含两个标志位:
enum {
NFS_DEVICEID_INVALID = 0, /* set when MDS clientid recalled */
NFS_DEVICEID_UNAVAILABLE, /* device temporarily unavailable */
};
NFS_DEVICEID_INVALID表示这是一个无效的deviceid,绝对不要使用这个deviceid了。NFS_DEVICEID_UNAVAILABLE表示这个deviceid有效,但是临时不可用,比如网络故障。如果在一定的时间内(120秒)网络正常了,客户端就会清除这个标志位。timestamp_unavailable就是设置标志位NFS_DEVICEID_UNAVAILABLE的时间。
客户端还定义了一个hash表nfs4_deviceid_cache,用来保存客户端所有的deviceid。static struct hlist_head nfs4_deviceid_cache[NFS4_DEVICE_ID_HASH_SIZE]; // #define NFS4_DEVICE_ID_HASH_SIZE 32
当接收到deviceid后客户端可以向MDS发起GETDEVICEINFO请求,这个请求会返回DS的详细信息。file layout中,GETDEVICEINFO请求返回的信息保存在数据结构struct nfs4_file_layout_dsaddr中了,这个数据结构定义如下:
struct nfs4_file_layout_dsaddr {
struct nfs4_deviceid_node id_node; // deviceid
u32 stripe_count; // Stripe Count
u8 *stripe_indices;// Stripe 信息
u32 ds_num; // DS组的数量
struct nfs4_pnfs_ds *ds_list[1]; // DS组中每个DS的地址信息
};
因此,前面介绍的数据结构nfs4_deviceid_node其实包含在数据结构nfs4_file_layout_dsaddr中。LAYOUTGET填充了nfs4_file_layout_dsaddr中的id_node字段,GETDEVICEINFO负责填充其他字段。前面的文章中介绍过,pNFS对DS进行了分组处理,每个DS组中包含多个DS。ds_list其实是一个指针数组,包含了pNFS中所有DS组的地址。struct nfs4_pnfs_ds是一个DS组的数据结构,定义如下:
struct nfs4_pnfs_ds {
// 将这个nfs4_pnfs_ds结构链接到全局链表nfs4_data_server_cache中.
struct list_head ds_node; /* nfs4_pnfs_dev_hlist dev_dslist */
// 这个字段应该是为调试准备的,没有实际用处.
char *ds_remotestr; /* comma sep list of addrs */
// 这是一个链表头,链表中保存的数据结构是nfs4_pnfs_ds_addr,每个nfs4_pnfs_ds_addr表示一个ds.
struct list_head ds_addrs;
// 这是为DS创建的客户端结构,这个客户端负责跟DS通信.
struct nfs_client *ds_clp;
atomic_t ds_count; // DS组中DS的数量.
};
因为DS组中包含了多个DS,索引struct nfs4_pnfs_ds中定义了一个链表,链表中每个元素代表一个DS。还需要注意的一点是ds_clp字段,按照RFC5661的规定,每个DS组中包含多个DS,客户端向组中每个DS发起I/O请求是等效的,因此客户端会随便挑选一个DS。但是发起I/O请求前需要先与这个DS客户端连接起来,ds_clp是发起I/O请求前创建的一个新的客户端,这个新客户端的作用是与DS通信。所有I/O请求通过这个新的客户端发送。
表示一个DS地址的数据结构是struct nfs4_pnfs_ds_addr,这个数据结构定义如下:struct nfs4_pnfs_ds_addr {
struct sockaddr_storage da_addr; // 这是一个DS的地址.
size_t da_addrlen; // DS地址的长度.
// 负责将这个DS链接到链表nfs4_pnfs_ds.ds_addrs中.
struct list_head da_node; /* nfs4_pnfs_dev_hlist dev_dslist */
// 这是字符串形式的DS地址,没什么用处.
char *da_remotestr; /* human readable addr+port */
};
现在可以介绍nfs4_find_get_deviceid()和filelayout_get_device_info()了。nfs4_find_get_deviceid()就是在nfs4_deviceid_cache中查找是否有符合条件的nfs4_deviceid_node结构。如果找到了,说明客户端以前获取过DS的地址信息了,就不需要发起GETDEVICEINFO请求了。查找条件有三个:客户端、layout类型、deviceid。如果没有找到符合条件的nfs4_deviceid_node结构,就说明客户端还没有获取DS的信息,这种情况下就会调用filelayout_get_device_info(),这个函数会发起GETDEVICEINFO请求,然后创建一个新的nfs4_file_layout_dsaddr结构,用应答报文中的信息填充这个结构,最后将这个结构添加到nfs4_deviceid_cache中。GETDEVICEINFO请求报文中需要包含下列数据:
struct GETDEVICEINFO4args {
deviceid4 gdia_device_id; // 这是一个deviceid,就是在请求这个device的详细信息。
layouttype4 gdia_layout_type; // layout类型
count4 gdia_maxcount; // 应答报文中数据的最大长度
bitmap4 gdia_notify_types; // 这是一种通知机制
};
gdia_notify_types表示一种通知机制。什么意思呢?当MDS中deviceid发生变化时MDS可以将这个事件告诉客户端,客户端接收到这个消息后就可以进行一些处理了。RFC5661定义了两种通知事件
NOTIFY_DEVICEID4_CHANGE 当deviceid发生变化时通知客户端
NOTIFY_DEVICEID4_DELETE 当deviceid删除时通知客户端
就不讲解GETDEVICEINFO请求的发起过程了。