Skip to content

Commit 2140513

Browse files
authored
feat(vfs): implement flock advisory file locking (DragonOS-Community#1785)
- Add flock syscall support with shared/exclusive locks and nonblocking mode - Introduce open file description ID for flock owner tracking - Add flock manager with sharded hashmap for lock state management - Support lock inheritance across dup/fork and release on last close - Add comprehensive test suite covering basic operations and edge cases - Fix SCM_RIGHTS fd passing to share open file description instead of cloning Signed-off-by: longjin <longjin@DragonOS.org>
1 parent c820bf9 commit 2140513

File tree

11 files changed

+945
-5
lines changed

11 files changed

+945
-5
lines changed

kernel/src/filesystem/vfs/file.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ use crate::{
4646
use crate::filesystem::vfs::InodeMode;
4747

4848
const MAX_LFS_FILESIZE: i64 = i64::MAX;
49+
static NEXT_OPEN_FILE_ID: AtomicUsize = AtomicUsize::new(1);
50+
51+
#[inline]
52+
fn alloc_open_file_id() -> usize {
53+
NEXT_OPEN_FILE_ID.fetch_add(1, Ordering::Relaxed)
54+
}
4955

5056
#[derive(Clone, Copy, Debug)]
5157
enum OffsetUpdate {
@@ -381,6 +387,8 @@ impl FileMode {
381387
/// @brief 抽象文件结构体
382388
#[derive(Debug)]
383389
pub struct File {
390+
/// 唯一 open file description id,用于 flock owner 标识。
391+
open_file_id: usize,
384392
inode: Arc<dyn IndexNode>,
385393
/// 对于文件,表示字节偏移量;对于文件夹,表示当前操作的子目录项偏移量
386394
offset: AtomicUsize,
@@ -603,6 +611,7 @@ impl File {
603611
}
604612

605613
let f = File {
614+
open_file_id: alloc_open_file_id(),
606615
inode,
607616
offset: AtomicUsize::new(0),
608617
flags: RwSem::new(flags),
@@ -1130,6 +1139,7 @@ impl File {
11301139
/// @return Option<File> 克隆后的文件结构体。如果克隆失败,返回None
11311140
pub fn try_clone(&self) -> Option<File> {
11321141
let res = Self {
1142+
open_file_id: alloc_open_file_id(),
11331143
inode: self.inode.clone(),
11341144
offset: AtomicUsize::new(self.offset.load(Ordering::SeqCst)),
11351145
flags: RwSem::new(self.flags()),
@@ -1160,6 +1170,11 @@ impl File {
11601170
return self.file_type;
11611171
}
11621172

1173+
#[inline]
1174+
pub fn open_file_id(&self) -> usize {
1175+
self.open_file_id
1176+
}
1177+
11631178
/// 获取当前文件偏移(等价于用户态的 file position)。
11641179
#[inline]
11651180
pub fn pos(&self) -> usize {
@@ -1360,6 +1375,7 @@ impl File {
13601375

13611376
impl Drop for File {
13621377
fn drop(&mut self) {
1378+
super::flock::release_all_for_file(self);
13631379
let r: Result<(), SystemError> = self.inode.close(self.private_data.lock());
13641380
// 打印错误信息
13651381
if r.is_err() {

kernel/src/filesystem/vfs/flock.rs

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
use alloc::{sync::Arc, vec::Vec};
2+
3+
use hashbrown::{HashMap, HashSet};
4+
use jhash::jhash2;
5+
use system_error::SystemError;
6+
7+
use crate::libs::{casting::DowncastArc, lazy_init::Lazy, mutex::Mutex, wait_queue::WaitQueue};
8+
9+
use super::{file::File, mount::MountFSInode, IndexNode, InodeId};
10+
11+
const FLOCK_SHARDS: usize = 53;
12+
type OwnerId = usize;
13+
14+
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
15+
struct FlockKey {
16+
dev_id: usize,
17+
inode_id: InodeId,
18+
}
19+
20+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21+
pub enum FlockOperation {
22+
Shared,
23+
Exclusive,
24+
Unlock,
25+
}
26+
27+
#[derive(Default)]
28+
struct FlockEntryState {
29+
exclusive_owner: Option<OwnerId>,
30+
shared_owners: HashSet<OwnerId>,
31+
}
32+
33+
impl FlockEntryState {
34+
#[inline]
35+
fn owner_lock(&self, owner: OwnerId) -> Option<FlockOperation> {
36+
if self.exclusive_owner == Some(owner) {
37+
Some(FlockOperation::Exclusive)
38+
} else if self.shared_owners.contains(&owner) {
39+
Some(FlockOperation::Shared)
40+
} else {
41+
None
42+
}
43+
}
44+
45+
#[inline]
46+
fn remove_owner(&mut self, owner: OwnerId) -> bool {
47+
let mut changed = false;
48+
if self.exclusive_owner == Some(owner) {
49+
self.exclusive_owner = None;
50+
changed = true;
51+
}
52+
if self.shared_owners.remove(&owner) {
53+
changed = true;
54+
}
55+
changed
56+
}
57+
58+
#[inline]
59+
fn has_conflict(&self, owner: OwnerId, req: FlockOperation) -> bool {
60+
match req {
61+
FlockOperation::Shared => self
62+
.exclusive_owner
63+
.is_some_and(|exclusive_owner| exclusive_owner != owner),
64+
FlockOperation::Exclusive => {
65+
if self
66+
.exclusive_owner
67+
.is_some_and(|exclusive_owner| exclusive_owner != owner)
68+
{
69+
return true;
70+
}
71+
self.shared_owners
72+
.iter()
73+
.any(|shared_owner| *shared_owner != owner)
74+
}
75+
FlockOperation::Unlock => false,
76+
}
77+
}
78+
79+
#[inline]
80+
fn acquire(&mut self, owner: OwnerId, req: FlockOperation) {
81+
match req {
82+
FlockOperation::Shared => {
83+
debug_assert!(self.exclusive_owner.is_none());
84+
self.shared_owners.insert(owner);
85+
}
86+
FlockOperation::Exclusive => {
87+
debug_assert!(self.exclusive_owner.is_none());
88+
debug_assert!(self.shared_owners.is_empty());
89+
self.exclusive_owner = Some(owner);
90+
}
91+
FlockOperation::Unlock => {}
92+
}
93+
}
94+
95+
#[inline]
96+
fn is_empty(&self) -> bool {
97+
self.exclusive_owner.is_none() && self.shared_owners.is_empty()
98+
}
99+
}
100+
101+
struct FlockEntry {
102+
state: Mutex<FlockEntryState>,
103+
waitq: WaitQueue,
104+
}
105+
106+
impl FlockEntry {
107+
#[inline]
108+
fn new() -> Self {
109+
Self {
110+
state: Mutex::new(FlockEntryState::default()),
111+
waitq: WaitQueue::default(),
112+
}
113+
}
114+
115+
#[inline]
116+
fn unlock_owner(&self, owner: OwnerId) -> bool {
117+
self.state.lock().remove_owner(owner)
118+
}
119+
120+
#[inline]
121+
fn is_empty(&self) -> bool {
122+
self.state.lock().is_empty()
123+
}
124+
}
125+
126+
struct FlockShard {
127+
map: Mutex<HashMap<FlockKey, Arc<FlockEntry>>>,
128+
}
129+
130+
pub struct FlockManager {
131+
shards: Vec<FlockShard>,
132+
}
133+
134+
impl FlockManager {
135+
pub fn new() -> Self {
136+
let mut shards = Vec::with_capacity(FLOCK_SHARDS);
137+
for _ in 0..FLOCK_SHARDS {
138+
shards.push(FlockShard {
139+
map: Mutex::new(HashMap::new()),
140+
});
141+
}
142+
Self { shards }
143+
}
144+
145+
#[inline]
146+
fn shard_index(key: &FlockKey) -> usize {
147+
let dev_id = key.dev_id as u64;
148+
let inode_id = key.inode_id.data() as u64;
149+
let key_array = [
150+
(dev_id >> 32) as u32,
151+
dev_id as u32,
152+
(inode_id >> 32) as u32,
153+
inode_id as u32,
154+
];
155+
let hash = jhash2(&key_array, 0);
156+
(hash as usize) % FLOCK_SHARDS
157+
}
158+
159+
#[inline]
160+
fn shard(&self, key: &FlockKey) -> &FlockShard {
161+
&self.shards[Self::shard_index(key)]
162+
}
163+
164+
fn canonical_inode_for_lock(file: &File) -> Arc<dyn IndexNode> {
165+
// 对 flock key 计算,统一剥离 MountFSInode 包装,避免 mount 侧
166+
// metadata.dev_id 合成策略导致同一底层 inode 被误判为不同锁对象。
167+
let mut inode = file.inode();
168+
loop {
169+
match inode.clone().downcast_arc::<MountFSInode>() {
170+
Some(mnt_inode) => inode = mnt_inode.underlying_inode(),
171+
None => return inode,
172+
}
173+
}
174+
}
175+
176+
fn key_from_file(file: &File) -> Result<FlockKey, SystemError> {
177+
let inode = Self::canonical_inode_for_lock(file);
178+
let md = inode.metadata()?;
179+
Ok(FlockKey {
180+
dev_id: md.dev_id,
181+
inode_id: md.inode_id,
182+
})
183+
}
184+
185+
fn get_or_create_entry(&self, key: FlockKey) -> Arc<FlockEntry> {
186+
let shard = self.shard(&key);
187+
let mut guard = shard.map.lock();
188+
guard
189+
.entry(key)
190+
.or_insert_with(|| Arc::new(FlockEntry::new()))
191+
.clone()
192+
}
193+
194+
fn get_entry(&self, key: &FlockKey) -> Option<Arc<FlockEntry>> {
195+
let shard = self.shard(key);
196+
shard.map.lock().get(key).cloned()
197+
}
198+
199+
fn lock_or_wait(
200+
entry: &Arc<FlockEntry>,
201+
owner: OwnerId,
202+
req: FlockOperation,
203+
nonblocking: bool,
204+
) -> Result<(), SystemError> {
205+
debug_assert!(matches!(
206+
req,
207+
FlockOperation::Shared | FlockOperation::Exclusive
208+
));
209+
210+
let mut dropped_old_lock = false;
211+
{
212+
let mut state = entry.state.lock();
213+
if let Some(current_lock) = state.owner_lock(owner) {
214+
if current_lock == req {
215+
return Ok(());
216+
}
217+
let _ = state.remove_owner(owner);
218+
dropped_old_lock = true;
219+
}
220+
221+
if !state.has_conflict(owner, req) {
222+
state.acquire(owner, req);
223+
drop(state);
224+
if dropped_old_lock {
225+
entry.waitq.wakeup_all(None);
226+
}
227+
return Ok(());
228+
}
229+
}
230+
231+
if dropped_old_lock {
232+
entry.waitq.wakeup_all(None);
233+
}
234+
235+
if nonblocking {
236+
return Err(SystemError::EAGAIN_OR_EWOULDBLOCK);
237+
}
238+
239+
entry.waitq.wait_until_interruptible(|| {
240+
let mut state = entry.state.lock();
241+
if state.has_conflict(owner, req) {
242+
None
243+
} else {
244+
state.acquire(owner, req);
245+
Some(())
246+
}
247+
})?;
248+
249+
Ok(())
250+
}
251+
252+
fn try_cleanup_entry(&self, key: &FlockKey, entry: &Arc<FlockEntry>) {
253+
if !entry.is_empty() || !entry.waitq.is_empty() {
254+
return;
255+
}
256+
257+
let shard = self.shard(key);
258+
let mut guard = shard.map.lock();
259+
if let Some(current) = guard.get(key) {
260+
if Arc::ptr_eq(current, entry)
261+
&& entry.is_empty()
262+
&& entry.waitq.is_empty()
263+
&& Arc::strong_count(entry) == 2
264+
{
265+
guard.remove(key);
266+
}
267+
}
268+
}
269+
270+
pub fn apply(
271+
&self,
272+
file: &Arc<File>,
273+
operation: FlockOperation,
274+
nonblocking: bool,
275+
) -> Result<(), SystemError> {
276+
let key = Self::key_from_file(file.as_ref())?;
277+
let owner = file.open_file_id();
278+
let entry = self.get_or_create_entry(key);
279+
280+
let result = match operation {
281+
FlockOperation::Unlock => {
282+
if entry.unlock_owner(owner) {
283+
entry.waitq.wakeup_all(None);
284+
}
285+
Ok(())
286+
}
287+
FlockOperation::Shared | FlockOperation::Exclusive => {
288+
Self::lock_or_wait(&entry, owner, operation, nonblocking)
289+
}
290+
};
291+
292+
self.try_cleanup_entry(&key, &entry);
293+
result
294+
}
295+
296+
pub fn release_file(&self, file: &File) {
297+
let Ok(key) = Self::key_from_file(file) else {
298+
return;
299+
};
300+
let owner = file.open_file_id();
301+
let Some(entry) = self.get_entry(&key) else {
302+
return;
303+
};
304+
305+
if entry.unlock_owner(owner) {
306+
entry.waitq.wakeup_all(None);
307+
}
308+
self.try_cleanup_entry(&key, &entry);
309+
}
310+
}
311+
312+
static FLOCK_MANAGER: Lazy<FlockManager> = Lazy::new();
313+
314+
pub fn init_flock_manager() {
315+
if !FLOCK_MANAGER.initialized() {
316+
FLOCK_MANAGER.init(FlockManager::new());
317+
}
318+
}
319+
320+
#[inline]
321+
pub fn apply_flock(
322+
file: &Arc<File>,
323+
operation: FlockOperation,
324+
nonblocking: bool,
325+
) -> Result<(), SystemError> {
326+
FLOCK_MANAGER.get().apply(file, operation, nonblocking)
327+
}
328+
329+
pub fn release_all_for_file(file: &File) {
330+
if !FLOCK_MANAGER.initialized() {
331+
return;
332+
}
333+
FLOCK_MANAGER.get().release_file(file);
334+
}

0 commit comments

Comments
 (0)