Glock 內部鎖規則

本文件描述了 glock 狀態機內部的基本原理。每個 glock (fs/gfs2/incore.h 中的 struct gfs2_glock) 都有兩個主要的(內部)鎖:

  1. 自旋鎖 (gl_lockref.lock),用於保護內部狀態,例如 gl_state、gl_target 和 holders 列表 (gl_holders)

  2. 非阻塞位鎖 GLF_LOCK,用於防止其他執行緒同時呼叫 DLM 等。如果一個執行緒獲取了這個鎖,它必須在釋放鎖時呼叫 run_queue(通常透過工作佇列),以確保任何掛起的任務都已完成。

gl_holders 列表包含與 glock 關聯的所有排隊的鎖請求(不僅僅是 holders)。 如果有任何已持有的鎖,它們將是列表頭部的連續條目。 鎖的授予嚴格按照排隊的順序。

glock 層的使用者可以請求三種鎖狀態,即共享 (SH)、延遲 (DF) 和獨佔 (EX)。 它們轉換為以下 DLM 鎖模式:

Glock 模式

DLM

鎖模式

UN

IV/NL

已解鎖(沒有與 glock 關聯的 DLM 鎖)或 NL

SH

PR

(受保護的讀取)

DF

CW

(併發寫入)

EX

EX

(獨佔)

因此,DF 基本上是一種與“正常”共享鎖模式 SH 不相容的共享模式。 在 GFS2 中,DF 模式專門用於直接 I/O 操作。 glocks 基本上是一個鎖加上一些處理快取管理的例程。 以下規則適用於快取:

Glock 模式

快取元資料

快取資料

髒資料

髒元資料

UN

DF

SH

EX

這些規則是使用為每種 glock 型別定義的各種 glock 操作來實現的。 並非所有型別的 glocks 都使用所有模式。 例如,只有 inode glocks 使用 DF 模式。

glock 操作表和每種型別的常量

欄位

目的

go_sync

在遠端狀態更改之前呼叫(例如,同步髒資料)

go_xmote_bh

在遠端狀態更改之後呼叫(例如,重新填充快取)

go_inval

如果遠端狀態更改需要使快取無效,則呼叫

go_instantiate

在獲取 glock 時呼叫

go_held

每次獲取 glock holder 時呼叫

go_dump

呼叫以列印 debugfs 檔案的物件內容,或在發生錯誤時將 glock 轉儲到日誌。

go_callback

如果 DLM 傳送回撥以釋放此鎖,則呼叫

go_unlocked

在 glock 被解鎖時呼叫 (dlm_unlock())

go_type

glock 的型別,LM_TYPE_*

go_flags

如果 glock 具有關聯的地址空間,則設定 GLOF_ASPACE

每個鎖的最小保持時間是在遠端鎖授予後,我們忽略遠端降級請求的時間。 這是為了防止鎖在叢集中的節點之間來回跳動,而沒有一個節點取得任何進展的情況。 這往往在多個節點寫入的共享 mmapped 檔案中最為常見。 透過延遲響應遠端回撥的降級,可以讓使用者空間程式在頁面被取消對映之前取得一些進展。

最終,我們希望使 glock “EX” 模式在本地共享,這樣任何本地鎖定都將根據需要使用 i_mutex 完成,而不是透過 glock。

glock 操作的鎖定規則

操作

GLF_LOCK 位鎖被持有

gl_lockref.lock 自旋鎖被持有

go_sync

go_xmote_bh

go_inval

go_instantiate

go_held

go_dump

有時

go_callback

有時 (N/A)

go_unlocked

注意

操作不得在進入時釋放位鎖或自旋鎖(如果已持有)。 go_dump 和 do_demote_ok 絕不能阻塞。 請注意,只有當 glock 的狀態表明它正在快取最新資料時,才會呼叫 go_dump。

GFS2 中 Glock 的鎖定順序

  1. i_rwsem(如果需要)

  2. 重新命名 glock(僅用於重新命名)

  3. Inode glock(s)(父目錄在子目錄之前,“同一級別”的 inode 按照鎖號排序,具有相同的父目錄)

  4. Rgrp glock(s)(用於(取消)分配操作)

  5. 事務 glock(透過 gfs2_trans_begin)用於非讀取操作

  6. i_rw_mutex(如果需要)

  7. 頁面鎖(始終最後,非常重要!)

每個 inode 有兩個 glocks。 一個處理對 inode 本身的訪問(如上所述的鎖定順序),另一個(稱為 iopen glock)與 inode 中的 i_nlink 欄位結合使用,以確定相關 inode 的生命週期。 inode 的鎖定是基於每個 inode 的。 rgrp 的鎖定是基於每個 rgrp 的。 一般來說,我們更喜歡在叢集鎖之前鎖定本地鎖。

Glock 統計資訊

統計資訊分為兩組:與超級塊相關的統計資訊和與單個 glock 相關的統計資訊。 超級塊統計資訊是基於每個 CPU 完成的,目的是儘量減少收集它們的開銷。 它們還按 glock 型別進一步劃分。 所有計時均以納秒為單位。

對於超級塊和 glock 統計資訊,每種情況都會收集相同的資訊。 超級塊計時統計資訊用於為 glock 計時統計資訊提供預設值,以便新建立的 glocks 應儘可能具有合理的起點。 建立 glock 時,每個 glock 計數器都初始化為零。 當 glock 從記憶體中彈出時,每個 glock 統計資訊將丟失。

統計資訊分為三對平均值和方差,以及兩個計數器。 平均值/方差對是平滑的指數估計值,所使用的演算法對於那些習慣於計算網路程式碼中的往返時間的人來說非常熟悉。 請參閱“TCP/IP Illustrated, Volume 1”,W. Richard Stevens,第 21.3 節,“往返時間測量”,第 299 頁及之後。 另請參閱第 2 卷,第 25.10 節,第 838 頁及之後。 與 TCP/IP Illustrated 案例不同,平均值和方差未縮放,而是以整數納秒為單位。

三對平均值/方差測量以下內容:

  1. DLM 鎖時間(非阻塞請求)

  2. DLM 鎖時間(阻塞請求)

  3. 請求間時間(再次針對 DLM)

非阻塞請求是指可以立即完成的請求,無論相關 DLM 鎖的狀態如何。 目前,這意味著以下情況下的任何請求: (a) 鎖的當前狀態是獨佔的,即鎖降級 (b) 請求的狀態為 null 或已解鎖(同樣是降級)或 (c) 設定了“嘗試鎖”標誌。 阻塞請求涵蓋所有其他鎖請求。

有兩個計數器。 第一個主要用於顯示已發出多少個鎖請求,以及有多少資料已進入平均值/方差計算。 另一個計數器正在計算 glock 程式碼頂層的 holder 的排隊。 希望該數字比發出的 dlm 鎖請求的數量大得多。

那麼,為什麼要收集這些統計資訊? 我們希望更好地瞭解這些計時的原因有以下幾個:

  1. 為了能夠更好地設定 glock“最小保持時間”

  2. 為了更容易地發現效能問題

  3. 為了改進選擇資源組進行分配的演算法(基於鎖等待時間,而不是盲目地使用“嘗試鎖”)

由於更新的平滑作用,取樣的某些輸入量的階躍變化只有在 8 個樣本(方差為 4 個)之後才會完全考慮在內,這在解釋結果時需要仔細考慮。

瞭解鎖請求完成所需的時間以及 glock 的鎖請求之間的平均時間意味著我們可以計算節點能夠使用 glock 的總時間百分比,而不是叢集其餘部分共享的時間。 這對於設定鎖最小保持時間非常有用。

已非常謹慎地確保我們儘可能準確地測量我們想要的量。 任何測量系統中總會有不準確之處,但我希望這能儘可能準確地實現。

每個 sb 的統計資訊可以在這裡找到

/sys/kernel/debug/gfs2/<fsname>/sbstats

每個 glock 的統計資訊可以在這裡找到

/sys/kernel/debug/gfs2/<fsname>/glstats

假設 debugfs 掛載在 /sys/kernel/debug 上,並且 被替換為相關 gfs2 檔案系統的名稱。

輸出中使用的縮寫如下:

srtt

非阻塞 dlm 請求的平滑往返時間

srttvar

srtt 的方差估計

srttb

(可能)阻塞 dlm 請求的平滑往返時間

srttvarb

srttb 的方差估計

sirt

平滑的請求間時間(對於 dlm 請求)

sirtvar

sirt 的方差估計

dlm

發出的 dlm 請求數(glstats 檔案中的 dcnt)

queue

排隊的 glock 請求數(glstats 檔案中的 qcnt)

sbstats 檔案包含每種 glock 型別的一組這些統計資訊(因此每種型別 8 行)和每個 CPU(每個 CPU 一列)。 glstats 檔案包含每個 glock 的一組這些統計資訊,格式類似於 glocks 檔案,但每個計時統計資訊使用格式 mean/variance。

gfs2_glock_lock_time 跟蹤點打印出相關 glock 的統計資訊的當前值,以及收到的每個 dlm 回覆的一些附加資訊。

status

dlm 請求的狀態

flags

dlm 請求標誌

tdiff

此特定請求所用的時間

(其餘欄位與上述列表相同)