F欄 フリーター dossyのプログラミング初心者日記  〜ええんやで〜

自分が思ったこと、試したこと、outputしたことを書き殴ってやる。夢は、賢い人になること!!!

golangでos packageがどうなっているか気になったので、できる範囲で調べてみた

最近は、pythonを触ってます。 golangでもそうなのですが、os.Open("python.py")みたいな記述ってありますよね。

いわゆる、自分のpcのfileを取得しようとしているのですが、正直全部packageがやってくれてるので使う身としては気にしなくていいところなんですよね。

でも、気になってしまいました。正直この辺を知っておけば、夢であるサードパーティ作成に近くのではないかと思ってます。

なので、わかる範囲までですが、os packageを調べました。

1 import osする os.Open() ではopen funcを使用してます。

2 open funcをみる

func Open(name string) (*File, error) {
    return OpenFile(name, O_RDONLY, 0)
}

引数として、strを取得している => "python.py"のfile名かな そして、return でOpenFile funcを呼び出している

3 OpenFile funcをみる

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    testlog.Open(name)
    f, err := openFileNolog(name, flag, perm)
    if err != nil {
        return nil, err
    }
    f.appendMode = flag&O_APPEND != 0

    return f, nil
}

このあたりからこんがらがってくる。 引数で取得するのは、3つ nameとflag とperm nameはfile名。flagは?permは?

flagはO_RDONLYを渡している。これは、定数として定義されてるらしい。

const (
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
)

では、その右辺はなんなのか?syscall.O_RDONLY? syscall packageを使用して、O_RDONLYを呼び出しているみたいです。 では、syscall packageの中身をみると?

const{
O_RDONLY = 0x0
O_RDWR     = 0x2
O_CREAT    = 0x200
)

中には0x0という値が入ってました。これ、binary(二進数)だと思います。

permの値は0を渡してます。type FileMode uint32 のstructが存在していて、その型で0を表現していると思われます。

4 openFileNolog(name, flag, perm) を呼び出す

func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
    setSticky := false
    if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
        if _, err := Stat(name); IsNotExist(err) {
            setSticky = true
        }
    }

    var r int
    for {
        var e error
        r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
        if e == nil {
            break
        }

        // On OS X, sigaction(2) doesn't guarantee that SA_RESTART will cause
        // open(2) to be restarted for regular files. This is easy to reproduce on
        // fuse file systems (see https://golang.org/issue/11180).
        if runtime.GOOS == "darwin" && e == syscall.EINTR {
            continue
        }

        return nil, &PathError{"open", name, e}
    }

    // open(2) itself won't handle the sticky bit on *BSD and Solaris
    if setSticky {
        setStickyBit(name)
    }

    // There's a race here with fork/exec, which we are
    // content to live with. See ../syscall/exec_unix.go.
    if !supportsCloseOnExec {
        syscall.CloseOnExec(r)
    }

    return newFile(uintptr(r), name, kindOpenFile), nil
}

ここの部分。

if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {

supportとflag&O_とperm&Modeが0以外の時にtrueに行くようにされてます。

const supportsCreateWithStickyBit = false  #定数

ここで、&が出てきます。&はbit演算子なるもので、2進数を比較するものです。右辺左辺を比較し、どちらの位も1なら1をreturn その他はreturn 0です。 例) 0101 & 1100 なら =>答えは0100かな。 perm =0 O_CREATE = 0x200 この時、permが0なのでどうやってもreturn 0です。

5 syscall.Openを呼び出す。 func OpenFileNologではOpenが呼ばれています。 |もbit演算子だろうと思います。 or演算子かな。

r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
func Open(path string, mode int, perm uint32) (fd int, err error) {
    var _p0 *byte
    _p0, err = BytePtrFromString(path)
    if err != nil {
        return
    }
    r0, _, e1 := syscall(funcPC(libc_open_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm))
    fd = int(r0)
    if e1 != 0 {
        err = errnoErr(e1)
    }
    return
}

6 BytePtrFromStringを呼び出す 呼び出すとそのさきでSliceFromStringが呼ばれてます。そこをみてみるとようやく読めそうなcodeが書いてあります。 stringたちを1つ1つ分解しrune型でsliceに入れていると思います。 stringがbinaryに分解されていることがわかりました。

_p0, err = BytePtrFromString(path)
func BytePtrFromString(s string) (*byte, error) {
    a, err := ByteSliceFromString(s)
    if err != nil {
        return nil, err
    }
    return &a[0], nil
}
func ByteSliceFromString(s string) ([]byte, error) {
    for i := 0; i < len(s); i++ {
        if s[i] == 0 {
            return nil, EINVAL
        }
    }
    a := make([]byte, len(s)+1)
    copy(a, s) #copyでsの中身をaにコピー
    return a, nil
}

7 newFile funcを呼び出す openfilelog funcでは最後のreturnで newfileが呼ばれています。 現時点では、rにはbinaryのsliceが入っていそうです。 rのtypeはuintptr型。以下だそう。

`// uintptr is an integer type that is large enough to hold the bit pattern of any pointer.

type uintptr uintptr`

その情報が入ったfileのdateをfと変数にpoll.FDというstructで入れ込みます。正直この辺は本当に何をしているかわかりませんでした。 ここでreturn されるfが"python.py"を表すdataだと思います。思うだけです。自身ありません。

func newFile(fd uintptr, name string, kind newFileKind) *File {
    fdi := int(fd)
    if fdi < 0 {
        return nil
    }
    f := &File{&file{
        pfd: poll.FD{
            Sysfd:         fdi,
            IsStream:      true,
            ZeroReadIsEOF: true,
        },
        name:        name,
        stdoutOrErr: fdi == 1 || fdi == 2,
    }}

    pollable := kind == kindOpenFile || kind == kindPipe || kind == kindNonBlock

    // If the caller passed a non-blocking filedes (kindNonBlock),
    // we assume they know what they are doing so we allow it to be
    // used with kqueue.
    if kind == kindOpenFile {
        switch runtime.GOOS {
        case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd":
            var st syscall.Stat_t
            err := syscall.Fstat(fdi, &st)
            typ := st.Mode & syscall.S_IFMT
            // Don't try to use kqueue with regular files on *BSDs.
            // On FreeBSD a regular file is always
            // reported as ready for writing.
            // On Dragonfly, NetBSD and OpenBSD the fd is signaled
            // only once as ready (both read and write).
            // Issue 19093.
            // Also don't add directories to the netpoller.
            if err == nil && (typ == syscall.S_IFREG || typ == syscall.S_IFDIR) {
                pollable = false
            }

            // In addition to the behavior described above for regular files,
            // on Darwin, kqueue does not work properly with fifos:
            // closing the last writer does not cause a kqueue event
            // for any readers. See issue #24164.
            if runtime.GOOS == "darwin" && typ == syscall.S_IFIFO {
                pollable = false
            }
        }
    }

    if err := f.pfd.Init("file", pollable); err != nil {
        // An error here indicates a failure to register
        // with the netpoll system. That can happen for
        // a file descriptor that is not supported by
        // epoll/kqueue; for example, disk files on
        // GNU/Linux systems. We assume that any real error
        // will show up in later I/O.
    } else if pollable {
        // We successfully registered with netpoll, so put
        // the file into nonblocking mode.
        if err := syscall.SetNonblock(fdi, true); err == nil {
            f.nonblock = true
        }
    }

    runtime.SetFinalizer(f.file, (*file).close)
    return f
}

8 最後

6-7あたりは本当に何をしているかわかりませんでしたが、 どうやら、stringをrune型のsliceに分解し、その情報と0x200のようなpcの設定定数情報を駆使して開いているのではないかと思います。 pcを操るのはやはりbinaryなんだと調べていて感じました。

実際に、pcの中をどうやって調べているのか、まではわかりませんでしたが収穫はあったのでまぁいいとしましょう。 こういう、こと知ってる人って周りにいないから如何しようも無い。。。 いろんな知らない型が出てきて世界は広いと感じました。

あと、このレベルのcode書けるのに何年書かんねやろ。

以上、自分の覚書程度にまとめました。