對于寫后端語言來說的人,文件操作是很常見的。go對文件操作的支持非常的好。今天通過go中文件操作記錄下syscall相關(guān)內(nèi)容。
先看下文件定義:
type File struct {
*file
}
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
fd int
name string
dirinfo *dirInfo // nil unless directory being read
}
// Auxiliary information if the File describes a directory
type dirInfo struct {
buf []byte // buffer for directory I/O
nbuf int // length of buf; return value from Getdirentries
bufp int // location of next record in buf.
}
是不是夠簡潔的,而且注釋寫的很清楚。嘿嘿
我們從文件創(chuàng)建開始
// Create creates the named file with mode 0666 (before umask), truncating
// it if it already exists. If successful, methods on the returned
// File can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
第一個參數(shù)是文件名,第二個參數(shù)是文件模式,第三個參數(shù)是文件權(quán)限,默認(rèn)權(quán)限是0666
O_RDWR O_CREATE O_TRUNC是file.go文件中定義好的一些常量,標(biāo)識文件以什么模式打開,常見的模式有讀寫,只寫,只讀,權(quán)限依次降低。
// Flags to OpenFile wrapping those of the underlying system. Not all
// flags may be implemented on a given system.
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.
O_APPEND int = syscall.O_APPEND // append data to the file when writing.
O_CREATE int = syscall.O_CREAT // create a new file if none exists.
O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist
O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
O_TRUNC int = syscall.O_TRUNC // if possible, truncate file when opened.
)
syscall 里面也是一些常量
O_RDWR = 0x2
O_RSYNC = 0x101000
O_SYNC = 0x101000
O_TRUNC = 0x200
O_WRONLY = 0x1
OpenFile 函數(shù)參數(shù)介紹完,進(jìn)到函數(shù)中,看到關(guān)鍵一句
r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
看到syscall對于文件的操作進(jìn)行了封裝,繼續(xù)進(jìn)入
func Open(path string, mode int, perm uint32) (fd int, err error) {
return openat(_AT_FDCWD, path, mode|O_LARGEFILE, perm)
}
//sys openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
return openat(dirfd, path, flags|O_LARGEFILE, mode)
}
func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
use(unsafe.Pointer(_p0))
fd = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
跟進(jìn)Syscall6,看到
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
這里,看到相似的函數(shù)有四個,后面沒數(shù)字的,就是4個參數(shù),后面為6的,就是6個參數(shù)。調(diào)用的是操作系統(tǒng)封裝好的API??梢?man syscall 或者這里http://man7.org/linux/man-pages/man2/syscall.2.html#top_of_page看下詳細(xì)信息。
NAME
syscall - indirect system call
SYNOPSIS
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h> /* For SYS_xxx definitions */
long syscall(long number, ...);
對于不同架構(gòu)的參數(shù):
arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 Notes
──────────────────────────────────────────────────────────────────
arm/OABI a1 a2 a3 a4 v1 v2 v3
arm/EABI r0 r1 r2 r3 r4 r5 r6
arm64 x0 x1 x2 x3 x4 x5 -
blackfin R0 R1 R2 R3 R4 R5 -
i386 ebx ecx edx esi edi ebp -
ia64 out0 out1 out2 out3 out4 out5 -
mips/o32 a0 a1 a2 a3 - - - See below
mips/n32,64 a0 a1 a2 a3 a4 a5 -
parisc r26 r25 r24 r23 r22 r21 -
s390 r2 r3 r4 r5 r6 r7 -
s390x r2 r3 r4 r5 r6 r7 -
sparc/32 o0 o1 o2 o3 o4 o5 -
sparc/64 o0 o1 o2 o3 o4 o5 -
x86_64 rdi rsi rdx r10 r8 r9 -
x32 rdi rsi rdx r10 r8 r9 -
好了,到這里大致上有了解了。我們在看下細(xì)節(jié)的東西。
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
系統(tǒng)調(diào)用:
第一個參數(shù)是系統(tǒng)調(diào)用號,每個系統(tǒng)調(diào)用在操作系統(tǒng)里面都有一個唯一的操作碼,后面的參數(shù)是系統(tǒng)調(diào)用所需要的參數(shù)。返回的參數(shù)是系統(tǒng)調(diào)用的結(jié)果和錯誤(如果有的話)
r0, _, e1 := Syscall(SYS_OPENAT, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
好了,回到我們系統(tǒng)調(diào)用的地方,操作碼為SYS_OPENAT 到定義常量的地方,我們看到,這里定義的都是系統(tǒng)調(diào)用的操作碼,細(xì)心的你可能已經(jīng)看到了,還有一個SYS_OPEN,那他們的差別是什么呢?
從Linux2.6.16開始,linux內(nèi)核提供了一系列新的系統(tǒng)調(diào)用,為了和以前的系統(tǒng)調(diào)用兼容和區(qū)分,這些新的系統(tǒng)調(diào)用就以at結(jié)尾,它們在執(zhí)行與傳統(tǒng)系統(tǒng)調(diào)用相似任務(wù)的同時,還提供了一些附加功能,對某些程序非常有用,這些系統(tǒng)調(diào)用使用目錄文件描述符來解釋相對路徑。
系統(tǒng)調(diào)用參數(shù)講完了,說下RawSyscall 和 Syscall的區(qū)別吧。Syscall在開始和結(jié)束的時候,會分別調(diào)用runtime中的進(jìn)入系統(tǒng)和退出系統(tǒng)的函數(shù),所以Syscall是受調(diào)度器控制的,因為調(diào)度器有開始和結(jié)束的的事件。而RawSyscall則不受調(diào)度器控制,
RawSyscall 可能會導(dǎo)致其他正在運(yùn)行的線程(協(xié)程)阻塞,調(diào)度器可能會在一段時間后運(yùn)行它們,但是也有可能不會。所以,我們在進(jìn)行系統(tǒng)調(diào)用的時候,應(yīng)該極力避免使用RawSyscall,除非你確定這個操作是非阻塞的。
看完了系統(tǒng)調(diào)用,但是系統(tǒng)調(diào)用到底有什么用呢??問的好,其實(shí),系統(tǒng)調(diào)用的函數(shù)是操作系統(tǒng)提供的,也就是如果我們想用系統(tǒng)的功能,你就必須使用系統(tǒng)調(diào)用。比如上面講的文件操作(創(chuàng)建,讀取,更新,刪除),網(wǎng)絡(luò)操作(監(jiān)聽端口,接受請求和數(shù)據(jù),發(fā)送數(shù)據(jù)),最近很火的docker,實(shí)現(xiàn)方式也是系統(tǒng)調(diào)用(NameSpace + CGroup),簡單的在命令行執(zhí)行 echo hello,也用到了系統(tǒng)調(diào)用。不信?來看看
jin@Desktop:~$ strace -c echo hello
hello
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00 0.000000 0 1 read
0.00 0.000000 0 1 write
0.00 0.000000 0 3 open
0.00 0.000000 0 5 close
0.00 0.000000 0 4 fstat
0.00 0.000000 0 8 mmap
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 3 brk
0.00 0.000000 0 3 3 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 35 3 total
jin@Desktop:~$
看到?jīng)]有,滿滿的都是系統(tǒng)調(diào)用。go 提供了 linux下面提供了303個系統(tǒng)調(diào)用。不管你實(shí)現(xiàn)多么牛逼的功能,都離不開這些系統(tǒng)調(diào)用,再牛逼的系統(tǒng),也離不開cup的指令集,致“國家高新企業(yè)”中興。
參考資料:
linux syscall 中文資料:https://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html
linux syscall 英文資料:https://syscalls.kernelgrok.com/