func (r *rx) init(mtu uint32, c *QueueConfig) error {
	txPipe, err := getBuffer(c.TxPipeFD)
	if err != nil {
		return err
	}
	rxPipe, err := getBuffer(c.RxPipeFD)
	if err != nil {
		syscall.Munmap(txPipe)
		return err
	}
	data, err := getBuffer(c.DataFD)
	if err != nil {
		syscall.Munmap(txPipe)
		syscall.Munmap(rxPipe)
		return err
	}
	sharedData, err := getBuffer(c.SharedDataFD)
	if err != nil {
		syscall.Munmap(txPipe)
		syscall.Munmap(rxPipe)
		syscall.Munmap(data)
		return err
	}
	efd, err := syscall.Dup(c.EventFD)
	if err != nil {
		syscall.Munmap(txPipe)
		syscall.Munmap(rxPipe)
		syscall.Munmap(data)
		syscall.Munmap(sharedData)
		return err
	}
	if err := syscall.SetNonblock(efd, true); err != nil {
		syscall.Munmap(txPipe)
		syscall.Munmap(rxPipe)
		syscall.Munmap(data)
		syscall.Munmap(sharedData)
		syscall.Close(efd)
		return err
	}
	r.q.Init(txPipe, rxPipe, sharedDataPointer(sharedData))
	r.data = data
	r.eventFD = efd
	r.sharedData = sharedData
	return nil
}
init initializes all state needed by the rx queue based on the information
provided.

The caller always retains ownership of all file descriptors passed in. The
queue implementation will duplicate any that it may need in the future.
package sharedmem
import (
	"sync/atomic"
	"syscall"
	"github.com/google/netstack/tcpip/link/rawfile"
	"github.com/google/netstack/tcpip/link/sharedmem/queue"
)
type rx struct {
	data       []byte
	sharedData []byte
	q          queue.Rx
	eventFD    int
}