はじめに
本記事はGoでlinuxのdirect I/Oを使う方法について書いたものです。A Tour of Goを全部終えた人なら読めると思います。
ソフトウェアバージョン
- OS: Ubuntu 16.04
- kernel: 4.4.0-83-generic
- go: 1.9
きっかけ
Linuxのdirect I/Oは、read/write時用に使うバッファの先頭アドレスおよびサイズを、ストレージのブロックサイズの倍数(通常512バイトないし4096バイト)にアライメントする必要があります。Goの標準ライブラリ内には次のようにO_DIRECTが定義されていますが、この条件に合致するバッファをどうやってとるのかが気になったので調べました。
const (
...
O_DIRECT = 0x4000
...
結論
仕様上direct I/Oの要求を満たすバッファを獲得できないため、自前でunsafeパッケージを使ってバッファをアライメントしたり、後述のdirectioパッケージなどを使うなりする必要がありそうです。
仕様の確認
仕様上は次のように、配列のアライメントは、配列の各要素のアライメント以上のものが保証されていませんでした。
The following minimal alignment properties are guaranteed: * For a variable x of any type: unsafe.Alignof(x) is at least 1. * For a variable x of struct type: unsafe.Alignof(x) is the largest of all the values unsafe.Alignof(x.f) for each field f of x, but at least 1. * For a variable x of array type: unsafe.Alignof(x) is the same as the alignment of a variable of the array's element type.
それに加えて、アライメントを考慮したメモリアロケーションの仕組みが標準ライブラリにあるかどうかを確認しましたが、こちらも該当するものはなかったです。
directioライブラリ
前節で述べた通り標準ではdirect I/Oに必要なバッファをとる確実な方法が無いのですが、上述したdirect I/O用のライブラリ、directioをweb上で見つけました。
directioライブラリはunsafeライブラリを活用して、バッファの獲得時に内部的に余分な領域をとることによって、direct I/O用バッファの先頭アドレスをアライメントしています。
// alignment returns alignment of the block in memory
// with reference to AlignSize
//
// Can't check alignment of a zero sized block as &block[0] is invalid
func alignment(block []byte, AlignSize int) int {
return int(uintptr(unsafe.Pointer(&block[0])) & uintptr(AlignSize-1))
}
// AlignedBlock returns []byte of size BlockSize aligned to a multiple
// of AlignSize in memory (must be power of two)
func AlignedBlock(BlockSize int) []byte {
block := make([]byte, BlockSize+AlignSize)
if AlignSize == 0 {
return block
}
a := alignment(block, AlignSize)
offset := 0
if a != 0 {
offset = AlignSize - a
}
block = block[offset : offset+BlockSize]
...
glibcのposix_memalign()も内部的には似たようなこと(だいぶ複雑ですが)をしています。興味のあるかたはglibcのソースを見てください。
おわりに
unsafeライブラリやdirectioライブラリを使わない、もっといい方法を知っているかたがいれば教えてほしいです。