這篇文章中我們接着講解服務器端的處理程序,首先介紹幾個與OPEN操作有關的數據結構。第1個數據結構是struct nfs4_openowner,這個數據結構跟客戶端的nfs4_state_owner相對應,保存了一個用戶的信息,這個數據結構的定義如下:
struct nfs4_openowner {
struct nfs4_stateowner oo_owner; /* must be first field */
// 每個nfs4_client結構中包含多個nfs4_openowner結構,這些結構構成了一個鏈表
// oo_perclient指向鏈表中相鄰的元素.
struct list_head oo_perclient;
/*
* We keep around openowners a little while after last close,
* which saves clients from having to confirm, and allows us to
* handle close replays if they come soon enough. The close_lru
* is a list of such openowners, to be reaped by the laundromat
* thread eventually if they remain unused:
*/
// 所有空閒未用的nfs4_openowner結構保存在一個全局鏈表(close_lru)中.
struct list_head oo_close_lru;
struct nfs4_ol_stateid *oo_last_closed_stid; // 最後使用的nfs4_ol_stateid結構
// 放置到close_lru鏈表中的時間.
time_t oo_time; /* time of placement on so_close_lru */
// 表示這個nfs4_openowner結構已經確認過了,可以使用了.
#define NFS4_OO_CONFIRMED 1 // OPEN_CONFIRM
// 設置這個標誌位後表示下次就需要釋放oo_last_closed_stid了
#define NFS4_OO_PURGE_CLOSE 2
// 這是一個新創建的nfs4_openowner結構,還不能使用.
#define NFS4_OO_NEW 4 // OPEN
unsigned char oo_flags;
};
nfs4_openowner結構中第一個字段的數據類型是nfs4_stateowner,這個結構的定義如下:
struct nfs4_stateowner {
// 所有的nfs4_stateowner(nfs4_openowner和nfs4_lockowner)保存在一個hash表中(ownerstr_hashtbl)
struct list_head so_strhash; /* hash by op_name */
// 這是一個鏈表,鏈表中的數據結構是nfs4_ol_stateid,表示了用戶打開的所有文件.
struct list_head so_stateids; //
struct nfs4_client * so_client; // 這個nfs4_stateowner結構所屬的客戶端
/* after increment in ENCODE_SEQID_OP_TAIL, represents the next
* sequence id expected from the client: */
// 這是一個序號,這個seqid和nfs4_state_owner->so_seqid.counter對應.
u32 so_seqid; // 每次操作都會更新seqid
// 這表示了一個用戶 open id:<server->s_dev><nfs_seqid_counter->create_time><nfs_seqid_counter->owner_id>
// so_owner保存了這次OPEN操作的名稱,從OPEN請求報文中解析得到的.
struct xdr_netobj so_owner; /* open owner name */
struct nfs4_replay so_replay;
bool so_is_open_owner; // 這是一個nfs4_opwnowner結構
};
因此nfs4_stateowner結構中的信息也是nfs4_openowner結構中的信息,那麼爲什麼要把這些信息獨立出來組成一個新的數據結構呢?因爲我們現在只講到了OPEN操作,還沒有講解LOCK操作。服務器端保存LOCK信息的數據結構是nfs4_lockowner,nfs4_openowner和nfs4_lockowner有些相似之處,這些相似的信息提取出來構成了nfs4_stateowner。
與客戶端中nfs4_state相對應的數據結構是nfs4_ol_stateid,這個數據結構保存了一次OPEN操作的信息,這個數據結構的定義如下:
struct nfs4_ol_stateid {
// 這裏包含了這個stateid的類型和歸屬的客戶端(nfs4_client)
struct nfs4_stid st_stid; /* must be first field */
// 同一個nfs4_file結構中所有的nfs4_ol_stateid結構構成了一個鏈表。
// st_perfile指向了鏈表中相鄰的元素.
struct list_head st_perfile;
// 一個nfs4_stateowner結構中包含多個nfs4_ol_stateid結構,st_perstateowner指向鏈表中下一個元素。
struct list_head st_perstateowner;
// 這是一個鏈表,鏈表中的數據結構是nfs4_lockowner.
struct list_head st_lockowners;
// 屬於的nfs4_stateowner結構,有可能是nfs4_openowner結構,也有可能是nfs4_lockowner結構.
struct nfs4_stateowner * st_stateowner;
// 這個nfs4_ol_stateid結構所屬於的nfs4_file
struct nfs4_file * st_file;
// 這是訪問權限標誌位
unsigned long st_access_bmap; // NONE READ WRITE BOTH
unsigned long st_deny_bmap; // NOEN READ WRITE BOTH
// 如果這是一個(nfs4_lockowner-->nfs4_ol_stateid),指向所在的(nfs4_openlock->nfs4_ol_stateid)
struct nfs4_ol_stateid * st_openstp;
};
NFS服務器端還定義了一個數據結構nfs4_file,這個數據結構表示一個文件,包含了文件的一些信息,如文件索引節點等。每個文件用一個nfs4_file結構表示,這個數據結構定義如下:
struct nfs4_file {
atomic_t fi_ref; // 這個結構的引用計數
// fi_hash將這個nfs4_file結構鏈接到file_hashtbl中
// file_hashtbl是一個全局hash表,保存了系統中所有的nfs4_file結構.
struct list_head fi_hash; /* hash by "struct inode *" */
// 這個文件上關聯了很多個nfs4_ol_stateid結構,這是鏈表頭.
struct list_head fi_stateids;
// 這個文件上還關聯了很多nfs4_delegation結構,這些delegation共用一個租借鎖
// 比如多個客戶端以只讀權限打開同一個文件,這是delegation的鏈表頭.
struct list_head fi_delegations;
/* One each for O_RDONLY, O_WRONLY, O_RDWR: */
// 這個文件可能打開了多次,有多個文件對象結構,保存在這裏.
struct file * fi_fds[3];
/*
* Each open or lock stateid contributes 0-4 to the counts
* below depending on which bits are set in st_access_bitmap:
* 1 to fi_access[O_RDONLY] if NFS4_SHARE_ACCES_READ is set
* + 1 to fi_access[O_WRONLY] if NFS4_SHARE_ACCESS_WRITE is set
* + 1 to both of the above if NFS4_SHARE_ACCESS_BOTH is set.
*/
// 這是各種權限的引用計數.
atomic_t fi_access[2];
// 關聯到的文件對象
struct file *fi_deleg_file;
// 這是一個租借鎖,NFSv4用租借鎖機制實現delegation
// 所有的delegation共用同一個租借鎖
struct file_lock *fi_lease; // 這是一個文件鎖結構
// 這應該是fi_lease指針的引用數量,這是一個租借鎖
atomic_t fi_delegees;
struct inode *fi_inode; // 這是文件的索引節點結構
// 文件鎖是否發生了衝突,當服務器刪除租借鎖時就會先將fi_had_conflict設置爲true,
// 這時客戶端就不能申請delegation了.
bool fi_had_conflict;
};
struct nfsd4_open {
// CLAIM_DELEGATE_CUR
u32 op_claim_type; /* request */
struct xdr_netobj op_fname; /* request - everything but CLAIM_PREV */
u32 op_delegate_type; /* request - CLAIM_PREV only */
// NFS4_OPEN_CLAIM_DELEGATE_CUR中這是客戶端傳過來的delegation的stateid
stateid_t op_delegate_stateid; /* request - response */
u32 op_why_no_deleg; /* response - DELEG_NONE_EXT only */
u32 op_create; /* request */
u32 op_createmode; /* request */
u32 op_bmval[3]; /* request */
struct iattr iattr; /* UNCHECKED4, GUARDED4, EXCLUSIVE4_1 */
nfs4_verifier op_verf __attribute__((aligned(32)));
/* EXCLUSIVE4 */
clientid_t op_clientid; /* request */
struct xdr_netobj op_owner; /* request */
u32 op_seqid; /* request */
u32 op_share_access; /* request */
u32 op_share_deny; /* request */
u32 op_deleg_want; /* request */
stateid_t op_stateid; /* response */
u32 op_recall; /* recall */
struct nfsd4_change_info op_cinfo; /* response */
u32 op_rflags; /* response */
bool op_truncate; /* used during processing */
bool op_created; /* used during processing */
struct nfs4_openowner *op_openowner; /* used during processing */
struct nfs4_file *op_file; /* used during processing */
struct nfs4_ol_stateid *op_stp; /* used during processing */
struct nfs4_acl *op_acl;
};
由於OPEN操作包含了多種情況,因此這個數據結構比較複雜。我們首先分析最基本的情況:打開一個服務器端存在的文件。與這個情況相關的幾個字段是
struct nfsd4_open {
// CLAIM類型 CLAIM_NULL
u32 op_claim_type; /* request */
// 這是要打開文件的名稱
struct xdr_netobj op_fname; /* request - everything but CLAIM_PREV */
// 這是客戶端的clientid
clientid_t op_clientid; /* request */
// 這是owner的名稱,確定了一個nfs4_openowner結構.
struct xdr_netobj op_owner; /* request */
// 這是序列號
u32 op_seqid; /* request */
// 用戶請求的訪問權限
u32 op_share_access; /* request */
// 0x00000000 OPEN4_SHARE_DENY_NONE
u32 op_share_deny; /* request */
// 這是stateid,在應答消息中返回給客戶端
stateid_t op_stateid; /* response */
// 這個父目錄的信息,在應答消息中返回給客戶端
struct nfsd4_change_info op_cinfo; /* response */
// 這是應答消息中的標誌信息
u32 op_rflags; /* response */
// 這是這次操作所屬用戶的信息
struct nfs4_openowner *op_openowner; /* used during processing */
// 關聯了文件信息,如文件索引節點等。
struct nfs4_file *op_file; /* used during processing */
// OPEN請求處理過程中使用的一個字段,查找是否存在stateid相同的nfs4_ol_stateid結構
struct nfs4_ol_stateid *op_stp; /* used during processing */
};
OPEN請求的處理函數是nfsd4_open()
static __be32
nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_open *open)
這個函數根據參數open中的信息創建或者更新服務器端與本次OPEN操作相關的數據結構,打開用戶請求的文件。由於OPEN操作包含多種情況,因此這個函數比較複雜,包含多個case語句。我們先介紹最常見的情況:打開服務器端存在的一個文件,後面的文章會對這個函數進行總結。簡化後這個函數流程如下:
static __be32
nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_open *open)
{
__be32 status;
struct nfsd4_compoundres *resp;
nfs4_lock_state();
/* check seqid for replay. set nfs4_owner */
resp = rqstp->rq_resp; // 這是保存返回值的緩存
// 這個函數主要在準備nfs4_openowner結構和nfs4_ol_stateid結構
// nfs4_openowner表示一個用戶 nfs4_ol_stateid表示一次OPEN過程
status = nfsd4_process_open1(&resp->cstate, open);
if (status)
goto out;
// 打開文件,cstate->current_fh是父目錄的文件句柄.
status = do_open_lookup(rqstp, &cstate->current_fh, open);
if (status)
goto out;
// 這個函數在設置stateid以及delegation.
status = nfsd4_process_open2(rqstp, &cstate->current_fh, open);
out:
nfsd4_cleanup_open_state(open, status); // 前面過程出錯了,進行清理工作.
if (open->op_openowner)
cstate->replay_owner = &open->op_openowner->oo_owner;
else
nfs4_unlock_state();
return status;
}
nfs4_openowner表示一個用戶,nfs4_ol_stateid表示一次OPEN過程。如果一個用戶執行了多個OPEN操作,則服務器端會創建多個nfs4_ol_stateid結構,共享同一個nfs4_openowner結構。nfsd4_process_open1()檢查服務器端是否已經爲用戶創建了nfs4_openower結構。如果沒有(第一次執行OPEN操作)就創建一個新的nfs4_openower結構。然後創建一個新的nfs4_ol_stateid結構代表本次OPEN操作。其實並不是每次操作都需要創建一個nfs4_ol_stateid結構,如果用戶先以讀權限打開文件,然後以寫權限再次打開權限,則第OPEN操作過程中並沒有創建nfs4_ol_stateid結構,而是修改了第一次OPEN操作中的nfs4_ol_stateid的信息。
nfsd4_open --> nfsd4_process_open1
__be32
nfsd4_process_open1(struct nfsd4_compound_state *cstate,
struct nfsd4_open *open)
{
// 取出clientid,這代表一個NFS客戶端.
clientid_t *clientid = &open->op_clientid; // 取出clientid.
struct nfs4_client *clp = NULL;
unsigned int strhashval;
struct nfs4_openowner *oo = NULL;
__be32 status;
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
// 檢查clientid是否已經過期. open->op_clientid.cl_boot是服務器的啓動時間.
if (STALE_CLIENTID(&open->op_clientid, nn)) // clientid中包含了服務器端NFS服務的啓動時間
return nfserr_stale_clientid; // clientid過期了,說明服務器已經重啓了.
/*
* In case we need it later, after we've already created the
* file and don't want to risk a further failure:
*/
open->op_file = nfsd4_alloc_file(); // 創建一個新的nfs4_file結構
if (open->op_file == NULL)
return nfserr_jukebox; // 分配內存失敗了
// 計算用戶的hash值
// clientid->cl_id表示了一個NFS客戶端 open->op_owner表示客戶端一個用戶.
strhashval = ownerstr_hashval(clientid->cl_id, &open->op_owner);
// 查找是否存在符合條件的nfs4_openowner結構,如果不存在就返回NULL
// 查找條件是兩個 open->op_owner和open->op_clientid,這對應於一個NFS客戶端中的特定的用戶了.
// 所有nfs4_openowner保存在全局鏈表ownerstr_hashtbl中了.
oo = find_openstateowner_str(strhashval, open);
open->op_openowner = oo; // nfs4_openowner結構
if (!oo) { // 不存在
clp = find_confirmed_client(clientid); // 找到客戶端
if (clp == NULL)
return nfserr_expired; // 客戶端不存在
goto new_owner; // 創建一個新的nfs4_openowner結構
}
// 存在符合條件的nfs4_openowner結構,還沒有經過CONFIRM確認.
// 那我們就釋放掉這個nfs4_openowner,然後創建一個新的nfs4_openowner結構.
if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {
/* Replace unconfirmed owners without checking for replay. */
clp = oo->oo_owner.so_client; // 取出客戶端結構
release_openowner(oo); // 既然還沒有經過確認,就可以釋放掉。
open->op_openowner = NULL;
goto new_owner;
}
// 存在符合條件的nfs4_openowner,並且經過確認了. 一個人打開多個文件. 還需要檢查seqid
// 順序必須一致
status = nfsd4_check_seqid(cstate, &oo->oo_owner, open->op_seqid);
if (status)
return status;
clp = oo->oo_owner.so_client;
goto alloc_stateid;
new_owner:
// 服務器中不存在符合<客戶端、用戶>的nfs4_openowner結構,創建一個新的nfs4_openowner結構
oo = alloc_init_open_stateowner(strhashval, clp, open);
if (oo == NULL)
return nfserr_jukebox;
open->op_openowner = oo; // 設置nfs4_openowner結構
alloc_stateid:
open->op_stp = nfs4_alloc_stateid(clp); // 分配一個新的nfs4_ol_stateid結構 對應客戶端的nfs4_state結構.
if (!open->op_stp) // 分配nfs4_ol_stateid失敗.
return nfserr_jukebox;
return nfs_ok;
}
查找用戶信息數據結構的函數是find_openstateowner_str()。系統中所有的nfs4_openowner和nfs4_lockowner結構保存在一個全局hash表ownerstr_hashtbl中。find_openstateowner_str()遍歷鏈表中的元素,比較客戶端信息和用戶信息。
nfsd4_open --> nfsd4_process_open1 --> find_openstateowner_str
static struct nfs4_openowner *
find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open)
{
struct nfs4_stateowner *so;
struct nfs4_openowner *oo;
// 這個鏈表中保存的是nfs4_stateowner結構,nfs4_stateowner是nfs4_openowner結構中第一個字段
// 遍歷鏈表中每一個元素
list_for_each_entry(so, &ownerstr_hashtbl[hashval], so_strhash) {
if (!so->so_is_open_owner) // 這不是nfs4_openowner結構,查找下一個元素.
continue;
// 客戶端相同,用戶相同,這就是符合條件的stateowner結構
// 這裏只需要比較clientid和owner兩項就可以了
if (same_owner_str(so, &open->op_owner, &open->op_clientid)) {
oo = openowner(so); // 根據nfs4_stateowner結構找到nfs4_openowner結構
renew_client(oo->oo_owner.so_client); // 檢查、更新這個客戶端的信息
return oo;
}
}
return NULL;
}
NFSv4需要保證客戶端的操作序列化。如果查找到了用戶信息的數據結構,還需要檢查序列號是否相同,這是通過函數nfsd4_check_seqid()實現的。這個函數比較簡單,就是將nfs4_openowner結構中保存的序號和OPEN請求報文中傳遞過來的序號進行比較。
nfsd4_open --> nfsd4_process_open1 --> nfsd4_check_seqid
static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4_stateowner *so, u32 seqid)
{
if (nfsd4_has_session(cstate))
return nfs_ok;
if (seqid == so->so_seqid - 1)
return nfserr_replay_me;
if (seqid == so->so_seqid)
return nfs_ok;
return nfserr_bad_seqid;
}
如果用戶第一次打開文件,則find_openstateowner_str()的返回值是NULL,需要創建一個新的nfs4_openowner結構,這個函數是alloc_init_open_stateowner()。這個函數也很簡單,創建一個nfs4_openowner結構,然後添加到兩個鏈表中。
nfsd4_open --> nfsd4_process_open1 --> alloc_init_open_stateowner
static struct nfs4_openowner *
alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) {
struct nfs4_openowner *oo;
// 爲nfs4_openowner結構分配內存
oo = alloc_stateowner(openowner_slab, &open->op_owner, clp);
if (!oo)
return NULL;
oo->oo_owner.so_is_open_owner = 1; // 這是一個nfs4_openowner結構
oo->oo_owner.so_seqid = open->op_seqid; // 這是一個序號 初始化爲客戶端傳遞過來的序號.
oo->oo_flags = NFS4_OO_NEW; // 這是新創建的一個結構
oo->oo_time = 0; // 這個nfs4_opwnowner結構最後的更新時間
oo->oo_last_closed_stid = NULL; // oo_last_closed_stid表示這個用戶最後一個使用的nfs4_ol_stateid結構.
INIT_LIST_HEAD(&oo->oo_close_lru); // 當nfs4_openowner不再使用後會放入到LRU鏈表close_lru中
// 將這個nfs4_openowner結構添加到兩個鏈表中:ownerstr_hashtbl和clp->cl_openowners
hash_openowner(oo, clp, strhashval);
return oo;
}
hash_openowner()將新創建的nfs4_openowner結構添加到了兩個鏈表中:
ownerstr_hashtbl:這是一個全局hash表,保存了系統中所有的nfs4_stateowner結構。nfs4_stateowner是nfs4_openowner結構和nfs4_lockowner結構中的第一個字段,因此ownerstr_hashtbl保存的是系統中所有的nfs4_openowner和nfs4_lockowner結構。
nfs4_client->cl_openowners:nfs4_client表示一個NFS客戶端,這個客戶端中包含多個用戶,同一個客戶端中的nfs4_openowner結構保存在這個鏈表中了。
創建nfs4_ol_stateid的函數是nfs4_alloc_stateid(),nfsd4_process_open1()沒有檢查是否存在符合條件的nfs4_ol_stateid,而是直接創建了一個新的nfs4_ol_stateid結構。因爲nfsd4_process_open1()只是一個準備函數,nfsd4_process_open2()進行了進一步檢查。如果存在符合條件的nfs4_ol_stateid結構,就會釋放這裏的nfs4_ol_stateid結構了。
同時nfsd4_process_open1()還創建了一個新的nfs4_file結構,創建nfs4_file結構的函數是nfsd4_alloc_file(),這個函數也很簡單,直接分配內存就可以了。
do_open_lookup()的作用是查找用戶請求的文件是否存在,然後組裝出這個文件的文件句柄。不考慮創建新文件的情況,簡化後的代碼如下:
參數current_fh是輸入參數,表示父目錄的文件句柄;同時還是輸出參數,函數結束後保存了目標文件的文件句柄。
參數open是輸入參數,保存了用戶請求文件的名稱。
nfsd4_open --> do_open_lookup
static __be32
do_open_lookup(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)
{
struct svc_fh *resfh;
__be32 status;
// 爲目標文件申請文件句柄
resfh = kmalloc(sizeof(struct svc_fh), GFP_KERNEL);
if (!resfh)
return nfserr_jukebox;
fh_init(resfh, NFS4_FHSIZE); // 初始化文件句柄
open->op_truncate = 0;
// 查找並打開指定的文件,然後組裝出了文件句柄.
// current_fh是父目錄的文件句柄,
// open->op_fname.data是目標文件的名稱
// open->op_fname.len表示目標文件名稱的長度
// resfh是輸出參數,表示目標文件的文件句柄
status = nfsd_lookup(rqstp, current_fh,
open->op_fname.data, open->op_fname.len, resfh);
fh_unlock(current_fh);
if (status)
goto out;
if (!open->op_created)
// 檢查用戶的訪問權限
status = do_open_permission(rqstp, resfh, open,
NFSD_MAY_NOP);
// 獲取父目錄的文件屬性,current_fh表示父目錄的文件句柄
set_change_info(&open->op_cinfo, current_fh);
fh_dup2(current_fh, resfh); // 現在current_fh就表示目標文件的文件句柄了
out:
fh_put(resfh);
kfree(resfh);
return status;
}
nfsd4_open()最後一個主要操作函數是nfsd4_process_open2(),nfsd4_process_open1()中只爲nfs4_ol_stateid分配了內存,但是沒有填充內容。nfsd4_process_open2()根據do_open_lookup()的結果填充nfs4_ol_stateid。nfsd4_process_open2()做的第二件事情是檢查是否可以給客戶端分配delegation,先跳過這部分內容,後面講解。這個函數的代碼如下:
nfsd4_open --> nfsd4_process_open2
參數rqstp:表示一次RPC請求
參數current_fh:這是目標文件的文件句柄
參數open:包含了本次OPEN操作中的信息
__be32
nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)
{
struct nfsd4_compoundres *resp = rqstp->rq_resp;
struct nfs4_client *cl = open->op_openowner->oo_owner.so_client; // 這是NFS客戶端
struct nfs4_file *fp = NULL;
struct inode *ino = current_fh->fh_dentry->d_inode; // 這是文件的索引節點
struct nfs4_ol_stateid *stp = NULL;
struct nfs4_delegation *dp = NULL;
__be32 status;
/*
* Lookup file; if found, lookup stateid and check open request,
* and check for delegations in the process of being recalled.
* If not found, create the nfs4_file struct
*/
// nfs4_file結構和一個文件對應. ino是目標文件的索引節點結構.
// 系統中所有的nfs4_file結構保存在全局hash表file_hashtbl中了,遍歷這個hash表,如果fp->fi_inode == ino就找到了.
fp = find_file(ino); // 檢查是否已經打開了這個文件
if (fp) { // 這個文件已經打開了
// 檢查用戶信息
if ((status = nfs4_check_open(fp, open, &stp))) // nfs4_ol_stateid
goto out; // 訪問權限發生了衝突.
// 如果設置了delegation,現在就找到delegation了.
status = nfs4_check_deleg(cl, fp, open, &dp); // nfs4_delegation
if (status)
goto out;
} else { // 這是第一次打開
status = nfserr_bad_stateid;
if (nfsd4_is_deleg_cur(open))
goto out;
status = nfserr_jukebox;
fp = open->op_file; // 這是新創建的一個nfs4_file結構,在nfsd4_process_open1()中創建的.
open->op_file = NULL; // 這裏也置空了.
nfsd4_init_file(fp, ino); // 根據inode初始化fp
}
/*
* OPEN the file, or upgrade an existing OPEN.
* If truncate fails, the OPEN fails.
*/
if (stp) { // 已經找到nfs4_ol_stateid結構了,不需要創建了.
// 更新stp中的信息
/* Stateid was found, this is an OPEN upgrade */
status = nfs4_upgrade_open(rqstp, fp, current_fh, stp, open);
if (status)
goto out;
} else { // 沒有找到,需要創建一個新的nfs4_ol_stateid結構.
// 打開文件,關聯nfs4_file結構和文件對象結構
status = nfs4_get_vfs_file(rqstp, fp, current_fh, open);
if (status)
goto out;
status = nfsd4_truncate(rqstp, current_fh, open);
if (status)
goto out;
stp = open->op_stp; // 這是nfs4_ol_stateid結構
open->op_stp = NULL; // 這裏就置空了.
// 根據fp和open中的信息初始化stp
// 一個nfs4_file結構中包含多個nfs4_ol_stateid結構
init_open_stateid(stp, fp, open); // 這個時候才設置
}
update_stateid(&stp->st_stid.sc_stateid); // 更新這個stateid
memcpy(&open->op_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
if (nfsd4_has_session(&resp->cstate)) {
open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED;
if (open->op_deleg_want & NFS4_SHARE_WANT_NO_DELEG) {
open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
open->op_why_no_deleg = WND4_NOT_WANTED;
goto nodeleg;
}
}
/*
* Attempt to hand out a delegation. No error return, because the
* OPEN succeeds even if we fail.
*/
// 這個函數在處理delegation. 這裏才處理delegation.
nfs4_open_delegation(SVC_NET(rqstp), current_fh, open, stp);
nodeleg:
status = nfs_ok;
dprintk("%s: stateid=" STATEID_FMT "\n", __func__,
STATEID_VAL(&stp->st_stid.sc_stateid));
out:
/* 4.1 client trying to upgrade/downgrade delegation? */
if (open->op_delegate_type == NFS4_OPEN_DELEGATE_NONE && dp &&
open->op_deleg_want)
nfsd4_deleg_xgrade_none_ext(open, dp);
if (fp)
put_nfs4_file(fp);
if (status == 0 && open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS)
nfs4_set_claim_prev(open, nfsd4_has_session(&resp->cstate));
/*
* To finish the open response, we just need to set the rflags.
*/
// 下面在設置result flags
// oo_flags是nfs4_openowner的標誌位信息.
// 當創建一個新的nfs4_openowner結構時只設置了標誌位NFS4_OO_NEW
open->op_rflags = NFS4_OPEN_RESULT_LOCKTYPE_POSIX;
if (!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED) &&
!nfsd4_has_session(&resp->cstate))
open->op_rflags |= NFS4_OPEN_RESULT_CONFIRM; // 需要執行OPEN_CONFIRM請求後纔可以使用這個文件.
return status;
}
nfsd4_process_open2()首先檢查服務器端是否已經打開了客戶端請求的文件,比如同一個用戶多次打開一個文件,或者其他用戶已經打開這個文件了。這是通>過函數find_file()實現的,函數代碼如下:
nfsd4_open --> nfsd4_process_open2 --> find_file
參數ino:目標文件的索引節點結構
static struct nfs4_file *find_file(struct inode *ino)
{
unsigned int hashval = file_hashval(ino);
struct nfs4_file *fp;
spin_lock(&recall_lock);
list_for_each_entry(fp, &file_hashtbl[hashval], fi_hash) {
if (fp->fi_inode == ino) {
get_nfs4_file(fp);
spin_unlock(&recall_lock);
return fp;
}
}
spin_unlock(&recall_lock);
return NULL;
}
NFS服務器端,每個打開的文件用數據結構nfs4_file表示,系統中所有的nfs4_file結構保存在全局hash表file_hashtbl中。find_file()首先計算出文件的
hash值,然後遍歷這個鏈表中的nfs4_file結構。如果索引節點結構相同就表示這個文件已經打開了。如果文件還沒有打開就返回NULL。根據find_file()的結構,下面的程序分爲三種情況:
(1)這是第一次OPEN操作,文件還沒有打開。這種情況下需要打開文件,然後創建一個新的nfs4_file結構,然後根據文件的信息填充這個結構。還需要創建一個新的nfs4_ol_stateid結構代表這次OPEN操作,然後根據文件的信息填充這個結構。
(2)文件已經打開了,但是是其他用戶打開的。這種情況需要更新文件的nfs4_file結構。還需要創建一個新的nfs4_ol_stateid結構代表本次OPEN操作,然後根據文件的信息填充這個結構。
(3)文件已經打開了,並且是本次OPEN操作的用戶打開的。這種情況下需要更新文件的nfs4_file結構,還需要更新用戶的nfs4_ol_stateid結構。
上面三種情況中新的nfs4_file結構和新的nfs4_ol_stateid結構並不是在nfsd4_process_open2()中創建的,而是在nfsd4_process_open1()中創建的,分別保存在了open->op_file和open->op_stp。
按照上面三種情況,如果文件已經打開了,需要檢查是本次OPEN操作的用戶打開的,還是其他用戶打開的。這個檢查工作是由函數nfs4_check_open()完>成的,代碼如下:
nfsd4_open --> nfsd4_process_open2 --> nfs4_check_open
參數fp:這是一個文件的數據結構
參數open:包含了本次OPEN操作的信息,特別是保存了本次OPEN操作的用戶信息。
參數stpp:這是一個輸出參數。如果本次OPEN操作的用戶以前打開過這個文件則返回對應的nfs4_ol_stateid指針,否則返回NULL。
static __be32
nfs4_check_open(struct nfs4_file *fp, struct nfsd4_open *open, struct nfs4_ol_stateid **stpp)
{
struct nfs4_ol_stateid *local;
struct nfs4_openowner *oo = open->op_openowner; // 這是用戶的信息.
// 遍歷nfs4_file結構中每一個nfs4_ol_stateid結構
list_for_each_entry(local, &fp->fi_stateids, st_perfile) {
/* ignore lock owners */ // 這不是一個open stateid.
if (local->st_stateowner->so_is_open_owner == 0)
continue; // 這是一個lock stateid.
/* remember if we have seen this open owner */
if (local->st_stateowner == &oo->oo_owner) // 屬於同一個用戶.
*stpp = local; // 找到了.
/* check for conflicting share reservations */
if (!test_share(local, open))
return nfserr_share_denied; // 訪問訪問權限發生了衝突。
}
return nfs_ok;
}
NFS文件系統中多個用戶可以同時打開一個文件,每次打開過程用數據結構nfs4_ol_stateid結構表示。多個用戶還可以同時對一個文件加鎖,每次加鎖過程用數據結構也用數據結構nfs4_ol_stateid表示。這些nfs4_ol_stateid結構構成了一個鏈表,鏈表頭是nfs4_file結構的fi_stateids。nfs4_check_open()遍歷鏈表中每個元素,如果這個nfs4_ol_stateid結構的用戶是本次OPEN操作的用戶,就表示這個用戶以前已經打開過這個文件了。
初始化一個新的nfs4_file結構的函數是nfsd4_init_file(),這個函數的代碼如下:
nfsd4_open --> nfsd4_process_open2 --> nfsd4_init_file
static void nfsd4_init_file(struct nfs4_file *fp, struct inode *ino)
{
unsigned int hashval = file_hashval(ino); // 計算hash值
atomic_set(&fp->fi_ref, 1); // 初始化引用計數爲1
INIT_LIST_HEAD(&fp->fi_hash);
INIT_LIST_HEAD(&fp->fi_stateids); // 這是一個鏈表頭,關聯了這個文件上所有的nfs4_ol_stateid結構
INIT_LIST_HEAD(&fp->fi_delegations); // 這是一個鏈表頭,關聯了這個文件上所有的nfs4_delegation結構
fp->fi_inode = igrab(ino); // 關聯文件索引節點
fp->fi_had_conflict = false; // 沒有發生衝突
fp->fi_lease = NULL; // 這是一個租借鎖,Linux中依靠租借鎖實現delegation,所有的delegation共享同一個租借鎖
memset(fp->fi_fds, 0, sizeof(fp->fi_fds));
memset(fp->fi_access, 0, sizeof(fp->fi_access));
spin_lock(&recall_lock);
list_add(&fp->fi_hash, &file_hashtbl[hashval]); // 添加到全局hash表file_hashtbl中
spin_unlock(&recall_lock);
}
更新nfs4_file結構的函數是nfs4_get_vfs_file(),這個函數檢查文件是否以本次OPEN操作請求的權限打開了。如果文件已經打開了,但是訪問權限不匹配,就調用函數nfsd_open()以相應的訪問權限再次打開文件,然後調用nfs4_file_get_access()增加相應權限的計數。
nfsd4_open --> nfsd4_process_open2 --> nfs4_get_vfs_file
static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
struct svc_fh *cur_fh, struct nfsd4_open *open)
{
__be32 status;
int oflag = nfs4_access_to_omode(open->op_share_access);
int access = nfs4_access_to_access(open->op_share_access);
if (!fp->fi_fds[oflag]) { // 文件沒有以OPEN操作請求的權限打開
// 以OPEN操作請求的權限打開文件,獲取文件的文件對象結構。
status = nfsd_open(rqstp, cur_fh, S_IFREG, access,
&fp->fi_fds[oflag]);
if (status)
return status;
}
nfs4_file_get_access(fp, oflag); // 增加相應訪問權限的計數
return nfs_ok;
}
再次看看nfs4_file結構。
struct nfs4_file {
// 這裏保存了三個文件對象結構,fi_fds[0]表示只讀權限的文件對象,fi_fds[1]表示只寫權限的文件對象,fi_fds[2]表示讀寫權限的文件對象。
struct file * fi_fds[3];
// 這是文件訪問權限的引用計數。fi_access[0]表示以讀權限打開這個文件的次數,fi_access[1]表示以寫權限打開這個文件的計數。
// 如果以只讀權限打開文件則增加fi_access[0]的計數,如果以只寫權限打開文件則增加fi_access[1]的計數,如果以讀寫權限打開文件則同時增加fi_access[0]和fi_access[1]的計數
atomic_t fi_access[2];
};
設置一個新的nfs4_ol_stateid結構的函數是init_open_stateid(),這個函數的代碼如下:
nfsd4_open --> nfsd4_process_open2 --> init_open_stateid
static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) {
// 一個nfs4_openowner結構包含多個nfs4_ol_stateid
// 取出nfs4_openowner結構
// 取出本次OPEN操作的用戶信息的數據結構
struct nfs4_openowner *oo = open->op_openowner; // 找到本次open對應的nfs4_openowner結構
// 找到所屬於的客戶端結構
struct nfs4_client *clp = oo->oo_owner.so_client;
init_stid(&stp->st_stid, clp, NFS4_OPEN_STID); // 設置stateid類型和客戶端
// 這是一個鏈表頭,鏈表中的數據結構是nfs4_lockowner,與文件鎖相關。
INIT_LIST_HEAD(&stp->st_lockowners);
// 一個用戶可能打開了多個文件,用多個nfs4_ol_stateid結構表示,這些結構構成了一個鏈表。
list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids);
// 鏈接到nfs4_file結構中
list_add(&stp->st_perfile, &fp->fi_stateids);
stp->st_stateowner = &oo->oo_owner; // 這個nfs4_ol_stateid結構所屬於的nfs4_stateowner結構
get_nfs4_file(fp); // 增加引用計數
stp->st_file = fp; // 這是文件的nfs4_file結構
stp->st_access_bmap = 0;
stp->st_deny_bmap = 0;
// 設置本次OPEN操作的訪問權限,從OPEN請求報文中獲取
set_access(open->op_share_access, stp); // 設置stp->st_access_bmap
// Linux中這個字段永遠是0.
set_deny(open->op_share_deny, stp); // 設置stp->st_deny_bmap
// 如果這是一個(nfs4_lockowner-->nfs4_ol_stateid),指向所在的(nfs4_openlock->nfs4_ol_stateid)
stp->st_openstp = NULL; // 這是文件鎖相關的一個字段,OPEN操作中不需要設置這個字段.
}
更新nfs4_ol_stateid結構的函數是nfs4_upgrade_open(),這個函數的代碼如下:
nfsd4_open --> nfsd4_process_open2 --> nfs4_upgrade_open
static __be32
nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open)
{
u32 op_share_access = open->op_share_access; // 取出新的訪問權限
bool new_access;
__be32 status;
new_access = !test_access(op_share_access, stp);
if (new_access) {
// 這個函數更新了nfs4_file結構中的信息
status = nfs4_get_vfs_file(rqstp, fp, cur_fh, open);
if (status)
return status;
}
status = nfsd4_truncate(rqstp, cur_fh, open);
if (status) {
if (new_access) {
int oflag = nfs4_access_to_omode(op_share_access);
nfs4_file_put_access(fp, oflag);
}
return status;
}
/* remember the open */
set_access(op_share_access, stp); // 這是nfs4_file結構修改後的訪問權限。
set_deny(open->op_share_deny, stp);
return nfs_ok;
}
nfsd4_process_open2()最後還設置了OPEN應答報文中的標誌位,這段代碼如下:
open->op_rflags = NFS4_OPEN_RESULT_LOCKTYPE_POSIX;
if (!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED) &&
!nfsd4_has_session(&resp->cstate))
open->op_rflags |= NFS4_OPEN_RESULT_CONFIRM;
NFS4_OPEN_RESULT_LOCKTYPE_POSIX表示服務器支持POSIX鎖
NFS4_OPEN_RESULT_CONFIRM表示客戶端接收到OPEN應答消息後需要發起OPEN_CONFIRM請求進行確認。這個標誌位的設置是有條件的,並不是每個OPEN請求都需要確認。在NFSv4.0中這個條件是!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED)。什麼意思呢?open->op_openowner的數據結構是nfs4_openowner,表示客戶端的一個用戶。如果用戶打開了多個文件,則多次OPEN操作共享同一個nfs4_openowner結構,只需要第一次執行OPEN操作時創建nfs4_openowner就可以了。前面介紹過,創建nfs4_openowner結構的函數是alloc_init_open_stateowner(),這個函數只設置了標誌位NFS4_OO_NEW表示這是一個新>的nfs4_openowner結構,因此用戶第一次執行OPEN操作時一定需要執行OPEN_CONFIRM請求,OPEN_CONFIRM請求的處理函數會設置標誌位NFS4_OO_CONFIRMED。因>此只有用戶打開第一個文件時需要執行OPEN_CONFIRM請求。