golang的非安全包(unsafe package)浅谈

372 阅读4分钟

非安全包

什么是非安全包(unsafe)

官方文档的描述

Package unsafe contains operations that step around the type safety of Go programs. Packages that import unsafe may be non-portable and are not protected by the Go 1 compatibility guidelines.

非安全包包含了一些避开go语言类型安全的操作 引入非安全包可能会非便携(non-protable)和不能被go version1版本一致性指导方针所保护

类型安全

在go中,每一个变量都有自己的类型,在分配给其他变量之前可以转换为另一种类型:

var i int8 = -1 // -1 二进制表示: 11111111
var j = int16(i) // -1 二进制表示: 11111111 11111111
println(i, j) // -1 -1

unsafe包可以让我们直接获取这个变量内存的值,这个值在地址上保存二进制的值。这样的话我们可以绕过类型约束。

承接上面的代码:

var k uint8 = *(*uint8)(unsafe.Pointer(&i))
println(k) // 255 因为在内存地址中使用11111111二进制表示当前的值

GO 1一致性指导方针

Packages that import unsafe may depend on internal properties of the Go implementation. We reserve the right to make changes to the implementation that may break such programs.

有些要注意的是,可能在两个版本之间会有一些细小的变化,这些变化可以能出现一些问题,但是go的标准库中还是会使用一些标准库

在反射包中使用unsafe

反射包是基于空接口(empty interface)的内部数据。为了能够读取数据,go知识转换了我们的变量到空接口中,然后读取它们通过映射一个结构体,这个结构体匹配空接口的内部展示,使用它的指针地址的内存

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
	if i == nil {
		return Value{}
	}

	// TODO: Maybe allow contents of a Value to live on the stack.
	// For now we make the contents always escape to the heap. It
	// makes life easier in a few places (see chanrecv/mapassign
	// comment below).
	escapes(i)

	return unpackEface(i)
}



// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
	e := (*emptyInterface)(unsafe.Pointer(&i))
	// NOTE: don't read e.word until we know whether it is really a pointer or not.
	t := e.typ
	if t == nil {
		return Value{}
	}
	f := flag(t.Kind())
	if ifaceIndir(t) {
		f |= flagIndir
	}
	return Value{t, e.word, f}
}

这里的e包含了我们所有的信息,包括类型,是否值可以暴漏。反射也使用unsafe包来改变反射变量的值通过直接修改内存的地址。

sync包使用

sync包中池(pool)通过所有的通过一部分内存在所有的goroutines/porcessors共享,所有的goroutines可以通过unsafe包来直接获取:

func indexLocal(l unsafe.Pointer, i int) *poolLocal {
   lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
   return (*poolLocal)(lp)
}

这里面l是内存段,i是处理器数量,所以indexLocal方法就是读了内存段--这个内存段包含了x(处理器数量)个poolLocal结构体--用一个他读到的相关索引偏移量来表示。存一个单个执政到整个内存快是一种非常轻量的方式来实现共享池。

在runtime包中使用

go在runtime环境下使用unsafe包因为它可以对内存进行操作,比如栈分配或者释放栈内存。栈在它的结构体使用两个边界表示:

type stack struct {
   lo uintptr
   hi uintptr
}

unsafe包帮助来实现操作

// stackfree frees an n byte stack allocation at stk.
//
// stackfree must run on the system stack because it uses per-P
// resources and must not split the stack.
//
//go:systemstack
func stackfree(stk stack) {
	gp := getg()
	v := unsafe.Pointer(stk.lo)
	n := stk.hi - stk.lo
	if n&(n-1) != 0 {
		throw("stack not a power of 2")
	}
	if stk.lo+n < stk.hi {
		throw("bad stack size")
	}
	if stackDebug >= 1 {
		println("stackfree", v, n)
		memclrNoHeapPointers(v, n) // for testing, clobber stack data
	}
	if debug.efence != 0 || stackFromSystem != 0 {
		if debug.efence != 0 || stackFaultOnFree != 0 {
			sysFault(v, n)
		} else {
			sysFree(v, n, &memstats.stacks_sys)
		}
		return
	}
	if msanenabled {
		msanfree(v, n)
	}
	if n < _FixedStack<<_NumStackOrders && n < _StackCacheSize {
		order := uint8(0)
		n2 := n
		for n2 > _FixedStack {
			order++
			n2 >>= 1
		}
		x := gclinkptr(v)
		if stackNoCache != 0 || gp.m.p == 0 || gp.m.preemptoff != "" {
			lock(&stackpool[order].item.mu)
			stackpoolfree(x, order)
			unlock(&stackpool[order].item.mu)
		} else {
			c := gp.m.p.ptr().mcache
			if c.stackcache[order].size >= _StackCacheSize {
				stackcacherelease(c, order)
			}
			x.ptr().next = c.stackcache[order].list
			c.stackcache[order].list = x
			c.stackcache[order].size += n
		}
	} else {
		s := spanOfUnchecked(uintptr(v))
		if s.state.get() != mSpanManual {
			println(hex(s.base()), v)
			throw("bad span state")
		}
		if gcphase == _GCoff {
			// Free the stack immediately if we're
			// sweeping.
			osStackFree(s)
			mheap_.freeManual(s, spanAllocStack)
		} else {
			// If the GC is running, we can't return a
			// stack span to the heap because it could be
			// reused as a heap span, and this state
			// change would race with GC. Add it to the
			// large stack cache instead.
			log2npage := stacklog2(s.npages)
			lock(&stackLarge.lock)
			stackLarge.free[log2npage].insert(s)
			unlock(&stackLarge.lock)
		}
	}
}

作为开发者使用

如果两个结构体类型有相同的内部结构,那么可以通过非安全指针进行这两种类型的转换。

type A struct {
	A int8
	B string
	C float32
}

type B struct {
	D int8
	E string
	F float32
}

a := A{A: 1, B: "hello world", C: 5.0}
//b := B(a)  //不能转换
b := *(*B)(unsafe.Pointer(&a))
fmt.Printf("%v", b)  //{1 hello world 5}

这里需要注意的是在转换过程中,AB的内的成员顺序一定要相同,因为unsafe操作是对内存地址操作,如果顺序出现问题就会出现内存无法分配的问题