github.com/dshulyak/uring@v0.0.0-20210209113719-1b2ec51f1542/ring.go (about)

     1  package uring
     2  
     3  import (
     4  	"sync/atomic"
     5  	"syscall"
     6  	"unsafe"
     7  )
     8  
     9  // sqRing ...
    10  type sqRing struct {
    11  	head        *uint32
    12  	tail        *uint32
    13  	ringMask    *uint32
    14  	ringEntries *uint32
    15  	dropped     *uint32
    16  	flags       *uint32
    17  	array       uint32Array
    18  
    19  	sqes    sqeArray
    20  	sqeHead uint32
    21  	sqeTail uint32
    22  }
    23  
    24  type uint32Array uintptr
    25  
    26  func (a uint32Array) get(idx uint32) uint32 {
    27  	return *(*uint32)(unsafe.Pointer(uintptr(a) + uintptr(idx*4)))
    28  }
    29  
    30  func (a uint32Array) set(idx uint32, value uint32) {
    31  	*(*uint32)(unsafe.Pointer(uintptr(a) + uintptr(idx*4))) = value
    32  }
    33  
    34  type sqeArray uintptr
    35  
    36  func (a sqeArray) get(idx uint32) *SQEntry {
    37  	return (*SQEntry)(unsafe.Pointer(uintptr(a) + uintptr(idx)*sqeSize))
    38  }
    39  
    40  func (a sqeArray) set(idx uint32, value SQEntry) {
    41  	*(*SQEntry)(unsafe.Pointer(uintptr(a) + uintptr(idx)*sqeSize)) = value
    42  }
    43  
    44  type cqRing struct {
    45  	head        *uint32
    46  	tail        *uint32
    47  	ringmask    *uint32
    48  	ringEntries *uint32
    49  	overflow    *uint32
    50  	flags       *uint32
    51  	cqes        cqeArray
    52  }
    53  
    54  type cqeArray uintptr
    55  
    56  func (a cqeArray) get(idx uint32) CQEntry {
    57  	return *(*CQEntry)(unsafe.Pointer(uintptr(a) + uintptr(idx)*cqeSize))
    58  }
    59  
    60  func (a cqeArray) set(idx uint32, value CQEntry) {
    61  	*(*CQEntry)(unsafe.Pointer(uintptr(a) + uintptr(idx)*cqeSize)) = value
    62  }
    63  
    64  // Ring is an interface to io_uring kernel framework.
    65  // Not safe to use from multiple goroutines without additional synchronization.
    66  // API is inspired mostly by liburing.
    67  type Ring struct {
    68  	// fd returned by IO_URING_SETUP
    69  	fd     int
    70  	params IOUringParams
    71  
    72  	sq sqRing
    73  	cq cqRing
    74  
    75  	// pointers returned by mmap calls, used only for munmap
    76  	// sqData ...
    77  	sqData []byte
    78  	// cqData can be nil if kernel supports IORING_FEAT_SINGLE_MMAP
    79  	cqData []byte
    80  	// sqArrayData array mapped with Ring.fd at IORING_OFF_SQES offset
    81  	sqArrayData []byte
    82  
    83  	eventfd uintptr
    84  }
    85  
    86  // Fd is a io_uring fd returned from IO_URING_SETUP syscall.
    87  func (r *Ring) Fd() uintptr {
    88  	return uintptr(r.fd)
    89  }
    90  
    91  // Eventfd is a eventfd for this uring instance. Call ring.Setupeventfd() to setup one.
    92  func (r *Ring) Eventfd() uintptr {
    93  	return r.eventfd
    94  }
    95  
    96  func (r *Ring) CQSize() uint32 {
    97  	return r.params.CQEntries
    98  }
    99  
   100  func (r *Ring) SQSize() uint32 {
   101  	return r.params.SQEntries
   102  }
   103  
   104  // GetSQEntry returns earliest available SQEntry. May return nil if there are
   105  // no available entries.
   106  // Entry can be reused after Submit or Enter.
   107  //
   108  //   sqe := ring.GetSQEntry()
   109  //   ring.Submit(0)
   110  //
   111  // ... or ...
   112  //
   113  //   sqe := ring.GetSQEntry()
   114  //   ring.Flush()
   115  //   ring.Enter(1, 0)
   116  func (r *Ring) GetSQEntry() *SQEntry {
   117  	head := atomic.LoadUint32(r.sq.head)
   118  	next := r.sq.sqeTail + 1
   119  	if next-head <= *r.sq.ringEntries {
   120  		idx := r.sq.sqeTail & *r.sq.ringMask
   121  		r.sq.sqeTail = next
   122  		sqe := r.sq.sqes.get(idx)
   123  		sqe.Reset()
   124  		return sqe
   125  	}
   126  	return nil
   127  }
   128  
   129  // Flush submission queue.
   130  func (r *Ring) Flush() uint32 {
   131  	toSubmit := r.sq.sqeTail - r.sq.sqeHead
   132  	if toSubmit == 0 {
   133  		return 0
   134  	}
   135  
   136  	tail := *r.sq.tail
   137  	mask := *r.sq.ringMask
   138  	for i := toSubmit; i > 0; i-- {
   139  		r.sq.array.set(tail&mask, r.sq.sqeHead&mask)
   140  		tail++
   141  		r.sq.sqeHead++
   142  	}
   143  	atomic.StoreUint32(r.sq.tail, tail)
   144  	return toSubmit
   145  }
   146  
   147  // Enter io_uring instance. submited and minComplete will be passed as is.
   148  func (r *Ring) Enter(submitted uint32, minComplete uint32) (uint32, error) {
   149  	var flags uint32
   150  	if r.sqNeedsEnter(submitted, &flags) || minComplete > 0 {
   151  		if minComplete > 0 || (r.params.Flags&IORING_SETUP_IOPOLL) > 0 {
   152  			flags |= IORING_ENTER_GETEVENTS
   153  		}
   154  		return r.enter(submitted, minComplete, flags, true)
   155  	}
   156  	return 0, nil
   157  }
   158  
   159  // Submit and wait for specified number of entries.
   160  func (r *Ring) Submit(minComplete uint32) (uint32, error) {
   161  	return r.Enter(r.Flush(), minComplete)
   162  }
   163  
   164  // GetCQEntry returns entry from completion queue, performing IO_URING_ENTER syscall if necessary.
   165  // CQE is copied from mmaped region to avoid additional sync step after CQE was consumed.
   166  // syscall.EAGAIN will be returned if there are no completed entries and minComplete is 0.
   167  // syscall.EINTR will be returned if IO_URING_ENTER was interrupted while waiting for completion.
   168  func (r *Ring) GetCQEntry(minComplete uint32) (CQEntry, error) {
   169  	needs := r.cqNeedsEnter()
   170  	if needs {
   171  		if _, err := r.enter(0, minComplete, 0, false); err != nil {
   172  			return CQEntry{}, err
   173  		}
   174  	}
   175  	exit := needs
   176  	for {
   177  		cqe, found := r.peekCQEntry()
   178  		if found {
   179  			return cqe, nil
   180  		}
   181  		if exit {
   182  			break
   183  		}
   184  		if minComplete > 0 {
   185  			if _, err := r.enter(0, minComplete, IORING_ENTER_GETEVENTS, false); err != nil {
   186  				return CQEntry{}, err
   187  			}
   188  		}
   189  		exit = true
   190  	}
   191  	return CQEntry{}, syscall.EAGAIN
   192  }
   193  
   194  func (r *Ring) enter(submitted, minComplete, flags uint32, raw bool) (uint32, error) {
   195  	var (
   196  		r1    uintptr
   197  		errno syscall.Errno
   198  	)
   199  	if raw {
   200  		r1, _, errno = syscall.RawSyscall6(IO_URING_ENTER, uintptr(r.fd), uintptr(submitted), uintptr(minComplete), uintptr(flags), 0, 0)
   201  	} else {
   202  		r1, _, errno = syscall.Syscall6(IO_URING_ENTER, uintptr(r.fd), uintptr(submitted), uintptr(minComplete), uintptr(flags), 0, 0)
   203  	}
   204  	if errno == 0 {
   205  		return uint32(r1), nil
   206  	}
   207  	return uint32(r1), error(errno)
   208  }
   209  
   210  // peekCQEntry returns cqe is available and updates head
   211  func (r *Ring) peekCQEntry() (CQEntry, bool) {
   212  	next := *r.cq.head
   213  	if next < atomic.LoadUint32(r.cq.tail) {
   214  		cqe := r.cq.cqes.get(next & *r.cq.ringmask)
   215  		atomic.StoreUint32(r.cq.head, next+1)
   216  		return cqe, true
   217  	}
   218  	return CQEntry{}, false
   219  }
   220  
   221  func (r *Ring) cqNeedsEnter() bool {
   222  	if r.cq.flags != nil {
   223  		if atomic.LoadUint32(r.cq.flags)&IORING_SQ_CQ_OVERFLOW > 0 {
   224  			return true
   225  		}
   226  	}
   227  	return r.params.Flags&IORING_SETUP_IOPOLL > 0
   228  }
   229  
   230  func (r *Ring) sqNeedsEnter(submitted uint32, flags *uint32) bool {
   231  	if (r.params.Flags&IORING_SETUP_SQPOLL) == 0 && submitted > 0 {
   232  		return true
   233  	}
   234  	if (atomic.LoadUint32(r.sq.flags) & IORING_SQ_NEED_WAKEUP) > 0 {
   235  		*flags |= IORING_ENTER_SQ_WAKEUP
   236  		return true
   237  	}
   238  	return false
   239  }