1.CLOSE和OPEN_DOWNGRADE
客戶端用戶執行完讀寫操作之後就需要關閉文件了,一般情況下客戶端用戶調用close(2)關閉文件。NFS文件系統中有兩個請求與關閉文件相關:CLOSE和OPEN_DOWNGRADE。
CLOSE:這個請求的作用是釋放OPEN請求中申請的stateid。客戶端在OPEN操作中向服務器申請了stateid,stateid表示了客戶端用戶請求的對文件的訪問權限。當執行CLOSE請求後,客戶端用戶就放棄了這種訪問權限,就不能繼續使用這個stateid讀寫文件了。如果想再次訪問文件,必須再次執行OPEN操作申請訪問權限。
OPEN_DOWNGRADE:表示用戶釋放了部分訪問權限,比如用戶申請了讀權限和寫權限,可以通過OPEN_DOWNGRADE放棄部分訪問權限。
下面先以幾個具體例子說明什麼情況下會發起CLOSE請求,什麼情況下會發起OPEN_DOWNGRADE請求。
情況1:
fd1 = open(file1, O_RDONLY);
close(fd1);
這是最簡單的情況,當執行close(fd1)時會向服務器發起CLOSE請求。
情況2:
fd1 = open(file1, O_RDONLY);
fd2 = open(file1, O_RDWR);
close(fd2);
close(fd1);
執行close(fd2)時會向服務器發起OPEN_DOWNGRADE請求,放棄寫權限。執行close(fd1)時會向服務器發起CLOSE請求。
情況3:
fd1 = open(file1, O_RDONLY);
fd2 = open(file1, O_RDWR);
close(fd1);
close(fd2);
執行close(fd1)時不會向服務器發起任何請求,因爲fd2仍然需要讀寫權限,執行close(fd2)時會向服務器發起CLOSE請求。
情況4:
fd1 = open(file1, O_RDONLY);
fd2 = open(file1, O_RDONLY);
close(fd1);
close(fd2);
執行close(fd1)時不會向服務器發起任何請求,因爲fd2仍然需要讀權限,執行close(fd2)時會向服務器發起CLOSE請求。
總結:如果執行open(2)後用戶的訪問權限不變,則不會向服務器發起任何請求。如果執行open(2)後用戶的訪問權限變小了,但是仍有訪問權限,則執行open(2)時向服務器發起OPEN_DOWNGRADE請求。如果執行open(2)後用戶沒有任何訪問權限了,則執行open(2)時向服務器發起CLOSE請求。
情況5:
如果用戶通過delegation訪問文件,則關閉文件時不會執行任何請求。
user1:
fd1 = open(file1, O_RDONLY);
sleep(100);
close(fd1);
user2:
fd2 = open(file1, O_RDONLY);
close(fd2);
在這個例子中,user1先以只讀權限打開了文件file1(假設服務器分配了delegation),然後等待了100秒。在user1等待的過程中user2也執行了open()操作,然後馬上執行了close()。執行close(fd2)不會發起任何請求,執行close(fd1)時會向服務器發起CLOSE請求。
2.客戶端代碼
當用戶執行close(2)時,客戶端會執行到函數__nfs4_close(),這個函數代碼如下:
參數state:這是OPEN操作中創建的nfs4_state結構,包含了申請的stateid.
參數fmode:這是關閉的訪問權限。
參數gfp_mask:分配內存的標誌位
參數wait:同步操作還是異步操作
static void __nfs4_close(struct nfs4_state *state,
fmode_t fmode, gfp_t gfp_mask, int wait)
{
// 找到nfs4_state_owner結構,這裏包含了用戶信息.
struct nfs4_state_owner *owner = state->owner;
int call_close = 0;
fmode_t newstate;
atomic_inc(&owner->so_count); // 先增加引用計數,處理完畢後再減少引用計數.
/* Protect against nfs4_find_state() */
spin_lock(&owner->so_lock); // 加鎖
// 先減少相應訪問權限的計數.
switch (fmode & (FMODE_READ | FMODE_WRITE)) {
case FMODE_READ: // 我們釋放的是讀權限
state->n_rdonly--;
break;
case FMODE_WRITE: // 我們釋放的是寫權限
state->n_wronly--;
break;
case FMODE_READ|FMODE_WRITE: // 我們釋放的是讀寫權限
state->n_rdwr--;
}
// newstate表示剩餘的訪問權限
newstate = FMODE_READ|FMODE_WRITE;
// 讀寫權限的計數爲0了,說明沒有以O_RDWR權限打開文件的進程了
if (state->n_rdwr == 0) {
if (state->n_rdonly == 0) { // 以O_RDONLY權限打開文件的進程數量也爲0了
newstate &= ~FMODE_READ; // 表示可以釋放讀權限了
call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (state->n_wronly == 0) { // 以O_WRONLY權限打開文件的進程數量爲0了
newstate &= ~FMODE_WRITE; // 表示可以釋放寫權限了
call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (newstate == 0)
clear_bit(NFS_DELEGATED_STATE, &state->flags);
}
// 設置nfs4_state結構中現在的訪問權限.
nfs4_state_set_mode_locked(state, newstate);
spin_unlock(&owner->so_lock); // 解鎖
// 訪問權限沒有變化,不需要向服務器發起CLOSE或者OPEN_DOWNGRADE請求
if (!call_close) {
nfs4_put_open_state(state); // 只需減少state引用計數就可以了,當計數減到0時釋放內存.
nfs4_put_state_owner(owner); // 減少owner引用計數
} else {
bool roc = pnfs_roc(state->inode);
// 這就需要向服務器發請求了.
nfs4_do_close(state, gfp_mask, wait, roc);
}
}
這個函數理解起來有一定的難度,因爲這裏包含了多種情況。先說說參數fmode的含義,fmode表示用戶執行close(2)時關閉的訪問權限,也就是執行open(2)時指定的權限。nfs4_state結構中的n_rdonly、n_wronly、n_rdwr分別表示這個用戶以只讀權限、只寫權限、讀寫權限打開文件的次數,每執行一次close(2)操作都需要減少相應權限的計數,一般情況下,當n_rdonly和n_rdwr都減到0時就可以向服務器發起請求釋放讀權限了,當n_wronly和n_rdwr都減到0時就可以向服務器發起請求釋放寫權限了。下面這段代碼就是在計算究竟需要釋放哪些權限。
int call_close = 0;
newstate = FMODE_READ|FMODE_WRITE;
if (state->n_rdwr == 0) {
if (state->n_rdonly == 0) {
newstate &= ~FMODE_READ;
call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (state->n_wronly == 0) {
newstate &= ~FMODE_WRITE;
call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (newstate == 0)
clear_bit(NFS_DELEGATED_STATE, &state->flags);
}
這裏定義了兩個變量:newstate和call_close,起作用的是call_close。如果n_rdwr!=0,或者n_rdwr==0 && n_rdonly!=0 &&n_wronly!=0,則call_close就是初始值0,則不向服務器發起任何請求。前面講過,一般情況下:當n_rdonly==0 && n_rdwr==0時就可以釋放讀權限,當n_wronly==0 && n_rdwr==0時就可以釋放寫權限了。有一般情況那就有特殊情況,上段程序中的四個test_bit()函數就是在測試特殊情況,這種情況就是有delegation的情況。假設user1通過OPEN請求以只讀權限打開了文件,且服務器分配了一個delegation,user2就可以使用這個delegation以只讀權限訪問這個文件,而不必向服務器發起OPEN請求了。客戶端會分別爲user1和user2創建一個nfs4_state結構,並且n_rdonly的計數都是1,但是user1中的nfs4_state結構會設置標誌位NFS_O_RDONLY,user2中的nfs4_state結構中不會設置標誌位NFS_O_RDONLY。因此在上段代碼中,對於user1執行call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags)後call_close的值就不爲0了,就需要向服務器發起CLOSE或者OPEN_DOWNGRADE請求,具體發起哪種請求由後面的程序處理。而對於user2執行後call_close的值仍然爲0,就不需要發起請求了。爲什麼user2不需要發起CLOSE或OPEN_DOWNGRADE請求呢?最直接的解釋是user2打開文件時也沒有發起OPEN請求,關閉文件時當然也不需要發起CLOSE請求了。
if (!call_close) {
nfs4_put_open_state(state);
nfs4_put_state_owner(owner);
} else {
bool roc = pnfs_roc(state->inode);
nfs4_do_close(state, gfp_mask, wait, roc);
}
當call_close的值爲0時就不需要發起請求了,當call_close的值不爲0時就需要調用nfs_do_close()發起請求了。上面代碼中的roc是return on close的縮寫,這是pNFS中的功能,後面講到pNFS時再介紹。下面講講CLOSE請求和OPEN_DOWNGRADE請求中客戶端分別需要向服務器傳送哪些數據。
CLOSE請求中需要傳遞兩個數據:
(1)seqid:這是一個操作順序號,保證請求按順序執行。
(2)stateid:這是客戶端指定的stateid,服務器釋放這個stateid上關聯的所有訪問權限。
OPEN_DOWNGRADE請求中需要傳遞四個數據:
(1)seqid:這是操作順序號,保證請求按順序執行。
(2)stateid:這是客戶端指定的stateid,就是要釋放這個stateid上關聯的部分訪問權限。
(3)share_access:這是請求執行完畢後,stateid上剩餘的訪問權限。
(4)share_deny:這是請求執行完畢後,stateid上剩餘的禁止訪問權限。Linux中這個值永遠爲0,不用考慮它了。
接下來看看nfs_do_close()的處理過程
int nfs4_do_close(struct nfs4_state *state, gfp_t gfp_mask, int wait, bool roc)
{
....
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE],
.rpc_cred = state->owner->so_cred,
};
struct rpc_task_setup task_setup_data = {
.rpc_client = server->client,
.rpc_message = &msg,
.callback_ops = &nfs4_close_ops,
.workqueue = nfsiod_workqueue,
.flags = RPC_TASK_ASYNC,
};
....
}
這個函數邏輯很簡單,就是創建了一個RPC任務,設置了請求報文中的數據,然後發起RPC請求,因此就不詳細介紹這個函數了,但是需要注意兩點:(1)這個函數將請求設置成了CLOSE,沒有任何判斷條件,就是直接設置程了CLOSE。(2)這個RPC任務中關聯了輔助函數nfs4_close_ops。這個RPC任務中的輔助函數如下:
static const struct rpc_call_ops nfs4_close_ops = {
.rpc_call_prepare = nfs4_close_prepare, // 這個函數在向服務器發送請求報文前執行
.rpc_call_done = nfs4_close_done, // 這個函數在解析完應答報文後執行
.rpc_release = nfs4_free_closedata, // 這個函數在解析完應答報文或者處理過程出錯時執行
};
我們重點關注發送報文前的準備函數,正是這個函數確定了到底發送CLOSE請求還是OPEN_DOWNGRADE請求。
static void nfs4_close_prepare(struct rpc_task *task, void *data)
{
struct nfs4_closedata *calldata = data;
struct nfs4_state *state = calldata->state; // 取出nfs4_state結構.
int call_close = 0;
dprintk("%s: begin!\n", __func__);
// 步驟1:判斷RPC請求是否可以馬上執行
if (nfs_wait_on_sequence(calldata->arg.seqid, task) != 0)
return;
// 步驟2:初始化請求爲OPEN_DOWNGRADE
task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_DOWNGRADE];
// 步驟3:計算需要釋放的權限.
if (state->n_rdwr == 0) {
if (state->n_rdonly == 0) {
call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
calldata->arg.fmode &= ~FMODE_READ;
}
if (state->n_wronly == 0) {
call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
calldata->arg.fmode &= ~FMODE_WRITE;
}
}
// 步驟4:確定請求
if (!call_close) { // 權限沒有改變,不需要發起請求了.
/* Note: exit _without_ calling nfs4_close_done */
task->tk_action = NULL;
goto out;
}
// n_rdwr、n_rdonly、n_wronly的值都是0了,發起CLOSE請求.
// 否則就發起OPEN_DOWNGRADE請求.
if (calldata->arg.fmode == 0) {
task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE];
....
}
....
rpc_call_start(task);
out:
dprintk("%s: done!\n", __func__);
}
爲了清晰起見,我刪除了NFSv4.1相關的代碼。這個函數的主要作用是確定發起CLOSE請求還是OPEN_DOWNGRADE請求,但是我們看到計算方法和__nfs4_close()的計算方法完全一樣,都是計算n_rdonly、n_wronly、n_rdwr三個變量的值,那麼同樣的代碼爲什麼要寫兩邊呢?問題出在nfs_wait_on_sequence(calldata->arg.seqid, task)。NFSv4中,OPEN、OPEN_CONFIRM、OPEN_DOWNGRADE、CLOSE需要保證序列化。如果想發起一個CLOSE請求,但是發現客戶端正在處理一個OPEN請求,則這個CLOSE操作必須等待,直到OPEN請求處理結束後才能處理CLOSE請求,而這時n_rdonly、n_wronly、n_rdwr的值可能由於OPEN操作已經發生了變化,因此真正發送CLOSE/OPEN_DOWNGRADE請求報文前需要再次計算這三個值。3.服務器端代碼
服務器端,CLOSE請求和OPEN_DOWNGRADE請求的處理過程是分開的。如果客戶端發起了CLOSE請求,則執行函數nfsd4_close()。如果客戶端發起了OPEN_DOWNGRADE則執行函數nfsd4_open_downgrade()。
3.1nfsd4_open_downgrade
__be32
nfsd4_open_downgrade(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate,
struct nfsd4_open_downgrade *od)
{
__be32 status;
struct nfs4_ol_stateid *stp;
nfs4_lock_state();
// 步驟1:根據stateid查找nfs4_ol_stateid結構
status = nfs4_preprocess_confirmed_seqid_op(cstate, od->od_seqid,
&od->od_stateid, &stp);
if (status)
goto out; // 沒有找到.
status = nfserr_inval;
// 步驟2:檢查允許訪問權限
if (!test_access(od->od_share_access, stp)) {
dprintk("NFSD: access not a subset current bitmap: 0x%lx, input access=%08x\n",
stp->st_access_bmap, od->od_share_access);
goto out;
}
// 步驟3:重新設置訪問權限
nfs4_stateid_downgrade(stp, od->od_share_access);
// 步驟4:更新stateid.
update_stateid(&stp->st_stid.sc_stateid);
memcpy(&od->od_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
status = nfs_ok;
out:
if (!cstate->replay_owner)
nfs4_unlock_state();
return status;
}
我去掉了函數中NFSv4.1相關的代碼和share_deny相關的代碼,不考慮這些內容了,OPEN_DOWNGRADE的處理過程可以分爲四個步驟。
步驟1:查找nfs4_ol_stateid結構。
nfs4_ol_stateid結構跟客戶端nfs4_state結構對應。這個步驟的主要作用是檢查客戶端傳遞過來的stateid是否有效。如果客戶端傳遞過來一個非法的stateid,那服務器就不需要處理了,服務器只處理有效的stateid。如果stateid有效,就查找對應的nfs4_ol_stateid結構。這裏簡單介紹檢查和查找過程就可以了,就不講解代碼了,這個步驟中的檢查和查找過程是通過idr機制實現的。簡單來說,idr機制是一種身份認證機制,和我們的身份證機制類似。公安部門爲全國人們分配一個身份證號,一個身份證號和一個人對應,公安部門可以保證每個人的身份證號不相同,可以根據身份證號查找到對應的人。在我們的代碼中,idr機制就相當於公安部門,stateid就相當於身份證號,nfs4_ol_stateid結構就相當於人。nfs4_preprocess_confirmed_seqid_op()根據stateid就可以找到對應的nfs4_ol_stateid結構了。
步驟2:檢查訪問權限。
這個函數在檢查客戶端傳遞過來的share_access字段,share_access中保存的是請求執行完畢後剩餘的訪問權限。OPEN_DOWNGRADE的作用是釋放部分訪問權限。如果這個stateid本來只關聯了寫權限,而share_access是讀權限,這就不對了。OPEN_DOWNGRADE不能增加新權限。
步驟3:重新設置訪問權限,這是實際的操作函數。
步驟4:更新stateid
stateid中也包含一個順序號,更新這個順序號。這個順序號跟RPC報文中的順序號seqid是有區別的。客戶端可能會打開多個文件,無論對哪個文件進行操作,RPC報文中的順序號都需要更新。而stateid只關聯一個文件,只有操作這個文件時,stateid中的順序號才需要更新。
下面看看步驟3的代碼,這個函數根據請求報文中的share_access字段關閉了其他的訪問權限
nfsd4_open_downgrade --> nfs4_stateid_downgrade
static inline void nfs4_stateid_downgrade(struct nfs4_ol_stateid *stp, u32 to_access)
{
// to_access對應請求報文中的share_access字段
switch (to_access) {
case NFS4_SHARE_ACCESS_READ: // 只保留讀權限
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_WRITE);
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
break;
case NFS4_SHARE_ACCESS_WRITE: // 只保留寫權限
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_READ);
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
break;
case NFS4_SHARE_ACCESS_BOTH: // 保存讀寫權限
break;
default:
BUG();
}
}
最終調用了一個處理函數nfs4_stateid_downgrade_bit(),這個函數的作用是關閉相應的訪問權限,參數access表示要關閉的訪問權限,代碼如下:
nfsd4_open_downgrade --> nfs4_stateid_downgrade --> nfs4_stateid_downgrade_bit
static inline void nfs4_stateid_downgrade_bit(struct nfs4_ol_stateid *stp, u32 access)
{
if (!test_access(access, stp)) // 根本沒有這種訪問權限,也就不需要處理了.
return;
// 關閉相應的文件對象結構
nfs4_file_put_access(stp->st_file, nfs4_access_to_omode(access));
// 清除相應權限標誌位
clear_access(access, stp);
}
關閉訪問權限包含兩個步驟:(1)清除相應訪問權限標誌位;(2)關閉相應權限的文件對象結構。
nfsd4_open_downgrade --> nfs4_stateid_downgrade --> nfs4_stateid_downgrade_bit --> nfs4_file_put_access
static void nfs4_file_put_access(struct nfs4_file *fp, int oflag)
{
if (oflag == O_RDWR) {
__nfs4_file_put_access(fp, O_RDONLY);
__nfs4_file_put_access(fp, O_WRONLY);
} else
__nfs4_file_put_access(fp, oflag);
}
我們看看nfs4_ol_stateid和nfs4_file中相關字段的含義就能夠明白了。
struct nfs4_ol_stateid {
....
unsigned long st_access_bmap;
....
};
st_access_bpam的類型是unsigned long,在32位系統中長度是4字節,共32bit。但是其實這是一個狀態標誌位,只使用了其中的3個bit。
bit 0:沒有使用
bit 1:表示用戶是否具有隻讀權限,置位表示有隻讀權限,復位表示沒有隻讀權限
bit 2:表示用戶是否具有隻寫權限,置位表示有隻寫權限,復位表示沒有隻寫權限
bit 3:表示用戶是否具有讀寫權限,置位表示有讀寫權限,復位表示沒有讀寫權限
clear_access()的作用就是將相應權限的標誌位置0,用戶就沒有相應的訪問權限了。
struct nfs4_file {
....
struct file * fi_fds[3];
atomic_t fi_access[2];
};
fi_fds是一個指針數組,保存的是文件對象結構
fi_fds[0]:保存的是隻讀權限的文件對象結構
fi_fds[1]:保存的是隻寫權限的文件對象結構
fi_fds[2]:保存的是讀寫權限的文件對象結構
fi_access是一個數組,保存的是訪問權限的計數
fi_access[0]:保存的是讀權限的計數
fi_access[1]:保存的是寫權限的計數。
注意fi_access數組中只有兩個元素。當用戶以只讀權限打開文件時,fi_access[0]++。當用戶以只寫權限打開文件時,fi_access[1]++。當用戶以讀寫權限打開文件時fi_access[0]++,且fi_access[1]++。
__nfs4_file_put_access()的作用就是減少fi_access中相應權限的計數,當計數減到0時釋放相應的文件對象結構。
nfsd4_open_downgrade --> nfs4_stateid_downgrade --> nfs4_stateid_downgrade_bit --> nfs4_file_put_access --> __nfs4_file_put_access
static void __nfs4_file_put_access(struct nfs4_file *fp, int oflag)
{
// 計數減少1,如果結果爲0返回真,否則返回假.
if (atomic_dec_and_test(&fp->fi_access[oflag])) {
nfs4_file_put_fd(fp, oflag); // 好吧,釋放對應的文件對象結構
/*
* It's also safe to get rid of the RDWR open *if*
* we no longer have need of the other kind of access
* or if we already have the other kind of open:
*/
// 判斷是否需要釋放讀寫權限的文件對象結構.
if (fp->fi_fds[1-oflag]
|| atomic_read(&fp->fi_access[1 - oflag]) == 0)
nfs4_file_put_fd(fp, O_RDWR);
}
}
函數中oflag的取值只能是O_RDONLY或者O_WRONLYD。我們以O_RDONLY爲例進行講解。函數首先減少fi_access[0]的計數,當計數減到0時表示需要釋放讀權限,因此調用nfs4_file_put_fd()將fi_fds[0]設置爲NULL。同還還需要檢查是否需要釋放讀寫權限。那麼什麼情況下需要釋放讀寫權限呢?兩種情況:(1)fi_fds[1]!=NULL,表示還有隻寫權限的文件對象結構,這種情況下可以保證用戶仍能以只寫權限訪問文件,因此就可以釋放讀寫權限了。(2)fi_access[1]==0,表示寫權限計數也已經降到0了,用戶以後再也不會通過寫權限訪問文件了,因此也可以釋放讀寫權限了。3.2nfsd4_close
__be32
nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_close *close)
{
__be32 status;
struct nfs4_openowner *oo;
struct nfs4_ol_stateid *stp;
nfs4_lock_state();
// 步驟1: 查找nfs4_ol_stateid結構
status = nfs4_preprocess_seqid_op(cstate, close->cl_seqid,
&close->cl_stateid,
NFS4_OPEN_STID|NFS4_CLOSED_STID,
&stp);
if (status)
goto out;
oo = openowner(stp->st_stateowner);
// 步驟2: 更新stateid
status = nfs_ok;
update_stateid(&stp->st_stid.sc_stateid); // 更新stateid.
memcpy(&close->cl_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
// 步驟3: 撤銷stateid
nfsd4_close_open_stateid(stp);
oo->oo_last_closed_stid = stp;
// 步驟4:如果nfs4_openowner中沒有nfs4_ol_stateid了,處理.
if (list_empty(&oo->oo_owner.so_stateids)) {
if (cstate->minorversion) {
release_openowner(oo);
cstate->replay_owner = NULL;
} else {
/*
* In the 4.0 case we need to keep the owners around a
* little while to handle CLOSE replay.
*/
move_to_close_lru(oo);
}
}
out:
if (!cstate->replay_owner)
nfs4_unlock_state();
return status;
}
CLOSE和OPEN_DOWNGRADE的處理過程基本相同,最主要的差別是OPEN_DOWNGRADE撤銷了部分訪問權限,而CLOSE撤銷了全部訪問權限。CLOSE處理過程基本上分爲下面幾個步驟:
步驟1:檢查stateid,查找對應的nfs4_ol_stateid結構,OPEN_DOWNGRADE也有這個步驟。
步驟2:更新stateid,OPEN_DOWNGRADE也有這個步驟。
步驟3:撤銷stateid,這是最主要的操作。
步驟4:如果用戶關閉了所有的文件,則這個用戶對應的nfs4_openowner結構就可以處理了,,NFSv4.0和NFSv4.1的處理過程不同。NFSv4.1直接釋放nfs4_openowner結構了,而NFSV4.0則將nfs4_openowner放入到一個lru鏈表中了。
因此,nfsd4_close()中最主要的處理函數是nfsd4_close_open_stateid(),其實這個函數很簡單,函數代碼如下:
nfsd4_close --> nfsd4_close_open_stateid
static void nfsd4_close_open_stateid(struct nfs4_ol_stateid *s)
{
// 這個函數在處理nfs4_ol_stateid結構中的各種信息.
unhash_open_stateid(s);
// 將其設置爲NFS4_CLOSE_STID,以後就不會使用它了.
s->st_stid.sc_type = NFS4_CLOSED_STID;
}
nfsd4_close --> nfsd4_close_open_stateid --> unhash_open_stateidstatic void unhash_open_stateid(struct nfs4_ol_stateid *stp)
{
// 將stp從各種鏈表中摘除.
unhash_generic_stateid(stp);
// 這個函數在處理文件鎖,如果文件中還有未釋放的文件鎖,需要先釋放這些文件鎖.
release_stateid_lockowners(stp);
// 這個函數在清除stp中訪問權限標誌位,撤銷與文件的關聯.
close_generic_stateid(stp);
}
nfsd4_close --> nfsd4_close_open_stateid --> unhash_open_stateid --> unhash_generic_stateid
static void unhash_generic_stateid(struct nfs4_ol_stateid *stp)
{
list_del(&stp->st_perfile); // 從nfs4_file結構中刪除
list_del(&stp->st_perstateowner); // 從nfs4_stateowner結構中刪除
}
nfsd4_close --> nfsd4_close_open_stateid --> unhash_open_stateid --> close_generic_stateid
static void close_generic_stateid(struct nfs4_ol_stateid *stp)
{
release_all_access(stp); // 清除訪問權限標誌位
put_nfs4_file(stp->st_file); // 這個函數減少nfs4_file結構的計數
stp->st_file = NULL;
}
從上面幾個短小的函數我們可以看清楚整個處理過程。就是清除了nfs4_ol_stateid結構中的訪問權限標誌位st_access_bmap,然後將類型設置爲NFS4_CLOSED_STID,撤銷了與nfs4_file結構的關聯,然後將nfs4_ol_stateid結構從所在的鏈表中摘除了,就這麼簡單。但是需要注意的是:這個處理過程中並沒有釋放nfs4_ol_stateid結構佔用的內存,而是用nfs4_openowner結構中的指針oo_last_closed_stid執行了待刪除的ns4_ol_stateid結構。爲什麼這麼處理呢?主要是爲了處理RPC重傳的問題。如果服務器處理完請求後馬上刪除nfs4_ol_stateid結構,在處理重傳問題時比較麻煩,相關說明見RFC3530
8.10.1。那麼什麼時候可以釋放內存呢?當再次接收到該用戶發送過來的OPEN、OPEN_CONFIRM、OPEN_DOWNGRADE、CLOSE請求時就可以釋放了,釋放過程是在組裝應答報文的函數是完成的。如果這是用戶關閉的最後一個文件,以後再也不會發送請求了,則服務器等待一段時間後也會釋放內存。還有一點,nfsd4_close()中存在一個bug,爲了方便處理客戶端的CLOSE重傳請求,RFC3530建議服務器執行完CLOSE請求後暫時不要釋放stateid,而是等待接收到下一個OPEN、OPEN_CONFIRM等請求後再釋放stateid,但是目前的處理過程存在問題,這個問題正在協商中。