如何将UEFI非阻塞EFI_TCP6_PROTOCOL转为Rust阻塞式实现?
解决UEFI非阻塞TCP转Rust阻塞式操作的方案
核心思路:用自定义可等待事件替代EVT_NOTIFY_SIGNAL
既然EVT_NOTIFY_SIGNAL类型事件无法调用WaitForEvent(),我们可以自己创建**EVT_NOTIFY_WAIT类型的事件**,配合UEFI的回调机制实现阻塞等待逻辑,这是适配Rust标准库阻塞语义的关键。
1. 实现TcpListener::accept()的阻塞逻辑
- 先通过
CreateEvent()创建一个EVT_NOTIFY_WAIT | EVT_EVENT_GROUP_EXIT_BOOT_SERVICES类型的事件,这类事件支持WaitForEvent()调用。 - 初始化
EFI_TCP6_LISTEN_TOKEN时,把这个自定义事件绑定到CompletionToken.Event字段。 - 给事件注册一个极简回调函数:只需要在回调里调用
SetEvent()触发我们的自定义事件即可,不用做其他逻辑。 - 调用
Tcp6.Listen()提交非阻塞请求后,立刻调用WaitForEvent()阻塞等待事件触发。事件触发后,再检查ListenToken的状态,处理连接建立后的逻辑(比如取出新的TCP6实例封装成Rust的TcpStream)。
示例伪代码(Rust风格):
// 创建自定义可等待事件 let mut wait_event = unsafe { let mut event = core::ptr::null_mut(); uefi_services::boot_services() .create_event( EVT_NOTIFY_WAIT | EVT_EVENT_GROUP_EXIT_BOOT_SERVICES, TPL_CALLBACK, Some(tcp_listen_callback), core::ptr::null_mut(), &mut event, ) .unwrap(); event }; // 初始化ListenToken let mut listen_token = EFI_TCP6_LISTEN_TOKEN { CompletionToken: EFI_TCP6_COMPLETION_TOKEN { Event: wait_event, // 绑定自定义事件 Status: EFI_SUCCESS, }, // 填充其他必要字段(比如LocalAddress、RemoteAddress等) ..Default::default() }; // 提交非阻塞Listen请求 unsafe { (*tcp6_protocol).Listen(tcp6_protocol, &mut listen_token).unwrap(); } // 阻塞等待连接事件触发 unsafe { let mut index = 0; uefi_services::boot_services() .wait_for_event(1, &mut wait_event, &mut index) .unwrap(); } // 处理连接结果 let result = if listen_token.CompletionToken.Status == EFI_SUCCESS { // 将新的TCP6实例封装为Rust TcpStream Ok(TcpStream::new(listen_token.NewConnection)) } else { // 转换为Rust标准库的IO错误类型 Err(std::io::Error::from_raw_os_error( listen_token.CompletionToken.Status as i32, )) }; // 清理事件资源 unsafe { uefi_services::boot_services().close_event(wait_event).unwrap(); } result // 回调函数:仅触发事件 extern "efiapi" fn tcp_listen_callback(event: *mut EFI_EVENT, _context: *mut c_void) { unsafe { uefi_services::boot_services().set_event(event).unwrap(); } }
2. 阻塞式收发操作的通用方案
对于TcpStream的read()/write(),可以复用完全相同的逻辑:
- 每次发起
Tcp6.Receive()或Tcp6.Transmit()请求时,创建独立的EVT_NOTIFY_WAIT事件。 - 在请求的
CompletionToken中绑定该事件,回调函数仅负责触发事件。 - 调用收发API后立刻
WaitForEvent()阻塞等待,事件触发后检查操作状态,读取或处理数据。
关键注意事项
- 每次请求完成后,务必调用
CloseEvent()清理事件资源,避免UEFI环境下的内存泄漏。 - 错误处理要做好:把UEFI的状态码正确转换为Rust标准库的
std::io::Error类型,符合Rust网络API的错误语义。 - UEFI环境通常是单线程模型,无需考虑多线程下的事件竞争,但要保证每个请求对应独立的事件实例,避免交叉触发。
内容的提问来源于stack exchange,提问作者Ayush Singh
相关产品推荐
相关产品推荐

