登场|RustMagazineio_uring无疑是近两年内核圈最热门的话题之一。作为流行的Linux异步I/O接口,它的野心更大。想把Linux的I/O操作完全异步化,也希望把所有的Linux系统调用都异步化。作为一种系统级编程语言,Rust具有安全和高性能的特点。想必大家想用Rust语言来“尝鲜”io_uring。不幸的是,io_uring的作者JensAxboe只维护一个C语言库。如果用户想使用Rust来调用,一方面需要做一些封装。另一方面,C语言的接口还是太底层了,要在Rust的异步框架中使用它还有很多工作要做。好消息是github上已经出现了一些用Rust语言封装的io_uring库。今天就让我们挑选一些用户量大(以star数来判断)的库来分析一下,看看能不能给大家使用io_uring带来方便。Tokioio-uringTokio是github上Star数最多的异步框架,那么他们团队封装的io_uringlib呢?阅读代码不难发现,io_uring库完全摒弃了C语言中的liburing库,在io_uring系统调用上从头封装了一层,实现了提交队列、完成队列和提交器。上述三层抽象比C语言的封装略高,但仍然需要用户将请求放入提交队列,将响应从完成队列中取出,这与同步读写有很大区别方法,也与Rust现有的异步I/O框架的设计有很大的不同。下面是一个简单的示例代码:letmutring=IoUring::new(256)?;let(submitter,mutsq,mutcq)=ring.split();letmutaccept=AcceptCount::new(listener.as_raw_fd(),token_alloc.insert(Token::Accept),3);//将请求放入提交队列accept.push_to(&mutsq);//提交请求匹配submitter.submit_and_wait(1){Ok(_)=>(),Err(referr)iferr.raw_os_error()==Some(libc::EBUSY)=>(),Err(err)=>returnErr(err.into()),}//getcompleteeventsfromthecompletionqueueforcqein&mutcq{...}io_uring库的优缺点如下:优点:1.纯Rust封装,安全性更好。2、与封装高层的C语言库相比,接口更简单易用。缺点:1.维护成本较高,需要根据内核更新手动添加新特性,包括新的数据结构。2.封装不够彻底,暴露了底层实现的两个队列,用户使用不便。Spacejamrio的io_uring库在撰写本文时已达到590星,该库的作者还创建了sled嵌入式数据库。由于sled数据库也使用了这个io_uring库,我们有理由相信rio是一个经过实际项目验证的库,其更友好的用户界面降低了用户的使用难度。通过下面这个简单的例子,你可以很容易地感受到接口的易用性:///Readfileexampleletring=rio::new().expect("createuring");letfile=std::fs::open("file").expect("openat");让数据:&mut[u8]=&mut[0;66];letcompletion=ring.read_at(&file,&mutdata,at);//如果使用线程完成。wait()?;//如果使用asynccompletion.await?rio同时提供了线程和异步编程模型的接口,在提供便利的同时大大减少了用户的约束,可以自由选择自己喜欢的编程模型。但是,这个库是不健全的,即它可能被错误或恶意使用。而且根据作者在issue中的回复,作者是不会修复的。这将使构建在该库之上的软件变得不安全。io_uring库的优缺点罗列如下:优点:1.接口丰富,使用方便。2、有实际项目验证。缺点:1.不健全,安全性差。ringbahnringbahn的作者是withoutboats,Rust语言的核心开发者之一。该库由三个抽象层组成。第一层是C语言libfuse的Rust包,命名为uring-sys;第二层是SubmissionQueue和CompletionQueue等数据结构的封装,命名为iou;最后一层是封装Rust异步编程的接口。不难看出,ringbahn在设计上有更多的考虑,从界面易用性到安全性。以下是复制文件的示例:///CopyFilefromprops.txttotest.txtfutures::executor::block_on(asyncmove{letmutinput:File=File::open("props.txt").await.unwrap();letmutoutput:File=File::create("test.txt").await.unwrap();letmutbuf=vec![0;1024];letlen=input.read(&mutbuf).await.unwrap();output.write(&mutbuf[0..len]).await.unwrap();output.flush().await.unwrap();});library并不完美,它还有以下缺陷:1.并发性不友好,SubmissionQueue上有一个大锁,每个提交任务的线程都会被序列化。2.读写操作会导致内存在用户态被复制。对于大数据量的操作,冗余的内存副本会带来明显的性能下降。之所以进行内存复制,是为了保证传给内核的内存缓冲区不会被用户态异步修改,以保证安全。作者在Readme文件中也说明,顶层ringbahn包只是尝试,不适合正式生产使用。DatenLordring-io基于以上讨论,我们的团队Datenlord也实现了自己的io_uringRustlib,命名为ring-io。目前的实现借鉴了Tokioio-uring和iou的经验,也实现了SubmissionQueue和CompletionQueue的抽象。具体实现细节可以参考王旭阳写的文章。目前的实现还存在以下问题:1.暴露了一些不安全的接口,提醒用户注意某些操作,不正确的与内核交互会带来不可预知的结果。2、抽象层低,使用不方便。接下来我们会针对一些特定的buffer类型实现异步I/O接口,方便用户使用,暴露安全接口。在实现的过程中,我们也会考虑效率,避免不必要的内存拷贝。与ringbahn的方式不同,我们保证内存安全的方式是Rust提供的内存所有权的转移,即用户在发送I/O请求后不再拥有buffer的所有权,所有权不归还直到请求返回。具体实现细节将在下一篇文章中讨论。下面是设计的架构图:SQ提交器负责将用户Task发送的I/O请求通过io_uring发送给内核。CQ收集器负责将内核返回的结果返回给用户。UserTasks将在各自的通道上被阻塞,并且在I/O任务完成之前不会重新安排UserTasks。总结虽然io_uring很受欢迎,国内外很多团队都进行了Rust封装,但是目前还没有一个完美的方案同时解决安全性、高性能和易用性的问题。大家可以根据自己的情况选择符合自己需求的库。当然,我们也希望大家积极为社区做贡献,提出自己的想法,打造更好、更安全、更快的io_uring库。DatenLordDatenLord是Rust实现的新一代开源分布式存储,为云原生场景提供高性能存储解决方案。一方面,在今天的硬件架构下,CPU和GPU的计算速度远远超过了IO的速度。即使是现在,NVMeSSD的IO速度也比过去机械硬盘的速度提升了一百倍,网络速度也至少快了一百倍。但是还是经常会遇到IO跟不上计算速度的问题,导致计算等待数据,降低了计算的性能。另一方面,操作系统的IO模型很长时间没有发生大的变化,内核仍然是执行IO任务的主体。这种方式带来了很多额外的开销,比如数据拷贝、系统调用造成的阻塞、进程上下文切换等等。为了提高IO性能,DatenLord采用绕过内核(bypassKernel)的方式,主要在用户态实现IO功能,避免内核执行IO任务带来的额外开销,实现高性能的分布式存储。推荐阅读io_uringRust异步库实现方法
