第三章 文件I/O

I/O函数就是打开文件,读文件,写文件,在绝大数unix系统中只需用到5个函数open、read、write、lseek、close。

不同缓存长度对read和write有影响。unbuffered I/O(不带缓冲的I/O)与后面标准I/O函数

所谓不带缓存就是说read和write都调用内核的一个系统调用。

不带缓冲的I/O不是ISO C的组成部分,是POSIX.1核Single UNIX Specification的组成部分

文件描述符-fd

对于内核而言,打开的文件都是通过fd引用

非负整数

打开或者创建文件时,内核向进程返回一个fd

POSIX.1 幻数0、1、2被标准化,STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,这些常量通常写在<unistd.h>

fd的范围 0~OPEN_MAX-1,现在基本没啥限制

open和openat

int open(char *path,int oflag,...);//返回fd
/*
oflag:(在<fcntl.h>)
O_RDONLY 0
O_WRONLY 1
O_RDWR   2
O_EXEC
O_SEARCH(应用与目录,其实就是打开目录时验证搜索权限,就是后续对返回的fd进行操作就不需要再次检查对该目录的搜索权限,不过书中涉及的操作系统都不支持这个参数,不用记了)
----
可选,也就是第三个参数及以上
O_APPEND
O_CLOEXEC
O_CEEAT
O_DIRECTOR
O_EXCL 如果同时指定O_CREAT,如文件已存在则出错,其实用途就是测试一个文件是否存在,而且原子操作
O_NOCTTY path是终端设备的情况
O_NONBLOCK 后面说吧
O_SYNC 每次write都要等待物理上I/O操作完成,就是文件+属性都更新后
O_TRUNC 文件存在而且写或读写打开,那么将其长度截断为0
O_TTY_INIT 又是终端,有TTY
O_DSYNC 和sync区别就是,文件更新后,不用等到属性也更新后才去write
O_RSYNC 直到所有进程对文件同一部分挂起的写操作完成,在这之前所有进程对这个fd进行read操作等待,就是等
*/

int openat(int fd,const *path,int oflag,...);//返回fd

open与openat三种可能性,区别就是fd引起的

  • path为绝对路径,那么fd就被忽略,两函数相当了
  • path是相对路径,fd是相对路径在文件系统中的开始地址(fd需要相对路径获得,感觉在openat之前用open获取到了这个fd)
  • path相对路径,fd是AT_FDCWD,两者又相当了

openat是POSIX.1加的,不属于ISO C,主要为了线程可以用相对路径打开,因为一个进程下的多个不同线程很难在统一时间工作在不同目录中

还可以避免TOCTTOU,time-of-check-to-time-of-use,其实就是2依赖1,1执行后2执行,但是要上中间1的涉及的文件变了,那么2就很难讲了,其实就是原子性

小ps:文件名过长,是默默截断不报错还是报错,是个历史问题,不同系统处理方式不同,但是POSIX.1有个常量POSIX_NO_TRUNC(trunc出现了,上面oflag可选参数也有这个单词),但是现在大多数系统文件名255最大长度,通常没谁蛋疼超过这个,所有很少会有这个问题
2018-4-3 23:11

文件I/O

I/O函数就是打开文件,读文件,写文件,在绝大数unix系统中只需用到5个函数open、read、write、lseek、close。

不同缓存长度对read和write有影响。unbuffered I/O(不带缓冲的I/O)与后面标准I/O函数

所谓不带缓存就是说read和write都调用内核的一个系统调用。

不带缓冲的I/O不是ISO C的组成部分,是POSIX.1核Single UNIX Specification的组成部分

文件描述符-fd

对于内核而言,打开的文件都是通过fd引用

非负整数

打开或者创建文件时,内核向进程返回一个fd

POSIX.1 幻数0、1、2被标准化,STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,这些常量通常写在<unistd.h>

fd的范围 0~OPEN_MAX-1,现在基本没啥限制

open和openat

#include <fcntl.h>
int open(char *path,int oflag,...);//返回fd
/*
oflag:(在<fcntl.h>)
O_RDONLY 0
O_WRONLY 1
O_RDWR   2
O_EXEC
O_SEARCH(应用与目录,其实就是打开目录时验证搜索权限,就是后续对返回的fd进行操作就不需要再次检查对该目录的搜索权限,不过书中涉及的操作系统都不支持这个参数,不用记了)
----
上面5个只能有一个,下面的可选通过或,比如 O_RDWR|O_APPEND
O_APPEND
O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述符标志
O_CREAT 没有就创建
O_DIRECTOR
O_EXCL 如果同时指定O_CREAT,如文件已存在则出错,其实用途就是测试一个文件是否存在,而且原子操作(也就是测试+创建)
O_NOCTTY path是终端设备的情况
O_NONBLOCK 后面说吧--非阻塞模式
O_SYNC 每次write都要等待物理上I/O操作完成,就是文件+属性都更新后--等待同步写完成
O_TRUNC 有点复杂
O_TTY_INIT 又是终端,有TTY
O_DSYNC 和sync区别就是,文件更新后,不用等到属性也更新后才去write--等待同步写完成
O_RSYNC 直到所有进程对文件同一部分挂起的写操作完成,在这之前所有进程对这个fd进行read操作等待,就是等--同步读和写
*/

int openat(int fd,const *path,int oflag,...);//返回fd

open与openat三种可能性,区别就是fd引起的

  • path为绝对路径,那么fd就被忽略,两函数相当了
  • path是相对路径,fd是相对路径在文件系统中的开始地址(fd需要相对路径获得,感觉在openat之前用open获取到了这个fd)
  • path相对路径,fd是AT_FDCWD,两者又相当了

openat是POSIX.1加的,不属于ISO C,主要为了线程可以用相对路径打开,因为一个进程下的多个不同线程很难在统一时间工作在不同目录中

还可以避免TOCTTOU,time-of-check-to-time-of-use,其实就是2依赖1,1执行后2执行,但是要上中间1的涉及的文件变了,那么2就很难讲了,其实就是原子性

小ps:文件名过长,是默默截断不报错还是报错,是个历史问题,不同系统处理方式不同,但是POSIX.1有个常量POSIX_NO_TRUNC(trunc出现了,上面oflag可选参数也有这个单词),但是现在大多数系统文件名255最大长度,通常没谁蛋疼超过这个,所有很少会有这个问题

2018-4-3 23:11

creat

#include <fcntl.h>
int creat(const char *path,mode_t mode);
//等效与
int open(path,O_WRONLY|O_CREAT|O_TRUNC,mode);

早期,open只能打开存在的文件

现在open新版支持了

creat不足之处在于只能以只写方式打开和创建文件

如果文件已经存在那么会被截断0

close

#include <unistd.h>
int close(int fd);// 0成功,-1出错

一个进程终止时,内核自动关闭它所有打开的文件,所有可以利用这一点,不显式

lseek

#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);
/*
whence:
SEEK_SET 从头开始+offset
SEEK_CUR 从当前开始+offset,为正或负
SEEK_END 为文件末尾+offset,为正或负
*/
od -c file.txt // -c是以字符打印文件内容,打印空洞,空洞会显示\0

当打开一个文件的时候,除非指定O_APPEND,否则该偏移量被设置为0

空洞不会占用磁盘块

偏移量,如果off_t 是32位整型,那么文件最大长度就是2^31-1

read

#include <unistd.h>
ssize_t read(int fd,void *buf,size_t nbytes);// 0表示已读到文件尾
/*
经典定义
int read(int fd,char *buf,unsigned nbytes);
为了和ISO C一致才改的
void *表示通用指针
*/

ssize_t有符号,size_t无符号,和 SSIZE_MAX常量有关

write

#include <unistd.h>
ssize_t write(int fd,const void *buf,size_t nbytes);

出错,要么磁盘写满了,要么超过了一个给定进程的文件长度的限制

注意,如果打开时指定了O_APPEND,那么每次写操作之前,会追加写

I/O的效率

通常unix的shell提供一种方法,在标准输入打开一个文件读,在标准输出上创建或重写一个文件,这使得程序不必打开输入和输出文件,而且可以使用I/O的重定向功能

unix系统在进程终止时会自动关闭所有打开的fd

unix系统内核,对应文本文件和二进制文件没区别

关于读write的参数buf多少,需要测试

早期计算机主存是用铁氧体磁芯(ferrite cord),也是'core dump'词的由来

文件共享

unix支持不同进程间共享打开文件

内核的所有I/O数据结构有3种

  1. 每个进程都有一个进程表项,包含fd表,里面有fdfd标志指向一个文件表的指针
  2. 文件表,文件状态标志(读,写,追加,同步,非阻塞等)、当前文件偏移量指向该文件v节点的指针
  3. v节点结构,文件类型对此文件进行操作函数的指针i节点-索引节点(大多数文件)(当前文件长度)

ps i节点包含了文件所有者文件长度指向文件实际数据块在磁盘上的位置指针等,linux没有使用v节点,而是使用了i节点结构

文件表各自拥有

场景:两个独立进程各自打开同一个文件,那么分成各自文件表分开,但是都指向同一个v节点表

流程:

write后,文件表中偏移量写入,如果超过当前文件长度,那么会将i节点中当前文件长度设为当前偏移量

lseek定位到文件尾,和用O_APPEND打开文件是不同的,前者写不会追加写

fd标志和文件状态标志的作用范围区别,前者只用于一个进程的一个fd,后者应用于所有指向该文件表的任何进程中的fd
2018-4-8 23:55

原子操作

早期unix不支持open的O_APPEND

多个进程同时追加写同一个文件(都有各自的文件表,但是共享同一个v节点),会有问题

O_APPEND提供了原子性,在内核每次写之前,都将偏移量设置到尾端

原子函数pread和pwrite

#include <unistd.h>
ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset);
sseize_t pwrite(int fd,const void *buf,size_t nbytes,off_t offset);

相当于先lseek再read/write

创建一个文件

open(char,O_RDWR|O_CREAT|O_EXCL),原子操作:测试+新建文件

dup和dup2

#include <unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
---------------------------
int fd1, fd2, fd3, fd4;
fd1 = open("f1", O_RDWR);
fd2 = open("f2", O_RDWR);

两函数都可以复制一个现有的fd,进程表两条记录的fd都指向同一个文件表(注意和上面两个独立进程打开同一个文件的区别,同一个进程的文件表会共用)

open同一个文件两次返回不同fd,那么也会有不同文件表(每次调用open函数都会分配一个新的文件表)

sync,fsync和fdatasync

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
以上两个成功返回0
void sync(void);

sync只是将写入任务排入队列就返回,不会阻塞

系统守护进程update一般30s调用sync

fsync等写到磁盘(data+atrribute)才会结束

fdatasync等写到磁盘(data)才会结束

fcntl

改变已经打开文件的属性

#include <fcntl.h>
int fcntl(int fd,int cmd,.../* arg*/);//成功则依赖于cmd
/*
cmd 5中功能
F_DUPFD或F_DUPFD_CLOEXEC  都复制一个已有fd,后者还设置新fd的标志值
dup(fd) 相当于 fcntl(fd,F_DUPFD,0)
dup(fd,fd2) 相当于 close(fd2) fcntl(fd,F_DUPFD,fd2)
涉及的FD_CLOEXEC在第八章说
F_GETFD或F_SETFD          fd标志(就是这个常量FD_CLOEXEC)
很多现有程序不使用FD_CLOEXEC,直接用默认的0,或者1
F_GETFL或F_SETFL          文件状态标志(open函数里的)
F_SETFL:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC,O_ASYNC
F_GETOWN或F_SETOWN        异步I/O所有权,获取/设置信号正的进程id或者负的进程组ID(绝对值)
第14章说
F_GETLK,F_SSETLK或F_SETKW 记录锁
*/

标准输出上fcntl设置O_SYNC将不起作用,不报错,因为这是shell打开的

ioctl

#include <unistd.h>//system v
#include <sys/ioctl.h>//bsd and linux
int ioctl(int fd, int request,...);

/dev/fd

打开文件/dev/fd/n等效与复制描述符n

fd = open("/dev/fd/0",mode) 等效于 fd=dup(0)

如果先前fd 0被打开为只读,那么我们也只能对fd进行读操作,即使fd = open("/dev/fd/0",O_RDWR)

小结

因为read和write都在内核执行,所有称为不带缓冲的I/O函数

2018-4-9 23:55

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容