unsafe.Pointer 和系統調用

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。按照 Go 語言官方文檔所說, unsafe 是關注 Go 程式操作類型安全的包。 像包名暗示的一樣,使用它要格外小心; unsafe 可以特別危險,但它也可以特別有效。例如,當處理系統調用時,Go 的結構體必須和 C 的結構體擁有相同的記憶體結構,這時你可能除了使用 unsafe 以外,別無選擇。unsafe.Pointer 可以讓你無視 Go 的類型系統,完成任何類型與內建的 uintptr 類型之間的轉化。根據文檔,unsafe.Pointer 可以實現四種其他類型不能的操作:* 任何類型的指標都可以轉化為一個 unsafe.Pointer * 一個 unsafe.Pointer 可以轉化成任何類型的指標* 一個 uintptr 可以轉化成一個 unsafe.Pointer * 一個 unsafe.Pointer 可以轉化成一個 uintptr 這裡主要關注兩種只能藉助 unsafe 包才能完成的操作:使用 unsafe.Pointer 實現兩種類型間轉換和使用 unsafe.Pointer 處理系統調用。## 使用 unsafe.Pointer 做類型轉換### 操作方式可以簡潔適宜的轉換兩個在記憶體中結構一樣的類型是使用 unsafe.Pointer 的一個主要原因。 文檔描述:> 如果T2與T1一樣大,並且兩者有相同的記憶體結構;那麼就允許把一個類型的資料,重新定義成另一個類型的資料經典的例子,是文檔中的一次使用,用來實現 math.Float64bits: ```gofunc Float64bits(f float64) uint64 {return *(*uint64)(unsafe.Pointer(&f))}```這似乎是一種非常簡潔的完成這樣轉換的方法,但是這個過程中具體發生了什嗎?讓我們一步步拆分一下:* &f 拿到一個指向 f 存放 float64 值的指標。* unsafe.Pointer(&f) 將 *float64 類型轉化成了 unsafe.Pointer 類型。* (*uint64)(unsafe.Pointer(&f)) 將 unsafe.Pointer 類型轉化成了 *uint64。* *(*uint64)(unsafe.Pointer(&f)) 引用這個 *uint64 類型指標,轉化為一個 uint64 類型的值。第一個例子是下面過程的一個簡潔表達:```gofunc Float64bits(floatVal float64) uint64 {// 擷取一個指向儲存這個float64類型值的指標。floatPtr := &floatVal// 轉化*float64類型到unsafe.Pointer類型。unsafePtr := unsafe.Pointer(floatPtr)// 轉化unsafe.Pointer類型到*uint64類型.uintPtr := (*uint64)(unsafePtr)// 解引用成一個uint64值uintVal := *uintPtrreturn uintVal}```這是一個非常有用的操作,有些時候也是一個必要操作。現在你已經理解了 unsafe.Pointer 是如何使用的,那麼讓我們再看一個真實的項目例子 ### 現實列子:taskstats我最近正在研究 [Linux 的 taskstats 介面](https://www.kernel.org/doc/Documentation/accounting/taskstats.txt),我想了一個辦法在 Go 中取到了核心的 C 的 taskstats 結構。然後發送一個 CL 把這個結構加到 x/sys/unix 中,我意識到這個結構實際上是如此的[龐大和複雜](https://godoc.org/golang.org/x/sys/unix#Taskstats)。 為了使用這個結構,我需要從一個 byte 類型的切片中精確的分析每一個欄位。更複雜的是,每一個 integer 類型在本地有序的儲存,所以這些整數可能根據你的cpu在記憶體中以不同的格式儲存。這個情況就非常適合使用簡潔的 unsafe.Pointer 轉換,下面是我的寫法:```go// 通過這個包證實包含一個 unix.Taskstats 結構的byte類型的切片是預計的大小,我們不能盲目的將這個byte類型的切片放入一個錯誤尺寸的結構中。const sizeofTaskstats = int(unsafe.Sizeof(unix.Taskstats{}))if want, got := sizeofTaskstats, len(buf); want != got {return nil, fmt.Errorf("unexpected taskstats structure size, want %d, got %d", want, got)}stats := *(*unix.Taskstats)(unsafe.Pointer(&buf[0]))```它是怎麼做的? 首先,我通過參數傳來的結構體執行個體,使用 unsafe.Sizeof,確定了該結構在記憶體中佔有的準確的大小。 接下來,我確認需要轉換的byte類型的切片大小和 unix.Taskstats 結構大小一樣,這樣我就可以唯讀取我想要的資料區塊,而不是隨意讀取記憶體。 最後,我使用 unsafe.Pointer 向 unix.Taskstats 結構轉換。 但是,我為什麼必須指定切片索引的0位置呢?如果你瞭解[切片的內部結構](https://blog.golang.org/go-slices-usage-and-internals),你將知道一個切片實際上是一個頭和一個指向底層數組的指標。當使用 unsafe.Pointer 來轉換切片資料時,必須指定數組第一個元素的記憶體位址,而不是切片本身的首地址。使用 unsafe 使得轉換非常的簡潔、簡單。因為整型資料根據我們的CPU以相同的位元組順序儲存,使用 unsafe.Pointer 轉化意味著整型值是我們預期的。你可以去看看我 [taskstats](https://github.com/mdlayher/taskstats) 包中的代碼。## 使用 unsafe.Pointer 處理系統調用### 操作方式當處理系統調用時,有些時候需要傳入一個指向某塊記憶體的指標給核心,以允許它執行某些任務。這是 unsafe.Pointer 在Go中另一個重要的使用情境。當需要處理系統調用時,就必須使用 unsafe.Pointer ,因為為了使用 syscall.Syscall 家族函數,它可以被轉化成 uintptr 類型。對於許多不同的作業系統,都擁有大量的系統調用。但是在這個例子中,我們將重點關注 ioctl 。ioctl,在UNIX類系統中,經常被用來操作那些無法直接映射到典型的檔案系統操作,例如讀和寫的檔案描述符。事實上,由於 ioctl 系統調用十分靈活,它並不在Go的 syscall 或者 x/sys/unix 包中。讓我看看另一個真實的例子。### 現執行個體子:ioctl/vsock在過去的幾年裡,Linux增加了一個新的 socket 家族,AF_VSOCK,它可以使管理中心和它的虛擬機器之間雙向,多對一的通訊。 這些通訊端使用一個上下文ID進行通訊。通過發送一個帶有特殊請求號的 ioctl 到 /dev/vsock 驅動,可以取到這個上下文ID。 下面是 ioctl 函數的定義: ```gofunc Ioctl(fd uintptr, request int, argp unsafe.Pointer) error {_, _, errno := unix.Syscall(unix.SYS_IOCTL,fd,uintptr(request),// 在這個調用運算式中,從 unsafe.Pointer 到 uintptr 的轉換是必須做的。詳情可以查看 unsafe 包的文檔uintptr(argp),)if errno != 0 {return os.NewSyscallError("ioctl", fmt.Errorf("%d", int(errno)))}return nil}``` 像代碼注釋所寫一樣,在這種情境下使用 unsafe.Pointer 有一個很重要的說明: > 在 syscall 包中的系統調用函數通過它們的 uintptr 型別參數直接作業系統,然後根據調用的詳細情況,將它們中的一些轉化為指標。換句話說,系統調用的執行,是其中某些參數從 uintptr 類型到指標類型的隱式轉換。 > 如果一個指標參數必須轉換成 uintptr 才能使用,那麼這種轉換必須出現在運算式內部。但是為什麼會這樣?這是編譯器識別的特殊模式,本質上是指示垃圾收集器在函數調用完成之前,不能將被指標引用的記憶體再次安排。 你可以通過閱讀文檔來獲得更多的技術細節,但是你在Go中處理系統調用時必須記住這個規則。事實上,在寫這篇文章時,我意識到My Code違反了這一規則,現在已經被修複了。 意識到這一點,我們可以看到這個函數是如何使用的。在 VM 通訊端的例子裡,我們想傳遞一個 *uint32 到核心,以便它可以把我們當時的上下文ID賦值到這塊記憶體位址中。 ```gof, err := fs.Open("/dev/vsock")if err != nil {return 0, err}defer f.Close()// 儲存上下文IDvar cid uint32// 從這台機器的 /dev/vsock 中擷取上下文IDerr = Ioctl(f.Fd(), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID, unsafe.Pointer(&cid))if err != nil {return 0, err}// 返回當前的上下文ID給調用者return cid, nil```這隻是在系統調用時使用 unsafe.Pointer 的一個例子。你可以使用這麼模式發送、接收任何資料,或者是用一些特殊方式配置一個核心介面。有很多可能的情況! 你可以去看看我 [vsock](https://github.com/mdlayher/vsock) 包中的代碼。 ## 結尾 雖然使用 unsafe 包可能存在風險,但當使用恰當時,它可以是一個非常強大、有用的工具。 既然你在讀這篇文章,我建議你在你的程式使用它之前,去[讀一下 unsafe 包的官方文檔](https://golang.org/pkg/unsafe/)。 如果你有任何問題,請隨時聯絡我!在 [Gophers Slack](https://gophers.slack.com/), [GitHub](https://github.com/mdlayher) and [Twitter](https://twitter.com/mdlayher)上我的名字是 mdlayher。 非常感謝 [Hazel Virdó](https://twitter.com/HazelVirdo) 對這篇文章的建議和修改! ## 連結* [unsafe 包](https://golang.org/pkg/unsafe/)* [位元組順序](https://en.wikipedia.org/wiki/Endianness)* [taskstats 包](https://github.com/mdlayher/taskstats)* [Go中Slice的使用和內部實現](https://blog.golang.org/go-slices-usage-and-internals)* [ioctl](https://en.wikipedia.org/wiki/Ioctl)* [vsock 包](https://github.com/mdlayher/vsock)

via: https://blog.gopheracademy.com/advent-2017/unsafe-pointer-and-system-calls/

作者:Matt Layher 譯者:yiyulantian 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

424 次點擊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.