關閉文件

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_stateid

static 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,但是目前的處理過程存在問題,這個問題正在協商中。

發佈了70 篇原創文章 · 獲贊 4 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章