io_uring_enter with NOP implementation for the IO_URING.

With io_uring_setup() and mmap() we can proceed with implementing a simplest
operation, namely, NOP. Once we make sure that we can send this command and
receive a response from the gVisor, we can move forward to read operation.

PiperOrigin-RevId: 485106019
This commit is contained in:
Sergey Madaminov
2022-10-31 11:39:18 -07:00
committed by gVisor bot
parent 6b3b5493d0
commit 9ae2eea3ba
11 changed files with 1032 additions and 77 deletions
+32 -9
View File
@@ -116,9 +116,9 @@ type IOUringParams struct {
//
// +marshal
type IOUringCqe struct {
userData uint64
res int32
flags uint32
UserData uint64
Res int32
Flags uint32
}
// IOUring implements io_uring struct.
@@ -128,9 +128,9 @@ type IOUringCqe struct {
type IOUring struct {
// Both head and tail should be cacheline aligned. And we assume that
// cacheline size is 64 bytes.
head uint32
Head uint32
_ [60]byte
tail uint32
Tail uint32
_ [60]byte
}
@@ -140,14 +140,37 @@ type IOUring struct {
//
// +marshal
type IORings struct {
sq, cq IOUring
sqRingMask, cqRingMask uint32
sqRingEntries, cqRingEntries uint32
Sq, Cq IOUring
SqRingMask, CqRingMask uint32
SqRingEntries, CqRingEntries uint32
sqDropped uint32
sqFlags int32
cqFlags uint32
cqOverflow uint32
CqOverflow uint32
_ [32]byte // Padding so cqes is cacheline aligned
// Linux has an additional field struct io_uring_cqe cqes[], which represents
// a dynamic array. We don't include it here in order to enable marshalling.
}
// IOUringSqe implements io_uring_sqe struct.
// This struct represents IO submission data structure (Submission Queue Entry). As we don't yet
// support IORING_SETUP_SQE128 flag, its size is 64 bytes with no extra padding at the end.
// See include/uapi/linux/io_uring.h.
//
// +marshal
type IOUringSqe struct {
Opcode uint8
Flags uint8
ioPrio uint16
fd int32
offOrAddrOrCmdOp uint64
addrOrSpliceOff uint64
len uint32
specialFlags uint32
UserData uint64
bufIndexOrGroup uint16
personality uint16
spliceFDOrFileIndex int32
addr3 uint64
_ uint64
}
+9 -9
View File
@@ -17,10 +17,10 @@ package linux
// PreComputedIOSqRingOffsets returns precomputed values for IOSqRingOffsets.
func PreComputedIOSqRingOffsets() IOSqRingOffsets {
return IOSqRingOffsets{
Head: {{ .IORings.sq.Offset }} + {{ .IOUring.head.Offset }},
Tail: {{ .IORings.sq.Offset }} + {{ .IOUring.tail.Offset }},
RingMask: {{ .IORings.sqRingMask.Offset }},
RingEntries: {{ .IORings.sqRingEntries.Offset }},
Head: {{ .IORings.Sq.Offset }} + {{ .IOUring.Head.Offset }},
Tail: {{ .IORings.Sq.Offset }} + {{ .IOUring.Tail.Offset }},
RingMask: {{ .IORings.SqRingMask.Offset }},
RingEntries: {{ .IORings.SqRingEntries.Offset }},
Flags: {{ .IORings.sqFlags.Offset }},
Dropped: {{ .IORings.sqDropped.Offset }},
}
@@ -29,11 +29,11 @@ func PreComputedIOSqRingOffsets() IOSqRingOffsets {
// PreComputedIOCqRingOffsets returns precomputed values for IOCqRingOffsets.
func PreComputedIOCqRingOffsets() IOCqRingOffsets {
return IOCqRingOffsets {
Head: {{ .IORings.cq.Offset }} + {{ .IOUring.head.Offset }},
Tail: {{ .IORings.cq.Offset }} + {{ .IOUring.tail.Offset }},
RingMask: {{ .IORings.cqRingMask.Offset }},
RingEntries: {{ .IORings.cqRingEntries.Offset }},
Overflow: {{ .IORings.cqOverflow.Offset }},
Head: {{ .IORings.Cq.Offset }} + {{ .IOUring.Head.Offset }},
Tail: {{ .IORings.Cq.Offset }} + {{ .IOUring.Tail.Offset }},
RingMask: {{ .IORings.CqRingMask.Offset }},
RingEntries: {{ .IORings.CqRingEntries.Offset }},
Overflow: {{ .IORings.CqOverflow.Offset }},
Flags: {{ .IORings.cqFlags.Offset }},
}
}
+2
View File
@@ -8,9 +8,11 @@ go_library(
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/hostarch",
"//pkg/safemem",
"//pkg/sentry/memmap",
"//pkg/sentry/pgalloc",
"//pkg/sentry/usage",
+249 -26
View File
@@ -13,16 +13,25 @@
// limitations under the License.
// Package iouringfs provides a filesystem implementation for IO_URING basing
// it on anonfs.
// it on anonfs. Currently, we don't support neither IOPOLL nor SQPOLL modes.
// Thus, user needs to set up IO_URING first with io_uring_setup(2) syscall and
// then issue submission request using io_uring_enter(2).
//
// Another important note, as of now, we don't support deferred CQE. In other
// words, the size of the backlogged set of CQE is zero. Whenever, completion
// queue ring buffer is full, we drop the subsequent completion queue entries.
package iouringfs
import (
"fmt"
"sync"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/usage"
@@ -33,7 +42,7 @@ import (
// It is based on io_rings struct. See io_uring/io_uring.c.
//
// +stateify savable
type fileDescription struct {
type FileDescription struct {
vfsfd vfs.FileDescription
vfs.FileDescriptionDefaultImpl
vfs.DentryMetadataFileDescriptionImpl
@@ -41,9 +50,16 @@ type fileDescription struct {
rbmf ringsBufferFile
sqemf sqEntriesFile
// mu protects the fields below.
mu sync.Mutex `state:"nosave"`
ioRings *safemem.BlockSeq
sqes *safemem.BlockSeq
cqes *safemem.BlockSeq
}
var _ vfs.FileDescriptionImpl = (*fileDescription)(nil)
var _ vfs.FileDescriptionImpl = (*FileDescription)(nil)
func roundUpPowerOfTwo(n uint32) (uint32, bool) {
if n > (1 << 31) {
@@ -98,15 +114,14 @@ func New(ctx context.Context, vfsObj *vfs.VirtualFilesystem, entries uint32, par
}
// Allocate enough space to store the given number of submission queue entries.
sqEntriesSize := uint64(numSqEntries * 64)
sqEntriesSize := uint64(numSqEntries * uint32((*linux.IOUringSqe)(nil).SizeBytes()))
sqEntriesSize = uint64(hostarch.Addr(sqEntriesSize).MustRoundUp())
sqefr, err := mfp.MemoryFile().Allocate(sqEntriesSize, pgalloc.AllocOpts{Kind: usage.Anonymous})
if err != nil {
return nil, linuxerr.ENOMEM
}
iouringfd := &fileDescription{
iouringfd := &FileDescription{
rbmf: ringsBufferFile{
mf: mfp.MemoryFile(),
fr: rbfr,
@@ -152,15 +167,135 @@ func New(ctx context.Context, vfsObj *vfs.VirtualFilesystem, entries uint32, par
// Set features supported by the current IO_URING implementation.
params.Features = linux.IORING_FEAT_SINGLE_MMAP
if err := iouringfd.populateIORings(params); err != nil {
return nil, err
}
if err := iouringfd.cacheSqesMapping(); err != nil {
return nil, err
}
if err := iouringfd.cacheCqesMapping(); err != nil {
return nil, err
}
return &iouringfd.vfsfd, nil
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *fileDescription) Release(context.Context) {
func (fd *FileDescription) Release(context.Context) {
fd.rbmf.mf.DecRef(fd.rbmf.fr)
fd.sqemf.mf.DecRef(fd.sqemf.fr)
}
// unmarshalIORings handles unmarshalling IORings struct considering that there could be more than
// one block in the BlockSeq.
func unmarshalIORings(ioRings *linux.IORings, bs *safemem.BlockSeq) error {
if bs.NumBlocks() == 1 && !bs.Head().NeedSafecopy() {
ioRings.UnmarshalBytes(bs.Head().TakeFirst((*linux.IORings)(nil).SizeBytes()).ToSlice())
return nil
}
buf := make([]byte, (*linux.IORings)(nil).SizeBytes())
cp, cperr := safemem.CopySeq(safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)), *bs)
if cp == 0 {
return cperr
}
ioRings.UnmarshalBytes(buf)
return nil
}
// marshalIORings handles marshalling IORings struct considering that there could be more than one
// BlockSeq.
func marshalIORings(ioRings *linux.IORings, bs *safemem.BlockSeq) error {
if bs.NumBlocks() == 1 && !bs.Head().NeedSafecopy() {
ioRings.MarshalBytes(bs.Head().TakeFirst((*linux.IORings)(nil).SizeBytes()).ToSlice())
}
buf := make([]byte, (*linux.IORings)(nil).SizeBytes())
ioRings.MarshalBytes(buf)
cp, cperr := safemem.CopySeq(*bs, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)))
if cp == 0 {
return cperr
}
return nil
}
// unmarshalSqe handles unmarshalling SQE struct considering that there could be more than one block
// in the BlockSeq.
func unmarshalSqe(sqe *linux.IOUringSqe, sqes *safemem.BlockSeq, sqHead uint32) error {
sqeSize := uint32((*linux.IOUringSqe)(nil).SizeBytes())
if sqes.NumBlocks() == 1 && !sqes.Head().NeedSafecopy() {
sqe.UnmarshalBytes(sqes.Head().ToSlice()[sqHead*sqeSize : (sqHead+1)*sqeSize])
return nil
}
buf := make([]byte, sqes.NumBytes())
cp, cperr := safemem.CopySeq(safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf[sqHead*sqeSize:(sqHead+1)*sqeSize])), *sqes)
if cp == 0 {
return cperr
}
sqe.UnmarshalBytes(buf)
return nil
}
// populateIORings populates IORings struct backed by the allocated memory.
func (fd *FileDescription) populateIORings(params *linux.IOUringParams) error {
bs, err := fd.rbmf.mf.MapInternal(fd.rbmf.fr, hostarch.ReadWrite)
if err != nil {
return err
}
fd.ioRings = &bs
var ioRings linux.IORings
if err = unmarshalIORings(&ioRings, &bs); err != nil {
return err
}
ioRings.SqRingMask = params.SqEntries - 1
ioRings.CqRingMask = params.CqEntries - 1
ioRings.SqRingEntries = params.SqEntries
ioRings.CqRingEntries = params.CqEntries
if err = marshalIORings(&ioRings, &bs); err != nil {
return err
}
return nil
}
// cacheSqesMapping caches the beginning of an area for the SQEs backed by the allocated memory.
func (fd *FileDescription) cacheSqesMapping() error {
bs, err := fd.sqemf.mf.MapInternal(fd.sqemf.fr, hostarch.ReadWrite)
if err != nil {
return err
}
fd.sqes = &bs
return nil
}
// cacheCqesMapping caches the beginning of an area for the CQEs backed by the allocated memory.
func (fd *FileDescription) cacheCqesMapping() error {
bs := *fd.ioRings
cqesOffset := uint64(hostarch.Addr((*linux.IORings)(nil).SizeBytes()))
cqesOffset, ok := hostarch.CacheLineRoundUp(cqesOffset)
if !ok {
return linuxerr.EOVERFLOW
}
bs = bs.DropFirst(int(cqesOffset))
fd.cqes = &bs
return nil
}
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
func (fd *fileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
func (fd *FileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
var mf memmap.Mappable
switch opts.Offset {
case linux.IORING_OFF_SQ_RING, linux.IORING_OFF_CQ_RING:
@@ -171,9 +306,115 @@ func (fd *fileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapO
return linuxerr.EINVAL
}
opts.Offset = 0
return vfs.GenericConfigureMMap(&fd.vfsfd, mf, opts)
}
// ProcessSubmissions processes submission requests.
func (fd *FileDescription) ProcessSubmissions(toSubmit uint32, minComplete uint32, flags uint32) (int, error) {
fd.mu.Lock()
defer fd.mu.Unlock()
var ioRings linux.IORings
err := fd.getIORings(&ioRings)
if err != nil {
return -1, err
}
sqes := fd.sqes
cqes := fd.cqes
var sqe linux.IOUringSqe
sqHead := atomicbitops.FromUint32(ioRings.Sq.Head)
sqTail := atomicbitops.FromUint32(ioRings.Sq.Tail)
cqHead := atomicbitops.FromUint32(ioRings.Cq.Head)
cqTail := atomicbitops.FromUint32(ioRings.Cq.Tail)
submitted := uint32(0)
for toSubmit > submitted {
sqHeadMasked := sqHead.Load() & ioRings.SqRingMask
cqTailMasked := cqTail.Load() & ioRings.CqRingMask
// This means that the submission queue is empty.
if sqHead == sqTail {
return int(submitted), nil
}
if err = unmarshalSqe(&sqe, sqes, sqHeadMasked); err != nil {
return -1, err
}
cqe, err := fd.ProcessSubmission(&sqe, flags)
if err != nil {
return -1, err
}
sqHead.Add(1)
if (cqTail.Load()-cqHead.Load())/ioRings.CqRingEntries == 1 {
ioRings.CqOverflow++
} else {
if err = fd.updateCq(cqes, cqe, cqTailMasked); err != nil {
return -1, err
}
cqTail.Add(1)
}
submitted++
}
ioRings.Sq.Head = sqHead.Load()
ioRings.Cq.Tail = cqTail.Load()
if err = marshalIORings(&ioRings, fd.ioRings); err != nil {
return -1, err
}
return int(submitted), nil
}
// ProcessSubmission processes a single submission request.
func (fd *FileDescription) ProcessSubmission(sqe *linux.IOUringSqe, flags uint32) (*linux.IOUringCqe, error) {
switch op := sqe.Opcode; op {
case 0: // NOP
return &linux.IOUringCqe{
UserData: sqe.UserData,
Res: 0,
Flags: 0,
}, nil
default: // Unsupported operation
return &linux.IOUringCqe{
UserData: sqe.UserData,
Res: -int32(linuxerr.EINVAL.Errno()),
Flags: 0,
}, nil
}
}
// updateCq updates a completion queue by adding a given completion queue entry.
func (fd *FileDescription) updateCq(cqes *safemem.BlockSeq, cqe *linux.IOUringCqe, cqTail uint32) error {
cqeSize := uint32((*linux.IOUringCqe)(nil).SizeBytes())
if cqes.NumBlocks() == 1 && !cqes.Head().NeedSafecopy() {
cqe.MarshalBytes(cqes.Head().ToSlice()[cqTail*cqeSize : (cqTail+1)*cqeSize])
return nil
}
buf := make([]byte, cqes.NumBytes())
cqe.MarshalBytes(buf)
cp, cperr := safemem.CopySeq(cqes.DropFirst64(uint64(cqTail*cqeSize)), safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)))
if cp == 0 {
return cperr
}
return nil
}
// getIORings unmarshalls IORings struct backed by the allocated memory.
func (fd *FileDescription) getIORings(ioRings *linux.IORings) error {
if err := unmarshalIORings(ioRings, fd.ioRings); err != nil {
return err
}
return nil
}
// sqEntriesFile implements memmap.Mappable for SQ entries.
type sqEntriesFile struct {
mf *pgalloc.MemoryFile
@@ -196,15 +437,6 @@ func (sqemf *sqEntriesFile) CopyMapping(ctx context.Context, ms memmap.MappingSp
// Translate implements memmap.Mappable.Translate.
func (sqemf *sqEntriesFile) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
expectedAccessType := hostarch.AccessType{
Read: true,
Write: true,
Execute: false,
}
if at != expectedAccessType {
return nil, &memmap.BusError{linuxerr.EPERM}
}
if required.End > sqemf.fr.Length() {
return nil, &memmap.BusError{linuxerr.EFAULT}
}
@@ -250,15 +482,6 @@ func (rbmf *ringsBufferFile) CopyMapping(ctx context.Context, ms memmap.MappingS
// Translate implements memmap.Mappable.Translate.
func (rbmf *ringsBufferFile) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
expectedAccessType := hostarch.AccessType{
Read: true,
Write: true,
Execute: false,
}
if at != expectedAccessType {
return nil, &memmap.BusError{linuxerr.EPERM}
}
if required.End > rbmf.fr.Length() {
return nil, &memmap.BusError{linuxerr.EFAULT}
}
+49 -2
View File
@@ -17,6 +17,7 @@ package vfs2
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/iouringfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
@@ -42,7 +43,7 @@ func IOUringSetup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.
}
// List of currently supported flags in our IO_URING implementation.
const supportedFlags = linux.IORING_SETUP_IOPOLL
const supportedFlags = 0 // Currently support none
// Since we don't implement everything, we fail explicitly on flags that are unimplemented.
if params.Flags|supportedFlags != supportedFlags {
@@ -53,7 +54,8 @@ func IOUringSetup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.
iouringfd, err := iouringfs.New(t, vfsObj, entries, &params)
if err != nil {
return 0, nil, err
// return 0, nil, err
return 0, nil, linuxerr.EPERM
}
defer iouringfd.DecRef(t)
@@ -72,3 +74,48 @@ func IOUringSetup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.
return uintptr(fd), nil, nil
}
// IOUringEnter implements linux syscall io_uring_enter(2).
func IOUringEnter(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
fd := int32(args[0].Int())
toSubmit := uint32(args[1].Uint())
minComplete := uint32(args[2].Uint())
flags := uint32(args[3].Uint())
sigSet := args[4].Pointer()
ret := -1
// List of currently supported flags for io_uring_enter(2).
const supportedFlags = 0 // Currently support none
// Since we don't implement everything, we fail explicitly on flags that are unimplemented.
if flags|supportedFlags != supportedFlags {
return uintptr(ret), nil, linuxerr.EINVAL
}
// Currently don't support replacing an existing signal mask.
if sigSet != hostarch.Addr(0) {
return uintptr(ret), nil, linuxerr.EFAULT
}
// If a user requested to submit zero SQEs, then we don't process any and return right away.
if toSubmit == 0 {
return uintptr(ret), nil, nil
}
file := t.GetFileVFS2(fd)
if file == nil {
return uintptr(ret), nil, linuxerr.EBADF
}
defer file.DecRef(t)
iouringfd, ok := file.Impl().(*iouringfs.FileDescription)
if !ok {
return uintptr(ret), nil, linuxerr.EBADF
}
ret, err := iouringfd.ProcessSubmissions(toSubmit, minComplete, flags)
if err != nil {
return uintptr(ret), nil, err
}
return uintptr(ret), nil, nil
}
+2
View File
@@ -164,6 +164,7 @@ func Override() {
s.Table[328] = syscalls.Supported("pwritev2", Pwritev2)
s.Table[332] = syscalls.Supported("statx", Statx)
s.Table[425] = syscalls.PartiallySupported("io_uring_setup", IOUringSetup, "Not all flags and functionality supported.", nil)
s.Table[426] = syscalls.PartiallySupported("io_uring_enter", IOUringEnter, "Not all flags and functionality supported.", nil)
s.Table[436] = syscalls.Supported("close_range", CloseRange)
s.Table[439] = syscalls.Supported("faccessat2", Faccessat2)
s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2)
@@ -281,6 +282,7 @@ func Override() {
s.Table[287] = syscalls.Supported("pwritev2", Pwritev2)
s.Table[291] = syscalls.Supported("statx", Statx)
s.Table[425] = syscalls.PartiallySupported("io_uring_setup", IOUringSetup, "Not all flags and functionality supported.", nil)
s.Table[426] = syscalls.PartiallySupported("io_uring_enter", IOUringEnter, "Not all flags and functionality supported.", nil)
s.Table[436] = syscalls.Supported("close_range", CloseRange)
s.Table[439] = syscalls.Supported("faccessat2", Faccessat2)
s.Table[441] = syscalls.Supported("epoll_pwait2", EpollPwait2)
+1
View File
@@ -1076,6 +1076,7 @@ cc_binary(
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
],
)
File diff suppressed because it is too large Load Diff
+1
View File
@@ -62,6 +62,7 @@ cc_library(
cc_library(
name = "io_uring_util",
testonly = 1,
srcs = ["io_uring_util.cc"],
hdrs = ["io_uring_util.h"],
deps = [
":file_descriptor",
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test/util/io_uring_util.h"
#include <memory>
namespace gvisor {
namespace testing {
PosixErrorOr<std::unique_ptr<IOUring>> IOUring::InitIOUring(
unsigned int entries, IOUringParams &params) {
PosixErrorOr<FileDescriptor> fd = NewIOUringFD(entries, params);
if (!fd.ok()) {
return fd.error();
}
return std::make_unique<IOUring>(std::move(fd.ValueOrDie()), entries, params);
}
IOUring::IOUring(FileDescriptor &&fd, unsigned int entries,
IOUringParams &params)
: iouringfd_(std::move(fd)) {
cring_sz_ = params.cq_off.cqes + params.cq_entries * sizeof(IOUringCqe);
sring_sz_ = params.sq_off.array + params.sq_entries * sizeof(unsigned);
sqes_sz_ = params.sq_entries * sizeof(IOUringSqe);
cq_ptr_ =
mmap(0, cring_sz_, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
iouringfd_.get(), IORING_OFF_SQ_RING);
sq_ptr_ =
mmap(0, sring_sz_, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
iouringfd_.get(), IORING_OFF_SQ_RING);
sqe_ptr_ = mmap(0, sqes_sz_, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, iouringfd_.get(), IORING_OFF_SQES);
cqes_ = reinterpret_cast<IOUringCqe *>(reinterpret_cast<char *>(cq_ptr_) +
params.cq_off.cqes);
cq_head_ptr_ = reinterpret_cast<uint32_t *>(
reinterpret_cast<char *>(cq_ptr_) + params.cq_off.head);
cq_tail_ptr_ = reinterpret_cast<uint32_t *>(
reinterpret_cast<char *>(cq_ptr_) + params.cq_off.tail);
sq_head_ptr_ = reinterpret_cast<uint32_t *>(
reinterpret_cast<char *>(sq_ptr_) + params.sq_off.head);
sq_tail_ptr_ = reinterpret_cast<uint32_t *>(
reinterpret_cast<char *>(sq_ptr_) + params.sq_off.tail);
cq_overflow_ptr_ = reinterpret_cast<uint32_t *>(
reinterpret_cast<char *>(cq_ptr_) + params.cq_off.overflow);
sq_dropped_ptr_ = reinterpret_cast<uint32_t *>(
reinterpret_cast<char *>(sq_ptr_) + params.sq_off.dropped);
sq_mask_ = *(reinterpret_cast<uint32_t *>(reinterpret_cast<char *>(sq_ptr_) +
params.sq_off.ring_mask));
sq_array_ = reinterpret_cast<unsigned *>(reinterpret_cast<char *>(sq_ptr_) +
params.sq_off.array);
}
IOUring::~IOUring() {
munmap(cq_ptr_, cring_sz_);
munmap(sq_ptr_, sring_sz_);
munmap(sqe_ptr_, sqes_sz_);
}
uint32_t IOUring::load_cq_head() { return io_uring_atomic_read(cq_head_ptr_); }
uint32_t IOUring::load_cq_tail() { return io_uring_atomic_read(cq_tail_ptr_); }
uint32_t IOUring::load_sq_head() { return io_uring_atomic_read(sq_head_ptr_); }
uint32_t IOUring::load_sq_tail() { return io_uring_atomic_read(sq_tail_ptr_); }
uint32_t IOUring::load_cq_overflow() {
return io_uring_atomic_read(cq_overflow_ptr_);
}
uint32_t IOUring::load_sq_dropped() {
return io_uring_atomic_read(sq_dropped_ptr_);
}
void IOUring::store_cq_head(uint32_t cq_head_val) {
io_uring_atomic_write(cq_head_ptr_, cq_head_val);
}
void IOUring::store_sq_tail(uint32_t sq_tail_val) {
io_uring_atomic_write(sq_tail_ptr_, sq_tail_val);
}
int IOUring::Enter(unsigned int to_submit, unsigned int min_complete,
unsigned int flags, sigset_t *sig) {
return IOUringEnter(iouringfd_.get(), to_submit, min_complete, flags, sig);
}
IOUringCqe *IOUring::get_cqes() { return cqes_; }
IOUringSqe *IOUring::get_sqes() {
return reinterpret_cast<IOUringSqe *>(sqe_ptr_);
}
uint32_t IOUring::get_sq_mask() { return sq_mask_; }
unsigned *IOUring::get_sq_array() { return sq_array_; }
} // namespace testing
} // namespace gvisor
+145 -3
View File
@@ -15,6 +15,10 @@
#ifndef GVISOR_TEST_UTIL_IOURING_UTIL_H_
#define GVISOR_TEST_UTIL_IOURING_UTIL_H_
#include <linux/fs.h>
#include <sys/mman.h>
#include <atomic>
#include <cerrno>
#include <cstdint>
@@ -26,6 +30,7 @@ namespace gvisor {
namespace testing {
#define __NR_io_uring_setup 425
#define __NR_io_uring_enter 426
// io_uring_setup(2) flags.
#define IORING_SETUP_SQPOLL (1U << 1)
@@ -36,6 +41,9 @@ namespace testing {
#define IORING_OFF_CQ_RING 0x8000000ULL
#define IORING_OFF_SQES 0x10000000ULL
// IO_URING operation codes.
#define IORING_OP_NOP 0
struct io_sqring_offsets {
uint32_t head;
uint32_t tail;
@@ -73,14 +81,136 @@ struct io_uring_params {
struct io_cqring_offsets cq_off;
};
struct io_uring_cqe {
uint64_t user_data;
int32_t res;
uint32_t flags;
};
struct io_uring_sqe {
uint8_t opcode;
uint8_t flags;
uint16_t ioprio;
int32_t fd;
union {
uint64_t off;
uint64_t addr2;
struct {
uint32_t cmd_op;
uint32_t __pad1;
};
};
union {
uint64_t addr;
uint64_t splice_off_in;
};
uint32_t len;
union {
__kernel_rwf_t rw_flags;
uint32_t fsync_flags;
uint16_t poll_events;
uint32_t poll32_events;
uint32_t sync_range_flags;
uint32_t msg_flags;
uint32_t timeout_flags;
uint32_t accept_flags;
uint32_t cancel_flags;
uint32_t open_flags;
uint32_t statx_flags;
uint32_t fadvise_advice;
uint32_t splice_flags;
uint32_t rename_flags;
uint32_t unlink_flags;
uint32_t hardlink_flags;
uint32_t xattr_flags;
};
uint64_t user_data;
union {
uint16_t buf_index;
uint16_t buf_group;
} __attribute__((packed));
uint16_t personality;
union {
int32_t splice_fd_in;
uint32_t file_index;
};
union {
struct {
uint64_t addr3;
uint64_t __pad2[1];
};
uint8_t cmd[0];
};
};
using IOSqringOffsets = struct io_sqring_offsets;
using ICqringOffsets = struct io_cqring_offsets;
using IOUringCqe = struct io_uring_cqe;
using IOUringParams = struct io_uring_params;
using IOUringSqe = struct io_uring_sqe;
// Helper class for IO_URING
class IOUring {
public:
IOUring() = delete;
IOUring(FileDescriptor &&fd, unsigned int entries, IOUringParams &params);
~IOUring();
static PosixErrorOr<std::unique_ptr<IOUring>> InitIOUring(
unsigned int entries, IOUringParams &params);
uint32_t load_cq_head();
uint32_t load_cq_tail();
uint32_t load_sq_head();
uint32_t load_sq_tail();
uint32_t load_cq_overflow();
uint32_t load_sq_dropped();
void store_cq_head(uint32_t cq_head_val);
void store_sq_tail(uint32_t sq_tail_val);
int Enter(unsigned int to_submit, unsigned int min_complete,
unsigned int flags, sigset_t *sig);
IOUringCqe *get_cqes();
IOUringSqe *get_sqes();
uint32_t get_sq_mask();
unsigned *get_sq_array();
int Fd() { return iouringfd_.get(); }
private:
IOUringCqe *cqes_ = nullptr;
FileDescriptor iouringfd_;
size_t cring_sz_;
size_t sring_sz_;
size_t sqes_sz_;
uint32_t sq_mask_;
unsigned *sq_array_ = nullptr;
uint32_t *cq_head_ptr_ = nullptr;
uint32_t *cq_tail_ptr_ = nullptr;
uint32_t *sq_head_ptr_ = nullptr;
uint32_t *sq_tail_ptr_ = nullptr;
uint32_t *cq_overflow_ptr_ = nullptr;
uint32_t *sq_dropped_ptr_ = nullptr;
void *sq_ptr_ = nullptr;
void *cq_ptr_ = nullptr;
void *sqe_ptr_ = nullptr;
};
// This is a wrapper for the io_uring_setup(2) system call.
inline int IOUringSetup(uint32_t entries, struct io_uring_params* params) {
inline int IOUringSetup(uint32_t entries, IOUringParams *params) {
return syscall(__NR_io_uring_setup, entries, params);
}
// This is a wrapper for the io_uring_enter(2) system call.
inline int IOUringEnter(unsigned int fd, unsigned int to_submit,
unsigned int min_complete, unsigned int flags,
sigset_t *sig) {
return syscall(__NR_io_uring_enter, fd, to_submit, min_complete, flags, sig);
}
// Returns a new iouringfd with the given number of entries.
inline PosixErrorOr<FileDescriptor> NewIOUringFD(
uint32_t entries, struct io_uring_params& params) {
inline PosixErrorOr<FileDescriptor> NewIOUringFD(uint32_t entries,
IOUringParams &params) {
memset(&params, 0, sizeof(params));
int fd = IOUringSetup(entries, &params);
MaybeSave();
@@ -90,6 +220,18 @@ inline PosixErrorOr<FileDescriptor> NewIOUringFD(
return FileDescriptor(fd);
}
template <typename T>
static inline void io_uring_atomic_write(T *p, T v) {
std::atomic_store_explicit(reinterpret_cast<std::atomic<T> *>(p), v,
std::memory_order_release);
}
template <typename T>
static inline T io_uring_atomic_read(const T *p) {
return std::atomic_load_explicit(reinterpret_cast<const std::atomic<T> *>(p),
std::memory_order_acquire);
}
} // namespace testing
} // namespace gvisor