SylixOS 文件I/O函数

目录

1.函数open

2.函数creat

3.函数close

4.函数read

5.函数write

6.函数lseek

7.函数pread和pwrite

8.函数dup和dup2

9.函数sync、fsync和fdatasync

10.函数fcntl


1函数open

调用 open 函数可以打开或者创建个文件。

#include <fcntl.h>
int open(const char *cpcName, int iFlag, ...);
函数 open 原型分析:
  • 此函数成功返回文件描述符,失败返回-1 并设置错误号;
  • 参数 cpcName 是需要打开的文件名
  • 参数 iFlag 打开文件标志
  • 参数...是可变参数。

open 函数的最后一个参数写为...ISO C用这种方法表示余下参数的数量及其类型是可变的,对于 open 函数而言,只有在创建新文件时才会用到此参数。

参数 iFlag 包含多个选项,如下图所示,多个选项之间通常以“或”的方式来构成此参数。

open 函数返回的文件描述符一定是系统中最小的且未使用的描述符数值,这一点被某些应用程序用来在标准输入、标准输出或者标准错误上打开新的文件。例如,一个应用程序 可以先关闭标准输出(文件描述符 1),然后打开另一个文件,执行打开操作前就能了解到 该文件一定会在文件描述符 1 上打开。在说明 dup2 时,会了解到有更好的方法来保证在一 个给定的文件描述符上打开一个文件。

SylixOS I/O 系统最大可支持 2TB 的文件,但是受限于某些文件系统设计,例如 FAT 文件系统最大只能支持 4GB 的文件大小。在应用程序中,为了能够显式地指定打开大的文件,在调用 open 函数的时候需要指定 O_LARGEFILE 标志。SylixOS 也提供了下列函数来打开大的文件。

#include <fcntl.h>
int open64(const char *cpcName, int iFlag, ...);

函数 open64 原型分析:

  • 此函数成功返回文件描述符,失败返回-1 并设置错误号;
  • 参数 cpcName 是要打开的文件名;
  • 参数 iFlag 是打开文件标志;
  • 参数...是可变参数。

 

2函数creat

调用 creat 函数可以创建一个文件。

#include <fcntl.h>
int creat(const char *cpcName, int iMode);
函数 creat 原型分析:
  • 此函数成功返回文件描述符,失败返回-1 并设置错误号;
  • 参数 cpcName 是要创建的文件名;
  • 参数 iMode 创建文件的模式
iMode文件访问权限模式
调用 creat 函数可以创建一个文件,此函数等效于下面函数调用:
open(cpcName, O_WRONLY | O_CREAT | O_TRUNC, iMode);

creat 函数的一个不足之处是它以只写的方式打开所创建的文件,如果通过 creat 函数创 建一个文件,然后又要读这个文件,则必须要先调用 creat 函数创建这个文件,然后调用 close 关闭这个文件,再以读的方式打开这个文件才行,而这种方式可通过调用 open 函数直接实现

open(cpcName, O_RDWR | O_CREAT | O_TRUNC, iMode);

 

3函数close

调用 close 函数会将文件描述符的引用计数和文件的总引用计数减一。

#include <unistd.h>
int close(int iFd);
函数 close 原型分析:
  • 此函数成功返回 0,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符。

调用 close 函数会将文件描述符的引用计数和文件的总引用计数减一,当文件描述符的引用计数为零时,则删除此文件描述符(介绍 dup 函数将看到这一点),当总引用计数减为零时将关闭这个文件,并且会释放当前进程加在该文件上的所有记录锁

当一个进程终止时,内核自动关闭它打开的所有文件,许多程序都利用了这一功能不显式地调用 close 函数关闭打开的文件。

 

4函数read

调用 read 函数可以从打开的文件中读取数据。

#include <unistd.h>
ssize_t read(int iFd, void *pvBuffer, size_t stMaxBytes);
函数 read 原型分析:
  • 此函数成功返回读取的字节数,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符;
  • 输出参数 pvBuffer 是接收缓冲区;
  • 参数 stMaxBytes 是接收缓冲区大小。
调用 read 函数可以从打开的文件中读取数据,有很多情况实际读到的字节数少于要求
读的字节数:
  • 读普通文件时,在读到要求字节数之前已到达了文件尾端;
  • 当从终端设备读时,通常一次最多读一行;
  • 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数;
  • 当被信号中断,而已经读了部分数据时。

通常情况,我们需要通过 read 的返回值来判断读取数据的数量与正确性。

 

5函数write

调用函数 write 向打开的文件中写入数据。

#include <unistd.h>
ssize_t write(int iFd, const void *pvBuffer, size_t stNBytes);
函数 write 原型分析:
  • 此函数成功返回写的字节数,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符;
  • 参数 pvBuffer 是要写入文件的数据缓冲区地址;
  • 参数 stNBytes 是写入文件的字节数。

调用函数 write 向打开的文件中写入数据,其返回值通常与参数 stNBytes 数值相同,否则表示出错。write 出错的一个常见原因是磁盘已满,或者超过了一个进程的文件长度限制。

对于普通文件,写操作从文件的当前偏移量处开始,如果在打开文件时指定了O_APPEND 标志,则在每次操作之前,将文件偏移量设置在文件的结尾处。在一次写成功之后,该文件偏移量在文件末尾增加实际写的字节数。

 

6函数lseek

调用 lseek 函数可以为一个已打开的文件设置偏移量。

每一个打开的文件都有一个与其相关联的当前文件偏移量,这通常是一个整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量开始,并使偏移量增加所读写的字节数。SylixOS 默认的情况,当打开一个文件时,除非指定了 O_APPEND 标志,否则当前文件偏移量总是被设置为 0

#include <fcntl.h>
off_t lseek(int iFd, off_t oftOffset, int iWhence);
函数 lseek 原型分析:
  • 此函数成功返回新的文件偏移量,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符;
  • 参数 oftOffset 是偏移量;
  • 参数 iWhence 是定位基准。

调用 lseek 函数可以显式地为一个已打开的文件设置偏移量,注意,lseek 调用只是调整内核中与文件描述符相关的文件偏移量记录,并没有引起对任何物理设备的访问。

参数 oftOffset 的意义根据参数 iWhence 的不同而不同,如下图所示:

如下图所示, iWhence 参数含义。

这里给出了函数 lseek 调用的一些例子,注释中说明了将当前文件指针移到的具体位置。
lseek(fd, 0, SEEK_SET);                                 /* 文件开始处 */
lseek(fd, 0, SEEK_END);                                 /* 文件结尾处*/
lseek(fd, -1, SEEK_END);                                /* 文件倒数第一个字节处*/
lseek(fd, -20, SEEK_CUR);                               /* 文件当前位置之前的 20 个字节处*/
lseek(fd, 100, SEEK_END);                               /* 文件末尾处扩展 100 个字节*/

如果程序的文件偏移量已然跨越了文件结尾,然后再执行 I/O 操作,read 函数调用将返回 0,表示文件结尾,但是 write 函数可以在文件结尾后的任意位置写入数据。

从文件结尾后到新写入数据间的这段空间被称为文件空洞。从编程角度看,文件空洞中是存在字节的,读取空洞将返回以 0 填充的缓冲区。

空洞的存在意味着一个文件名义上的大小可能要比其占用的磁盘存储总量要大(有时会大很多),当然,具体的处理方式与文件系统的实现有关。

 

7函数preadpwrite

原子性的操作方法。

SylixOS 中多个进程可以读取同一个文件,每一个进程都有它自己的文件结构,其中也有它自己的当前文件偏移量。但是,在非 NEW_1 型文件系统中(没有唯一的文件节点存在),当多个进程写同一文件时,则可能产生预想不到的结果。为了说明如何避免这种情况,需要了解原子操作的概念。

考虑下面代码,在进程中打开一个文件向其中追加数据。

……
 ret = lseek(fd, 0, SEEK_END);
 if (ret < 0) {
 fprintf(stderr, "Lseek error.\n");
 }
 
 ret = write(fd, buf, 10);
 if (ret != 10) {
 fprintf(stderr, "Write data error.\n")
}
……

这段代码在单进程的情况是没有问题的,事实也证明了这一点,但是如果有多个进程时,使用这种方法追加数据到文件将会产生问题。

假如有两个独立的进程 1 和进程 2 同时对一个文件进行追加写操作,每个进程打开文件时都没有使用 O_APPEND 标志,此时各数据结构的关系,如图 5.4 所示,每个进程都有它自己的文件结构和文件当前偏移量,但是共享了一个文件节点。假如进程 1 调用了 lseek 函数将文件当前偏移量设置到了文件尾,此时进程 2 运行,也调用了 lseek 函数,也将文件当前偏移量设置到了文件尾。然后进程 2 调用 write 函数将进程 2 的文件偏移量推后了 10 个字节,此时文件变长,内核将文件节点中的文件长度也增加了 10 个字节。而后,内核切换进程 1 运行,调用 write 函数,此时进程 1 就从自己的当前偏移量开始写,这样就覆盖了进程 2 刚才写入的数据

从上面的过程可以看出,问题出在“先定位到文件尾,再写文件”上,这个过程使用了两个函数来完成,这样就造成了一个非原子性的操作,因为在两个函数之间可能会造成进程的切换。所以,我们可以得出,如果这个过程是在一个函数中完成(形成一个原子性的操作)问题就可以解决。

SylixOS 为这样操作提供了一个原子性的操作方法,在打开文件时设置 O_APPEND 标志,这样内核在每次写操作时,都会将当前偏移量设置为文件尾,也就不用每次写之前再调用 lseek 函数。

SylixOS 提供了一种原子性的定位并执行 I/O 操作的函数:preadpwrite

#include <unistd.h>
ssize_t pread(int iFd, void *pvBuffer, size_t stMaxBytes, off_t oftPos);
ssize_t pwrite(int iFd, const void *pvBuffer, size_t stNBytes, off_t oftPos);
函数 pread 原型分析:
  • 此函数成功返回读的字节数,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符;
  • 输出参数 pvBuffer 是接收缓冲区;
  • 参数 stMaxBytes 是缓冲区大小;
  • 参数 oftPos 指定读的位置。
 
函数 pwrite 原型分析:
  • 此函数成功返回写的字节数,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符;
  • 参数 pvBuffer 是数据缓冲区;
  • 参数 stNBytes 是写的字节数;
  • 参数 oftPos 指定写的位置。
调用 pread 函数相当于先调用 lseek 函数后调用 read 函数,但是 pread 函数与这种顺序有下列重要区别:
  • 调用 pread 函数时,无法中断其定位和读操作(原子操作过程);
  • 不更新当前文件偏移量。

调用 pwrite 函数相当于先调用 lseek 函数后调用 write 函数,但也与上述有类似的区别。一般而言,原子操作指的是由多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

 

8函数dupdup2

调用此函数可以复制一个现有的文件描述符。

#include <unistd.h>
int dup(int iFd);
int dup2(int iFd1, int iFd2);
函数 dup 原型分析:
  • 此函数成功返回新文件描述符,失败返回-1 并设置错误号;
  • 参数 iFd 是原始文件描述符。
 
函数 dup2 原型分析:
  • 此函数成功返回 iFd2 文件描述符,失败返回-1 并设置错误号;
  • 参数 iFd1 是文件描述符 1
  • 参数 iFd2 是文件描述符 2

调用 dup 函数和 dup2 函数可以复制一个现有的文件描述符,由 dup 函数返回的新文件描述符一定是当前可用文件描述符中的最小数值。对于 dup2 函数可以用参数 iFd2 指定新文件描述符的值。如果 iFd2 已经打开,则先将其关闭,iFd2 FD_CLOEXEC 文件描述符标志将被清除,这样 iFd2 在进程调用 exec 函数时是打开状态。注意,SylixOS 内核目前并不支持 iFd1 等于 iFd2 的情况。

dup 函数返回的文件描述符与参数 iFd 共享同一个文件结构项(文件表项),相同地,dup2 函数的文件描述符 iFd1 iFd2 也共享同一个文件结构项,如下图所示。进程中调用了:

fd = dup(3);

 

假设文件描述符 3 已被占用(这是很有可能的),此时我们调用 dup 函数将可能使用文件描述符 4,因为两个文件描述符指向同一个文件结构(文件表项),所以,它们共享同一文件属性标志(读、写、追加等)以及同一文件当前指针(文件偏移量)。

每个文件都有它自己的一套文件描述符标志,新的文件描述符标志(FD_CLOEXEC)总是由 dup 函数清除。复制描述符的另一种方法是使用 fcntl 函数。

实际上,调用

dup(fd);

等效于

fcntl(fd, F_DUPFD, 0);

 

而调用

dup2(fd, fd2);

等效于

fcntl(fd, F_DUP2FD①, fd2);

 或者

close(fd2);
fcntl(fd, F_DUPFD, fd2);

前面介绍过 SylixOS 每个进程都有自己的一个文件描述符表,同时内核也存在一个全局的文件描述符表。那么如果在进程中打开一个文件,内核是看不到这个文件描述符的,但是有一些情况需要内核操作进程打开的文件描述符(例如:日志系统中向应用程序指定的文件中写入数据)。SylixOS 提供了下面函数以实现进程文件描述符向内核空间的复制。

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

函数 dup2kernel 原型分析:

  • 此函数成功返回内核文件描述符,失败返回-1 并设置错误号;
  • 参数 fd 是进程文件描述符。

 

9函数syncfsyncfdatasync

SylixOS 系统在内核中设有磁盘高速缓存,大多数磁盘 I/O 都通过缓冲区进行。当我们向文件中写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,恰当的时候再写入磁盘(由线程“t_diskcache”完成),种方式被称为延迟写

通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据块写入磁盘。为了保证磁盘上实际文件系统与缓冲区中内容的一致性,SylixOS 提供了 syncfsync、和 fdatasync 三个函数。

#include <fcntl.h>
void sync(void);
int fsync(int iFd);
int fdatasync(int iFd);

函数 fsync 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符。

 

函数 fdatasync 原型分析:

  • 此函数成功返回 0,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符。

sync 函数是将系统中所有修改过的磁盘高速缓冲排入写队列,然后等待实际写磁盘操作结束后返回。

fsync 函数只对由文件描述符 iFd 指定的一个文件起作用。

fdatasync 函数类似于 fsync 函数,但它只影响文件的数据部分,除数据外,fsync 还会同步更新文件的属性

 
 

10函数fcntl

#include <fcntl.h>
int fcntl(int iFd, int iCmd, ...);
函数 fcntl 原型分析:
  • 此函数成功时根据参数 iCmd 的不同而返回不同的值,失败返回-1 并设置错误号;
  • 参数 iFd 是文件描述符;
  • 参数 iCmd 是命令;
  • 参数...是命令参数。

调用 fcntl 函数可以改变已经打开文件的属性,在本节的实例中,第 3 个参数总是一个整数,但是在说明记录锁时,第 3 个参数则是指向一个结构的指针

SylixOS fcntl 函数支持以下 4 种功能:
  • 复制一个已有的文件描述符(iCmd = F_DUPFDF_DUPFD_CLOEXECF_DUP2FD、F_DUP2FD_CLOEXEC);
  • 获取/设置文件描述符标志(iCmd = F_GETFDF_SETFD);
  • 获取/设置文件属性标志(iCmd = F_GETFLF_SETFL);
  • 获取/设置文件记录锁(iCmd = F_GETLKF_SETLKF_SETLKW)。

下图介绍了前 3 种命令的功能,

打开文件的属性标志不同,fcntl 函数获得的文件属性标志也跟着变化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值