UNIX环境高级编程读书笔记(3)

来源:互联网 发布:电视直播 for mac 编辑:程序博客网 时间:2024/06/10 01:27

UNIX环境高级编程第三章 读书笔记

一、文件描述符

对于内核而言,所有打开的文件通过文件描述符引用文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用opencreat返回文件描述符标识该文件,将其作为参数传给read或write。
UNIX系统shell把文件描述符0( STDIN_FILENO )进程的标准输入关联,文件描述符1( STDOUT_FILEOUT)进程的标准输出关联,文件描述符2(STDERR_FILENO)进程的标准错误关联。
文件描述符的变化范围是0~OPEN_MAX-1

二、函数open和openat

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

#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*/

代表最后一个参数是可变参数,代表其参数数量个数类别可变的
path参数是打开或创建文件的名字。oflag参数可用来说明此函数的多个选项。用下列一个或多个常量进行“或”运算构成oflag参数(头文件< fcntl.h >)。

O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开
O_EXEC 只执行打开
O_SEARCH 只搜索打开(应用于目录)
O_APPEND 每次写时都追加到文件尾端
O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述符标志。
O_CREAT 若此文件不存在则创建它。使用此选项时,open函数需要同时说明第三个函数mode,用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_TRUNC 如果此文件存在,而且为只写或者读-写成功打开,则将其长度截断为0。
O_TTY_INIT 如果打开一个还未打开的终端设备,设置为非标准的termios结构。
同步输入和输出选项的可选参数
O_DSYNC 使每次write要等待物理I/O操作完成,但是如果该写操作并不影响读取刚写入的数据,则不需要等待文件属性被更新。
O_RSYNC 使一个以文件描述符进行参数进行的read操作等待,直到所有对文件同一部分挂起的写操作都完成。

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

常量_POSIX_NO_TRUNC决定是要截断过长的文件名或路径名,还是返回一个出错。根据文件系统的类型,此值可以变化。我们可以用fpathconf或pathconf来查询目录具体支持何种行为,到底是截取过长的文件名还是返回出错。若_POSIX_NO_TRUNC有效,则在整个路径名超过PATH_MAX,或路径名中的任一文件名超过NAME_MAX时,出错返回,并将errno设置为ENAMETOOLONG

三、函数creat

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

#include < fcntl.h >
int creat(const char *path,mode_t mode)
若成功,返回为只写打开的文件描述符;若出错,返回-1
等价于 open(path,O_WRONLY|O_CREAT|O_TRUNC,mode)

不足之处:以只写方式打开所创建的文件。

四、函数close

可调用close函数关闭一个打开文件。

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

关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它的所有打开文件。

五、函数lseek

打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0
可以调用lseek显式地为一个打开文件设置偏移量

#include < unistd.h >
off_t lseek(int fd,off_t offset,int whence)
若成功,返回新的文件偏移值,若出错,返回-1

对参数offset的解释与参数whence的值有关
- 若whence是SEEK_SET,则将该文件的偏移值设置为距文件开始处offset个字节
- 若whence是SEEK_CUR,则将该文件的偏移值设置为其当前值加offsetoffset可为正或负
- 若whence是SEEK_END,则将文件的偏移值设置为文件长度加offsetoffset可正可负
若lseek成功执行,则返回新的文件偏移量

lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。然后。该偏移值用于下一个读或写操作。

文件偏移值可以大于文件的当前长度,在这种情况下,对该文件的下一次读写将加长该文件,并在文件中产生空洞,位于文件中但没有写过的字节都被读为0。文件中的空洞并不要求在磁盘上占用存储区。在新写的数据需要需要分配磁盘块,但是对于源文件尾部和新开始写位置的部分则不需要分配磁盘块。

通过sysconf来判断函数是支持32位文件偏移值还是64位文件偏移值
这里写图片描述

六、函数read

调用read函数从打开文件中读文件。

#include < unistd.h >
ssize_t read(int fd,void *buf,size_t nbytes)
返回读到的字符数,若已处于文件尾,返回0,若出错,返回-1

多种情况下可使实际的字符数少于要求读的字符数。
- 读普通文件时,在读到要求字符数之前已到达了文件尾端。
- 当从终端文件读时,通常一次最多读一行。
- 当从网络读时,网络中的缓存机制可能造成返回值小于所要求读的字符数。
- 当管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
- 当从某些面向记录的设备读时,一次最多返回一个记录。
- 当一信号造成中断,而已经读了部分数据量时,读操作从文件的当前偏移量处开始,在开始返回成功之前,该偏移量将增加实际读到的字符数。

七、函数write

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

#include < unistd.h >
ssize_t write(int fd,const void *buf,size_t nbytes)
若成功,返回已写的字符数,若出错,返回-1

返回值通常与参数nbytes的值相同,否则表示出错。对于普通文件,写操作从文件当前值偏移量处开始。如果在打开该文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。一次成功写之后,该文件的偏移量增加实际写的字节数
当完成每个write后,在文件表项中的当前文件偏移量即增加所写入的字节数。如果导致当前文件便宜量超过当前文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量。

八、文件共享

UNIX系统共享支持在不同进程间共享打开。
内核使用3种数据结构表示打开文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项,与每个文件描述符想关联的是:
a.文件描述符标志
b.指向一个文件表项的指针
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:
a.文件状态标志
b.当前文件偏移值
c.指向该文件v节点表项的指针
(3)每个打开文件都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点。这些信息是在打开文件时,从磁盘中读入内存中。
打开文件的内核数据结构
这里写图片描述

如果两个独立进程各自打开了同一文件,则有向下图的关系。
这里写图片描述
打开该文件的每个进程都获得各自的一个文件表项,但对一个给定的文件只有一个v节点表项。为了使每个进程都有它自己对该文件的当前偏移量,才设置了多个文件表项。

九、原子操作

原子操作指的是多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步都不执行,不可能只执行所有步骤的一个子集

函数pread和pwrite
调用pread和pwrite来进行原子性的读或写。

#include < unistd.h >
ssize_t pread(int fd,void *buf,size_t nbytes,off_t offset)
ssize_t pwrite(int fd,const void *buf,size_t nbytes,off_t offset)
若成功返回已写的字节数,若出错,返回-1

调用pread相当于调用lseek后调用read,但是pread又与这种顺序有下列重要区别
- 调用pread时,无法中断其定位和读操作。
- 不更新当前文件的偏移值。
调用pwrite相当于调用lseek后调用write。

十、函数dup和dup2

下面两个函数都可用来复制现有的文件描述符。

#include < unistd.h >
int dup(ubt fd)
int dup2(int fd, int fd2)
若成功,返回新的文件描述符。若出错,返回-1

由dup返回的新的文件描述符一定是当前可用文件描述符中的最小数值。对于dup2,可以用fd2参数指定新描述符的值。如果fd2已经打开,则先将其关闭。若fd等于fd2,则dup2返回fd2,而不关闭它。否则fd的FD_CLOEXEC文件描述符标志就被清除,这样fd2调用exec时是打开状态。返回的新文件描述符与参数fd共享同一个文件表项。
这里写图片描述
调用dup(fd)等同于fcntl(fd,F_DUPFD,0)
调用dup2(fd,fd2)等同于fcntl(fd,F_DUPFD,fd2)
(1)dup2是一个原子操作,close和fcntl包括两个函数调用。有可能在close和fcntl之间调用了信号捕获函数,他可能修改文件描述符。如果不同的线程改变了文件描述符的话也会出现相同的问题。
(2)dup2和fcntl有一些不同的errno。

十一、函数sync,fsync,fdatasync

#include < unistd.h >
int fsync(int fd)
int fdatasync(int fd)
若成功,返回0,若出错,返回-1
void sync(void)

sync只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
fsync函数只是对由文件描述符fd指定的一个文件起作用,并且等待写操作结束后才返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘。
fdatasync函数类似于fsync,但它只影响文件的数据部分,fsync还会同步更新文件的属性。

十二、函数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_GETFK或F_SETFL)
(4)获取/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
(5)获取/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW)

cmd的取值范围:
- F_DUPFD 复制文件描述符fd。新文件描述符作为函数值返回。它是尚未打开各描述符中大于或等于第3个参数值中各值的最小值。新描述符有自己的一套文件描述符标志,其FD_CLOEXEC文件描述符标志被清除
- F_DUPFD_CLOEXEC 复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符标志的值
- F_GETFD 对应于fd的文件描述符标志作为函数值返回
- F_SETFD 对于fd**设置文件描述符标志**。
- F_GETFL 对应于fd的文件状态标志作为函数值返回
对于fcntl的文件状态标志
这里写图片描述
- F_SETFL 将文件状态标志设置为第3个参数的值。可以更改的几个标志是: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**,负的arg表示等于arg绝对值的一个进程组ID
fcntl返回值命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。(特殊:F_DUPFD返回一个新的文件描述符,F_GETFD和F_GETFL返回相应的标志,F_GETOWN返回一个正的进程ID或负的进程组ID

显示文件的读写权限的程序

#include "apue.h"#include <fcntl.h>intmain(int argc, char *argv[]){    int  val;    if (argc != 2)        err_quit("usage: a.out <descriptor#>");    if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)        err_sys("fcntl error for fd %d", atoi(argv[1]));    switch (val & O_ACCMODE) {        case O_RDONLY:            printf("read only");            break;        case O_WRONLY:            printf("write only");            break;        case O_RDWR:            printf("read write");        break;        default:            err_dump("unknown access mode");    }    if (val & O_APPEND)        printf(", append");    if (val & O_NONBLOCK)        printf(", nonblocking");    if (val & O_SYNC)        printf(", synchronous writes");    #if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)    if (val & O_FSYNC)        printf(", synchronous writes");    #endif    putchar(’\n’);    exit(0);}

这里写图片描述

十三、函数ioct1

ioctl函数用来标识I/O操作的函数,较多用于终端I/O。

#include < unistd.h > /* System V */
#include < sys/ioctl.h >/* BSD and linux */
int ioctl(int fd , int request , … )

对于ISO C原型,它用省略号表示其余参数。但是,通常只有另外一个参数,它常常是指向一个变量或结构的指针。
每个设备驱动设备可以定义它自己专用的一组ioctl命令,系统则为不同种类的设备挑战通用的ioctl命令。
FreeBSD中通用的ioctl操作
这里写图片描述

十四、/dev/fd

较新的系统都提供了名为/dev/fd的目录,其目录项时名为0、1、2等文件。打开文件/dev/fd/n等效于复制描述符n(假定描述符n时打开的)。

下列函数调用中:
fd=open(“/dev/fd/0”,mode);
大多数系统忽略它所指定的mode,而另一些系统则要求mode必须时所引用的文件初始时打开模式是一个子集。上面打开等效于fp=dup(0)。所有描述符0和fd共享同一个文件表项。

对于某些系统提供路径名为/dev/stdin、/dev/stdout和/dev/stderr,这些等效于/dev/fd/0、/dev/fd/1和、/dev/fd/2文件。