Turning, well copying blobs of memory into Go structures
As before, suppose (not entirely hypothetically) that you're writing a
package to connect Go up to something that will provide it with
blobs of memory that are actually C structs; these might be mmap()'d
files, information from a library, or whatever. Once you have a
compatible Go struct
, you still have to
get the data from a C struct (or raw memory) to the Go struct.
One way to do this is to manually write your own struct copy function
that does it field by field (eg 'io.Field = ks_io.field
' for
each field). As with defining the Go structs by hand, this is tedious
and potentially error prone. You can do it and you'll probably have
to if the C struct contains unions or other hard to deal with things,
but we'd like an easier approach. Fortunately there are two good
ones for two different cases. In both cases we will wind up copying
the C struct or the raw memory to a Go struct variable that is an
exact equivalent of the C struct (or at
least we hope it is).
The easy case is when we're dealing with a fixed struct
that we
have a known Go type for. Assuming that we have a C void *
pointer
to the original memory area called ks.ks_data
, we can adopt the
C programmer approach and write:
var io IO io = *((*IO)(ks.ks_data)) return &io
This casts ks.ks_data
to a pointer to an IO
struct and then
dereferences it to copy the struct itself into the Go variable we
made for this. Depending on the C type of ks_data
, you may need
to use the hammer of unsafe.Pointer()
here:
io = *((*IO)(unsafe.Pointer(ks.ks_data)))
At this point, some people will be tempted to skip the copying and
just return the 'casted-to-*IO' ks.ks_data
pointer. You don't
want to do this, because if you return a Go pointer to C data,
you're coupling Go and C memory management lifetimes. The C
memory must not be freed or reused for something else for as long
as Go retains at least one pointer to it, and there is no way for
you to find out when the last Go reference goes away so that you
can free the C memory. It's much simpler to treat 'C memory' as
completely disjoint from 'Go memory'; any time you want to move
some information across the boundary, you must copy it. With copying
we know we can free ks.ks_data
safely the moment the copy is
done and the Go runtime will handle the lifetime of the io
variable
for us.
The more difficult case is when we don't know what struct
s we're
dealing with; we're providing the access package, but it's the
callers who actually know the struct
s are. This situation might
come up in a package for accessing kernel stats, where drivers or
other kernel systems can export custom stats structs. Our access
package can provide specific support for known struct
s, but we
need an escape hatch for when the callers knows that some specific
kernel system is providing a 'struct whatever' and it wants to
retrieve that (probably into an identical Go struct created through
cgo
).
The C programmer approach to this problem is memmove()
. You can
write memmove()
in Go with sufficiently perverse use of the
unsafe
package, but you don't want to. Instead we can use the
reflect
package to create a generic version of the specific 'cast
and copy' code we used above. How to do this wasn't obvious to me
until I did a significant amount of flailing around with the package,
so I'm going to go through the logic of what we're doing in detail.
We'll start with our call signature:
func (k *KStat) CopyTo(ptri interface{}) error { ... }
CopyTo takes a pointer to a Go struct and copies our C memory in
ks.ks_data
into the struct. I'm going to omit the reflect
-based
code to check ptri
to make sure it's actually a pointer to a
suitable struct in the interests of space, but you shouldn't in
real code. Also, there are a whole raft of qualifications you're
going to want to impose on what types of fields that struct can
contain if you want to at least pretend that your package is somewhat
memory safe.
To actually do the copy, we first need to turn this ptri
interface
value into a reflect.Value
that is the destination struct itself:
ptr := reflect.ValueOf(ptri) dst := ptr.Elem()
We now need to cast ks.ks_data
to a Value with the type 'pointer to
dst's type'. This is most easily done by creating a new pointer of the
right type with the address taken from ks.ks_data
:
src := reflect.NewAt(dst.Type(), unsafe.Pointer(ks.ks_data))
This is the equivalent of 'src := ((*IO)(ks.ks_data))
' in the
type-specific version. Reflect.NewAt is there for doing just
this; its purpose is to create pointers for 'type X at address Y',
which is exactly the operation we need.
Having created this pointer, we then dereference it to copy the
data into dst
:
dst.Set(reflect.Indirect(src))
This is the equivalent of 'io = *src
' in the type-specific
version. We're done.
In my testing, this approach is surprisingly robust; it will deal
with even structs that I didn't expect it to (such as ones with
unexported fields). But you probably don't want to count on that;
it's safest to give CopyTo()
straightforward structs with only
exported fields.
On the whole I'm both happy and pleasantly surprised by how easy
it turned out to be to use the reflect package here; I expected it
to require a much more involved and bureaucratic process. Getting
to this final form involved a lot of missteps and unnecessarily
complicated approaches, but the final form itself is about as minimal
as I could expect. A lot of this is due to the existence of
reflect.NewAt()
, but there's also that Value.Set()
works fine
even on complex and nested types.
(Note that while you could use the reflect-based version even for the first, fixed struct type case, my understanding is that the reflect package has not insignificant overheads. By contrast the hard coded fixed struct type code is about as minimal and low overhead as you can get; it should normally compile down to basically a memory copy.)
Sidebar: preserving Go memory safety here
I'm not fully confident that I have this right, but I think that to
preserve memory safety in the face of this memory copying you must
insure that the target struct
type does not contain any embedded
pointers, either explicit ones or ones implicitly embedded into types
like maps, chans, interfaces, strings, slices, and so on. Fixed-size
arrays are safe because in Go those are just fixed size blocks of
memory.
If you copy a C struct containing pointers into a Go struct containing
pointers, what you're doing is the equivalent of directly returning
the 'casted-to-*IO' ks.ks_data
pointer. You've allowed the
creation of a Go object that points to C memory and you now have
the same C and Go memory lifetime issues. And if some of the pointers
are invalid or point to garbage memory, not only is normal Go code
at risk of bad things but it's possible that the Go garbage collector
will wind up trying to dereference them and take a fault.
(This makes it impossible to easily copy certain sorts of C structures into Go structures. Fortunately such structures rarely appear in this sort of C API because they often raise awkward memory lifetime issues even in C.)
|
|