2. 支援的檔案操作¶
下面將討論 iomap 實現的高階檔案操作。
2.1. 快取 I/O¶
快取 I/O 是 Linux 中預設的檔案 I/O 路徑。檔案內容快取在記憶體中(“頁快取”)以滿足讀寫請求。髒快取將在某個時刻寫回磁碟,可以透過 fsync 及其變體強制執行。
iomap 實現了幾乎所有的 folio 和頁快取管理,而這些在傳統 I/O 模型下檔案系統必須自行實現。這意味著檔案系統無需瞭解分配、對映、管理最新和髒狀態以及頁快取 folio 回寫的細節。在傳統 I/O 模型下,這是透過緩衝區頭的連結串列來管理的,效率非常低,而 iomap 使用每 folio 點陣圖。除非檔案系統明確選擇使用緩衝區頭,否則它們將不會被使用,這使得快取 I/O 效率更高,頁快取維護者也更滿意。
2.1.1. struct address_space_operations¶
以下 iomap 函式可以直接從 address_space_operations 結構中引用
iomap_dirty_folio
iomap_release_folio
iomap_invalidate_folio
iomap_is_partially_uptodate
以下 address space 操作可以輕鬆封裝
read_folio
readahead
writepages
bmap
swap_activate
2.1.2. struct iomap_folio_ops¶
頁快取操作的 ->iomap_begin 函式可以將 struct iomap::folio_ops 欄位設定為一個 ops 結構體,以覆蓋 iomap 的預設行為
struct iomap_folio_ops {
struct folio *(*get_folio)(struct iomap_iter *iter, loff_t pos,
unsigned len);
void (*put_folio)(struct inode *inode, loff_t pos, unsigned copied,
struct folio *folio);
bool (*iomap_valid)(struct inode *inode, const struct iomap *iomap);
};
iomap 呼叫這些函式
get_folio:在開始寫入之前,用於分配並返回一個鎖定的 folio 的活動引用。如果未提供此函式,iomap 將呼叫iomap_get_folio。這可用於為寫入設定每 folio 檔案系統狀態。
put_folio:在頁快取操作完成後,用於解鎖並釋放 folio。如果未提供此函式,iomap 將自行呼叫folio_unlock和folio_put。這可用於提交由->get_folio設定的每 folio 檔案系統狀態。
iomap_valid:檔案系統在->iomap_begin和->iomap_end之間可能不持有鎖,因為頁快取操作會獲取 folio 鎖、在使用者空間頁面上發生頁錯誤、啟動回寫以回收記憶體,或執行其他耗時操作。如果檔案的空間對映資料是可變的,則在分配、安裝和鎖定該 folio 所需的時間內,特定頁快取 folio 的對映可能會發生變化。對於頁快取,如果回寫不獲取
i_rwsem或invalidate_lock並更新對映資訊,則可能發生競爭。如果檔案系統允許併發寫入,也可能發生競爭。對於此類檔案,在獲取 folio 鎖後必須重新驗證對映,以便 iomap 能夠正確管理 folio。fsdax 不需要這種重新驗證,因為它沒有回寫,也不支援未寫入的區段。
受此類競爭影響的檔案系統必須提供一個
->iomap_valid函式來判斷對映是否仍然有效。如果對映無效,將再次取樣對映。為了支援做出有效性決策,檔案系統的
->iomap_begin函式在填充其他 iomap 欄位的同時,可以設定struct iomap::validity_cookie。一個簡單的驗證 cookie 實現是一個序列計數器。如果檔案系統在每次修改 inode 的區段對映時遞增序列計數器,則可以在->iomap_begin期間將其放置在struct iomap::validity_cookie中。如果在將對映傳回->iomap_valid時,發現 cookie 中的值與檔案系統持有的值不同,則應認為 iomap 過時,驗證失敗。
這些 struct kiocb 標誌對於 iomap 的快取 I/O 至關重要
IOCB_NOWAIT:開啟IOMAP_NOWAIT。
IOCB_DONTCACHE:開啟IOMAP_DONTCACHE。
2.1.3. 內部每 Folio 狀態¶
如果檔案系統塊大小與頁快取 folio 的大小匹配,則假定所有磁碟 I/O 操作都將作用於整個 folio。對於這種情況,只需 folio 的最新狀態(記憶體內容至少與磁碟上的內容一樣新)和髒狀態(記憶體內容比磁碟上的內容新)即可。
如果檔案系統塊大小小於頁快取 folio 的大小,iomap 會自行跟蹤每檔案系統塊的最新狀態和髒狀態。這使得 iomap 能夠處理“bs < ps”的檔案系統以及頁快取中的大 folio。
iomap 內部跟蹤每個檔案系統塊的兩個狀態位
uptodate:iomap 將嘗試保持 folio 完全最新。如果發生讀(預讀)錯誤,則不會將這些檔案系統塊標記為最新。當 folio 中的所有檔案系統塊都最新時,folio 本身將被標記為最新。
dirty:當程式寫入檔案時,iomap 將設定每塊的髒狀態。當 folio 中的任何檔案系統塊變髒時,folio 本身將被標記為髒。
iomap 還跟蹤正在進行的讀寫磁碟 I/O 量。這種結構比 struct buffer_head 輕量得多,因為它每個 folio 只有一個,並且每個檔案系統塊的開銷是兩位,而不是 104 位元組。
希望在頁快取中啟用大 folio 的檔案系統應在初始化核心 inode 時呼叫 mapping_set_large_folios。
2.1.4. 快取預讀和讀取¶
iomap_readahead 函式啟動對頁快取的預讀。iomap_read_folio 函式將一個 folio 的資料讀入頁快取。->iomap_begin 的 flags 引數將設定為零。頁快取會在呼叫檔案系統之前獲取其所需的任何鎖。
2.1.5. 快取寫入¶
iomap_file_buffered_write 函式將一個 iocb 寫入頁快取。IOMAP_WRITE 或 IOMAP_WRITE | IOMAP_NOWAIT 將作為 flags 引數傳遞給 ->iomap_begin。呼叫者通常在呼叫此函式之前以共享或排他模式獲取 i_rwsem。
2.1.5.1. mmap 寫錯誤¶
iomap_page_mkwrite 函式處理頁快取中 folio 的寫錯誤。IOMAP_WRITE | IOMAP_FAULT 將作為 flags 引數傳遞給 ->iomap_begin。呼叫者通常在呼叫此函式之前以共享或排他模式獲取 mmap invalidate_lock。
2.1.5.2. 快取寫入失敗¶
對頁快取進行短寫入後,未寫入的區域將不會被標記為髒。檔案系統必須安排取消此類保留,因為回寫不會消耗該保留。iomap_write_delalloc_release 可以從 ->iomap_end 函式中呼叫,以查詢快取了新(IOMAP_F_NEW)延遲分配對映的 folio 的所有乾淨區域。它會獲取 invalidate_lock。
檔案系統必須提供一個函式 punch,以便在此狀態下為每個檔案範圍呼叫。此函式只能刪除延遲分配保留,以防另一個與當前執行緒競爭的執行緒成功寫入同一區域並觸發回寫以將髒資料重新整理到磁碟。
2.1.5.3. 檔案操作的清零¶
檔案系統可以呼叫 iomap_zero_range 來對非截斷檔案操作的頁快取進行清零,這些操作未對齊檔案系統塊大小。IOMAP_ZERO 將作為 flags 引數傳遞給 ->iomap_begin。呼叫者通常在呼叫此函式之前以排他模式持有 i_rwsem 和 invalidate_lock。
2.1.5.4. 解除 Reflink 檔案資料共享¶
檔案系統可以呼叫 iomap_file_unshare 來強制與另一個檔案共享儲存的檔案搶先將共享資料複製到新分配的儲存。 IOMAP_WRITE | IOMAP_UNSHARE 將作為 flags 引數傳遞給 ->iomap_begin。 呼叫者通常在呼叫此函式之前以排他模式持有 i_rwsem 和 invalidate_lock。
2.1.6. 截斷¶
檔案系統可以在檔案截斷操作期間呼叫 iomap_truncate_page,以將頁快取中從 EOF 到檔案系統塊末尾的位元組清零。truncate_setsize 或 truncate_pagecache 將處理 EOF 塊之後的所有內容。IOMAP_ZERO 將作為 flags 引數傳遞給 ->iomap_begin。呼叫者通常在呼叫此函式之前以排他模式持有 i_rwsem 和 invalidate_lock。
2.1.7. 頁快取回寫¶
檔案系統可以呼叫 iomap_writepages 來響應將髒頁快取 folio 寫回磁碟的請求。mapping 和 wbc 引數應不變地傳遞。wpc 指標應由檔案系統分配並初始化為零。
頁快取將在嘗試安排每個 folio 進行回寫之前鎖定它。它不會鎖定 i_rwsem 或 invalidate_lock。
即使回寫失敗,透過下面描述的 ->map_blocks 機制處理的所有 folio 的髒位都將被清除。這是為了防止儲存裝置故障時出現髒 folio 凝塊;一個 -EIO 會被記錄下來供使用者空間透過 fsync 收集。
ops 結構必須指定,並且如下所示
2.1.7.1. struct iomap_writeback_ops¶
struct iomap_writeback_ops {
int (*map_blocks)(struct iomap_writepage_ctx *wpc, struct inode *inode,
loff_t offset, unsigned len);
int (*submit_ioend)(struct iomap_writepage_ctx *wpc, int status);
void (*discard_folio)(struct folio *folio, loff_t pos);
};
欄位如下
map_blocks:將wpc->iomap設定為由offset和len給出的檔案範圍(以位元組為單位)的空間對映。iomap 為每個髒 folio 中的每個髒檔案系統塊呼叫此函式,儘管它會重用 folio 中連續髒檔案系統塊的對映。不要在此處返回IOMAP_INLINE對映;->iomap_end函式必須處理已寫入資料的持久化。不要在此處返回IOMAP_DELALLOC對映;iomap 當前需要對映到已分配的空間。如果對映未更改,檔案系統可以跳過潛在昂貴的對映查詢。這種重新驗證必須由檔案系統自行實現;目前尚不清楚iomap::validity_cookie是否可以為此目的重新使用。此函式必須由檔案系統提供。
submit_ioend:允許檔案系統掛鉤到回寫 bio 提交。這可能包括預寫空間記賬更新,或安裝自定義的->bi_end_io函式用於內部目的,例如將 ioend 完成推遲到工作佇列以在提交 bio 之前從程序上下文執行元資料更新事務。此函式是可選的。
discard_folio:在->map_blocks未能為髒 folio 的任何部分安排 I/O 後,iomap 會呼叫此函式。該函式應丟棄可能已為寫入進行的任何保留。該 folio 將被標記為乾淨,並在頁快取中記錄一個-EIO。檔案系統可以使用此回撥來移除延遲分配保留,以避免對乾淨頁快取進行延遲分配保留。此函式是可選的。
2.1.7.2. 頁快取回寫完成¶
為了處理磁碟 I/O 回寫完成後必須進行的簿記工作,iomap 建立了 struct iomap_ioend 物件的鏈,這些物件封裝了用於將頁快取資料寫入磁碟的 bio。預設情況下,iomap 透過清除附加到 ioend 的 folio 上的回寫位來完成回寫 ioend。如果寫入失敗,它還會設定 folio 和地址空間上的錯誤位。這可以在中斷或程序上下文中發生,具體取決於儲存裝置。
需要更新內部簿記(例如未寫入區段轉換)的檔案系統應提供 ->submit_ioend 函式以將其自己的函式設定為 struct iomap_end::bio::bi_end_io。此函式應在完成其自身工作(例如未寫入區段轉換)後呼叫 iomap_finish_ioends。
某些檔案系統可能希望透過批處理來分攤執行元資料事務的成本,以進行回寫後更新。它們還可能要求事務從程序上下文執行,這意味著將批處理傳遞給工作佇列。iomap ioends 包含一個 list_head 以實現批處理。
給定一批 ioend,iomap 提供了一些輔助函式來幫助分攤
iomap_sort_ioends:按檔案偏移量對列表中所有 ioend 進行排序。
iomap_ioend_try_merge:給定一個不在任何列表中的 ioend 和一個單獨的已排序 ioend 列表,將列表中開頭的儘可能多的 ioend 合併到給定的 ioend 中。ioend 只有在檔案範圍和儲存地址連續;未寫入和共享狀態相同;並且寫入 I/O 結果相同的情況下才能合併。合併的 ioend 成為其自己的列表。
iomap_finish_ioends:完成一個可能連結有其他 ioend 的 ioend。
2.2. 直接 I/O¶
在 Linux 中,直接 I/O 定義為直接向儲存裝置發出,繞過頁快取的檔案 I/O。iomap_dio_rw 函式為檔案實現了 O_DIRECT(直接 I/O)讀寫。
ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
const struct iomap_ops *ops,
const struct iomap_dio_ops *dops,
unsigned int dio_flags, void *private,
size_t done_before);
如果檔案系統需要在向儲存裝置發出 I/O 之前或之後執行額外工作,它可以提供 dops 引數。done_before 引數告訴請求已傳輸了多少。它用於在請求的部分內容已同步完成時非同步繼續請求。
如果 iocb 的寫入在呼叫之前已經啟動,則應設定 done_before 引數。I/O 的方向由傳入的 iocb 確定。
dio_flags 引數可以設定為以下值的任意組合
IOMAP_DIO_FORCE_WAIT:即使 kiocb 不是同步的,也要等待 I/O 完成。
IOMAP_DIO_OVERWRITE_ONLY:對此範圍執行純覆蓋寫入,否則返回-EAGAIN錯誤。檔案系統可以使用此功能為複雜的不對齊 I/O 寫入路徑提供最佳化的快速路徑。如果可以執行純覆蓋寫入,則無需與其他 I/O 對同一檔案系統塊進行序列化,因為不存在陳舊資料暴露或資料丟失的風險。如果無法執行純覆蓋寫入,則檔案系統可以執行所需的序列化步驟,以提供對不對齊 I/O 範圍的獨佔訪問,以便安全地執行分配和子塊清零。檔案系統可以使用此標誌來嘗試減少鎖競爭,但需要進行大量詳細檢查才能正確執行。
IOMAP_DIO_PARTIAL:如果發生頁錯誤,則返回已經取得的任何進展。呼叫者可以處理頁錯誤並重試操作。如果呼叫者決定重試操作,它應將所有先前呼叫的累積返回值作為done_before引數傳遞給下一次呼叫。
這些 struct kiocb 標誌對於 iomap 的直接 I/O 至關重要
IOCB_NOWAIT:開啟IOMAP_NOWAIT。
IOCB_SYNC:確保裝置在完成呼叫之前將資料持久化到磁碟。對於純覆蓋寫入,I/O 可以啟用 FUA。
IOCB_HIPRI:輪詢 I/O 完成而不是等待中斷。僅對非同步 I/O 有意義,並且僅當整個 I/O 可以作為單個struct bio發出時。
IOCB_DIO_CALLER_COMP:嘗試從呼叫者的程序上下文執行 I/O 完成。有關更多詳細資訊,請參見linux/fs.h。
檔案系統應從 ->read_iter 和 ->write_iter 呼叫 iomap_dio_rw,並在檔案的 ->open 函式中設定 FMODE_CAN_ODIRECT。它們不應設定已棄用的 ->direct_IO。
如果檔案系統希望在直接 I/O 完成之前執行自己的工作,它應該呼叫 __iomap_dio_rw。如果其返回值不是錯誤指標或 NULL 指標,檔案系統應在完成其內部工作後將返回值傳遞給 iomap_dio_complete。
2.2.1. 返回值¶
iomap_dio_rw 可以返回以下之一
非負的已傳輸位元組數。
-ENOTBLK:回退到快取 I/O。如果 iomap 本身在向儲存裝置發出 I/O 之前無法使頁快取失效,則它將返回此值。->iomap_begin或->iomap_end函式也可能返回此值。
-EIOCBQUEUED:非同步直接 I/O 請求已排隊,將單獨完成。任何其他負錯誤程式碼。
2.2.2. 直接讀取¶
直接 I/O 讀取啟動從儲存裝置到呼叫者緩衝區的讀 I/O。在啟動讀 I/O 之前,頁快取的髒部分會重新整理到儲存裝置。->iomap_begin 的 flags 值將是 IOMAP_DIRECT,並可與以下增強功能任意組合
IOMAP_NOWAIT,如前所述。
呼叫者通常在呼叫此函式之前以共享模式持有 i_rwsem。
2.2.3. 直接寫入¶
直接 I/O 寫入啟動從呼叫者緩衝區到儲存裝置的寫 I/O。在啟動寫 I/O 之前,頁快取的髒部分會重新整理到儲存裝置。頁快取會在寫 I/O 之前和之後都失效。->iomap_begin 的 flags 值將是 IOMAP_DIRECT | IOMAP_WRITE,並可與以下增強功能任意組合
IOMAP_NOWAIT,如前所述。
IOMAP_OVERWRITE_ONLY:不允許分配塊和清零部分塊。整個檔案範圍必須對映到單個已寫入或未寫入的區段。如果對映是未寫入的且檔案系統無法處理未對齊區域的清零而不暴露陳舊內容,則檔案 I/O 範圍必須與檔案系統塊大小對齊。
IOMAP_ATOMIC:此寫入以撕裂寫入保護髮出。撕裂寫入保護可能基於硬體解除安裝或檔案系統提供的軟體機制。對於基於硬體解除安裝的支援,寫入只能建立一個 bio,並且寫入不能拆分為多個 I/O 請求,即必須設定標誌 REQ_ATOMIC。要寫入的檔案範圍必須對齊,以滿足檔案系統和底層塊裝置的原子提交能力的要求。如果需要檔案系統元資料更新(例如未寫入區段轉換或寫時複製),則整個檔案範圍的所有更新也必須原子提交。非撕裂寫入可能比單個檔案塊更長。在所有情況下,對映起始磁碟塊必須具有與寫入偏移量相同的對齊方式。檔案系統必須設定 IOMAP_F_ATOMIC_BIO 以通知 iomap 核心基於硬體解除安裝的非撕裂寫入。
對於基於檔案系統提供的軟體機制的非撕裂寫入,適用於硬體解除安裝的非撕裂寫入的所有磁碟塊對齊和單個 bio 限制不適用。該機制通常用作無法發出基於硬體解除安裝的非撕裂寫入時的回退,例如寫入範圍覆蓋多個區段,這意味著無法發出單個 bio。整個檔案範圍的所有檔案系統元資料更新也必須原子提交。
呼叫者通常在呼叫此函式之前以共享或排他模式持有 i_rwsem。
2.2.4. struct iomap_dio_ops:¶
struct iomap_dio_ops {
void (*submit_io)(const struct iomap_iter *iter, struct bio *bio,
loff_t file_offset);
int (*end_io)(struct kiocb *iocb, ssize_t size, int error,
unsigned flags);
struct bio_set *bio_set;
};
此結構的欄位如下
submit_io:iomap 在構建了請求 I/O 的struct bio物件並希望將其提交給塊裝置時呼叫此函式。如果未提供函式,則將直接呼叫submit_bio。希望在之前執行額外工作(例如 btrfs 的資料複製)的檔案系統應實現此函式。
end_io:在struct bio完成後呼叫此函式。此函式應執行未寫入區段對映的寫入後轉換、處理寫入失敗等。flags引數可以設定為以下組合
IOMAP_DIO_UNWRITTEN:對映是未寫入的,因此 ioend 應將區段標記為已寫入。
IOMAP_DIO_COW:寫入對映中的空間需要寫時複製操作,因此 ioend 應切換對映。
bio_set:這允許檔案系統提供自定義的 bio_set 用於分配直接 I/O bio。這使檔案系統能夠儲存額外的每 bio 資訊以供私用。如果此欄位為 NULL,則將使用通用的struct bio物件。
希望在 I/O 完成後執行額外工作的檔案系統應透過 ->submit_io 設定自定義的 ->bi_end_io 函式。之後,自定義的 endio 函式必須呼叫 iomap_dio_bio_end_io 來完成直接 I/O。
2.3. DAX I/O¶
某些儲存裝置可以直接對映為記憶體。這些裝置支援一種稱為“fsdax”的新訪問模式,允許透過 CPU 和記憶體控制器進行載入和儲存。
2.3.1. fsdax 讀取¶
fsdax 讀取執行從儲存裝置到呼叫者緩衝區的記憶體複製。->iomap_begin 的 flags 值將是 IOMAP_DAX,並可與以下增強功能任意組合
IOMAP_NOWAIT,如前所述。
呼叫者通常在呼叫此函式之前以共享模式持有 i_rwsem。
2.3.2. fsdax 寫入¶
fsdax 寫入啟動從呼叫者緩衝區到儲存裝置的記憶體複製。->iomap_begin 的 flags 值將是 IOMAP_DAX | IOMAP_WRITE,並可與以下增強功能任意組合
IOMAP_NOWAIT,如前所述。
IOMAP_OVERWRITE_ONLY:呼叫者要求從此對映執行純覆蓋寫入。這要求檔案系統區段對映已經以IOMAP_MAPPED型別存在並跨越寫入 I/O 請求的整個範圍。如果檔案系統無法以允許 iomap 基礎設施執行純覆蓋寫入的方式對映此請求,則必須以-EAGAIN失敗對映操作。
呼叫者通常在呼叫此函式之前以排他模式持有 i_rwsem。
2.3.2.1. fsdax mmap 錯誤¶
dax_iomap_fault 函式處理 fsdax 儲存的讀寫錯誤。對於讀錯誤,IOMAP_DAX | IOMAP_FAULT 將作為 flags 引數傳遞給 ->iomap_begin。對於寫錯誤,IOMAP_DAX | IOMAP_FAULT | IOMAP_WRITE 將作為 flags 引數傳遞給 ->iomap_begin。
呼叫者通常持有與呼叫其 iomap 頁快取對應函式時相同的鎖。
2.3.3. fsdax 截斷、fallocate 和解除共享¶
對於 fsdax 檔案,提供了以下函式來替換其 iomap 頁快取 I/O 對應函式。->iomap_begin 的 flags 引數與頁快取對應函式相同,並添加了 IOMAP_DAX。
dax_file_unshare
dax_zero_range
dax_truncate_page
呼叫者通常持有與呼叫其 iomap 頁快取對應函式時相同的鎖。
2.3.4. fsdax 重複資料刪除¶
實現 FIDEDUPERANGE ioctl 的檔案系統必須使用自己的 iomap 讀取操作呼叫 dax_remap_file_range_prep 函式。
2.4. 檔案定位¶
iomap 實現了 llseek 系統呼叫的兩種迭代 whence 模式。
2.4.1. SEEK_DATA¶
iomap_seek_data 函式實現了 llseek 的 SEEK_DATA “whence” 值。IOMAP_REPORT 將作為 flags 引數傳遞給 ->iomap_begin。
對於未寫入的對映,將搜尋頁快取。頁快取中已對映 folio 且這些 folio 內檔案系統塊最新的區域將被報告為資料區域。
呼叫者通常在呼叫此函式之前以共享模式持有 i_rwsem。
2.4.2. SEEK_HOLE¶
iomap_seek_hole 函式實現了 llseek 的 SEEK_HOLE “whence” 值。IOMAP_REPORT 將作為 flags 引數傳遞給 ->iomap_begin。
對於未寫入的對映,將搜尋頁快取。頁快取中未對映 folio 的區域,或者 folio 中檔案系統塊非最新的區域將被報告為稀疏空洞區域。
呼叫者通常在呼叫此函式之前以共享模式持有 i_rwsem。
2.5. 交換檔案啟用¶
iomap_swapfile_activate 函式查詢檔案中的所有基頁對齊區域,並將它們設定為交換空間。檔案在啟用前將進行 fsync()。 IOMAP_REPORT 將作為 flags 引數傳遞給 ->iomap_begin。所有對映必須是已對映或未寫入的;不能是髒的或共享的,也不能跨越多個塊裝置。呼叫者必須以排他模式持有 i_rwsem;這已由 swapon 提供。
2.6. 檔案空間對映報告¶
iomap 實現了其中兩個檔案空間對映系統呼叫。
2.6.1. FS_IOC_FIEMAP¶
iomap_fiemap 函式以 FS_IOC_FIEMAP ioctl 指定的格式將檔案區段對映匯出到使用者空間。IOMAP_REPORT 將作為 flags 引數傳遞給 ->iomap_begin。呼叫者通常在呼叫此函式之前以共享模式持有 i_rwsem。
2.6.2. FIBMAP (已棄用)¶
iomap_bmap 實現了 FIBMAP。呼叫約定與 FIEMAP 相同。此函式僅為了保持與在轉換前實現了 FIBMAP 的檔案系統的相容性而提供。此 ioctl 已棄用;請不要向尚未實現 FIBMAP 的檔案系統新增 FIBMAP 實現。呼叫者在呼叫此函式之前應該以共享模式持有 i_rwsem,但這尚不明確。