所有执行I/O操作的系统调用,都使用一个非负整数(文件描述符)来描述打开的文件(文件、管道、socket、终端、设备)。
常见的3种文件描述符:
文件描述符 | 用途 | POSIX名称 | stdio流 |
---|---|---|---|
0 | 标准输入 | STDIN_FILENO | stdin |
1 | 标准输出 | STDOUT_FILENO | stdout |
2 | 标准错误 | STDERR_FILENO | stderr |
通用IO模型的4个系统调用
下面的四个系统调用为c语言的形式
- fd = open(pathname, flags, mode):pathname是打开的文件名,flags指定文件的打开方式,mode指定文件的访问权限,返回的fd为上文所说的文件描述符
- num = read(fd, buffer, count):从buffer中读取至多n个字节的数据
- num = write(fd, buffer, count):从buffer中写入至多n个字节的数据,返回num可能小于count(不清楚什么情况)
- status = close(fd)
在golang中,我们也可以通过标准库syscall包使用系统调用
- func Open(path string, mode int, perm uint32) (fd int, err error)
- func Read(fd int, p []byte) (n int, err error)
- func Write(fd int, p []byte) (n int, err error)
- func Close(fd int) (err error)
可见golang的系统调用没有count参数,默认Buffer的大小为count的长度。
下面是一段代码,用来进行文件的复制,没有检查全部的error
func Copy(oldFile, newFile string) {
inputFd, err := syscall.Open(oldFile, os.O_RDONLY, 0666)
if err != nil {
panic(err)
}
// 此处用到了部分flags指定文件的打开方式,mode指定文件的访问权限下一篇继续
outputFd, err := syscall.Open(newFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer syscall.Close(inputFd)
defer syscall.Close(outputFd)
buff := make([]byte, 10)
for {
size, _ := syscall.Read(inputFd, buff)
if size == 0 {
break
}
syscall.Write(outputFd, buff[:size])
}
}
系统调用的详细
open()
open函数会返回下面这些错误:
- syscall.EACCES:文件权限不允许以指定的flags参数打开文件。可能的原因有目录权限的限制、文件不存在并且无法创建
- syacall.EISDIR:调用者企图打开目录对该文件进行写操作
- syscall.EMFILE:进程打开的文件描述符数量达到了进程资源限制的上限
- syscall.ENFILE:文件打开的数量达到了系统允许的上限(EMFILE是进程上限)
- syscall.ENOENT:文件或路径不存在,且没指定O_CREAT标志(这个错误比较常见)
- syscall.EROFS:企图以写方式打开了只读文件
- syscall.ETXTBSY:指定文件为可执行文件且正在运行,系统不允许正在运行的程序
在系统调用中存在一个Create的系统调用,不过在golang中,这个func Creat(path string, mode uint32) (fd int, err error)
是使用open()这个系统调用实现的。
read()
read调用成功,将返回实际读取的字节数,如果文件结束(EOF)则返回0。
write()
num, err := syscall.Write(fd, buff)
,num为写入的数量,一般等于len(buff),当num<len(buff)
,这就被称为"部分写",可能是磁盘不足,write的调用成功不代表写入数据成功。
lseek():改变文件的偏移量
操作系统会保存打开文件的偏移量(也称读写偏移量或指针)。只适用于文件。
lssek在golang中为func Seek(fd int, offset int64, whence int) (off int64, err error)
fd: 文件描述符
offset: 文件偏移量
whence: 按照什么基点解释offset参数
whence
- SEEK_SET: io.SeekStart,从文件头部开始offset个字节
- SEEK_CUR: io.SeekCurrent,从当前文件偏移量起offset个字节
- SEEK_END: io.SeekEnd,文件尾起offset个字节
文件空洞
场景:当文件的偏移量超过了文件结尾
- read: 返回0,表示读到文件结尾
- write: 可以在文件结尾任意位置写入数据
从文件结尾到新写入的数据的开始位置的这一段空间被称为文件空洞。
文件空洞不占用任何磁盘空间,知道持续某个点,在文件空洞写入了数据系统才会被磁盘分配磁盘块。文件空洞的优势在于:与实际需要的空字节分配磁盘相比,稀疏填充的文件占用更小的磁盘空间。不支持文件空洞的系统会将空字节写入到文件。存在文件空洞意味名义上的大小要比占用的磁盘存储总量要大。
有疑问加站长微信联系(非本文作者)