Unix/Linux编程-系统调用I/O

系统调用I/O

1.1 文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数,当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。在符合POSIX.1的应用程序中,幻数0,1,2虽然已经被标准化,但应当把它们替换成符号常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量都在头文件<unistd.h>中。

1.2 打开或创建一个文件

#include <fcntl.h>

int open(const char *path, int oflag,…/* mode_t mode */);

int openat(int fd, const char *path, int oflag,…/* mode_t mode */);

返回值:成功返回文件描述符,失败返回-1


仅当创建新文件时才使用最后一个参数。
path参数是要打开或创建文件的路径名。
oflag参数用来说明函数的多个选项,用下列一个或多个常量进行或运算构成oflag参数。这些常量在<fcntl.h>中定义:

oflag

说明

备注

O_RDONLY

只读打开

O_WRONLY

只写打开

O_RDWR

读写打开

O_EXEC

只执行打开

O_SEARCH

只搜索打开(用于目录)

以上有且仅有一个存在,O_SEARCH常量的母的在于在目录了打开时验证它的搜索权限。

O_APPEND

将文件偏移量设置到文件末尾处

O_CLOEXEC

把FD_CLOEXEC常量设置为文件描述符标志

O_CREAT

文件不存在则创建它。使用这个选项需要设置最后的mode参数

O_DIRECTORY

Path不是目录则出错

O_EXCL

如果同时指定了O_CREAT,而文件已经存在则出错,不存在则创建它。

O_NOCTTY

如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端。

O_NOFOLLOW

如果path引用的是一个符号链接则出错

O_NONBLOCK

如果path引用的是一个FIFO、一块特殊文件或一个字符特殊文件,则此选项为文本的本次打开操作和后续I/O操作设置非阻塞方式。

O_SYNC

使每次write等待物理I/O操作完成,包括由该write操作引起的文件属性更新所需的I/O

O_DSYNC

使每次write要等待物理I/O操作完成,但是如果该操作并不影响读取刚写入的数据,则不需要等待文件属性被更新。

当文件用O_DSYN标志打开,在重写其现有的部分内容时,文件时间属性不会同步更新。于此相反,如果文件使用O_SYNC标志打开,那么对该文件的每一次write都将在write返回前更新文件时间,这与是否改写现有字节或追加文件无关。

O_TRUNC

如果文件存在,而且为只写或读写成功打开,则将其长度截断为0

O_TTY_INIT

如果打开一个还未打开的终端设备,设置非标准termios参数值。

1.2.1 open与openat的区别

fd参数把open和openat函数区分开,有2种可能性。
(1) path参数指定的是绝对路径名,在这种情况下,fd参数被忽略,两个函数没有区别。
(2) path参数指定的是相对路径,fd参数指出了相对路径名在文件系统中的开始地址,fd参数是通过打开相对路径名所在的路径来获取。
(3) path参数之指定了相对路径名,fd参数具有特殊值AT_FDCWD。这种情况下,路径名在当前目录中获取,openat函数在操作上与open函数类似。

1.3 改变文件偏移量

每个打开文件都对应有文件表项,纪录了当前文件偏移量,通常是非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

返回值:成功返回新的文件偏移量,错误返回-1


whence参数:

选项

说明

SEEK_SET

文件偏移量设置为文件开始出offset个字节

SEEK_CUR

文件偏移量设置为文件当前值加offset个字节,offset可正可负

SEEK_END

文件偏移量设置为文件长度加offset个字节,offset可正可负


文件偏移量可以大于文件的当前长度,对该文件的下一次写将加长该文件,并在文件中构成一个空洞。位于文件中但没有写过的字节都被读为0。
文件中的空洞并不要求在磁盘上占用存储区,具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但对于原文件尾端和新开始写位置之前的部分则不需要分配磁盘块。

1.4 读取文件数据

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t nbyts);

返回值:成功则返回已写的字节数,出错返回-1


有多种情况可使实际读到的字节数少于要求读的字节数:
(1) 读普通文件,在读到要求字节数之前已到达文件尾端。
(2) 读终端设备时,通常一次最多读一行。
(3) 读网络时,网络中的缓冲机制可能造成返回值小于要求读的字节数。
(4) 读管道或FIFO时,若管道包含的字节少于所需的数量,那么read将只返回可用的字节数。
(5) 当信号中断,而已经读了部分数据量。

1.5 向文件写数据

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t nbyts);

返回值:成功则返回已写的字节数,出错返回-1


返回值通常与参数nbytes的值相同,否则表示出错。磁盘已满或超过一个给定进程的文件长度限制都可能导致出错。在一次成功写之后,文件偏移量增加实际写的字节数。

1.6 文件共享

内核使用了3种数据结构来表示打开文件。分别是打开文件描述符表、文件表和v节点(i节点)。
(1) 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,每个描述符占用一项,与每个文件描述符相关联的是:
  a. 文件描述符标志(close_on_exec)
  b. 指向一个文件表项的指针
(2) 内核为所有打开文件维持一张文件表,每个文件表项包含:
  a. 文件状态标志(读、写、添写、同步和非阻塞等)
  b. 当前文件偏移量
  c. 指向该文件v节点的指针。
(3) 每个打开文件(或设备)都有一个v节点结构,v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点。Linux中没有v节点,而是使用了通用的i节点结构。
下图展示了一个进程对应3张表之间的关系:


如果两个独立进程各自打开 了同一个文件,关系入下所示:

1.7 原子操作

原子操作指的是由多不组成的一个操作,如果该操作原子执行,则要么执行完所有步骤,要么都不执行。Single UNIX Specification包括了XSi扩展,该扩展允许原子性地定位并执行I/O。
pread和pwrite就是这种扩展。

1.7.1 pread和pwrite函数

#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);

返回值:读到的字节数,若到文件尾则返回0,出错返回-1

ssize_t pwite(int fd, const void *buf, size_t nbytes, off_t offset);

返回值:返回已写的字节数,错误返回-1


与read和write不同的是,无法终端其定位和读(写)操作。

1.7.2 创建一个文件

对于open函数,同时指定O_CREAT和O_EXCL选项时,而该文件已经存在则open失败,否则会原子操作地创建并打开文件。

1.8 复制现有的文件描述符

#include <unistd.h>

int dup(int fd);

int dup2(int fd, int fd2);

返回值:成功则返回新的文件描述符;错误返回-1。


dup函数返回的新文件描述符一定是当前可用文件描述符中最小的整数。dup2函数使用fd2参数指定新描述符的值。如果fd2已经打开,则会先将其关闭(不报错)。如果fd2等于fd,返回fd2,而不关闭它。这些函数返回的新文件描述符与参数fd共享同一个文件表项:

复制现有的文件描述符的另一种方式fcntl(fd, F_DUPFD, fd2)。

1.9 函数fcntl

fcntl函数可以改变已经打开文件的属性。

#include <fcntl.h>

int fcntl(int fd, int cmd,… /* int arg */);

返回值:成功,则依赖于cmd,出错则返回-1


fcntl函数有一下5个作用:
(1) 复制一个已有的文件描述符 (cmd=F_DUPFD 或 F_DUPFD_CLOEXEC )。
(2) 获取/设置文件描述符标志(cmd=F_GETFD 或 F_SETFD)。
(3) 获取/设置文件状态标志(cmd=F_GETFL 或 F_SETFL)。
(4) 获取/设置异步I/O所有权(cmd=F_GETOWN 或 F_SETOWN)。
(5) 获取/设置记录锁(cmd=F_GETFK、F_SETLK或F_SETLKW)。

Cmd

说明

F_DUPFD

复制文件描述符fd。新文件描述符作为函数值返回。它是尚未打开的各描述符中大于或等于第3个参数值中的最小值。

F_DUPFD_CLOEXEC

复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符标志的值,返回新文件描述符。

F_GETFD

对应于fd的 文件描述符标志作为函数的返回值,当前只有FD_CLOEXEC这个标志。

F_SETFD

对应fd设置文件描述符的标志。,新的值按第三个参数设置。

F_GETFL

对应fd的文件状态标志作为函数值返回。遗憾的是O_RDONLY,O_WRONLY,O_RDWR,O_EXEC,O_SEARCH这五个标志并不是各占一位,因此首先使用屏蔽字O_ACCMODE取得访问方式位,然后将结果与5个值做相等比较,其他的标志与函数返回值做与运算。

F_SETFL

将文件状态标志设置为第三个参数的值,可以更改的标志是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC和O_ASYNC。

F_GETOWN

获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。

F_SETOWN

设置接收SIGIO和SIGURG信号的进程ID或进程组ID,正的arg表示一个进程ID,负的 表示等于绝对值的一个进程组ID。

1.10 sync、fsync和fdatasync函数

内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区,然后排入队列,晚些再写入磁盘。这种方式称为延迟写。
通常,当内核需要重用缓冲区来存放其他磁盘块数据时,会把所有延迟写数据写入磁盘。为了保证磁盘上实际文件系统与缓冲区中内容的一致性,Unix提供了这三个函数。

#include <unistd.h>

int fsync(int fd);

int fdatasync(int fd);

返回值:成功返回0,出错返回-1

void sync(void);


sync只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束。通常称为update的系统守护进程周期性的调用sync函数定期冲洗内核的块缓冲区。
fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘结束才返回。
fdatasync函数类似于fsync,但他只影响文件的数据部分,而除了数据外,fsync还会同步更新文件的属性。

猜你喜欢

转载自blog.csdn.net/water_3700348/article/details/78222932