您的位置:首页 > 产品设计 > UI/UE

APUE 第3-5章

2017-09-20 20:19 106 查看

第3章 文件I/O

文件描述符(file descriptor)是一个非负整数,当打开一个现有文件或者新建文件,内核向进程返回一个文件描述符,将其作为参数传给一些函数。

shell将文件描述符0=标准输入,即STDIN_FILENO,1=标准输出,即STDOUT_FILENO,2=标准错误,即STDERR_FILENO,定义域”unistd.h”,文件描述符应小于OPEN_MAX

open()和openat()总是返回最小的可用文件描述符,后者可以指定工作目录。

_POSIX_NO_TRUNC决定是要截断过长的文件名还是返回一个出错。

open()可以通过设置标志位直接打开不存在的文件,等同于create(),且create只能以只写方式打开所创建的文件,而open()则没有这个局限

每个打开文件都有一个“当前文件偏移量”(current file offset),系统默认,打开一个文件,除非是O_APPEND,该量为0。lseek()可以为一个打开文件设置偏移量,返回设置后的offset。如果文件描述符指向一个管道、FIFO或网络套接字,lseek返回-1,并将errno设为ESPIPE。文件偏移量可大于文件当前长度,会设置空洞,空洞不需要空间,被读为0,但文件大小也占。使用实用程序对空洞复制,则空洞被填0,磁盘块会增多。

v节点结构用于支持多文件系统类型,其指向一个和文件系统关联的i节点,被称为虚拟文件系统。Linux无v节点,而是采用一个与文件系统相关的i节点和一个与文件系统无关的i节点。

打开的文件,每个进程有一个记录项,包含一张打开文件描述符表,会记录文件描述符标志并指向一个文件表项;内核维持一张文件表,该表的项记录文件状态标识、当前偏移量并指向一个v节点;v节点包含文件类型并指向i节点。

打开同一文件的每个进程获得各自文件表项,但只有一个v节点,这样不同进程可以有自己的offset。

内核使用三种数据结构表示打开的文件,分别是文件描述符表、文件表和 V 节点表。

每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每个描述符占用一项。与每个文件描述符相关联的是:

文件描述符标志。

指向一个文件表项的指针。

内核为所有打开文件维持一张文件表。每个文件表项包含:

文件状态标志(读、写、添写、同步和非阻塞等)。

当前文件偏移量。

指向该文件 V 节点表项的指针。

每个打开文件(或设备)都有一个 v 节点(v-node)结构。v 节点包含了文件类型和对此文件进行各种操作的函数的指针。v 节点还包含了从磁盘读取的 i 节点(i-node)的信息,i 节点信息包含了文件的所有者、文件长度、文件所在的设备、指向文件的实际数据块在磁盘上的所在位置的指针等。

图 1 显示了一个进程的三张表之间的关系。该进程有两个不同的打开文件,一个文件打开为标准输入(文件描述符为 0),另一个打开为标准输出(文件描述符为 1)。


图1

图 2 给出了两个进程打开同一个文件的内核数据结构。假定第一个进程在文件描述符 3 上打开该文件,而另一个进程在文件描述符 4 上打开该文件。打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个 v 节点表项。


图2

这样的结构显示了:

- 每个进程都有自己的对打开文件的当前偏移量。

- 在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。如果这使当前文件偏移量超过了当前文件长度,则在 i 节点表项中的当前文件长度被设置为当前文件偏移量。

- 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为 i 节点表项中的当前文件长度。

- 如果用O_APPEND标志打开了一个文件,则相应标志被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作时,在文件表项中的当前文件偏移量首先被设置成i节点表项中的文件长度。这就使得每次写的数据都添加到文件的当前尾端处。

- lseek 函数只修改文件表项中的当前文件偏移量,没有进行任何 I/O操作。

dup()、dup2()用来复制一个现有的文件描述符:



sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。

fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。

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

fcntl()函数用于获取/设置文件的状态,进行状态修改时谨记先获取以前的状态位再|需要添加的状态位,以免清除之前已设置的位。这个函数的好处就是只需要知道fd就可以更改标志位。

int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);


fcntl函数有5种功能:

1. 复制一个现有的描述符(cmd=F_DUPFD).

2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

5. 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

BUFFSIZE应不设的太小,会影响性能。(预取机制)

/dev/fd/#num表示打开的文件描述符,例如,可用/dev/fd/0来代替cat程序使用”-“表示stdin。

第4章 文件和目录

stat(),fstat(),lstat(),fstatat()四个函数用于获取文件信息。stat接收pathname,fstat接收fd,lstat类似stat,但是当命名文件是一个symbol link,lstat返回该link的信息。fstatat更为复杂和高级。这些函数都将设置一个stat struct。

文件类型:

普通文件。

目录文件。有读权限的任一进程都可以读,但只有内核可以写,所以需要使用系统调用来改写目录。d

块特殊文件。提供对设备带缓冲的访问,每次访问以固定长度进行。b

字符特殊文件。提供对设备不带缓冲的访问,访问长度可变。系统所有设备要么是字符特殊文件,要么是块特殊文件。c

FIFO。用于进程间通信,也称为命名管道(named pipe)。p

套接字。用于进程间的网络通信。s

符号链接。该文件指向另一个文件。l

有一组宏可以用来确定文件类型

与进程相关的ID:

- 实际用户/组ID:登录时取自口令,通常并不改变,但root有办法改变。

- 有效用户/组ID:决定文件访问权限

- 保存的设置用户/组ID:在执行一个程序时包含了有效用户/组ID的副本。

通常,有效用户/组ID等于实际用户/组ID。

设置用户/组ID位可以使得“当执行此文件是,进程的有效ID设置为文件所有者的用户ID”

对目录的读权限意味着可以查看文件列表,而如果要访问该目录下的文件或者增删,则需要执行权限。

新文件的拥有者id为进程的有效id。

- 新文件组id可以是进程的有效组ID

- 或者是所在目录的组ID。

access()和faccessat()按实际用户/组ID进行访问权限测试。faccessat()的flag如果设置为AT_EACCESS,则使用有效而非实际。

umask用于制定默认的文件屏蔽字,在shell和c中都可使用。

chmod(),fchmod(),fchmodat()用于更改现有文件的访问权限。进程的有效ID必须是所有者,或root。

沾着位(sticky bit)或者说保存正文位(saved-text bit)使得某一文件的副本被保存在交换区,以加速那些经常需要使用的文件。对目录设置这个位,使得只有对目录有写权限且满足(拥有文件|拥有目录|root)才能删除文件。否则,只要有目录的执行权限和写权限,就可以删除了。

chown(),fchown(),fchownat(),lchown()用于更改文件的用户ID和组ID,_POSIX_CHOWN_RESTRICTED有效时,不能更改其他用户文件的用户Id,可以更改你的文件的组Id,但只能改到你的组。

stat结构成员st_size表示以字节为单位的文件长度。只适用于普通文件、目录、符号链接。符号链接的长度为实际文件名的字节数。比如 lib -> usr/lib 的size为7。

truncate(),ftruncate()用于更改文件长度,打开文件时设置O_TRUNC标志用于将文件大小设为0。

stat结构大多数信息来自i节点,而目录项只有两项重要信息:文件名和i节点编号。目录项指向i节点,i节点指向其数据块。硬链接是创建了新的目录项指向i节点,i节点的链接计数+1.每个目录都有至少2的链接数,如果其有子目录,每个子目录的..会使其链接数+1

不改变文件系统重命名或移动文件,只是建立新目录项指向原i节点即可,数据块并不变化。

link(),linkat()用于创建硬链接,很多文件系统不允许对目录的硬链接以免形成循环。unlink(),unlinkat()删除一个现有的目录项。通常使用rmdir()删除目录。remove()函数对于文件,相当于unlink,对于目录,相当于rmdir。

rename(),renameat()用于重命名文件或目录。

符号链接打破了硬链接的一些局限:无文件系统限制,可链接到目录,可能造成目录循环引发错误。对文件进行处理的函数有的会跟随符号链接去处理原文件,而有的则处理文件本身,所以需要注意。。使用symlink(),symlinkat()创建符合链接。因为open()跟随链接,所以使用readlink(),readlinkat()读取链接本身。

最后访问时间st_atim,最后修改时间st_mtim,i节点状态最后更改时间st_ctim。利用futimens(),utimensat(),utimes()来更改文件的访问和修改时间。需要拥有该文件除非更改的值为UTIME_NOW或UTIME_OMIT(不变)。

使用mkdir(),mkdirat(),rmdir()来操作dir。只有内核可以写目录,一个目录的写和执行权限位决定了能否创建或删除文件,不代表可以写目录本身。opendir(),fdopendir(),readdir(),closedir(),telldir(),seekdir(),rewinddir()用于读取目录。chdir(),fchdir()更改当前工作目录,当前工作目录是进程的一个属性,所以只影响调用函数的进程本身。getcwd()返回当前目录的完整路径名。利用open打开当前工作目录并保存fd,再利用fchdir打开fd就可以便捷地返回原工作目录。

每个文件系统所在的存储设备都由其主次设备号表示。主设备号标识设备驱动程序,次 设备号标识特定的子设备。major和minor可以用来访问主次设备号,例如,一个磁盘驱动器包含若干个分区,各分区具有相同主设备号,次设备号不同。每个文件的st_dev表示与该文件相关的文件系统的设备号。只有字符特殊文件和块特殊文件才有st_rdev值,包含实际设备的设备号。

第5章 标准I/O库

文件I/O围绕文件描述符进行,而标准I/O围绕流进行。FILE结构体类型是ANSI C编译系统提供的以数据文件为对象的输入输出操作的结构体类型变量,亦称文件I/O流。

STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO与流stdin,stdout,stderr相对应。

标准I/O库对每个流进行自动的缓冲管理,提供3种类型的缓冲:

1. 全缓冲。填满缓冲区才进行实际I/O操作。flush由标准I/O自动调用或者可以调用fflush进行冲洗。++标准I/O库中的flush意味着将缓冲区数据实际写,而终端驱动程序的flush则意味着丢弃缓冲区的数据。++

2. 行缓冲。遇到换行符进行I/O操作。当流涉及一个终端(如标准输入和标准输出),通常使用行缓冲。限制:(1)缓冲区如果满了还没有换行符,也进行I/O

3. 不带缓冲。标准错误流通常不带缓冲。

注意:

- 当且仅当stdin和stdout不指向交互式设备时,才是全缓冲的

- stderr不能是全缓冲的。

setbuf(),setvbuf()用来更改给定流的缓冲类型。

fopen(),freopen(),fdopen()用于打开一个标准I/O流。fdopen()可以将标准I/O流和fd结合。可用于创建绑定于管道和网络通信通道的流。

每次一个字符的I/O。

每次一行。

直接I/O,二进制I/O

getc(),fgetc(),getchar()用于一次读一个字符,与之相应的有3个put函数。对于出错和到达文件尾端,这3个函数返回同样的值,为了区分,需调用ferror(),feof(),因为每个流在FILE对象中维护了出错标识和文件结束标识。clearerr可以清除这俩标识。ungetc可以将一个字符压回一个缓冲区。

使用fread(),fwrite()实现二进制I/O,读写结构、数组。只适用于同一文件系统。

ftell(),fseek(),ftello(),fseeko(),fgetpos(),fsetpos()用于定位标准I/O流。

printf(),fprintf(),dprintf().

int snprintf(char *str, size_t size, const char *format, …)

将可变个参数(…)按照format格式化成字符串,然后将其复制到str中

1. 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符(‘\0’);返回实际写入的长度

2. 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符(‘\0’),返回值为欲写入的字符串长度。

长度不包含’\0’

int sprintf( char *buffer, const char *format, [ argument] … );

buffer:char型指针,指向将要写入的字符串的缓冲区。

format:格式化字符串。

[argument]…:可选参数,可以是任何类型的数据。

返回写入buffer 的字符数,出错则返回-1. 如果 buffer 或 format是空指针,且不出错而继续,函数将返回-1,并且 errno 会被设置为 EINVAL。sprintf 返回以format为格式argument为内容组成的结果被写入buffer 的字节数,结束字符‘\0’不计入内。

可能会溢出,不建议使用

上述5个函数的5个变体,前加一个‘v’,将最后一个参数…换成了va_list。

scanf(),fscanf(),sscanf(),同样有3个变体。

tmpnam(),tmpfile()用于创建临时文件。tmpnam返回一个唯一的路径名,tmpfile先调用tmpnam并用其返回值创建一个文件,立即unlink它,所以关闭该文件就会删除它。

fmemopen()创建内存流
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  UNIX 文件IO 标准IO