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 }