第三章 文件I/O

文件描述符

所有打开的文件都通过文件描述符引用。操作(读写)该文件描述符就相当于操作该文件。
文件描述符是一个非负的整数。

同时文件描述符0,1,2都已经对应标准输出(输出在终端上),标准输入(从标准终端输入),标准错误。
对应STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO
这三个常量定义在<unistd.h>中。

open和openat

#include <fcntl.h>
int open(const char * path ,int flag, mode_t =NULL)
int openat(int fd,const char * path ,int flag, mode_t =NULL)

两者的主要区别是,open只能相对于程序运行的目录,而openat可以使相对于fd指定的目录进行打开操作,同时fd的特殊值AT_FDCWD指的是当前目录。
同时可以避免TOCTTOU,意思是如果一个操作需要两个文件操作才能完成,那么中间可能cpu调度的原因,第一个文件操作已经被更改,从而后一个操作也会不正确。可以用来获取特殊权限。

flag

这个参数主要指定了打开模式:
一下五个只能出现一个:

  1. O_RDONLY
    只读打开,0
  2. O_WDONLY
    只写打开,1
  3. O_RDWE
    读写打开,2
  4. O_EXEC
    只 执行打开。
  5. SEARCH
    只搜索打开,用于目录

一下这些常量是可选的使用|运算与上面的结合。

  1. O_APPEND
    每次写入到尾端
  2. O_CLOEXEC
    当调用exec()函数成功后,文件描述符会自动关闭
  3. O_CREAT
    如果打开的文档不存在,就自动创建,同时需要指定mode_t参数
  4. O_DICECTORY
    如果打开的不是目录jiubaocuo
  5. O_EXCL
    如果同时指定 O_CREATO_EXCL,当文件存在的时候就出错。
  6. O_NOCTTY
  7. O_NOFOLLOW
    如果打开的是一个连接符号,就出错
  8. O_NONBLOCK
    非阻塞打开,如果没读到数据就出错返回。
  9. O_SYNC
    写入数据是,需要等到数据真正写入到硬盘时才返回,包括文件属性的更新。
    ext4下,同步写和延时写差距不大?hfs下差距很大。和系统有关?
  10. O_DSYNC
    写入数据是,需要等到数据真正写入到硬盘时才返回,如果不影响读取刚写入的数据,则不需要等待文件属性更新。
  11. O_RSYNC
    读操作会等待所有对该文件的写操作完成以后才执行。
  12. O_DICECT
    看不懂

creat()

#include <fnctl.h>
int creat(const char* path,mode_t mode)
//出错返回-1

缺点是都是以只写操作打开该文档,所以如果要读,需要关闭该文件描述符重新打开。
一般使用open(path,O_RDWR|O_CREAT,mode)代替。

close()

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

关闭文档,释放所有记录锁。
进程关闭以后,会关闭所有该进程所打开的文件。

lseek()

#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);
//出错返回-1,否则返回新的稳健偏移量

whence

偏移的起点

  1. SEEK_SET
    将文件的偏移量设置为距文件开头offset个字节,也就是说,起点是开头
  2. SEEK_CUR
    偏移起点文当前起点,offset可正可负
  3. SEEK_END
    偏移起点为文件的总长度。也就是说起点在末尾。

如果将文件描述符对一个管道FIFO或者套接字使用,那么返回-1,并且,errno为ESPIPE
因为这三个都是流式的,只能读写,不能设置偏移量。

如果将偏移量定位在结尾之后开始写入数据,那么中间的空洞被读为0。

read()

#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
//成功返回读到的字节数,到文件尾返回0,出错返回-1

ssize_t是一个带符号的整数。而size_t是一个不带符号的整数。

读到的字节小于设置的字节数原因:

  1. 读到末尾
    本次返回读到的数据,下一次返回0
  2. 从终端设备读,每次只能一行
  3. 从网络中读是,网络的缓存机制可能造成
  4. 管道和FIFO中时,管道中的字节数少于所需数量
  5. 信号中断前已经读到数据

读文件从文件当前偏移量开始,成功返回之前,改变偏移量。

write()

#include <unistd.h>
ssize_t write(int fd,const buf*,size_t n);
//成功返回写入的字节数,错误返回-1

从偏移量位置开始写。
如果设置了O_APPEND则每次写操作之前将文件偏移量设置在文件的结尾处。

根据局部性原理,系统会预读比所指定数量多的数据,并假设它们很快会被用到。

文件共享

多进程间操作同一文件。

文件描述符标志和文件状态标志

  1. 文件描述符标志
    是体现进程的文件描述符的状态,fork进程时,文件描述符被复制;
    目前只有一种文件描述符:FD_CLOEXEC。指明是否复制进新的进程。
  2. 文件状态标志
    是体现进程打开文件的一些标志,fork时不会复制file 结构,而是两个进程文件描述符指向同一个file(当FD的exec标志为0时)

内和使用三种结构表示打开的文件

  1. 内核的记录项:文件描述符标志,文件表项,V节点表项
    每个进程在进程表中都有一个记录项,每个描述符占一项。
    每一项都有一个标志,该标志表示该文件描述符的特性,可以使用fnctl()获取和改变。
    一个指向文件表项的指针(指向内核中)
  2. 内核的文件表
    内核记录文件的状态标志,文件表项中记录着文件状态标志:阻塞,同步的方式。在该进程中打开的文件的偏移量(不同进程中可以不同)。还有一个指向V节点表项的指针。
  3. V节点结构
    V节点表项是所有进程共有一份,表示文件的信息:长度,修改时间等。
    应该是存在硬盘上。
    不同进程公用一份。

原子操作的IO

读写

#include <unistd.h>
ssize_t pread(int fd,void *buf,size_t n,off_t offset);
ssize_t pwrite(int fd,const void *buf,size_t n,off_t offset);
//返回值同以前

但是有用吗?offset的值如何确定?

复制文件描述符

#include <unistd.h>
int dup(int fd);
int dup2(int fd,init fd2);
//出错返回-1

第二个函数,在fd2出复制fd1,如果fd2处已经有一个文件打开了,那么先关闭,在打开。是一个院子操作。
如果相同,那么步步操作,直接返回。

复制以后,两个文件描述符共享一个文件表项。

缓冲

#incldue <unistd.h>
int sync(void);
int fsync(int fd);
int fdataasync(int fd)
//错误返回-1
  1. sync
    将所有修改过的缓存,排入写队列。也就是实际写入硬盘,但是不等待写入结束。
    对所有文件
    系统周期性执行该函数。
  2. fsync
    对指定fd,将修改过的缓存写入硬盘,并且修改文件属性,结束后返回
  3. fdatasync
    对指定fd,将修改过的缓存写入硬盘,结束后返回,可以不用等待修改文件属性结束。

fcntl()

#include <fcntl.h>
int fcntl(int fd, int cmd,...)

第三个参数是一个整数(在get时,不需要该参数,设为0),或是一个指针
功能如下:

  1. 复制文件描述符cmd = F_DUPFD 或 F_DUPFD_CLOEXEC
    cmd = F_DUPFD返回未用最小的整数作为新的描述符返回,但是该文件描述符有自己独立的文件描述符标志,同时清除了O_CLOEXEC标志。而dup()打开的是共享的。
    cmd = F_DUPFD_CLOEXECdup2`,需要第三个参数
  2. 设置获取文件描述符状态cmd = F_GETFD 和 F_SETFD
    目前可设置和获取的只有一种,是否在新的线程中复制一份。
    set时需要第三个参数。0不关闭,1关闭。
  3. 设置获取文件状态标志cmd = F_GETFL 和 F_SETFL
    指的是,读写,阻塞,写模式(append),同步读写.
    也就是打开open的参数。
    在获取的时候,由于读写的五中模式互斥,所以需要使用一个屏蔽字O_ACCMODE与运算,取得访问方式位,然后挨个比较
int val = fcntl(fd,F_GETFL,0);
switch (val & O_ACCMODE)
  case O_RDONLY:
  case .....

而其余可选的状态标志,不需要。直接进行与运算

if (val & O_APPEN)
{
}

但是设置的时候只能是堵塞模式,缓存模式。设置的时候需要先获取,然后进行|=然后再设置。不然会清除之前的设置。

  1. 设置获取异步IO cmd = F_GETOWN 和 F_SETOWN
  2. 获取设置记录所 cmd = F_GETLK F_SETLK F_SETLKW

/dev/fd/n

打开文件,等于复制文教描述符。

int fd=open("/dev/fd/1",O_RDWR);

虽然设置了读写,但是只能使用之前打开的模式,比如之前是写模式,那么使用这种方式打开也不能读。

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

推荐阅读更多精彩内容