Saturday, 8 May 2021

Linux io_uring example and internals

Linux io_uring basics and details can be fetched from here https://kernel.dk/io_uring.pdf. Reader is encouraged to complete this document first. 

Also various examples of io_uring are mentioned here:
https://github.com/shuveb/io_uring-by-example   

I am using 5.3.18-22 kernel in this blog. 

Lets see the program to do read of twenty(20) 512bytes blocks from a block device.

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <linux/fs.h>

#include <fcntl.h>
#include <unistd.h>
#include <string.h>
/* If your compilation fails because the header file below is missing,
 * your kernel is probably too old to support io_uring.
 * */
#include <linux/io_uring.h>

#define QUEUE_DEPTH 1
#define BLOCK_SZ    512

/* This is x86 specific */
#define read_barrier()  __asm__ __volatile__("":::"memory")
#define write_barrier() __asm__ __volatile__("":::"memory")

struct app_io_sq_ring {
    unsigned *head;
    unsigned *tail;
    unsigned *ring_mask;
    unsigned *ring_entries;
    unsigned *flags;
    unsigned *array;
};

struct app_io_cq_ring {
    unsigned *head;
    unsigned *tail;
    unsigned *ring_mask;
    unsigned *ring_entries;
    struct io_uring_cqe *cqes;
};

struct submitter {
    int ring_fd;
    struct app_io_sq_ring sq_ring;
    struct io_uring_sqe *sqes;
    struct app_io_cq_ring cq_ring;
};

int io_uring_setup(unsigned entries, struct io_uring_params *p)
{
    return (int) syscall(__NR_io_uring_setup, entries, p);
}

int io_uring_enter(int ring_fd, unsigned int to_submit,
                          unsigned int min_complete, unsigned int flags)
{
    return (int) syscall(__NR_io_uring_enter, ring_fd, to_submit, min_complete,
                   flags, NULL, 0);
}

struct io_info {
    int num_io;
    struct iovec iovecs[];
};

struct file_info {
    off_t file_sz;
    struct iovec iovecs[];      /* Referred by readv/writev */
};

int submit_to_sq(char *file_path, struct submitter *s) {
    struct io_info *ii;
    struct stat st;

    int file_fd = open(file_path, O_RDONLY);
    if (file_fd < 0 ) {
        perror("open");
        return 1;
    }

    struct app_io_sq_ring *sring = &s->sq_ring;
    unsigned index = 0, tail = 0, next_tail = 0;
    off_t file_sz = 0;

    // fetch the block device size

    if(fstat(file_fd, &st) < 0) {
        perror("fstat");
        return -1;
    }

    if (S_ISBLK(st.st_mode)) {
        unsigned long long bytes;
        if (ioctl(file_fd, BLKGETSIZE64, &bytes) != 0) {
            perror("ioctl");
            return -1;
        }
        file_sz =  bytes;
    } else if (S_ISREG(st.st_mode))
        file_sz =  st.st_size;

    printf("sz = %llu\n", file_sz);

    if (file_sz < 0)
        return 1;


    //Lets submit IO to read first 20 blocks
    struct iovec *iovecs;//[20];
    ii = malloc(sizeof(*ii) + sizeof(struct iovec) * 20);
    memset (ii, 0, sizeof(*ii) + (sizeof(struct iovec) * 20));

    iovecs = ii->iovecs;
    ii->num_io = 20;

    for (int i = 0;i < 20;i++)
    {
        iovecs[i].iov_len = 512;

        void *buf;
        if( posix_memalign(&buf, BLOCK_SZ, BLOCK_SZ)) {
            perror("posix_memalign");
            return 1;
        }

        iovecs[i].iov_base = buf;
    }
    /* Add our submission queue entry to the tail of the SQE ring buffer */

    next_tail = tail = *sring->tail;
    read_barrier();
    index = tail & *s->sq_ring.ring_mask;
    next_tail++;

    struct io_uring_sqe *sqe = &s->sqes[index];
    sqe->fd = file_fd;
    sqe->flags = 0;
    sqe->opcode = IORING_OP_READV;
    sqe->addr = (unsigned long) iovecs;
    sqe->len = 20;
    sqe->off = 0;
    sqe->user_data = (unsigned long long) ii;
    sring->array[index] = index;
    tail = next_tail;

    /* Update the tail so the kernel can see it. */
    if(*sring->tail != tail) {
        *sring->tail = tail;
        write_barrier();
    }

    int ret =  io_uring_enter(s->ring_fd, 1, 1,  IORING_ENTER_GETEVENTS);
    if(ret < 0) {
        perror("io_uring_enter");
        return 1;
    }
    return 0;
}

void read_from_cq(struct submitter *s) {
    struct iovec *iovecs;
    struct io_info *ii;
    struct app_io_cq_ring *cring = &s->cq_ring;
    struct io_uring_cqe *cqe;
    unsigned head, reaped = 0;
    head = *cring->head;
    do {
        read_barrier();
        /*
         * Remember, this is a ring buffer. If head == tail, it means that the
         * buffer is empty.
         * */
        if (head == *cring->tail)
            break;

        /* Get the entry */
        cqe = &cring->cqes[head & *s->cq_ring.ring_mask];
        ii = (struct io_info*) cqe->user_data;

        iovecs = ii->iovecs;
        if (cqe->res < 0)
            fprintf(stderr, "Error: %s\n", strerror(abs(cqe->res)));

        for (int i = 0; i < 20; i++)
        {
             printf("iov_base  = %p, iov_len = %d\n",
                 iovecs[i].iov_base, iovecs[i].iov_len);
             printf("%d, %d\n", *((char *)iovecs[i].iov_base) , *((char *)iovecs[i].iov_base + 1));

        }
        head++;
    } while (1);
    cring->head = head;
    write_barrier();
}


int app_setup_uring(struct submitter *s) {
struct app_io_sq_ring *sring = &s->sq_ring;
    struct app_io_cq_ring *cring = &s->cq_ring;
    struct io_uring_params p;
    void *sq_ptr, *cq_ptr;
    memset(&p, 0, sizeof(p));
    s->ring_fd = io_uring_setup(QUEUE_DEPTH, &p);
    if (s->ring_fd < 0) {
        perror("io_uring_setup");
        return 1;
    }

    // Fetch and decide on submission and completion ring sizes
    int sring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned);
    int cring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);
    printf("sring_sz  = %d, cring_sz = %d\n", sring_sz, cring_sz);

    //  mmap the submission ring
    sq_ptr = mmap(0, sring_sz, PROT_READ | PROT_WRITE,
            MAP_SHARED | MAP_POPULATE,
            s->ring_fd, IORING_OFF_SQ_RING);
    if (sq_ptr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }


    // mmap the completion ring
    /* Map in the completion queue ring buffer in older kernels separately */
        cq_ptr = mmap(0, cring_sz, PROT_READ | PROT_WRITE,
                MAP_SHARED | MAP_POPULATE,
                s->ring_fd, IORING_OFF_CQ_RING);

        if (cq_ptr == MAP_FAILED) {

            perror("mmap");
            return 1;
        }
    /* Save useful fields in a global app_io_sq_ring struct for later easy reference */
    sring->head = sq_ptr + p.sq_off.head;
    sring->tail = sq_ptr + p.sq_off.tail;
    sring->ring_mask = sq_ptr + p.sq_off.ring_mask;
    sring->ring_entries = sq_ptr + p.sq_off.ring_entries;
    sring->flags = sq_ptr + p.sq_off.flags;
    sring->array = sq_ptr + p.sq_off.array;
    /* Map in the submission queue entries array */
    s->sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe),
            PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
            s->ring_fd, IORING_OFF_SQES);
    if (s->sqes == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    /* Save useful fields in a global app_io_cq_ring struct for later easy reference */
    cring->head = cq_ptr + p.cq_off.head;
    cring->tail = cq_ptr + p.cq_off.tail;
    cring->ring_mask = cq_ptr + p.cq_off.ring_mask;
    cring->ring_entries = cq_ptr + p.cq_off.ring_entries;
    cring->cqes = cq_ptr + p.cq_off.cqes;
    return 0;
}
int main(int argc, char *argv[]) {
    struct submitter *s;
    s = malloc(sizeof(*s));
    if (!s) {
        perror("malloc");
        return 1;
    }
    memset(s, 0, sizeof(*s));
    if(app_setup_uring(s)) {
        fprintf(stderr, "Unable to setup uring!\n");
        return 1;
    }
    printf("setup completed\n");

    // Open the block device and read the data

    submit_to_sq(argv[1], s);
    printf("submit cq completed\n");
    read_from_cq(s);
    printf("read cq completed\n");
}

Output of this program :
 ./a.out /dev/sdej
sring_sz  = 148, cring_sz = 176
setup completed
sz = 5369364480
submit cq completed
iov_base  = 0x17aca00, iov_len = 512
0, 0
iov_base  = 0x17ace00, iov_len = 512
0, 0
iov_base  = 0x17ad200, iov_len = 512
-128, 2
iov_base  = 0x17ad600, iov_len = 512
0, 0
iov_base  = 0x17ada00, iov_len = 512
0, 0
iov_base  = 0x17ade00, iov_len = 512
0, 0
iov_base  = 0x17ae200, iov_len = 512
0, 0
iov_base  = 0x17ae800, iov_len = 512
0, 0
iov_base  = 0x17aec00, iov_len = 512
-127, 2
iov_base  = 0x17af000, iov_len = 512
-119, 2
iov_base  = 0x17af400, iov_len = 512
0, 0
iov_base  = 0x17af800, iov_len = 512
8, 0
iov_base  = 0x17afc00, iov_len = 512
0, 0
iov_base  = 0x17b0000, iov_len = 512
0, 0
iov_base  = 0x17b0400, iov_len = 512
0, 0
iov_base  = 0x17b0a00, iov_len = 512
0, 0
iov_base  = 0x17b0e00, iov_len = 512
2, -128
iov_base  = 0x17b1200, iov_len = 512
0, 0
iov_base  = 0x17b1600, iov_len = 512
0, 0
iov_base  = 0x17b1a00, iov_len = 512
0, 0
read cq completed

Lets see the system calls this program uses : 
cat /proc/kallsyms | grep uring_setup
ffffffff9cf30b30 t io_uring_setup
ffffffff9cf31350 T __x64_sys_io_uring_setup

Also after this mmap is done for submission and completion rings. Then we use io_uring_enter

 cat /proc/kallsyms | grep io_uring_enter
ffffffff9cf300c0 T __ia32_sys_io_uring_enter
ffffffff9cf30390 T __x64_sys_io_uring_enter


Ftrace for the io_uring_enter : 


# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
  9)               |  __x64_sys_io_uring_enter() {
  9)               |    __fdget() {
  9)   0.395 us    |      __fget_light();
  9)   1.620 us    |    }
  9)   0.370 us    |    mutex_lock();
  9)               |    io_ring_submit() {
  9)   0.315 us    |      io_get_sqring();
  9)               |      io_submit_sqe() {
  9)               |        kmem_cache_alloc() {
  9)   0.333 us    |          should_failslab();
  9)   1.075 us    |        }
  9)               |        fget() {
  9)   0.318 us    |          __fget();
  9)   0.960 us    |        }
  9)               |        io_queue_sqe() {
  9)               |          __io_submit_sqe() {
  9)               |            io_read() {
  9)   1.003 us    |              io_prep_rw();
  9)               |              io_import_iovec.isra.28() {
  9)               |                rw_copy_check_uvector() {
  9)               |                  __kmalloc() {
  9)   0.343 us    |                    kmalloc_slab();
  9)   0.325 us    |                    should_failslab();
  9)   1.579 us    |                  }
  9)   2.604 us    |                }
  9)   3.300 us    |              }
  9)               |              rw_verify_area() {
  9)               |                security_file_permission() {
  9)               |                  apparmor_file_permission() {
  9)   0.480 us    |                    common_file_perm();
  9)   1.030 us    |                  }
  9)   0.303 us    |                  __fsnotify_parent();
  9)   0.320 us    |                  fsnotify();
  9)   3.010 us    |                }
  9)   3.650 us    |              }
  9)               |              blkdev_read_iter() {
  9)               |                generic_file_read_iter() {
  9)               |                  _cond_resched() {
  9)   0.295 us    |                    rcu_all_qs();
  9)   0.888 us    |                  }
  9)               |                  pagecache_get_page() {
  9)   0.420 us    |                    find_get_entry();
  9)   1.133 us    |                  }
  9)               |                  touch_atime() {
  9)   0.758 us    |                    atime_needs_update();
  9)   0.493 us    |                    __sb_start_write();
  9)   0.622 us    |                    __mnt_want_write();
  9)   0.513 us    |                    current_time();
  9)   0.767 us    |                    generic_update_time();
  9)   0.293 us    |                    __mnt_drop_write();
  9)   0.290 us    |                    __sb_end_write();
  9)   6.542 us    |                  }
  9) + 10.372 us   |                }
  9) + 10.992 us   |              }
  9)   0.302 us    |              io_async_list_note();
  9)   0.385 us    |              kfree();
  9) + 22.211 us   |            }
  9) + 23.097 us   |          }
  9)               |          kmem_cache_alloc_trace() {
  9)   0.285 us    |            should_failslab();
  9)   0.888 us    |          }
  9)               |          queue_work_on() {
  9)               |            __queue_work() {
  9)   0.295 us    |              get_work_pool();
  9)   0.375 us    |              _raw_spin_lock();
  9)               |              insert_work() {
  9)   0.287 us    |                get_pwq.isra.24();
  9)               |                wake_up_process() {
  9)               |                  try_to_wake_up() {
  9)   0.480 us    |                    _raw_spin_lock_irqsave();
  9)   1.093 us    |                    select_task_rq_fair();
  9)   0.315 us    |                    _raw_spin_lock();
  9)   0.420 us    |                    update_rq_clock();
  9)   3.212 us    |                    ttwu_do_activate();
  9)   0.405 us    |                    _raw_spin_unlock_irqrestore();
  9)   8.424 us    |                  }
  9)   9.002 us    |                }
  9) + 10.362 us   |              }
  9) + 12.360 us   |            }
  9) + 12.982 us   |          }
  9) + 38.604 us   |        }
  9) + 42.868 us   |      }
  9) + 45.786 us   |    }
  9)   0.345 us    |    mutex_unlock();
  9)               |    io_cqring_wait() {
  9)   0.310 us    |      init_wait_entry();
  9)               |      prepare_to_wait_event() {
  9)   0.307 us    |        _raw_spin_lock_irqsave();
  9)   0.300 us    |        _raw_spin_unlock_irqrestore();
  9)   1.545 us    |      }
  9)               |      schedule() {
  9)               |        rcu_note_context_switch() {
  9)   0.293 us    |          rcu_qs();
  9)   0.905 us    |        }
  9)   0.315 us    |        _raw_spin_lock();
  9)               |        update_rq_clock() {
  9)   0.367 us    |          update_rq_clock.part.85();
  9)   0.912 us    |        }
  9)               |        deactivate_task() {
  9)               |          dequeue_task_fair() {
  9)               |            dequeue_entity() {
  9)               |              update_curr() {
  9)   0.352 us    |                update_min_vruntime();
  9)   0.570 us    |                cpuacct_charge();
  9)               |                __cgroup_account_cputime() {
  9)   0.552 us    |                  cgroup_rstat_updated();
  9)   1.487 us    |                }
  9)   4.268 us    |              }
  9)               |              __update_load_avg_se() {
  9)   0.467 us    |                __accumulate_pelt_segments();
  9)   1.555 us    |              }
  9)   0.508 us    |              _raw_spin_lock();
  9)               |              __update_load_avg_cfs_rq() {
  9)   0.455 us    |                __accumulate_pelt_segments();
  9)   1.525 us    |              }
  9)               |              dbs_update_util_handler() {
  9)   0.498 us    |                cpufreq_this_cpu_can_update();
  9)   1.433 us    |              }
  9)   0.450 us    |              clear_buddies();
  9)   0.448 us    |              account_entity_dequeue();
  9)   0.462 us    |              update_cfs_group();
  9)   0.552 us    |              update_min_vruntime();
  9) + 16.094 us   |            }
  9)   0.478 us    |            hrtick_update();
  9) + 18.077 us   |          }
  9) + 18.975 us   |        }
  9)               |        pick_next_task_fair() {
  9)               |          newidle_balance() {
  9)   0.583 us    |            __msecs_to_jiffies();
  9)   1.758 us    |          }
  9)   2.788 us    |        }
  9)               |        put_prev_task_fair() {
  9)               |          put_prev_entity() {
  9)   0.477 us    |            check_cfs_rq_runtime();
  9)   1.347 us    |          }
  9)   2.385 us    |        }
  9)               |        pick_next_task_idle() {
  9)               |          set_next_task_idle() {
  9)   0.573 us    |            __update_idle_core();
  9)   1.512 us    |          }
  9)   2.535 us    |        }
  9)   0.430 us    |        enter_lazy_tlb();
  9)   1.495 us    |        finish_task_switch();
  9) ! 853.302 us  |      }
  9)               |      prepare_to_wait_event() {
  9)   0.585 us    |        _raw_spin_lock_irqsave();
  9)   0.873 us    |        _raw_spin_unlock_irqrestore();
  9)   2.938 us    |      }
  9)               |      finish_wait() {
  9)   0.297 us    |        _raw_spin_lock_irqsave();
  9)   0.305 us    |        _raw_spin_unlock_irqrestore();
  9)   1.605 us    |      }
  9) ! 862.405 us  |    }
  9)   0.315 us    |    io_ring_drop_ctx_refs();
  9) ! 915.753 us  |  }





No comments:

Post a Comment