Skip to content

Commit

Permalink
Direct IO library factored from github.com/ncw/stressdisk
Browse files Browse the repository at this point in the history
  • Loading branch information
ncw committed Jun 28, 2013
0 parents commit f5011e2
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*~

20 changes: 20 additions & 0 deletions COPYING
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
DirectIO
========

This is library for the Go language to enable use of Direct IO under
all supported OSes of Go.

Direct IO does IO to and from disk without buffering data in the OS.
It is useful when you are reading or writing lots of data you don't
want to fill the OS cache up with.

Install
-------

Directio is a Go library and installs in the usual way

go get github.com/ncw/directio

Usage
-----

Instead of using os.OpenFile use directio.OpenFile

in, err := directio.OpenFile(file, os.O_RDONLY, 0666)

And when reading or writing blocks, make sure you do them in chunks of
directio.BlockSize using memory allocated by directio.AlignedBlock

block := directio.AlignedBlock(BlockSize)
_, err := io.ReadFull(in, block)

License
-------

This is free software under the terms of MIT the license (check the
COPYING file included in this package).

Contact and support
-------------------

The project website is at:

- https://github.com/ncw/directio

There you can file bug reports, ask for help or contribute patches.

Authors
-------

- Nick Craig-Wood <[email protected]>

Contributors
------------

- Your name goes here!
48 changes: 48 additions & 0 deletions direct_io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// This is library for the Go language to enable use of Direct IO under
// all supported OSes of Go.
//
// Direct IO does IO to and from disk without buffering data in the OS.
// It is useful when you are reading or writing lots of data you don't
// want to fill the OS cache up with.
//
// Instead of using os.OpenFile use directio.OpenFile
//
// in, err := directio.OpenFile(file, os.O_RDONLY, 0666)
//
// And when reading or writing blocks, make sure you do them in chunks of
// directio.BlockSize using memory allocated by directio.AlignedBlock
//
// block := directio.AlignedBlock(BlockSize)
// _, err := io.ReadFull(in, block)
package directio

import (
"log"
"unsafe"
)

// alignment returns alignment of the block in memory
// with reference to AlignSize
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]
a = alignment(block, AlignSize)
if a != 0 {
log.Fatal("Failed to align block")
}
return block
}
36 changes: 36 additions & 0 deletions direct_io_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Direct IO for darwin

package directio

import (
"fmt"
"os"
"syscall"
)

const (
// OSX doesn't need any alignment
AlignSize = 0

// Minimum block size
BlockSize = 4096
)

func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
file, err = os.OpenFile(name, flag, perm)
if err != nil {
return
}

// Set F_NOCACHE to avoid caching
// F_NOCACHE Turns data caching off/on. A non-zero value in arg turns data caching off. A value
// of zero in arg turns data caching on.
_, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(file.Fd()), syscall.F_NOCACHE, 1)
if e1 != 0 {
err = fmt.Errorf("Failed to set F_NOCACHE: %s", e1)
file.Close()
file = nil
}

return
}
21 changes: 21 additions & 0 deletions direct_io_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Direct IO for freebsd

package directio

import (
"os"
"syscall"
)

const (
// Size to align the buffer to
AlignSize = 4096

// Minimum block size
BlockSize = 4096
)

// OpenFile is a modified version of os.OpenFile which sets O_DIRECT
func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
return os.OpenFile(name, syscall.O_DIRECT|flag, perm)
}
21 changes: 21 additions & 0 deletions direct_io_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Direct IO for linux

package directio

import (
"os"
"syscall"
)

const (
// Size to align the buffer to
AlignSize = 4096

// Minimum block size
BlockSize = 4096
)

// OpenFile is a modified version of os.OpenFile which sets O_DIRECT
func OpenFile(name string, flag int, perm os.FileMode) (file *os.File, err error) {
return os.OpenFile(name, syscall.O_DIRECT|flag, perm)
}
92 changes: 92 additions & 0 deletions direct_io_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Direct IO for windows

package directio

import (
"os"
"syscall"
"unicode/utf16"
)

const (
// Size to align the buffer to
AlignSize = 4096

// Minimum block size
BlockSize = 4096

// Extra flags for windows
FILE_FLAG_NO_BUFFERING = 0x20000000
FILE_FLAG_WRITE_THROUGH = 0x80000000
)

// utf16FromString returns the UTF-16 encoding of the UTF-8 string
// s, with a terminating NUL added. If s contains a NUL byte at any
// location, it returns (nil, EINVAL).
//
// FIXME copied from go source
func utf16FromString(s string) ([]uint16, error) {
for i := 0; i < len(s); i++ {
if s[i] == 0 {
return nil, syscall.EINVAL
}
}
return utf16.Encode([]rune(s + "\x00")), nil
}

// OpenFile is a modified version of os.OpenFile which sets the
// passes the following flags to windows CreateFile.
//
// The FILE_FLAG_NO_BUFFERING takes this concept one step further and
// eliminates all read-ahead file buffering and disk caching as well,
// so that all reads are guaranteed to come from the file and not from
// any system buffer or disk cache. When using FILE_FLAG_NO_BUFFERING,
// disk reads and writes must be done on sector boundaries, and buffer
// addresses must be aligned on disk sector boundaries in memory.
//
// FIXME copied from go source then modified
func OpenFile(path string, mode int, perm os.FileMode) (file *os.File, err error) {
if len(path) == 0 {
return nil, &os.PathError{"open", path, syscall.ERROR_FILE_NOT_FOUND}
}
pathp, err := utf16FromString(path)
if err != nil {
return nil, &os.PathError{"open", path, err}
}
var access uint32
switch mode & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
case os.O_RDONLY:
access = syscall.GENERIC_READ
case os.O_WRONLY:
access = syscall.GENERIC_WRITE
case os.O_RDWR:
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
}
if mode&syscall.O_CREAT != 0 {
access |= syscall.GENERIC_WRITE
}
if mode&os.O_APPEND != 0 {
access &^= syscall.GENERIC_WRITE
access |= syscall.FILE_APPEND_DATA
}
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
var sa *syscall.SecurityAttributes
var createmode uint32
switch {
case mode&(syscall.O_CREAT|os.O_EXCL) == (syscall.O_CREAT | os.O_EXCL):
createmode = syscall.CREATE_NEW
case mode&(syscall.O_CREAT|os.O_TRUNC) == (syscall.O_CREAT | os.O_TRUNC):
createmode = syscall.CREATE_ALWAYS
case mode&syscall.O_CREAT == syscall.O_CREAT:
createmode = syscall.OPEN_ALWAYS
case mode&os.O_TRUNC == os.O_TRUNC:
createmode = syscall.TRUNCATE_EXISTING
default:
createmode = syscall.OPEN_EXISTING
}
h, e := syscall.CreateFile(&pathp[0], access, sharemode, sa, createmode, syscall.FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH, 0)
if e != nil {
return nil, &os.PathError{"open", path, e}
}
return os.NewFile(uintptr(h), path), nil
}

0 comments on commit f5011e2

Please sign in to comment.