Skip to content

Commit 567e2b1

Browse files
authored
fix(epoll): prevent hardirq registration deadlocks (#2188)
Encapsulate poll-source epoll registrations behind an IRQ-safe list API so task-context add and remove operations cannot be interrupted and re-enter the same spin lock from a device IRQ. Linearize callback publication with DEL, file release, and epoll close through an active registration state protected by the ready-state lock. Use open-file identity together with the descriptor as the registration key, remove socket registrations precisely, retain the signalfd registration owner, and clean up epoll sources using the registered file rather than the caller's descriptor table. Add bounded regressions for concurrent HVC TX completions, callback requeue after DEL, duplicated socket descriptors, and descriptor-number reuse. Signed-off-by: longjin <longjin@dragonos.org>
1 parent dace5e9 commit 567e2b1

17 files changed

Lines changed: 420 additions & 231 deletions

File tree

kernel/src/driver/tty/tty_core.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
arch::ipc::signal::Signal,
1414
driver::base::device::device_number::DeviceNumber,
1515
filesystem::epoll::{
16-
event_poll::{EventPoll, LockedEPItemLinkedList},
16+
event_poll::{EPollItemList, EventPoll},
1717
EPollEventType, EPollItem,
1818
},
1919
filesystem::vfs::fasync::{FAsyncItem, FAsyncItems},
@@ -150,7 +150,7 @@ impl TtyCore {
150150
hangup_generation: AtomicUsize::new(0),
151151
flow: SpinLock::new(TtyFlowState::default()),
152152
link: RwLock::default(),
153-
epitems: LockedEPItemLinkedList::default(),
153+
epitems: EPollItemList::default(),
154154
fasync_items: FAsyncItems::new(),
155155
device_number,
156156
privete_fields: SpinLock::new(None),
@@ -677,7 +677,7 @@ pub struct TtyCoreData {
677677
/// 链接tty
678678
link: RwLock<Weak<TtyCore>>,
679679
/// epitems
680-
epitems: LockedEPItemLinkedList,
680+
epitems: EPollItemList,
681681
/// Open file descriptions registered for asynchronous TTY notification.
682682
fasync_items: FAsyncItems,
683683
/// 设备号
@@ -954,20 +954,14 @@ impl TtyCoreData {
954954

955955
#[inline]
956956
pub fn add_epitem(&self, epitem: Arc<EPollItem>) {
957-
self.epitems.lock().push_back(epitem)
957+
self.epitems.add(epitem)
958958
}
959959

960960
pub fn remove_epitem(&self, epitem: &Arc<EPollItem>) -> Result<(), SystemError> {
961-
let mut guard = self.epitems.lock();
962-
let len = guard.len();
963-
guard.retain(|x| !Arc::ptr_eq(x, epitem));
964-
if len != guard.len() {
965-
return Ok(());
966-
}
967-
Err(SystemError::ENOENT)
961+
self.epitems.remove(epitem)
968962
}
969963

970-
pub fn epitems(&self) -> &LockedEPItemLinkedList {
964+
pub fn epitems(&self) -> &EPollItemList {
971965
&self.epitems
972966
}
973967

kernel/src/filesystem/epoll/event_poll.rs

Lines changed: 96 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ use system_error::SystemError;
2626

2727
use super::{fs::EPollInode, EPollCtlOption, EPollEvent, EPollEventType, EPollItem};
2828

29-
/// epoll 就绪状态,由独立的 irqsave SpinLock 保护。
29+
/// Epoll ready state protected by a dedicated irqsave spin lock.
3030
///
31-
/// 对标 Linux 6.6 中由 `ep->lock`(rwlock_t)保护的 `rdllist` + `ovflist` + `wq`。
32-
/// 回调路径(`wakeup_epoll`,等价于 Linux `ep_poll_callback`)仅获取此 SpinLock,
33-
/// 不碰外层 Mutex,因此完全 hardirq-safe。
31+
/// This corresponds to Linux 6.6's `ep->lock`, which protects `rdllist`,
32+
/// `ovflist`, and the wait queue. The callback path does not acquire the outer
33+
/// sleeping mutex. The source registration snapshot still allocates and scans
34+
/// entries in hardirq context; that existing latency concern is separate from
35+
/// this lock-discipline guarantee.
3436
pub(crate) struct ReadyState {
3537
/// 就绪列表(正常路径)
3638
ready_list: LinkedList<Arc<EPollItem>>,
@@ -46,6 +48,21 @@ pub(crate) struct ReadyState {
4648
epoll_wq: WaitQueue,
4749
}
4850

51+
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
52+
pub(crate) struct EPollKey {
53+
open_file_id: usize,
54+
fd: i32,
55+
}
56+
57+
impl EPollKey {
58+
pub(super) fn new(file: &File, fd: i32) -> Self {
59+
Self {
60+
open_file_id: file.open_file_id(),
61+
fd,
62+
}
63+
}
64+
}
65+
4966
impl Debug for ReadyState {
5067
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5168
f.debug_struct("ReadyState")
@@ -69,11 +86,11 @@ impl Debug for ReadyState {
6986
#[derive(Debug)]
7087
pub struct EventPoll {
7188
/// 维护所有添加进来的socket的红黑树(由外层 Mutex 保护)
72-
ep_items: RBTree<i32, Arc<EPollItem>>,
89+
ep_items: RBTree<EPollKey, Arc<EPollItem>>,
7390
/// 就绪状态(由内层 irqsave SpinLock 保护)
7491
ready_state: Arc<SpinLock<ReadyState>>,
7592
/// 监听本 epollfd 的 epitems(用于支持 epoll 嵌套:epollfd 被加入另一个 epoll)
76-
pub(super) poll_epitems: Arc<LockedEPItemLinkedList>,
93+
pub(super) poll_epitems: Arc<EPollItemList>,
7794
self_ref: Option<Weak<Mutex<EventPoll>>>,
7895
}
7996

@@ -90,25 +107,23 @@ impl EventPoll {
90107
ovflist: None,
91108
epoll_wq: WaitQueue::default(),
92109
})),
93-
poll_epitems: Arc::new(LockedEPItemLinkedList::default()),
110+
poll_epitems: Arc::new(EPollItemList::default()),
94111
self_ref: None,
95112
}
96113
}
97114

98115
/// 关闭epoll时,执行的逻辑
99116
pub(super) fn close(&mut self) -> Result<(), SystemError> {
100-
let fds: Vec<i32> = self.ep_items.keys().cloned().collect::<Vec<_>>();
101-
// 清理红黑树里面的epitems
102-
for fd in fds {
103-
let fdtable = ProcessManager::current_pcb().basic().try_fd_table().clone();
104-
let file = fdtable.and_then(|fdtable| fdtable.read().get_file_by_fd(fd));
105-
106-
if let Some(file) = file {
107-
let epitm = self.ep_items.get(&fd).unwrap();
108-
// 尝试移除epitem,忽略错误(对于普通文件,我们没有添加epitem,所以会失败)
109-
let _ = file.remove_epitem(epitm);
117+
let keys: Vec<EPollKey> = self.ep_items.keys().copied().collect();
118+
for key in keys {
119+
let Some(epitem) = self.ep_items.get(&key).cloned() else {
120+
continue;
121+
};
122+
if let Some(file) = epitem.file().upgrade() {
123+
let _ = file.remove_epitem(&epitem);
110124
}
111-
self.ep_items.remove(&fd);
125+
Self::deactivate_and_remove_ready(self, &epitem);
126+
self.ep_items.remove(&key);
112127
}
113128

114129
Ok(())
@@ -252,7 +267,8 @@ impl EventPoll {
252267
}
253268
};
254269

255-
let ep_item = epoll_guard.ep_items.get(&dstfd).cloned();
270+
let key = EPollKey::new(&dst_file, dstfd);
271+
let ep_item = epoll_guard.ep_items.get(&key).cloned();
256272
let notify_nested = match op {
257273
EPollCtlOption::Add => {
258274
// 如果已经存在,则返回错误
@@ -265,16 +281,16 @@ impl EventPoll {
265281
Arc::downgrade(&epoll_data.epoll.0),
266282
Arc::downgrade(&epoll_guard.ready_state),
267283
Arc::downgrade(&epoll_guard.poll_epitems),
284+
key,
268285
epds,
269-
dstfd,
270286
Arc::downgrade(&dst_file),
271287
));
272288
Self::ep_insert(&mut epoll_guard, dst_file, epitem)?
273289
}
274290
EPollCtlOption::Del => match ep_item {
275291
Some(ref ep_item) => {
276292
// 删除
277-
Self::ep_remove(&mut epoll_guard, dstfd, Some(dst_file), ep_item)?;
293+
Self::ep_remove(&mut epoll_guard, key, Some(dst_file), ep_item)?;
278294
false
279295
}
280296
None => {
@@ -700,13 +716,13 @@ impl EventPoll {
700716
return Err(SystemError::ENOSYS);
701717
}
702718

703-
epoll_guard.ep_items.insert(epitem.fd, epitem.clone());
719+
epoll_guard.ep_items.insert(epitem.key(), epitem.clone());
704720

705721
// 先将 epitem 添加到目标文件的 epoll_items 中,这样之后的 notify/wakeup_epoll
706722
// 才能找到并唤醒这个 epitem。
707723
if let Err(e) = dst_file.add_epitem(epitem.clone()) {
708724
// 如果添加失败,需要清理 ep_items 中已插入的项
709-
epoll_guard.ep_items.remove(&epitem.fd);
725+
epoll_guard.ep_items.remove(&epitem.key());
710726
return Err(e);
711727
}
712728

@@ -726,18 +742,16 @@ impl EventPoll {
726742
Ok(notify_nested)
727743
}
728744

729-
pub fn ep_remove(
745+
fn ep_remove(
730746
epoll: &mut MutexGuard<EventPoll>,
731-
fd: i32,
747+
key: EPollKey,
732748
dst_file: Option<Arc<File>>,
733749
epitem: &Arc<EPollItem>,
734750
) -> Result<(), SystemError> {
751+
let removed = epoll.ep_items.remove(&key).ok_or(SystemError::ENOENT)?;
752+
Self::deactivate_and_remove_ready(epoll, &removed);
735753
if let Some(dst_file) = dst_file {
736-
dst_file.remove_epitem(epitem)?;
737-
}
738-
739-
if let Some(removed) = epoll.ep_items.remove(&fd) {
740-
Self::remove_ready_item(epoll, &removed);
754+
let _ = dst_file.remove_epitem(epitem);
741755
}
742756

743757
Ok(())
@@ -752,20 +766,21 @@ impl EventPoll {
752766
return;
753767
};
754768
let mut epoll = epoll.lock();
755-
let fd = epitem.fd();
756-
let Some(current) = epoll.ep_items.get(&fd).cloned() else {
769+
let key = epitem.key();
770+
let Some(current) = epoll.ep_items.get(&key).cloned() else {
757771
return;
758772
};
759773
if !Arc::ptr_eq(&current, epitem) {
760774
return;
761775
}
762776

763-
epoll.ep_items.remove(&fd);
764-
Self::remove_ready_item(&mut epoll, &current);
777+
epoll.ep_items.remove(&key);
778+
Self::deactivate_and_remove_ready(&epoll, &current);
765779
}
766780

767-
fn remove_ready_item(epoll: &mut MutexGuard<EventPoll>, epitem: &Arc<EPollItem>) {
781+
fn deactivate_and_remove_ready(epoll: &EventPoll, epitem: &Arc<EPollItem>) {
768782
let mut rs = epoll.ready_state.lock_irqsave();
783+
epitem.deactivate();
769784
rs.ready_list.retain(|item| !Arc::ptr_eq(item, epitem));
770785
if let Some(ovflist) = rs.ovflist.as_mut() {
771786
ovflist.retain(|item| !Arc::ptr_eq(item, epitem));
@@ -859,23 +874,19 @@ impl EventPoll {
859874
Ok(())
860875
}
861876

862-
/// ### epoll的回调,支持epoll的文件有事件到来时直接调用该方法即可
863-
///
864-
/// 对标 Linux `ep_poll_callback()`。仅获取内层 SpinLock(irqsave),
865-
/// **不获取外层 Mutex**,因此完全 hardirq-safe。
877+
/// Processes notifications from files that support epoll.
866878
///
867-
/// 回调路径通过 `EPollItem::ready_state()` 直接访问 `ReadyState`,
868-
/// 绕过 `Mutex<EventPoll>`。
879+
/// Like Linux `ep_poll_callback()`, this path never acquires the outer
880+
/// sleeping `Mutex<EventPoll>`. Registration and ready-state locks use
881+
/// irqsave discipline. The registration snapshot's existing allocation and
882+
/// O(N) scan remain a separately documented hardirq latency risk.
869883
pub fn wakeup_epoll(
870-
epitems: &LockedEPItemLinkedList,
884+
epitems: &EPollItemList,
871885
pollflags: EPollEventType,
872886
) -> Result<(), SystemError> {
873887
// 在 epitems 锁下复制一份快照,然后释放锁,再逐个处理。
874888
// 避免持有 epitems 锁时再去获取其他锁导致 ABBA 死锁。
875-
let epitems_snapshot: Vec<Arc<EPollItem>> = {
876-
let epitems_guard = epitems.lock_irqsave();
877-
epitems_guard.iter().cloned().collect()
878-
};
889+
let epitems_snapshot = epitems.snapshot();
879890

880891
for epitem in epitems_snapshot.iter() {
881892
// 通过 EPollItem 的 ready_state Weak 直接访问 ReadyState — 不需要 Mutex
@@ -907,6 +918,9 @@ impl EventPoll {
907918
{
908919
// 仅获取 SpinLock(irqsave)— hardirq-safe
909920
let mut rs = rs_arc.lock_irqsave();
921+
if !epitem.is_active() {
922+
continue;
923+
}
910924

911925
if let Some(ref mut ovflist) = rs.ovflist {
912926
// 扫描进行中 — 推入溢出列表
@@ -941,16 +955,45 @@ impl EventPoll {
941955
}
942956
}
943957

944-
/// LockedEPItemLinkedList — 使用 irqsave SpinLock 保护的 epitem 链表。
958+
/// An IRQ-safe list of epoll registrations attached to a pollable source.
945959
///
946-
/// 从 Mutex 改为 SpinLock 使得 `wakeup_epoll` 中的快照操作在 hardirq
947-
/// 上下文中也是安全的。链表操作(push_back、retain、snapshot clone)
948-
/// 临界区都很短,适合 SpinLock。
949-
pub type LockedEPItemLinkedList = SpinLock<LinkedList<Arc<EPollItem>>>;
960+
/// The inner lock is deliberately private: wakeups can run in hardirq context,
961+
/// so task-context registration updates must use the same irqsave discipline.
962+
#[derive(Debug)]
963+
pub struct EPollItemList {
964+
items: SpinLock<LinkedList<Arc<EPollItem>>>,
965+
}
966+
967+
impl EPollItemList {
968+
pub fn add(&self, epitem: Arc<EPollItem>) {
969+
self.items.lock_irqsave().push_back(epitem);
970+
}
971+
972+
pub fn remove(&self, epitem: &Arc<EPollItem>) -> Result<(), SystemError> {
973+
let mut items = self.items.lock_irqsave();
974+
let old_len = items.len();
975+
items.retain(|item| !Arc::ptr_eq(item, epitem));
976+
if items.len() != old_len {
977+
Ok(())
978+
} else {
979+
Err(SystemError::ENOENT)
980+
}
981+
}
950982

951-
impl Default for LockedEPItemLinkedList {
983+
fn snapshot(&self) -> Vec<Arc<EPollItem>> {
984+
self.items.lock_irqsave().iter().cloned().collect()
985+
}
986+
987+
pub fn take_all(&self) -> LinkedList<Arc<EPollItem>> {
988+
core::mem::take(&mut *self.items.lock_irqsave())
989+
}
990+
}
991+
992+
impl Default for EPollItemList {
952993
fn default() -> Self {
953-
SpinLock::new(LinkedList::new())
994+
Self {
995+
items: SpinLock::new(LinkedList::new()),
996+
}
954997
}
955998
}
956999

kernel/src/filesystem/epoll/fs.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ impl PollableInode for EPollInode {
117117
_private_data: &FilePrivateData,
118118
) -> Result<(), SystemError> {
119119
let poll_epitems = { self.epoll.0.lock().poll_epitems.clone() };
120-
poll_epitems.lock_irqsave().push_back(epitem);
120+
poll_epitems.add(epitem);
121121
Ok(())
122122
}
123123

@@ -127,13 +127,6 @@ impl PollableInode for EPollInode {
127127
_private_data: &FilePrivateData,
128128
) -> Result<(), SystemError> {
129129
let poll_epitems = { self.epoll.0.lock().poll_epitems.clone() };
130-
let mut guard = poll_epitems.lock_irqsave();
131-
let len = guard.len();
132-
guard.retain(|x| !Arc::ptr_eq(x, epitem));
133-
if guard.len() != len {
134-
Ok(())
135-
} else {
136-
Err(SystemError::ENOENT)
137-
}
130+
poll_epitems.remove(epitem)
138131
}
139132
}

0 commit comments

Comments
 (0)