linux系统编程-day06-文件IO(1)

  • Linux遵循一切皆文件的理念,任何你能读写的东西都可以用文件描述符来访问。

内核为每个进程维护一个打开文件的列表,该表被称为文件表(file table)。该表由一些叫做文件描述符(file descriptors)(常缩写为fds)的非负整数进行索引。
用户空间和内核空间都把文件描述符作为每个进程的唯一cookies。

  • 每个进程按照惯例会至少有三个打开的文件描述符:0:标准输入(stdin),1:标准输出(stdout),2:标准错误(stderr)。C标准库提供了预处理器宏:STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO宏,以取代对以上整数的直接引用。
  • 文件描述符不仅仅用于普通文件的访问,也用于访问设备文件、管道、目录以及快速用户空间锁、FIFOs和套接字。

打开文件

最基本的访问文件的方法是read( )和write( )系统调用,在一个文件能被访问之前,必须通过open( )或者creat( )系统调用打开它,一旦使用完毕,就应该用close( )系统调用来关闭文件。

  • open( )系统调用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *name, int flags);
int open(const char *name, int flags, mode_t mode);

open( )系统调用将路径名name给出的文件与一个成功返回的文件描述符想关联,文件位置指针被设定为零,而文件则根据flags给出的标志位打开。

  • flags参数必须是以下之一:O_RDONLY(只读), O_WRONLY(只写), O_RDWR(可读可写)
  • flags参数可以和以下一个或多个值进行按位或运算, 用以修改打开文件请求的行为:
  1. O_APPEND: 文件以追加模式打开。在每次写操作之前,文件位置指针将被置于文件末尾。
  2. O_ASYNC: 当指定文件可写或者可读时产生一个信号(默认是SIGIO),这个标志仅用于终端和套接字,不能用于普通文件。
  3. O_CREAT: 当name指定的文件不存在时,将由内核来创建。
  4. O_DIRECT: 用于直接IO。
  5. O_DIRECTORY: 如果name不是一个目录,open( )调用将会失败。这个标志用于在opendir( )内部使用。
  6. O_EXCL: 和O_CREAT一起给出的时候,如果由name给定的文件已经存在,则open( )调用失败。用来防止文件创建时出现竞争。
  7. O_LARGEFILE: 给定文件打开时将使用64位偏移量,这样大于2G的文件也能被打开。
  8. O_NOCTTY: 如果name指向一个终端设备(/dev/tty),它将不会成为这个进程的控制终端,即时该进程目前没有控制终端。
  9. O_NOFOLLOW: 如果name是一个符号链接,open( )调用会失败。
  10. O_NONBLOCK: 如果可以,文件将在非阻塞模式下打开。open( )调用不会,任何其它操作都不会使该进程
  11. O_SYNC: 打开文件用于同步IO。
  12. O_TRUNC: 如果文件存在,且为普通文件,并允许写,将文件的长度截断为0。
  • 新文件所有者
    文件所有者的用户ID就是创建该文件的进程的有效用户id。
    创建文件的进程的组id赋予该文件的用户组。

  • 新文件权限
    除非创建了新文件,否则mode参数会被忽略。而当O_CREAT给出时则必须提供mode参数。

  • 如果在使用O_CREAT时忘记了提供mode参数,结果是未定义的,会很糟糕!

当文件创建时,mode参数提供新建文件的权限。mode参数是常见的Unix权限位集合,像八进制数0644(所有者可以读写,其他人只能读)。例如:

int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IROTH);
if (fd == -1)
    /* error */
  • creat( ) 函数
    O_WRONLY | O_CREAT | O_TRUNC组合经常被使用,以至于专门有个系统调用来实现:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat (const char *name, mode_t mode);

对creat的调用:

int fd;
fd = creat(file, 0644);
if (fd == -1)
    /* error */

等价于:

int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
   /* error */
  • 返回值和错误码
    open( )和creat( )系统调用成功时都返回一个fd,错误时都返回-1,并且将errno设置为一个合适的错误值。

用read( )读取文件

最基本、最常见的读取文件的机制是使用read( )系统调用:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t len);

该系统调用从fd指向的文件的当前偏移量至多读len个字节到buf中。成功时,将返回写入buf中的字节数。出错时,返回-1,并设置errno。

  • 一次读后,fd所指文件位置指针将会向前移动,移动的长度由之前读取的字节数决定。(如果无法在该文件(比如一个字符设备文件)中确定文件位置,读操作总是从“当前”位置开始)

  • 返回值
    返回值可能有以下结果:

  1. 等于len。则与预期一致
  2. 大于0但是小于len。合法,读取的字节存入buf中。
  3. 返回0。标志着EOF,没有可以读入的数据
  4. 返回-1,errno被设置为EINTR。表示在读入字节之前收到了一个信号,可以重新进行调用。
  5. 返回-1, errno被设置为EAGAIN。这表示读取会因为没有可用的数据而阻碍,而读请求应该在之后重开。这只在非阻塞模式下发生。
  6. 返回-1, errno被设置不同于EINTR或EAGAIN的值。这表示某种更严重的错误。
  • 读入所有的字节
    读入所有len个字节(至少读到EOF)的一个例子:
ssize_t ret;
while (len != 0 && (ret = read(fd, buf, len) != 0)) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror("read");
    break;
  }
  len -= ret;
  buf += ret;
}
  • 非阻塞读
    有时候,程序员不希望当没有可读数据时让read( )调用阻塞。相反,他们倾向于在没有可读数据时,让调用立即返回。这种情况被称为非阻塞I/O
  • 如果给定的fd在非阻塞模式下打开(open( )中给定O_NONBLOCK)并且没有可读数据,
    read( )调用会返回-1,且设置errno为EAGAIN而不是阻塞掉。
  • 在进行非阻塞I/O时,必须检查EAGAIN,否则将可能因数据缺失而导致严重的错误。
char buf[BUFSIZE];
ssize_t nr;
start:
  nr = read(fd, buf, BUFSIZE);
  if (nr == -1) {
    if (errno == EINTR)
      goto start; 
    if (errno == EAGAIN)
      /* resubmit later */
    else
      /* error */
  }
  • read( )大小限制
    在32系统上,size_t是unsigned int类型,ssize_t是有符号的size_t类型。
    size_t的最大值为SIZE_MAX;ssize_t的最大值为SSIZE_MAX。如果len比SSIZE_MAX大,read( )调用的结果是未定义的。
  • 在大部分linux系统上,SSIZE_MAX是LONG_MAX,在32位系统上即0x7fffffff。
  • 可增加如下代码:
if (len > SSIZE_MAX)
  len = SSIZE_MAX;
  • 一个len为0的read( )调用的结果是立即返回且返回值为0.

用write( )来写

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

一个write( )调用从由文件描述符fd引用文件的当前位置开始,将buf中至多count个字节写入文件。

  • 对于普通文件来说,除非发生一个错误,否则write( )将保证写入所有的请求。
  • 对于其他类型的文件--例如套接字--得有个循环来保证你真的写入了所有请求的字节。使用循环的另一个好处是,第二个write( )调用可能会返回一个错误值用来说明第一次调用为什么进行了一次部分写(尽管这种情况并不常见)。以下是一个实例代码:
ssize_t ret, nr;
while (len != 0 && (ret = write(fd, buf, len)) != 0) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror("write");
    break;
  }
  len -= ret;
  buf += ret;
}
  • 追加模式
    打开文件时,open( )通过指定O_APPEND参数,则在追加模式下打开。写操作就总是从当前文件末尾开始。

  • 追加写模式可使多个进程访问同个文件不出错。

  • ** 非阻塞写**
    当fd在非阻塞模式下打开时(通过设置O_NONBLOCK参数),并且发起的写操作会正常阻塞时,write( )系统调用返回-1,并设置errno值为EAGAIN。请求应该在稍后重新发起。通常普通文件不会出现这种情况。

  • write( )大小限制
    如果count比SSIZE_MAX还大,write( )调用的结果是未定义的。count值为0的write( )调用将会立即返回且返回值为0。

  • write( )的行为
    write是先写入page cache后就立刻返回了,然后由内核的后台线程来writeback所有的"脏"缓冲区,将它们排好序,并写入到磁盘上。

同步IO

由于一般都使用page cache & writeback的机制,然而又有应用想要控制数据写入磁盘的时间,于是Linux提供了同步机制,用性能来换取同步操作。

  1. fsync( )
#include <unistd.h>
int fsync(fd);

调用fsync( )可保证fd对应文件的脏数据回写到磁盘上。

  1. fdatasync( ):
#include <unistd.h>
int fdatasync(fd);

和fsync( )的区别在于,它仅仅写入数据,不保证元数据同步到磁盘上。

  • 注:这两个调用都不保证任何已经更新的包含该文件的目录项同步到磁盘,如果要保证目录项也同步,必须对目录本身也调用fsync( )进行同步
  1. sync( ):
#include <unistd.h>
void sync(void);

sync( )用来对磁盘上的所有缓冲区进行同步!

  • sync( )没有参数,也没有返回值。它总是成功返回,并确保所有的缓冲区--包括数据和元数据--都能写入磁盘。
  • sync( )并非一直等待到所有缓冲区都写到磁盘才返回,只需要调用它来启动将整个缓冲区写入磁盘的过程即可。
  1. O_SYNC标志
    O_SYNC标志在open( )中使用,使所有在文件上的I/O操作同步。例如:
int fd;
fd = open(file, O_WRONLY | O_SYNC);
if (fd == -1){
  perror("open");
  return -1;
}
  • O_SYNC看起来像是在每个write( )操作后都隐式地执行fsync( ).
  • O_SYNC会使总耗时增加一到两个数量级
  • 如果一般要确保数据写入磁盘的应用可以使用fsync( )或者fdatasync( )。相比O_SYNC来说,开销更小一点。
  1. O_DSYNC和O_RSYNC
  • O_DSYNC:只有普通数据被同步(与fdatasync( )类似)
  • O_RSYNC: 保证读请求同步。该标志只能和O_SYNC或O_DSYNC一起使用。O_RSYNC保证每次读操作后,元数据也立刻更新。

直接IO

由于Linux内核实现了一个复杂的缓存、缓冲以及设备和应用之间的I/O管理的层次结构。通过O_DIRECT标志使内核最小化I/O管理的影响。

  • 使用O_DIRECT标志时,I/O操作将忽略page cache机制,直接对用户空间缓冲区和设备进行初始化,所有的I/O将是同步的,操作在完成前不会返回。
  • 当使用直接I/O时,请求长度,缓冲区对齐,和文件偏移必须是设别扇区大小(通常是512字节)的整数倍。

关闭文件

程序完成对某个文件的操作后,可以使用close( )系统调用将文件描述符和对应的文件解除关联。

#include <unistd.h>
int close(int fd);

close( )调用解除了已打开的文件描述符的关联,并分离进程和文件的关联。

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

推荐阅读更多精彩内容