Libublk

license license

Rust library for building linux ublk target device, which talks with linux ublk driver[^1] for exposing standard linux block device, meantime all target IO logic can be moved to userspace.

Linux kernel 6.0 starts to support ublk covered by config option of CONFIGBLKDEV_UBLK.

Documentations

ublk doc links

ublk introduction

Quick Start

Follows one totally working 2-queue ublk-null target which is built over libublk 0.1, and each queue depth is 64, and each IO\'s max buffer size is 512KB.

To use libublk crate, first add this to your Cargo.toml:

toml [dependencies] libublk = "0.1"

Next we can start using libublk crate. The following is quick introduction for adding ublk-null block device, which is against low level APIs.

``` rust use libublk::ctrl::UblkCtrl; use libublk::io::{UblkDev, UblkIOCtx, UblkQueue}; use std::sync::Arc;

fn main() { let nrqueues = 2; //two queues //io depth: 64, max buf size: 512KB let mut ctrl = UblkCtrl::new(-1, nrqueues, 64, 512 << 10, 0, true).unwrap();

// target specific initialization by tgt_init closure, which is flexible
// for customizing target with captured environment
let tgt_init = |dev: &mut UblkDev| {
    dev.set_default_params(250_u64 << 30);
    Ok(serde_json::json!({}))
};
let ublk_dev =
    Arc::new(UblkDev::new("null".to_string(), tgt_init, &mut ctrl, 0).unwrap());
let mut threads = Vec::new();

for q in 0..nr_queues {
    let dev = Arc::clone(&ublk_dev);
    threads.push(std::thread::spawn(move || {
        let mut queue = UblkQueue::new(q as u16, &dev).unwrap();
        let ctx = queue.make_queue_ctx();

        //IO handling closure(FnMut), we are driven by io_uring
        //CQE, and this closure is called for every incoming CQE
        //(IO command or target io completion)
        let io_handler = move |io: &mut UblkIOCtx| {
            let iod = ctx.get_iod(io.get_tag());
            let bytes = unsafe { (*iod).nr_sectors << 9 } as i32;

            io.complete_io(bytes);
            Ok(0)
        };
        queue.wait_and_handle_io(io_handler);
    }));
}
ctrl.start_dev(&ublk_dev).unwrap();
ctrl.dump();
for qh in threads {
    qh.join().unwrap();
}
ctrl.stop_dev(&ublk_dev).unwrap();

} ```

The following ublk-null block device is built over high level APIs, which doesn't support IO closure of FnMut.

``` rust use libublk::io::{UblkDev, UblkIOCtx, UblkQueueCtx}; use libublk::{ctrl::UblkCtrl, UblkError};

fn main() { let sess = libublk::UblkSessionBuilder::default() .name("null") .depth(64u32) .nrqueues(2u32) .build() .unwrap(); let tgtinit = |dev: &mut UblkDev| { dev.setdefaultparams(250u64 << 30); Ok(serdejson::json!({})) }; let wh = { let (mut ctrl, dev) = sess.createdevices(tgtinit).unwrap(); let handleio = move |ctx: &UblkQueueCtx, io: &mut UblkIOCtx| -> Result { let iod = ctx.getiod(io.gettag()); io.completeio(unsafe { (*iod).nr_sectors << 9 } as i32); Ok(0) };

    sess.run(&mut ctrl, &dev, handle_io, |dev_id| {
        let mut d_ctrl = UblkCtrl::new(dev_id, 0, 0, 0, 0, false).unwrap();
        d_ctrl.dump();
    })
    .unwrap()
};
wh.join().unwrap();

} ```

Test

You can run the test of the library with the following command.

```

cargo test

```

Performance

When running fio t/io_uring /dev/ublkb0[^2], IOPS is basically same with running same test over ublk device created by blktests miniublk[^3], which is written by pure C. And the ublk device is null, which has 2 queues, each queue's depth is 64.

Examples

null

loop

License

This project is licensed under either of Apache License, Version 2.0 or MIT license at your option.

Contribution

Any kinds of contributions are welcome!

References