Articles in this blog

Tuesday, 3 August 2021

Linux fiemap internals

In this blog lets look and try to find the physical blocks of a file. Given a struct inode * of a file.

Every file has a inode. There is a on disk inode and one struct inode. The disk inode is stored in the disk, so across reboots it holds the information of a file whereabouts and details in the disk. 

On disk file inode has different layout for various filesystems: 

If we see the ext4.h, it looks like this :
/*
 * Structure of an inode on the disk
 */
struct ext4_inode {
..
..
};

This has mapping from file blocks to disk blocks.

For every filesystem, we can call an IOCTL and get the filemaps. 
A C program to call this IOCTL is mentioned here :
https://github.com/shekkbuilder/fiemap/blob/master/fiemap.c

User space C code looks like : 
/* Find out how many extents there are */
if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
fprintf(stderr, "fiemap ioctl() failed\n");
return NULL;
}
..
..
if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
fprintf(stderr, "fiemap ioctl() failed\n");
return NULL;
}

Kernel ioctl called for these is : 

static int ioctl_fiemap(struct file *filp, struct fiemap __user *ufiemap)
{  
..
error = inode->i_op->fiemap(inode, &fieinfo, fiemap.fm_start,
fiemap.fm_length);
..
}

This will call inode specific fiemap : 
For ext4 it is :
int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
u64 start, u64 len)
{
int error = 0;
For BTRFS is is :
static int btrfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
__u64 start, __u64 len)
{

For XFS it is :
STATIC int
xfs_vn_fiemap(
struct inode *inode,
struct fiemap_extent_info *fieinfo,
u64 start,
u64 length)
{


Kernel documentation talks about the fiemap here :
Documentation\filesystems\fiemap.rst

So can we print the filemap from kernel. Answers is yes, we can do it by calling inode fiemap function from kernel.

Lets look at the implementation which fetches this info from kernel : 
u64 traverse_extent(struct inode *inode)
{
        int n = 0;
        u64 sector_num = 0;
    if (inode->i_op->fiemap)
    {
        printk("traverse_extent fiemap present = %llx\n", inode->i_op->fiemap);
        struct fiemap fiemap;
        fiemap.fm_start = 0;
        fiemap.fm_extent_count = 1;

        struct fiemap_extent_info fieinfo = { 0, };
        struct fiemap_extent *ext1 = kmalloc(sizeof(struct fiemap_extent), GFP_KERNEL);
        memset(ext1, 0, sizeof(struct fiemap_extent));
        fieinfo.fi_extents_start = ext1;
        fieinfo.fi_extents_max = 1;
        u64 len = 4096;
        int ret = inode->i_op->fiemap(inode, &fieinfo, fiemap.fm_start, len);

        if(fieinfo.fi_extents_start)
        {
                printk("after dest logical = %llu\n", fieinfo.fi_extents_start->fe_logical);
                printk("after dest physical = %llu\n", fieinfo.fi_extents_start->fe_physical);
                if(fieinfo.fi_extents_start->fe_physical)
                        sector_num = fieinfo.fi_extents_start->fe_physical/512;
        }
        kfree(ext1);
    }

Very loosely written code, but if spent time it can be modified. 

Happy Hacking 😉😉

Monday, 10 May 2021

Linux libaio example and ftrace

LIBAIO

Lets see sample program which uses libaio and delve into the IO path for the same. 

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <linux/aio_abi.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
#include <assert.h>
typedef unsigned long uintptr_t;
int fd = 0;
int fd1 = 0;
// first operation
char buff1[512];
struct iocb iocb1    = {0};

// second operation
char buff2[512];
struct iocb iocb2    = {0};
aio_context_t ioctx = 0;
unsigned maxevents = 128;

// syscall wrappers
static inline int
io_setup(unsigned maxevents, aio_context_t *ctx) {
    return syscall(SYS_io_setup, maxevents, ctx);
}

static inline int
io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(SYS_io_submit, ctx, nr, iocbpp);
}

static inline int
io_getevents(aio_context_t ctx, long min_nr, long nr,
                 struct io_event *events, struct timespec *timeout) {
    return syscall(SYS_io_getevents, ctx, min_nr, nr, events, timeout);
}

static inline int
io_destroy(aio_context_t ctx)
{
    return syscall(SYS_io_destroy, ctx);
}

void fill_iocb_1(int local_fd)
{
iocb1.aio_data       = 0xbeef; // will be returned in completion
iocb1.aio_fildes     = local_fd;
iocb1.aio_lio_opcode = IOCB_CMD_PREAD;
iocb1.aio_reqprio    = 0;
iocb1.aio_buf        = (uintptr_t)buff1;
iocb1.aio_nbytes     = sizeof(buff1);
iocb1.aio_offset     = 0;      // read file at offset 0
}

void fill_iocb_2(int local_fd)
{
iocb2.aio_data       = 0xbaba; // will be returned in completion
iocb2.aio_fildes     = local_fd;
iocb2.aio_lio_opcode = IOCB_CMD_PREAD;
iocb2.aio_reqprio    = 0;
iocb2.aio_buf        = (uintptr_t)buff2;
iocb2.aio_nbytes     = sizeof(buff2);
iocb2.aio_offset     = 4096;   // read file at offset 4096 (bytes)
}

int main()
{
struct iocb *iocb_ptrs[2] = { &iocb1, &iocb2 };
size_t nevents = 2;
struct io_event events[nevents];
int ret = 0;
nevents = 2;
fd = open("/root/test", O_RDWR|O_DIRECT);
if(fd < 0)
{
perror("file open");
exit(1);
}
ioctx = 0;
if (io_setup(maxevents, &ioctx) < 0) {
perror("io_setup");
exit(1);
}
else {
printf("io_setup is successsful\n");
}
fill_iocb_1(fd);
fill_iocb_2(fd);
// submit operations
ret = io_submit(ioctx, 2, iocb_ptrs);
if (ret < 0) {
perror("io_submit");
exit(1);
} else if (ret != 2) {
perror("io_submit: unhandled partial success");
exit(1);
}
ret = io_getevents(ioctx, 1 /* min */, nevents, events, NULL);
if (ret < 0) {
perror("io_getevents");
exit(1);
}
printf("Got %d events\n", ret);
for (size_t i=0; i<ret; i++) {
struct io_event *ev = &events[i];
assert(ev->data == 0xbeef || ev->data == 0xbaba);
printf("Event returned with res=%lld res2=%lld\n", ev->res, ev->res2);
nevents--;
if (ev->res < 0 ){
printf("Error \n");
}
if(i == 0)
{
printf("Data = \n");
for (int j = 0; j < 512; j++)
printf("%d\n", buff1[j]);
printf("\n");
}
}
io_destroy(ioctx);
close(fd);
return 0;
}

Lets ftrace the io_submit and io_getevents functions : 
cd /sys/kernel/debug/tracing
cat /dev/null >  trace
echo __x64_sys_io_submit > set_graph_function
echo 10 > max_graph_depth
echo function_graph > current_tracer
echo 1 > tracing_on

/root/libaio/./a.out  

cp trace ~/io_submit_dio_trace_depth_10_2
echo 0 > tracing_on
echo > set_graph_function
echo 0 > max_graph_depth
cat /dev/null >  trace

libaio and O_DIRECT
https://stackoverflow.com/questions/34235892/should-libaio-engine-to-be-used-with-unbuffered-io-directonly

Compilation : 
gcc -D_GNU_SOURCE my_aio.c


# tracer: function_graph
#
# CPU  DURATION                  FUNCTION CALLS
# |     |   |                     |   |   |   |
 11)               |  __x64_sys_io_submit() {
 11)               |    lookup_ioctx() {
 11)               |      do_page_fault() {
 11)               |        __do_page_fault() {
 11)   0.307 us    |          down_read_trylock();
 11)               |          find_vma() {
 11)   0.433 us    |            vmacache_find();
 11)   0.270 us    |            vmacache_update();
 11)   1.626 us    |          }
 11)               |          handle_mm_fault() {
 11)   0.319 us    |            mem_cgroup_from_task();
 11)   0.264 us    |            __count_memcg_events();
 11)               |            __handle_mm_fault() {
 11)   0.330 us    |              pmd_devmap_trans_unstable();
 11)               |              do_fault() {
 11)               |                filemap_map_pages() {
 11)               |                  alloc_set_pte() {
 11)   0.322 us    |                    pmd_devmap_trans_unstable();
 11)   0.332 us    |                    _raw_spin_lock();
 11)   1.027 us    |                    page_add_file_rmap();
 11)   3.002 us    |                  }
 11)   0.346 us    |                  unlock_page();
 11)               |                  alloc_set_pte() {
 11)   0.801 us    |                    page_add_file_rmap();
 11)   1.358 us    |                  }
 11)   0.282 us    |                  unlock_page();
 11)               |                  alloc_set_pte() {
 11)   0.791 us    |                    page_add_file_rmap();
 11)   1.336 us    |                  }
 11)   0.270 us    |                  unlock_page();
 11)   8.944 us    |                }
 11)   9.706 us    |              }
 11) + 11.120 us   |            }
 11) + 12.799 us   |          }
 11)   0.266 us    |          up_read();
 11) + 17.076 us   |        }
 11) + 17.596 us   |      }
 11) + 19.061 us   |    }
 11)               |    io_submit_one() {
 11)               |      kmem_cache_alloc() {
 11)   0.358 us    |        should_failslab();
 11)   0.960 us    |      }
 11)   0.334 us    |      __get_reqs_available();
 11)               |      fget() {
 11)   0.300 us    |        __fget();
 11)   0.953 us    |      }
 11)               |      aio_read() {
 11)   0.755 us    |        aio_prep_rw();
 11)   0.545 us    |        aio_setup_rw();
 11)               |        rw_verify_area() {
 11)               |          security_file_permission() {
 11)               |            apparmor_file_permission() {
 11)               |              common_file_perm() {
 11)   0.285 us    |                aa_file_perm();
 11)   0.866 us    |              }
 11)   1.408 us    |            }
 11)   0.292 us    |            __fsnotify_parent();
 11)   0.319 us    |            fsnotify();
 11)   3.108 us    |          }
 11)   3.707 us    |        }
 11)               |        generic_file_read_iter() {
 11)               |          filemap_write_and_wait_range() {
 11)   0.392 us    |            __filemap_fdatawrite_range();
 11)               |            __filemap_fdatawait_range() {
 11)               |              pagevec_lookup_range_tag() {
 11)   0.760 us    |                find_get_pages_range_tag();
 11)   1.475 us    |              }
 11)   2.040 us    |            }
 11)   0.313 us    |            filemap_check_errors();
 11)   4.029 us    |          }
 11)               |          touch_atime() {
 11)               |            atime_needs_update() {
 11)               |              current_time() {
 11)   0.263 us    |                ktime_get_coarse_real_ts64();
 11)   0.266 us    |                timespec64_trunc();
 11)   1.393 us    |              }
 11)   1.980 us    |            }
 11)   2.514 us    |          }
 11)   0.690 us    |          btrfs_direct_IO [btrfs]();
 11)               |          _cond_resched() {
 11)   0.335 us    |            rcu_all_qs();
 11)   0.859 us    |          }
 11)               |          pagecache_get_page() {
 11)   0.574 us    |            find_get_entry();
 11)   2.355 us    |          }
 11)   0.340 us    |          mark_page_accessed();
 11)               |          touch_atime() {
 11)               |            atime_needs_update() {
 11)               |              current_time() {
 11)   0.268 us    |                ktime_get_coarse_real_ts64();
 11)   0.268 us    |                timespec64_trunc();
 11)   1.318 us    |              }
 11)   1.876 us    |            }
 11)   2.436 us    |          }
 11) + 17.043 us   |        }
 11)   0.429 us    |        aio_complete_rw();
 11)   0.295 us    |        kfree();
 11) + 25.152 us   |      }
 11)   0.276 us    |      _raw_spin_lock_irqsave();
 11)   0.274 us    |      _raw_spin_unlock_irqrestore();
 11)               |      fput() {
 11)   0.287 us    |        fput_many();
 11)   0.786 us    |      }
 11)   0.547 us    |      kmem_cache_free();
 11) + 32.749 us   |    }
 11)               |    io_submit_one() {
 11)               |      kmem_cache_alloc() {
 11)   0.266 us    |        should_failslab();
 11)   0.799 us    |      }
 11)   0.296 us    |      __get_reqs_available();
 11)               |      fget() {
 11)   0.328 us    |        __fget();
 11)   0.823 us    |      }
 11)               |      aio_read() {
 11)   0.366 us    |        aio_prep_rw();
 11)   0.294 us    |        aio_setup_rw();
 11)               |        rw_verify_area() {
 11)               |          security_file_permission() {
 11)               |            apparmor_file_permission() {
 11)               |              common_file_perm() {
 11)   0.330 us    |                aa_file_perm();
 11)   0.859 us    |              }
 11)   1.359 us    |            }
 11)   0.276 us    |            __fsnotify_parent();
 11)   0.285 us    |            fsnotify();
 11)   3.073 us    |          }
 11)   3.628 us    |        }
 11)               |        generic_file_read_iter() {
 11)               |          filemap_write_and_wait_range() {
 11)   0.281 us    |            __filemap_fdatawrite_range();
 11)               |            __filemap_fdatawait_range() {
 11)               |              pagevec_lookup_range_tag() {
 11)   0.401 us    |                find_get_pages_range_tag();
 11)   0.909 us    |              }
 11)   1.440 us    |            }
 11)   0.273 us    |            filemap_check_errors();
 11)   3.073 us    |          }
 11)               |          touch_atime() {
 11)               |            atime_needs_update() {
 11)               |              current_time() {
 11)   0.268 us    |                ktime_get_coarse_real_ts64();
 11)   0.268 us    |                timespec64_trunc();
 11)   1.388 us    |              }
 11)   1.974 us    |            }
 11)   2.546 us    |          }
 11)   0.311 us    |          btrfs_direct_IO [btrfs]();
 11)   7.110 us    |        }
 11)   0.344 us    |        aio_complete_rw();
 11)   0.308 us    |        kfree();
 11) + 14.100 us   |      }
 11)   0.322 us    |      _raw_spin_lock_irqsave();
 11)   0.304 us    |      refill_reqs_available();
 11)   0.344 us    |      _raw_spin_unlock_irqrestore();
 11)               |      fput() {
 11)   0.265 us    |        fput_many();
 11)   0.804 us    |      }
 11)   0.293 us    |      kmem_cache_free();
 11) + 21.363 us   |    }
 11) + 76.921 us   |  }


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  |  }





Tuesday, 2 March 2021

Kprobe Example

Kprobe can be used in latest kernels when the jprobe is discontinued. I am updating the blog with latest 5.19 linux kernel.

In the pre-handler the arguments need to be fetched using the registers. The arguments are passed in following sequence for x86 :
first 6 arguments in rdi, rsi, rdx, rcx, r8, r9

So if we are trying to probe a function "submit_bio" which has signature : 
void submit_bio(struct bio *bio)

Then we can take fetch the first argument using rdi register. It can be done like : 
bio_ptr = (struct bio *)regs->di;
 


/*kprobe_example.c*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/sched.h>
#include <linux/blk_types.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>

static char symbol[KSYM_NAME_LEN] = "submit_bio";
module_param_string(symbol, symbol, KSYM_NAME_LEN, 0644);

/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {
        .symbol_name    = symbol,
};

/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp;

/* kprobe pre_handler: called just before the probed instruction is executed */
int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    static int x = 0;
    struct bio *bio_ptr = NULL;
    if (x%10 == 0)
    {
        // first 6 arguments in rdi, rsi, rdx, rcx, r8, r9
        printk("MY pre_handler:rdi=0x%08lx, rsi=0x%08lx, rdx=0x%08lx\n",
            regs->di, regs->si, regs->dx);
        bio_ptr = (struct bio *)regs->di;
        if (bio_ptr && bio_ptr->bi_bdev && bio_ptr->bi_bdev->bd_disk ) {
            printk("disk_name = %s\n", bio_ptr->bi_bdev->bd_disk->disk_name);
            printk("disk_ptr = %p\n",bio_ptr->bi_bdev->bd_disk);
        }
         printk("MY pre_handler: p->addr=0x%p, eip=%lx, eflags=0x%lx\n",
                p->addr, regs->ip, regs->flags);
        dump_stack();
   }
    x++;
    return 0;
}

/* kprobe post_handler: called after the probed instruction is executed */
void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{
        static int y = 0;
        if (y%1000 == 0) {
                printk("MY post_handler: p->addr=0x%p, eflags=0x%lx\n", p->addr, regs->flags);
        }
        y++;
}


int init_module(void)
{
        int ret;
        kp.pre_handler = handler_pre;
        kp.post_handler = handler_post;
        if ((ret = register_kprobe(&kp) < 0)) {
                printk("register_kprobe failed, returned %d\n", ret);
                return -1;
        }
        printk("kprobe registered\n");
        return 0;
}

void cleanup_module(void)
{
        unregister_kprobe(&kp);
        printk("kprobe unregistered\n");
}
MODULE_LICENSE("GPL");

Makefile for this module :
obj-m = kprobe_example.o
all:
        make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

When ran this program gives the output : 
Sep  7 14:05:01  kernel: [530764.156938] MY pre_handler:rdi=0xffff95ae0cbdd000, rsi=0xfffff7480af750c0, rdx=0x00000000
Sep  7 14:05:01  kernel: [530764.156942] disk_name = sdb
Sep  7 14:05:01  kernel: [530764.156945] disk_ptr = 00000000241100e9
Sep  7 14:05:01  kernel: [530764.156948] MY pre_handler: p->addr=0x000000004b03cd92, eip=ffffffff9bff6001, eflags=0x246
Sep  7 14:05:01  kernel: [530764.156952] CPU: 8 PID: 33831 Comm: sync Tainted: P        W  OE     5.15.0-47-generic #51-Ubuntu
Sep  7 14:05:01  kernel: [530764.156956] Hardware name: VMware, Inc. VMware7,1/440BX Desktop Reference Platform, BIOS VMW71.00V.17369862.B64.2012240522 12/24/2020
Sep  7 14:05:01  kernel: [530764.156960] Call Trace:
Sep  7 14:05:01  kernel: [530764.156963]  <TASK>
Sep  7 14:05:01  kernel: [530764.156966]  show_stack+0x52/0x5c
Sep  7 14:05:01  kernel: [530764.156975]  dump_stack_lvl+0x4a/0x63
Sep  7 14:05:01  kernel: [530764.156982]  dump_stack+0x10/0x16
Sep  7 14:05:01  kernel: [530764.156988]  handler_pre.cold+0x90/0xac [kprobe_example]
Sep  7 14:05:01  kernel: [530764.156996]  kprobe_ftrace_handler+0xf3/0x1c0
Sep  7 14:05:01  kernel: [530764.157001]  ? submit_bio+0x5/0x130
Sep  7 14:05:01  kernel: [530764.157009]  ? submit_bio_noacct+0x120/0x120
Sep  7 14:05:01  kernel: [530764.157015]  ftrace_ops_list_func+0x181/0x1c0
Sep  7 14:05:01  kernel: [530764.157021]  ? submit_bh_wbc+0x18d/0x1c0
Sep  7 14:05:01  kernel: [530764.157029]  ? submit_bh_wbc+0x185/0x1c0
Sep  7 14:05:01  kernel: [530764.157040]  ftrace_regs_call+0x5/0x57
Sep  7 14:05:01  kernel: [530764.157046] RIP: 0010:submit_bio+0x1/0x130
Sep  7 14:05:01  kernel: [530764.157052] Code: 0c 00 00 00 00 00 00 e9 4b ff ff ff 48 c7 44 24 08 00 00 00 00 eb c7 48 89 38 e9 70 ff ff ff e8 f5 67 7a 00 0f 1f 44 00 00 e8 <1b> 54 a9 ff 55 48 89 e5 41 54 49 89 fc 48 83 ec 18 65 48 8b 04 25
Sep  7 14:05:01  kernel: [530764.157056] RSP: 0018:ffffa6b8c124ba98 EFLAGS: 00000246 ORIG_RAX: 0000000000000000
Sep  7 14:05:01  kernel: [530764.157062] RAX: ffff95ae03e6b000 RBX: ffff95ae07206750 RCX: 0000000000000000
Sep  7 14:05:01  kernel: [530764.157065] RDX: 0000000000000000 RSI: fffff7480af750c0 RDI: ffff95ae0cbdd000
Sep  7 14:05:01  kernel: [530764.157069] RBP: ffffa6b8c124bad0 R08: 0000000000000000 R09: 0000000000003801
Sep  7 14:05:01  kernel: [530764.157072] R10: ffff95ae0cbdd000 R11: 00000000000bffff R12: ffff95ae0cbdd000
Sep  7 14:05:01  kernel: [530764.157076] R13: 0000000000000001 R14: ffffa6b8c124bdf0 R15: 0000000000003800
Sep  7 14:05:01  kernel: [530764.157091]  ? submit_bio+0x5/0x130
Sep  7 14:05:01  kernel: [530764.157097]  ? submit_bh_wbc+0x18d/0x1c0
Sep  7 14:05:01  kernel: [530764.157104]  ? submit_bio+0x5/0x130
Sep  7 14:05:01  kernel: [530764.157109]  ? submit_bh_wbc+0x18d/0x1c0
Sep  7 14:05:01  kernel: [530764.157119]  __block_write_full_page+0x227/0x4a0
Sep  7 14:05:01  kernel: [530764.157126]  ? block_invalidatepage+0x160/0x160
Sep  7 14:05:01  kernel: [530764.157135]  ? blkdev_llseek+0x70/0x70
Sep  7 14:05:01  kernel: [530764.157141]  block_write_full_page+0x6f/0xa0
Sep  7 14:05:01  kernel: [530764.157149]  blkdev_writepage+0x18/0x20
Sep  7 14:05:01  kernel: [530764.157156]  __writepage+0x1b/0x70
Sep  7 14:05:01  kernel: [530764.157164]  write_cache_pages+0x1a6/0x460
Sep  7 14:05:01  kernel: [530764.157172]  ? __set_page_dirty_no_writeback+0x50/0x50
Sep  7 14:05:01  kernel: [530764.157183]  ? __set_page_dirty_no_writeback+0x50/0x50
Sep  7 14:05:01  kernel: [530764.157191]  ? write_cache_pages+0x5/0x460
Sep  7 14:05:01  kernel: [530764.157202]  generic_writepages+0x58/0x90
Sep  7 14:05:01  kernel: [530764.157211]  ? generic_writepages+0x5/0x90
Sep  7 14:05:01  kernel: [530764.157219]  blkdev_writepages+0xe/0x20
Sep  7 14:05:01  kernel: [530764.157226]  do_writepages+0xd7/0x200
Sep  7 14:05:01  kernel: [530764.157235]  ? do_writepages+0x5/0x200
Sep  7 14:05:01  kernel: [530764.157240]  ? rcu_read_unlock_strict+0x5/0x10
Sep  7 14:05:01  kernel: [530764.157248]  ? wbc_attach_and_unlock_inode+0xc2/0x150
Sep  7 14:05:01  kernel: [530764.157257]  filemap_fdatawrite_wbc+0x89/0xe0
Sep  7 14:05:01  kernel: [530764.157262]  ? filemap_fdatawrite_wbc+0x5/0xe0
Sep  7 14:05:01  kernel: [530764.157269]  filemap_fdatawrite+0x50/0x70
Sep  7 14:05:01  kernel: [530764.157281]  sync_bdevs+0x154/0x160
Sep  7 14:05:01  kernel: [530764.157291]  ksys_sync+0x69/0xa0
Sep  7 14:05:01  kernel: [530764.157297]  __do_sys_sync+0xe/0x20
Sep  7 14:05:01  kernel: [530764.157302]  do_syscall_64+0x59/0xc0









Monday, 15 February 2021

Linux RCU Usage and internals

For developing the understanding of Linux RCU we shall first go ahead with the the Paul McKenney's explanation on YouTube.
https://www.youtube.com/watch?v=obDzjElRj9c

This helps a lot in understanding the concept behind RCU. 

Next we try to execute RCU examples from here : https://www.kernel.org/doc/html/latest/RCU/whatisRCU.html#what-are-some-example-uses-of-core-rcu-api

Sample kernel module to test the core APIS of RCU :

#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/kprobes.h>
#include<linux/spinlock.h>
#include<linux/slab.h>
#include<linux/rcupdate.h>

struct foo {
        int a;
        char b;
        long c;
};
DEFINE_SPINLOCK(foo_mutex);
struct foo __rcu *gbl_foo;
void foo_init_a(int new_a)
{
        struct foo *fp = NULL;
        fp = kmalloc(sizeof(*fp), GFP_KERNEL);
        fp->a = new_a;
        gbl_foo = fp;
}
void foo_update_a(int new_a)
{
        struct foo *new_fp;
        struct foo *old_fp;
        new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);
        spin_lock(&foo_mutex);
        old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));
        *new_fp = *old_fp;
        new_fp->a = new_a;
        rcu_assign_pointer(gbl_foo, new_fp);
        printk("updated pointer\n");
        spin_unlock(&foo_mutex);
        printk("synchronize rcu\n");
        synchronize_rcu();
        kfree(old_fp);
}
int foo_get_a(void)
{
        int retval;
        rcu_read_lock();
        retval = rcu_dereference(gbl_foo)->a;
        rcu_read_unlock();
        printk("%s, %d fetched val is %d\n", __func__, __LINE__, retval);
        return retval;
}
void foo_del_a(void)
{
        if(gbl_foo != NULL)
                kfree(gbl_foo);
}
int myinit(void)
{
    printk("module inserted\n");
    foo_init_a(70);
    foo_get_a();
    foo_get_a();
    foo_update_a(20);
    foo_get_a();
    foo_update_a(30);
    foo_get_a();
    foo_del_a();
    return 0;
}
void myexit(void)
{
    printk("module removed\n");
}
module_init(myinit);
module_exit(myexit);
MODULE_AUTHOR("K_K");
MODULE_DESCRIPTION("RCU MODULE");
MODULE_LICENSE("GPL");


Makefile : 
obj-m += rcu_example.o

all :
        make -C /lib/modules/`uname -r`/build M=$(PWD) modules
clean :
        make -C /lib/modules/`uname -r`/build M=$(PWD) clean

We can also look at userspace RCU implementation : 
Userspace RCU is present here (examples also present): 


RCU all flavors API : 


Lets see write a program with linked list rcu API usage : 

#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/kprobes.h>
#include<linux/spinlock.h>
#include<linux/slab.h>
#include<linux/rcupdate.h>
#include<linux/rculist.h>
#include<linux/list.h>

struct foo {
        struct list_head list;
        int a;
        char b;
        long c;
};
struct list_head head =  LIST_HEAD_INIT(head);
DEFINE_SPINLOCK(foo_mutex);

void add_foo_to_list(int a, int b, int c)
{
        struct foo *p;
        printk("%s\n", __func__);
        p = kmalloc(sizeof(*p), GFP_KERNEL);
        p->a = a;
        p->b = b;
        p->c = c;
        spin_lock(&foo_mutex);
        list_add_rcu(&p->list, &head);
        spin_unlock(&foo_mutex);
        synchronize_rcu();
}

void print_list(void)
{
        struct foo *p;
        printk("%s\n", __func__);
        rcu_read_lock();
        list_for_each_entry_rcu(p, &head, list) {
                printk("a = %d, b = %d, c = %ld\n", p->a, p->b, p->c);
        }
        rcu_read_unlock();
}

void del_list(void)
{
        struct foo *p;
        printk("%s\n", __func__);
        spin_lock(&foo_mutex);
        list_for_each_entry_rcu(p, &head, list) {
                printk("a = %d, b = %d, c = %ld\n", p->a, p->b, p->c);
                list_del_rcu(&p->list);
        }
        spin_unlock(&foo_mutex);
        synchronize_rcu();
}
int myinit(void)
{
    printk("module inserted\n");
    add_foo_to_list(10,20,30);
    add_foo_to_list(11,22,33);
    add_foo_to_list(14,25,36);
    print_list();
    add_foo_to_list(17,28,39);
    print_list();
    del_list();
    return 0;
}

void myexit(void)
{
    printk("module removed\n");
}

module_init(myinit);
module_exit(myexit);

MODULE_AUTHOR("K_K");
MODULE_DESCRIPTION("RCU MODULE");
MODULE_LICENSE("GPL");

Sleepable RCU details are present here : https://lwn.net/Articles/202847/
Same implementation with SRCU APIs  :
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/kprobes.h>
#include<linux/spinlock.h>
#include<linux/slab.h>
#include<linux/rcupdate.h>
#include<linux/rculist.h>
#include<linux/list.h>

struct foo {
        struct list_head list;
        int a;
        char b;
        long c;
};
struct list_head head =  LIST_HEAD_INIT(head);
struct srcu_struct my_srcu;

DEFINE_SPINLOCK(foo_mutex);

void add_foo_to_list(int a, int b, int c)
{
        struct foo *p;
        printk("%s\n", __func__);
        p = kmalloc(sizeof(*p), GFP_KERNEL);
        p->a = a;
        p->b = b;
        p->c = c;
        spin_lock(&foo_mutex);
        list_add_rcu(&p->list, &head);
        spin_unlock(&foo_mutex);
        synchronize_srcu(&my_srcu);
}

void print_list(void)
{
        struct foo *p;
        int srcu_idx;
        printk("%s\n", __func__);
        srcu_idx = srcu_read_lock(&my_srcu);
        list_for_each_entry_rcu(p, &head, list) {
                printk("a = %d, b = %d, c = %ld\n", p->a, p->b, p->c);
        }
        srcu_read_unlock(&my_srcu, srcu_idx);
}

void del_list(void)
{
        struct foo *p;
        printk("%s\n", __func__);
        spin_lock(&foo_mutex);
        list_for_each_entry_rcu(p, &head, list) {
                printk("a = %d, b = %d, c = %ld\n", p->a, p->b, p->c);
                list_del_rcu(&p->list);
        }
        spin_unlock(&foo_mutex);
        synchronize_srcu(&my_srcu);
}
int myinit(void)
{
    int ret = 0;
    printk("module inserted\n");
    ret = init_srcu_struct(&my_srcu);
    if (ret)
        goto exit;
    add_foo_to_list(10,20,30);
    add_foo_to_list(11,22,33);
    add_foo_to_list(14,25,36);
    print_list();
    add_foo_to_list(17,28,39);
    print_list();
    del_list();
exit:
    return 0;
}

void myexit(void)
{
    cleanup_srcu_struct(&my_srcu);
    printk("module removed\n");
}

module_init(myinit);
module_exit(myexit);

MODULE_AUTHOR("K_K");
MODULE_DESCRIPTION("RCU MODULE");
MODULE_LICENSE("GPL");


Lets look at the rcu_read_lock() : The description says "it is illegal to block while in an RCU read-side critical section." In the case of preemptible kernel the rcu read critical section may get preempted. 

Its implementation  :)
static __always_inline void rcu_read_lock(void)
{
        __rcu_read_lock();
        __acquire(RCU);
        rcu_lock_acquire(&rcu_lock_map);
        RCU_LOCKDEP_WARN(!rcu_is_watching(),
                         "rcu_read_lock() used illegally while idle");
}



#ifdef CONFIG_PREEMPT_RCU

void __rcu_read_lock(void);
void __rcu_read_unlock(void);

/*
 * Defined as a macro as it is a very low level header included from
 * areas that don't even know about current.  This gives the rcu_read_lock()
 * nesting depth, but makes sense only if CONFIG_PREEMPT_RCU -- in other
 * types of kernel builds, the rcu_read_lock() nesting depth is unknowable.
 */
#define rcu_preempt_depth() (current->rcu_read_lock_nesting)

#else /* #ifdef CONFIG_PREEMPT_RCU */

static inline void __rcu_read_lock(void)
{
        preempt_disable();
}

static inline void __rcu_read_unlock(void)
{
        preempt_enable();
}

static inline int rcu_preempt_depth(void)
{
        return 0;
}

#endif /* #else #ifdef CONFIG_PREEMPT_RCU */

Basically the rcu_read_lock just do preempt_disable. What will happen if CPU with read critical section gets interrupt. in such cases we shall disable the interrupt on this CPU. 


We can see that API table says to disable interrupts : https://lwn.net/Articles/609973/#RCU%20Per-Flavor%20API%20Table
Wait for RCU-sched read-side critical sections, preempt-disable regions, hardirqs, & NMIs
preempt_disable()/preempt_enable()
local_irq_save()/local_irq_restore()


Synchronize RCU
void synchronize_rcu(void)
{
        RCU_LOCKDEP_WARN(lock_is_held(&rcu_bh_lock_map) ||
                         lock_is_held(&rcu_lock_map) ||
                         lock_is_held(&rcu_sched_lock_map),
                         "Illegal synchronize_rcu() in RCU read-side critical section");
        if (rcu_blocking_is_gp())
                return;
        if (rcu_gp_is_expedited())
                synchronize_rcu_expedited();
        else
                wait_rcu_gp(call_rcu);
}
EXPORT_SYMBOL_GPL(synchronize_rcu);


Basically synchronize is done either of these 2 functions : 
1. synchronize_rcu_expedited  -- looks like expedite the grace period
2. wait_rcu_gp -- looks like wait for the grace period

The comment before this function tells it all : 
Wait for an RCU grace period, but expedite it.  The basic idea is to IPI all non-idle non-nohz online CPUs.  The IPI handler checks whether the CPU is in an RCU critical section, and if so, it sets a flag that causes the outermost rcu_read_unlock() to report the quiescent state for RCU-preempt or asks the scheduler for help for RCU-sched.

synchronize_rcu_expedited : Implementation discussion pending



#define wait_rcu_gp(...) _wait_rcu_gp(false, __VA_ARGS__)

#define _wait_rcu_gp(checktiny, ...) \
do {                                                                    \
        call_rcu_func_t __crcu_array[] = { __VA_ARGS__ };               \
        struct rcu_synchronize __rs_array[ARRAY_SIZE(__crcu_array)];    \
        __wait_rcu_gp(checktiny, ARRAY_SIZE(__crcu_array),              \
                        __crcu_array, __rs_array);                      \
} while (0)

void __wait_rcu_gp(bool checktiny, int n, call_rcu_func_t *crcu_array,
                   struct rcu_synchronize *rs_array)
{
        int i;
        int j;

        /* Initialize and register callbacks for each crcu_array element. */
        for (i = 0; i < n; i++) {
                if (checktiny &&
                    (crcu_array[i] == call_rcu)) {
                        might_sleep();
                        continue;
                }
                init_rcu_head_on_stack(&rs_array[i].head);
                init_completion(&rs_array[i].completion);
                for (j = 0; j < i; j++)
                        if (crcu_array[j] == crcu_array[i])
                                break;
                if (j == i)
                        (crcu_array[i])(&rs_array[i].head, wakeme_after_rcu);
        }

        /* Wait for all callbacks to be invoked. */
        for (i = 0; i < n; i++) {
                if (checktiny &&
                    (crcu_array[i] == call_rcu))
                        continue;
                for (j = 0; j < i; j++)
                        if (crcu_array[j] == crcu_array[i])
                                break;
                if (j == i)
                        wait_for_completion(&rs_array[i].completion);
                destroy_rcu_head_on_stack(&rs_array[i].head);
        }
}
EXPORT_SYMBOL_GPL(__wait_rcu_gp);

__wait_rcu_gp is initiating the completions and waiting for them to complete. 

How to do synchronize_rcu() in interrupt handlers ?

The call_rcu() function may be used in a number of situations where neither synchronize_rcu() nor synchronize_rcu_expedited() would be legal, including within preempt-disable code, local_bh_disable() code, interrupt-disable code, and interrupt handlers.

call_rcu() - Queue an RCU callback for invocation after a grace period.
void call_rcu(struct rcu_head *head, rcu_callback_t func)
{
        __call_rcu(head, func, -1, 0);
}
EXPORT_SYMBOL_GPL(call_rcu);


static void
__call_rcu(struct rcu_head *head, rcu_callback_t func, int cpu, bool lazy)
{
local_irq_save(flags);
...
        /* Go handle any RCU core processing required. */
        __call_rcu_core(rdp, head, flags);
        local_irq_restore(flags);
}

Implementation TBD.