APUE 1 文件I/O

本章讨论不带缓冲的I/O, 代码实现采用golang的syscall包,其通常会包装一层系统调用不过大体相似


1.文件描述符(FD)

  • 文件描述符是一个非负的整数,内核通过该描述符对文件进行引用,进行与文件相关的操作

  • open、openat、creat函数会返回文件描述符

  • 已打开文件在内核的数据结构如图


    APUE 1 文件I/O_第1张图片
    已打开文件
    • 进程表项:1.文件描述符标志 2.指向文件表项的指针
    • 文件表项:1.文件状态标志(读、写、同步等) 2.当前文件offset 3.指向v节点指针
    • v节点表项:1文件类型和对此文件进行操作的函数指针 2.i节点指针
    • i节点:文件长度,所有者、磁盘实际位置

2.打开文件open和openat

func Open(path string, mode int, perm uint32) (fd int, err error)
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)

这两个函数在使用上的区别体现于dirfd参数和path参数,存在三种可能性:

  • 当path为绝对路径时,dirfd被忽略,二者相同
  • 当path为相对路径时,dirfd指出了相对路径的开始地址(所在目录的文件描述符)
  • 当path为相对路径时,dirfd=_AT_FDCWD,则dirfd指向当前程序的工作目录
模式 含义
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
O_EXEC 只执行打开
O_APPEND 每次写操作前都会将当期偏移量移动至文件尾部(原子操作),自行seek也没用
O_CLOEXEC FD_CLOEXEC写入文件描述符标志
O_CREAT 若文件不存在则创建,需要第三个参数perm值以确认文件权限
O_DIRECTORY 如果path不是目录的返回失败
O_EXCL 测试文件是否存在,与O_CREAT组合使用实现创建原子操作
O_LARGEFILE 支持大文件写入
O_NOFOLLOW 如果path是符号链接则出错
O_NONBLOCK 当path为FIFO、块特殊文件、字符特殊文件I/O操作为非阻塞
O_TRUNC 若文件存在,且可写,则长度截断为0
O_SYNC 每次write时等待物理I/O完成,包括本次write文件属性的更新更新
O_DSYNC 同SYNC但不需等待文件属性更新
O_RSYNC 使每个read此fd的操作的等待,直到所有对此文件挂起的write操作完成

支持打开模式:

模式 含义
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
O_EXEC 只执行打开
O_APPEND 每次写操作前都会将当期偏移量移动至文件尾部(原子操作),自行seek也没用
O_CLOEXEC FD_CLOEXEC写入文件描述符标志
O_CREAT 若文件不存在则创建,需要第三个参数perm值以确认文件权限
O_DIRECTORY 如果path不是目录的返回失败
O_EXCL 测试文件是否存在,与O_CREAT组合使用实现创建原子操作
O_LARGEFILE 支持大文件写入
O_NOFOLLOW 如果path是符号链接则出错
O_NONBLOCK 当path为FIFO、块特殊文件、字符特殊文件I/O操作为非阻塞
O_TRUNC 若文件存在,且可写,则长度截断为0
O_SYNC 每次write时等待物理I/O完成,包括本次write文件属性的更新更新
O_DSYNC 同SYNC但不需等待文件属性更新
O_RSYNC 使每个read此fd的操作的等待,直到所有对此文件挂起的write操作完成

3.创建文件creat

func Creat(path string, mode uint32) (fd int, err error)

等效调用
Open(path, O_CREAT|O_WRONLY|O_TRUNC, perm)
实践中可以使用
Open(path,O_RDWR|O_TRUNC|O_CREAT, perm)

4.关闭文件close

func Close(fd int) (err error)

进程终止时,内核自动关闭它打开的所有文件

5.调整偏移量lseek

func Seek(fd int, offset int64, whence int) (off int64, err error)

whence与offset关系有三种值:

  • whence=0 从文件开始处计算offset
  • whence=1 从文件当前位置处计算offset(可加可减)
  • whence=2 从文件结尾处计算offset

注意文件的偏移量可以调整至大于当前偏移量,当write后将在当前文件尾端和新开始写的位置间产生空洞,不会分配磁盘块

package main

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

func main() {
    b := []byte{'a','b','c'}
    fd, err := syscall.Open("/gocode/syscall/od.a", syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0777)
    if err != nil {
        os.Exit(1)
    }
    offset, err := syscall.Seek(fd, 10000, 0)
    if err != nil {
        os.Exit(1)
    }
    fmt.Println("offset: ", offset)
    n, err := syscall.Write(fd, b)
    if err != nil {
        os.Exit(1)
    }
    fmt.Println("n: ", n)

}

od.a文件只分配了1个磁盘块


ls

6.读文件read

func Read(fd int, p []byte) (n int, err error)

返回成功读取的字符数量n, p为缓冲buffer

7.写文件write

func Write(fd int, p []byte) (n int, err error)

8.复制文件描述符 dup 、dup2

func Dup(oldfd int) (fd int, err error)
func Dup2(oldfd int, newfd int) (err error)
func Dup3(oldfd int, newfd int, flags int) (err error)

函数为原子操作
Dup复制文件描述符,返回一个当前可用最小的描述符
Dup2复制描述符至指定的描述符,若指定的描述符存在则先close再创建(会擦出掉文件描述符标志)
Dup3同Dup2但可以设置文件描述符标志O_CLOEXEC

APUE 1 文件I/O_第2张图片
dup

8.fcntl函数

golang暂不支持全部的fcntl仅支持文件锁相关函数,后续文章再详细讨论

9.sync、fsync、fdatasync

func Sync()
func Fsync(fd int) (err error)
func Fdatasync(fd int) (err error)
  • sync将修改过的块缓冲区排入写队列后立即返回
  • 通常操作系统会周期性(一般30s)的执行sync操作保证延迟写入的数据至磁盘
  • fsync确保指定的fd文件数据和属性立即写入磁盘,全部写入成功后返回
  • fdatasync确保指定fd文件的数据立即写入磁盘,全部写入成功后返回

10./dev/fd目录

该目录下保存一些文件描述符
可以通过open函数打开其中的描述符实现复制或创建新描述符,通常会忽略open中的模式设置,而是继承该描述符自由的模式

在linux中要格外注意,其/dev/fd的实现是把描述符映射成指向底层文件的符号链接,所以可以创建新模式,creat会对底层文件截断

你可能感兴趣的:(APUE 1 文件I/O)