一、open()系统调用


open( )系统调用用来将路径名称name 所指定的文件映射至一个文件描述符,并于映射成功后返回该文件描述符。文件位置会被设为零,而且会根据flags所指定的标志来打开该文件以便进行访问。flags参数必须是O_RDONLY、o_WRONLY或O_RDWR 其中之一。这些标志值可分别让所打开的文件用于读取、写入、读写操作。

O_APPEND
以附加模式(append mode)打开文件。也就是在每次进行写入操作之前,文件位置将被更新成指向文件结尾。即使进程上次进行写入操作之后有另一个进程也对文件进行了写入操作,也会因此变更文件位置
O_ASYNC
如果所指定的文件变成可读取或可写入的,则产生一个信号(默认为SIGIO)。此标志可用于终端机以及sockets,但不可用于常规文件。
O__CREAT
如果name所指定的文件尚不存在,则内核将会创建它﹔如果该文件已经存在,除非同时指定了O_EXCL,否则此标志将毫无作用。
O_DIRECT
打开文件以便进行直接I/O
O_DIRECTORY
如果name不是一个目录,则open (〉调用会失败。此标志于内部供opendir ( )链接库调用所用。
O_EXCL
指定O_CREAT时,如果name所指定的文件已经存在,此标志将导致open ( )调用失败。这可用于避免文件的创建出现竞争条件 (race condition)。
O_LARGEFILE
所指定的文件将采用64位的偏移量打开,以便打开尺寸大于2GB的文件。在64位架构上这是默认值。
O_NOCTTY
如果name 所指定的是一个终端设备(例如/devty),它不会变成进程的控制终端,即使进程当前并不具有控制终端。此标志并不常被用到。
O_NOFOLLOW
如果name是一个符号链接,则open ( )调用将会失败。一般情况下,链接会被解析﹐而且会打开目标文件。如果所指定的路径中有其他元素是链接,则open ( )调用仍旧会执行成功。举例来说,当name是/etc/ship/plank.txt时,如果plank.txt是一个符号链接,则open ( )调用将会失败。然而,如果etc或ship是符号链接,则只要plank.txt不是符号链接,open ( )调用就会成功执行。
O_NONBLOCK
如果可能,将以非阻挡模式(nonblocking mode)打开文件。这使得 open ()调用以及任何其他操作都不会让进程在进行IO时受到阻挡(进入休眠状态)。此行为只能用于定义FIFO。
O_SYNC
打开文件以便进行同步IO。直到数据被实际写入磁盘之前都不会完成写入操作,一般的读取操作已经是同步的方式了,所以此标志并不会影响读取操作。POSIX额外定义了O_DSYNC和O_RSYNC,在Linux 上.这两个标志与O_SYNC是同义词。
O_TRUNC
如果文件存在,是常规文件,而且所指定的标志允许进行写入操作,则文件将被截短成零长度。对FIFO或终端设备使用O__TRUNC会被忽略。其他类型文件的用法并未被定义。同时指定O_RDONLY与O_TRUNC的用法也未被定义,因为你需要对文件进行写入操作以便截短它。


前面所列出的两种open (〉系统调用格式都是正确的。除非你是在创建文件,否则mode参数会被忽略﹔如果指定O_CREAT标志,则需要使用mode参数。如果你在使用O_CREAT时忘了提供mode参数,则其结果未定义,而且通常会很麻烦——所以不要忘了!
当一个文件被创建时,mode参数可用来为新创建的文件设定使用权限。

S_IRWXU
拥有者具有读取、写入和执行的权限。
S_IRUSR
拥有者具有读取的权限。
S_IWUSR
拥有者具有写入的权限。
S_IXUSR
拥有者具有执行的权限。
S_IRWXG
组具有读取、写入和执行的权限。
S_IRGRP
组具有读取的权限。
S_IWGRP
组具有写入的权限。
S_IXGRP
组具有执行的权限。
S_IRWXO
其他所有人具有读取、写人和执行的权限。
S_IROTH
其他所有人具有读取的权限。
S__IWOTH
其他所有人具有写入的权限。
S_IXOTH
其他所有人具有执行的权限。
磁盘实际的权限位 设定取决于“mode参数”与“用户的文件创建掩码(umask)的补码”进行binary-AND(二元AND逻辑运算)的结果。


二、creat()函数


在多数Linux架构中,creat ()是一个系统调用,系统调用的定义因架构而异。因此,尽管i386有一个creat ( )系统调用,但是Alpha并没有。当然,你可以在任何架构上使用creat( ),但是你所使用的可能是库函数而不是架构本身的系统调用。

    细心的铁子会发现,函数的名字漏掉了一个“e”。Unix 的创造者Thompson曾经开玩笑地说设计Unix时遗漏字母是他最大的遗憾。

下面是典型的creat ( )调用:

open( )与creat ( )会在执行成功时返回一个文件描述符。执行失败时,会返回-1并将errno 设定为适当的错误值。


三、read()进行读取操作


读取操作所使用的最基本且最常见的机制就是POSIX.1所定义的read ()系统调用:

    每次调用read ()就会从fd参数所引用文件的当前文件位置读取len个字节到 buf。执行成功后会返回写入 buf的字节数目,执行失败时会则返回–1并设定errno。文件位置会前进到从fd所读取的字节数目。如果fd所代表的对象不具有查找位置的能力(例如这是一个字符设备文件),则每次只会从“当前”位置读取数据。
    下面的范例会从文件描述符fd读取字节到word。所读进的字节数目等于 unsigned long数据类型的大小,在32位的Linux系统上这是4个字节的大小,在64位系统上这是8个字节的大小 。返回时,nr等于所读进的字节数目,如果发生错误则等于-1:


返回值

  • read ( )返回小于len的非零正数并不算错误。发生此情况的原因可能是:可供读取的字节数目小于len,系统调用可能收到信号而中断,管道可能坏了(如果 fd是管道)等。

  • 使用read()时,返回值为0的可能性是另一个应该考虑的因素。read()系统调用会返回0以指示到达了文件末端(end-of-file,常简写为EOF),在此情况下,当然没有字节可供读取。EOF并不算错误(因此不会以–1作为返回值),这表示文件位置已经超过了文件中最后一个有效偏移值,因此没有任何其他字节可供读取。

  • 以read ()调用读取len个字节,但是可供读取的字节尚未出现,则此调用将受到阻挡(进入休眠状态),直到出现可供读取的字节·(假设文件描述符被开启时并未进入非阻挡模式)。注意,这不同于返回EOF,也就是说,“no data available”(无数据可用)与“end of data”(到达数据末端)并不相同。就EOF的情况而言,这是指到达了文件末端﹔就受阻挡的情况而言,读取操作正在等待更多的数据——就像对一个socket或一个设备文件进行读取操作的情况。

  • 此调用的返回值等于len。所读取的len个字节会被存放到buf中。这是预期的结果。

  • 此调用的返回值小于len,但是大于零。所读取的字节会被存放到buf中。发生此情况的原因可能是:读取操作进行期间因收到信号而中断,读取操作进行期间发生了一个错误,可供读取的字节数目大于零但小于len,或者读取到len个字节之前先到达了文件末端。重新进行read( )调用(在buf和len的值根据前一次的进度做过更新的情况下)可把剩余的字节读进剩余的缓冲区或指出问题的原因。

  • 此调用的返回值为0。这表示EOF(到达了文件末端)。没有数据可读。

  • 此调用被阻挡,因为可供读取的数据目前尚未出现。在非阻挡模式中并不会发生此情况。

  • 此调用的返回值为–1,而且 errno被设为EINTR。这表示在任何字节被读取之前收到了一个信号。你可以再次进行此调用。

  • 此调用的返回值为-1,而且errno被设为EAGAIN。这表示读取操作被阻挡,因为可供读取的数据目前尚未出现,而且稍后应该再次进行此调用。此情况只发生在非阻挡模式中。

  • 此调用的返回值为-1,而且 errno被设为E工NTR或EAGAIN以外的其他值。这表示一个更严重的错误。


读取所有的字节


    这段程序代码可以处理5种可能会碰到的情况。程序的循环会从fd的当前文件位置读取len个字节到buf。它会继续读取完len个字节或直到抵达 EOF。如果所读取的字节数目大于零,但是小于len,则len的值会减去已读取的字节数目,buf的值会加上已读取的字节数目,并且再次进行此调用。如果此调用返回-1而且errno 等于EINTR,则不需要更新参数就可以再次进行此调用。如果此调用返回-1而且errno被设为任何其他值,则会调用perror()以便将相关说明输出至标准错误而且会终止循环。


非阻挡式读取操作

有时,程序设计者并不想让对read ( )的调用在尚无数据可用时受到阻挡。他们宁可让调用立即返回,指出尚无数据可用。这称为非阻挡式IO,它让应用程序可以在不受阻挡的情况下对多个文件进行IO操作,但是会因此错过另一个文件中的可用数据。
如果所指定的文件描述符是以非阻挡模式打开的(如果调用open ()时指定了O_NONBLOCK,参见“open ( )的标志”)而且可供读取的数据尚未出现,则read ()调用会返回-1并将errno 设定为EAGAIN来不被阻挡(也就是说不会进入休眠状态)。进行非阻挡式读取操作时你必须检查EAGAIN,否则你就会冒着“严重错误”与“只是缺乏数据”相混淆的风险。


其他错误值

read()执行失败时可能会设定的errno值包括:
EBADF
所指定的文件描述符无效或者未打开以备读取。
EFAULT
buf所提供的指针并非指向进行调用进程的地址空间范围内。
EINVAL
文件描述符被映射到一个不允许读取操作的对象。
EIO
发生了一个低级的IO错误。


read()的字节数量限制

size_t 与ssize_t 是POSIX所规定的数据类型。size_t 数据类型所存储的值用于度量以字节为单位的数目。ssize_t 数据类型则是size_t有正负号的版本(负值代表错误)。在32位的系统上,size_t 与ssize_t背后的C数据类型通常分别是unsigned int 与 int。因为这两种数据类型通常会一起使用,所以范围较小的ssize_t会对size_t 的范围造成限制。size_t的最大值是S工ZE_MAX,ssize_t的最大值是SS工ZE_MAX。如果len 的值大SSIZE_MAX,则read( )调用的结果未定义。在多数Linux系统上,SSIZE_MAX就是LONG_MAX,在32位的系统上,其值为0x7ffff。


四、write()进行写入操作


进行写入操作时,write ( )是最基本和最常见的系统调用。

进行write ( )调用,就会从buf开始将count 个字节写入fd 所指定文件的当前文件位置。如果文件背后的对象不支持查找位置的功能(例如字符设备),则总是会从头(head)写起。执行成功时,会返回所写入的字节数目,并以同样的方式更新文件位置。发生错误时,会返回-1并且为errno 设定适当的值。进行 write ( )调用有可能会返回0这个值,但是这个返回值并没有任何意义,它仅意味着被写入了0个字节。

如同read ( ) , write()也有最基本的用法:

和read ( )一样,这种用法也不太正确。调用者还需要检查部分写入的可能性:


部分写入


相比较于返回部分读取结果的read( )系统调用,write ()系统调用不太可能会返回部分写入的结果。此外,write ()系统调用也不会遇到EOF的情况。就常规文件而言,write ( )保证能够完成所要求的整个写入操作,除非有错误发生。因此,就常规文件而言,你并不需要以循环来进行写入操作。然而,就其他文件类型来说(例如,sockets ),可能需要使用循环才有办法保证实际写出所要求的所有字节。使用循环的另一个好处是第二次调用write ( )所返回的错误将可显示是何原因让第一次调用仅写入部分字节(但是,同样地,此情况并不常见)。

附加模式


如果以附加模式打开fd(使用O_APPEND标志),写入的动作并不会发生在文件描述符的当前文件位置,而会发生在当前文件的末端位置。
举例来说,假设有两个进程对同一个文件进行写入操作。不使用附加模式时,如果第一个进程将字节写入文件的末端,然后第二个进程进行同样的操作,则第一个进程的文件位置将不再指向文件的末端,而会指向文件的末端位置减去第二个进程所写入的字节数目后的位置。这就意味着,在没有明确采用任何同步化机制的情况下,不可以有多个进程对相同的文件进行附加操作,因为这将会造成竞争条件。
采用附加模式可避免此问题。它可以确保文件位置总是被设定为文件末端,所有写入操作总是附加到文件末端,即使存在多个写入者。你可以把前面的每个写入请求想成是会对文件位置进行不可分割的更新,因此文件位置会被更新成刚才所写入的字节的末端。这与下一个write(〉调用无关,因为文件位置的更新是自动进行的,但是如果因为某些奇怪的理由而让下一个调用是read ( ),那么可能就无法置身事外了。


非阻挡式写入操作


如果以非阻挡模式打开fd(使用O_NONBLOCK标志),而写人请求被阻挡,则write ()系统调用会返回-1且errno 会被设为EAGAIN。稍后应该再送出写人请求。一般来说,常规文件并不会发生此情况。


其他错误代码


其他值得注意的errno 值巴括:
EBADF
所指定的文件描述符无效或者未打开以备写入。
EFAULT
buf所提供的指针并非指向进行调用进程的地址空间范围内。
EFBIG
写入操作所产生的文件大小超过了每个进程文件大小上限或内部所实施的限制。
EINVAL
所指定的文件描述符被映射到一个不允许写入操作的对象。
EIO
发生了一个低级的IO错误。
ENOSPC
文件系统于背后所指定的文件描述符没有足够的空间可用。
EPIPE
所指定的文件描述符关联到读取端已经关闭的 pipe或socket。此时,进程还会收到SIGPIPE信号。SIGPIPE信号的默认行为是终止收到信号的进程。因此,只有在进程被明确要求去忽略、阻挡或处理S工GPTPE信号的情况下,进程才会收到此errno 值。

write()的字节数量限制

如果count 参数的值大于SSIZE_MAX,则write ()调用的结果未定义。调用write ( )时,如果将count 设为О,则此调用会立即返回0这个值。

五、 fsync() 与fdatasync()

确保数据能够写回磁盘的最简单方法就是使用POSIX.1b所定义的fsync ()系统调用:

调用fsync ( )可确保文件描述符fd所映射的文件中所有脏数据会被写回磁盘。文件描述符fd必须被打开以备写入。此调用会写回数据和元数据。
Linux还提供了fdatasync ()系统调用:

此系统调用和fsync()的行为一样,但是它只会刷新数据(flush data)。此调用无法保证元数据与磁盘是同步的,因此速度可能会更快。通常这样就够了。

返回值与错误代码

执行成功时,这两个调用都会返回0﹔执行失败时,这两个调用都会返回–1并且将errno的值设定为下面的其中一个:
EBADF
所指定的文件描述符无效或者未打开以备写入。
EINVAL
所指定的文件描述符被映射到–个不支持同步化机制的对象。
EIO
同步化期间发生了一个低级的I/O错误。这呈现了一个真正的IO错误,而且通常是在能够捕获到此类错误的地方。

sync()

虽然不是最理想的,但是应用范围较广的sync ()系统调用可让所有缓冲区与磁盘同步化:

此函数不需要参数,也没有返回值。它总是能成功并且返回,所有的缓冲区(包括数据和元数据)保证都会写回磁盘。
标准并没有要求sync ( )必须等到所有缓冲区全都被清空到磁盘才返回,标准只要求该调用启动将所有缓冲区交付给磁盘的进程。基于这个原因,标准通常建议进行多次同步化,以确保所有数据都已交付给磁盘。然而,Linux 会一直等到所有缓冲区都被交付为止。因此,调用一次sync()就够了。
注意,在忙碌的系统中,sync ( )可能需要花几分钟的时间才会完成。

O_SYNC标志

将O_SYNC标志传递给open ()表示文件的所有IO都需要同步化:

将O_SYNC标志传递给open ()表示文件的所有IO都需要同步化:
O_SYNC的功能是 :每次write ( 〉调用之后以及从调用返回之前隐式 (implicit)调用fsync ( )。然而,Linux内核所实现的O_SYNC的效率更高一点。

O_DSYNC与O_RSYNC

POSIX定义了另外两个与同步化I/O 相关的open ()标志:O_DSYNC与O_RSYNC。在Linux中,这两个标志被定义成O_SYNC的同义词,它们的行为也–样。
O_DSYNC标志指定每次写入操作之后只有一般数据需要同步化,不包括元数据。
O_RSYNC标志指定读取请求以及写入请求皆需要同步化。它必须从O_SYNC或O_DSYNC择一使用。

直接I/O

提供О__DIREC’T标志给open ( )可指示内核尽量减少I/O管理在场的机会。提供此标志时,会绕过页而缓存(page cache)直接启动用户空间缓冲区与设备之间的IO。所有I/O将会同步,直到操作完成后才会返回。
执行直接I/O时,请求的长度、缓冲区的调整以及文件的偏移量都必须是底层设备的扇区大小(通常就是512字节)的整数倍。

关闭文件

当程序用完文件描述符之后,它可以经close (〉系统调用取消文件描述符与相关联文件的映射关系:

调用close ( )可以取消已打开文件描述符fd 的映射关系,以及让进程与相关联文件分离。然后所指定的文件描述符不再有效,于是内核可以释放它,让它再次成为后续open ( )或creat ( ) 调用的返回值。close ( )调用会在执行成功时返回0,以及住发生错误时返回-1并且为errno 设定适当的值。它的用法很简单:


请注意,若文件已被刷新到磁盘,关闭文件的动作将毫无作用。如果应用程序想在关闭文件之前将文件提交给磁盘,那么它需要利用稍早在“同步化UO”一节中所讨论到的其中一个同步化选项。
错误值

标题

失败时的errno值有几种可能性。除了 EBADF(给定的文件描述符无效) ,最重要的错误值是 EITO(指出可能发生了与实际关闭动作无关的低级1/О错误) 。无论汇报的是何种错误,只要文件描述符是有效的,它总是会被关闭,而且.相应的数据结构也会被释放。
尽管POSIX允许,但是 close ()绝对不会返回EINTR

六、lseek()查找文件位置

lseek ()系统调用,通过它可将一个文件描述符的文件位置改定为指定的值。除了改变文件的位置,它不会执行其他动作,也不会启动任何I/O:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210507142247795.png

lseek()的行为取决于origin参数,此参数值可以是下面的其中之一:
SEEK_CUR
fd的当前文件位置会被设定为它的当前值加上 pos(其值可以是负数、零或正数)。pos的值为零时,返回的是当前的文件位置值。
SEEK__END
fd的当前文件位置会被设定为文件的当前长度加上 pos(其值可以是负数、零或正数)。pos的值为零时,偏移值(offset〉会被设定为文件的末端。
SEEK_SET
fd的当前文件位置会被设定为pos。pos 的值为零时,偏移值会被设定为文件的开头。
此调用执行成功时,会返回新的文件位置﹔执行错误时,会返回-1 并且将errno 设定为适当值。

错误值

发生错误时,lseek ( )会返回-1并且将errno 设定为下面的其中一-个值:
EBADF
指定的文件描述符并未引用一个已打开的文件描述符。
EINVAL
为origin指定的值并非SEEK_SET、SEEK_CUR、SEEK_END其中之一,或者由此找到的文件位置为负数。事实上,使用E工NVAL来表示这两种错误并不合适。前者几乎就是一个编译时的编程错误,而后者则代表比较诡异的运行时的逻辑错误。
EOVERFLOW
所产生的文件偏移值无法使用off_t来表示。这只会发生在32位架构上。然而文件位置仍会被更新,此错误只是表示文件位置无法被返回而已。
ESPIPE
所指定的文件描述符被关联到一个无法查找位置的对象,例如管道(pipe)、FIFO或socket。

针对特定位置的读取与写入

关于lseek ()的使用,Linux提供了read ( )和write ()系统调用的两种变体,它们会以文件位置为参数分别进行读取与写入。操作完成之后,它们并不会改变文件的位置。

截短文件

Linux提供了两个系统调用用来截短一个文件的长度,它们是各项POSIX标准(在不同程度上)所定义和规定的系统调用。它们是:

这两个系统调用会将指定的文件截短成ien所指定的长度。ftruncate()系统调用可用于处理fd 所指定的文件描述符,而且该文件描述符必须被打开以备写入。truncate ()系统调用可用于操作path 所指定的文件,而且.该文件必须是可写入的。执行成功时,这两个调用都会返回0;发生错误时,它们都会返回-1并且会将errno设定为适当的值。

七、多任务式I/O

多任务式I/O 让一个应用程序可以同时服务多个文件描述符,以及在其中有任何一个就绪可进行读取或写入时收到通知而不会受到阻挡。多任务式I/O因此成为了应用程序的支点,它的运作方式类似下面这样:
1.多任务式I/O:当这些文件描述符中有任何一个就绪可进行IO,请通知我。2.休眠,直到有一个或多个文件描述符准备妥当。
3.唤醒:什么i准备就绪了?
4.处理所有就绪可进行1/O的文件描述符而不会受到阻挡。5.回到步骤1,从头开始。
Linux提供了三种多任务式1/О解决方案:select、poll 以及 epoll接口。本章将说明前面两种接口,最后一种接口(属干高级Linux解决方案)。

select()

seiect ()系统调用提供了一个可用于实现多任务式同步IO的机制;


readfds  分组中的文件描述符用于查看是否有数据可供读取。
writefds  分组中的文件描述符用于查看写入操作是否可以完成而不会受到阻挡。
exceptfds  分组的文件描述符用于查看是否有异常发生,或者是否有紧急数据可用。


timeout参数是一个指向timeval结构的指针,该结构的定义如下所示:

如果此参数不是NULL,则select ()调用将在tv_sec秒与tv_usec微秒之后返回,即使尚无任何文件描述符就绪可进行IO。作为代价,此结构的状态跨系统调用会变成未定义的,因此每次调用之前必须重新予以初始化。


调用select (〉将受到阻挡,必须等到指定的文件描述符就绪可进行I/O,或者等到一个可指定的时间限额过去。

返回值与错误代码

执行成功时,select ( ) 会返回这三个分组中就绪可进行I/O 的文件描述符的数日。如果指定了timeoul参数,则返回值将会是0。发生错误时,此调用会返回-1并且将errno 设定为下面的其中一个值:
EBADF
分组中提供了一个无效的文件描述符。
EINTR
等待时捕捉到一个信号,你可以再次调用select ( )。
EINVAL
参数n是负数或者给定timeout是无效值。
ENOMEM
内存不足以完成这项请求。

select()范例

让我们来看一个范例程序,此范例平凡无奇但是功能齐全,足以说明select ( )的用法。此范例会等候stdin的输入数据5秒的时间。它只会检查单一文件描述符,因此它所进行的并非多任务式I/O、但是这样可明确表示此系统调用的用法:

#include <stdio.h>
#include <sys / time .h>
#include <sys/ types.h>
#include cunistd.h>
#defineT工MEOUT 5
* select的等待时间,以秒为单位*/
define BUF__LEN1024
/*读取缓冲区.,以字节为单位*/
int main (void)
struct timeval tv ;fd_set readfds ;int ret;
/*等候stdin的输人数据。*/
FD_ZERO(&readfds) ;
FD_SET(STDIN_FILENO,&readfds) ;
/*等候5秒的时间。*/
tv.tv_sec=T工MEOUT ;
tv.tv_usec = 0;
/*好了、开始提供服务!*/
ret = selecL ( STDIN_FILENO + l,&readfds,NUIL,NULL,&tv) ;
if i ret == -1) {perror("select" ) ;return 1:
}eise if i ! ret ) {printf ( "'d seconds elapsed .\n" , TINEOUT);
return 0;
)
/*我们的文件描述符可供读取了吗?
*(肯定可以,因为它是我们所提供的唯-- fd,
而且此调用会返回非零值,但是我们想幽自己-默。)*/
if (FD_ISSET(STDIN_FTTENO, &readfds)) {char buf [ BUF_LEN+1 ] ;
int len;
/*保证不会遭到阻挡*/
lon = rcad ( STDIN_FILENO, buf,BUF_LEN);
if ( len -: -1){perror (" rea3- );
return l;
)
if( len){buf[len]= ' \0 '
print f ( "read : %s\n" , buf ) ;
}
return 0;
fprintf istderr."This shou ]d not. happen ! \n");
return 1;
}

使用select()具可移植性的休眠机制

pselect()

select ( )系统调用是4.2BSD首先推出的,广受欢迎,但是POSIX 却在.POSIX1003.1g-2000以及之后的 POSIX 1003.1-2001中定义了自己的解决方案pselect ( ) :

pselect ( ) 与select ()之间有三点不同:

  1. pselect ( )为它的t imeout 参数使用的是timespec结构而不是timeva l
    结构。timespec结构采用的是秒和纳秒(nanosecond,即十亿分之一秒)级设定值,而不是秒和微秒(microsecond,即百万分之一秒)级设定值,理论上而言可以提供较佳的 timeout分辨率。然而事实上,这两种调用都只能可靠地提供微级的分辨率。
  2. 调用pselect ()的结果并不会改变timeout参数的值。因此,后续的调用并 不需要重新对此参数进行初始化。
  3. select ()系统调用并没有sigmask参数。就信号的处理而言,当此参数被设定为NULL时,pselect ( )的行为和select ( )一样。

timespec结构的定义如下所示:
#include <sys/time. h>
struct timeapec {
long tv_sec; /* seconds */
long tv nsec; /
* nanoseconds */
};

poll()

poll()系统调用是System V的多任务式I/O 解决方案。它解决了select() 的若干缺点,然而select()仍旧经常被用到(同样地,多半是出于习惯或者是基于可移植性):
#include<sys/poll.h>
int poll (struct pollfd fds,unsigned int nfds,int timeout);
不同于使用效率低下的三个基于位掩码的文件描述符分组, poll()使用的是nfds 个由fds指向的pollfd结构所组成的单一数组。pollfd结构的定义如下所示:
#include <sys/poll.h>
struct poll fd{
int fd; /
*文件描述符 * /
short events; /
*所要查看的事件 */
short revents; /
*返回所目击的事件 */
};
每个poll fd结构可用于指定一个要查看的文件描述符。

revents字段则是该文件描述符所日击事件的位掩码,内核会在返回时设定此字段。events字段中所要求的事件可能会从revents字段返回。有效的事件如下所示:

POLLIN
有数据可供读取。
POLLRDNORM
有一般数据可供读取。
POLLRDBAND
有优先数据可供读取。
POLLPRI
有紧急数据可供读取。
POLLOUT
写入操作将不受阻挡。
POLLWRNORM
写入一般数据将不受阻挡。
POLLWRBAND
写人优先数据将不受阻挡。
POLLMSG
有SIGPOLL消息可用。
此外,revents字段可能会返回下列事件:
POLLER
所指定的文件描述符发生错误。
POLLHUP
所指定的文件描述符发生挂起(hung up)事件。
POLLNVAL
所指定的文件描述符无效。

返回值与错误代码
执行成功时,po ll ()会返回有目击事件文件描述符(也就是其结构具有非零的revents 字段)的数目,如果在有任何事件发生之前此调用因逾时而返回,则会返回0。执行失败时,会返回-1并且将errno 设定为下面的其中一个值:
EBADF
在一个或多个结构中指定了无效的文件描述符。
EFAULT
指向fds的指针指向了进程地址空间以外的地方。
EINTR
所请求的事件发生之前收到了一-个信号。你可以重新进行此调用。
EINVAL
n fds参数的设定值超过了RLIMTT…NOFILE的限制。
ENOMEM
内存不足以完成这项请求。

poll()的范例

#include estaio.h>
#include cunistd.h>
#include <sys/ poll .h>
#dcfineTIMEXOUT5   /*poll 的等待时间,以秒为单位.*/
int main (void)
(
struct pollfd fds [2];
int ret ;
/*查看stdin 的输入*/
fds [ 0 ].id = STDIN__FILENO;
fds [ 0 ].events = POLLIN;
/*查看stdout是否可供写入(通常可以)*/
fds[ 1 ].fd = STDOUT_FILENO ;
fds [ 1 ].events = POLLOUT ;
/*都设定好了、开始提供服务!*/
ret = poll (fds,2,TIMEOUT *1000);
if (ret == -l){perror( "poll" );return 1 ;
}
if ( ! ret ){printf( "%d seconds elapsed.\n",TIMEOUT);return 0 ;
if(fas [0].revents & POLLIN)printf ( "stdin is reaclable \n");
if (fds [1].revents & POLLOUT)printf ( " stdout is writable \n");
return 0 ;
}

运行此程序,我们会得到如下的预期结果:
s ./ poll
st dout is wriLable
再运行一次此程序,不过这次将一个文件重定向至标准输入,于是我们会看到如下的结果:
s ./poll < ode_to_my_parrot .txt
stdin is readable
stdout is writable

ppoll()

如同 pselect ( ) ,Linux也提供了poll ( )的表亲ppoll ()。然而与 pselect ( )不同的是,ppoll()是Linux特有的接口:

#define _GNU_SOURCE
#include <sys/poll.h>
int ppoll(struct pollfd *fds,
nfds_t nfds,
const struct timespec *timeout,
const sigset_t  *sigmask) ;

比较poll()与select()

  1. poll ()并不需要用户计算并传递作为参数的最高编号的文件描述符的值加l。
  2. poll ()的效率优于采用最大值的文件描述符的做法。例如、你以select()来查看单一文件描述符的最大值为900——内核必须为每个分组检查900个位的设定状态。
  3. select ()的文件描述符分组的大小是固定的,这导致两难的权衡:如果太小,select ( )可查看的文件描述符数目就会大受限制;如果太大,就会没有效率。大型位掩码的处理很没效率﹐特别是在不知道位掩码设定的位是否太稀疏时,使用poll()则可创建–个大小刚好的数组。你只要查看一个条目,而且只需传递单一结构。
  4. 使用select ()会在返回时重新构建文件描述符分组,所以每个随后的调用必须重新初始化它们。poll()系统调用则把输入(events字段)与输出(revents字段)分离,并允许重复使用数组而不需要变更。
  5. 返回后,select ( )的 timeout参数会变成未定义的。所以具可移植性的程序代码需要重新对它进行初始化。然而,若使用pselect ( )则不会遇到此问题。

但是select (〉系统调用却具有以下优点:

  1. select ( )较具可移植性,因为有些Unix系统并不支持poll ( )。
  2. select (〉提供了较佳的 timeout分辨率:可以精确到微秒。尽管ppoll()与pselect()理论上可以提供纳秒级分辨率,但实际上它们甚至无法可靠地提供微秒级分辨率。

八、内核内部

  • 虚拟文件系统
    虚拟文件系统〈VFS),有时也称为虚拟文件切换系统(virtual file switch),是一个抽象的机制,它让Linux内核在调用文件系统函数以及操作文件系统数据的时候,不需要知道甚至关心所使用的是何种文件系统。
  • 页面缓存
    页面缓存(page cache)是一块内存存储区,用来临时存放最近从磁盘文件系统上访问的数据。磁盘的访问速度相当缓慢,尤其是相对于今日的处理器速度而言。将所请求的数据暂存在内存,让内核可以从内存来满足之后对相同数据所提出的访问请求,避免反复访问磁盘。
  • 页面写回
    内核的延后写入功能是通过缓冲区完成的。当一个进程送出写入请求时数据会被复制到一个缓冲区﹐该缓冲区会被标记成“已被改变”(dirty),这意味着内存中的副本比磁盘上的副本新。接着就会从写入请求返回。如果有另一个写入请求针对相同文件的同一个数据团,则缓冲区会被新数据所改变。如果写入请求是针对相同文件的其他地方,则会产生新的缓冲区。最后被改变的缓冲区需要提交给磁盘,让内存中的数据可以和磁盘上的文件同步。这就是所谓的“写回”( writcback)。
    两种情况下都会这么做:
    1.若可用内存空间已经缩减到低于可设定的阈值﹐则被改变的缓冲区会被写回磁盘,所以数据已经出清到磁盘的缓冲区可以被移除,释放内存空间。
    2.若被改变的缓冲区.的保存时间已经超过可设定的阈值,则该缓冲区会被写回磁盘。这样可避免数据永久维持在被改变的状态。

Linux自学笔记------Day02 文件I/O相关推荐

  1. Linux自学笔记 | 10 常用命令 - 压缩解压类

    Linux自学笔记 | 10 常用命令 - 压缩解压类 Linux自学笔记 | 01 文件系统和目录结构 Linux自学笔记 | 02 VIM编辑器的安装与使用 Linux自学笔记 | 03 Linu ...

  2. 嵌入式Linux自学笔记(二)——文件IO

    一.简介 本节讨论Unix系统中大多数文件用到的5个函数:open,read,write,lseek,close.本节描述的函数经常被称为不带缓冲的IO.不带缓冲指的是每个read和write都调用内 ...

  3. linux自学笔记(1)

    从今天起,开始linux的自学 先安装虚拟机VMware,打开配置好的Ubuntu镜像文件,进入Ubuntu系统,所有的操作均在终端命令行里执行 1)文件的命名规则:除了/,所有的字符都合法,大小写敏 ...

  4. Linux自学笔记——haproxy

    HAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠地一套解决方案.HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持 ...

  5. linux自学笔记——RAID级别特性以及软RAID的实现

    RAID,Redundant Arrays of Inexpensive Disks 廉价冗余磁盘阵列,又称为Redundant Arrays of Independent Disks,独立冗余磁盘阵 ...

  6. Linux自学笔记——Centos系统安装

    安装程序:anaconda bootloaderàkernel(initrd(rootfs))àanaconda anaconda的两种方式: tui:基于cureses的文本配置窗口: gui:图形 ...

  7. Linux学习笔记11——文件I/O之二

    一.文件共享 内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响. 1.每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述表 2.内 ...

  8. Linux学习笔记十三——文件压缩、解压缩和归档

    压缩.解压缩命令: 1.compress/uncompress:压缩格式为Z,文件后缀为.Z compress /path/to/file uncompress /path/to/file.Z 2.g ...

  9. Linux自学笔记——tcp wrapper

    tcp_wrapper:tcp包装器,是一个由wieste venema开发,旨在为unix/linux服务器提供防火墙服务的免费软件,它能够让系统管理员记录和控制wrappers支持基于tcp的服务 ...

最新文章

  1. Ubuntu中防火墙设置
  2. 大数据、云计算构筑百姓安全防护网
  3. python报错:TypeError: cant multiply sequence by non-int of type float(bug)(csdn标题没法用英文引号,以后注意别搜引号)
  4. 四、自然语言处理的主要挑战
  5. python生成器、迭代器、__call__、闭包简单说明
  6. 3亿Docker容器部署的挑战及应对方案
  7. 首次!海豚间像人类一样的交谈被水下麦克风记录
  8. python课件_如何20小时搞定Python量化期权实战?
  9. CIKM 2021 | 基于异质图学习的搜索广告关键词推荐
  10. 配置win2003 server IIS的总结,为什么IIs的工作进程会在空闲时间释放的问题。同时学会了throw的真正含义,throw的真正含义就是导致程序停止,崩溃,很简单,网摘也有记录。...
  11. 红米Note 8 Pro测评:论如何用1399元买到超大底四摄
  12. 深入浅出计算机组成原理03:处理器
  13. 无锁队列以及ABA问题
  14. 安卓TV开发(前言)— AndroidTV模拟器初识与搭建
  15. c语言中strncpy的用法,C语言中函数strcpy ,strncpy ,strlcpy的用法【转】
  16. ART-Pi 实现音乐播放器 --播放《天空之城》
  17. N年前的实习记录 - 职场生涯应如何规划?
  18. 用.net制作排序、分页及多条记录选择及删除的范例(不用.net内置的分页和排序机制)
  19. 目标和学习方法的重要性
  20. PS|你真的了解PS吗?

热门文章

  1. 关于计算机的论文 word,计算机网络专业论文(word文档)
  2. 【离散数学】Quasi-truth assignment 、Independence of Axioms
  3. 华硕游戏本如何一键重装Win10?
  4. 显示器接口_【知识小科普】什么是DP接口?DP接口可以转接那些接口?|显示器|mini|端口|amd|dvi...
  5. 推荐几个上谷哥的ip
  6. java舞伴配对_真心求助【舞伴问题】用JAVA实现
  7. 父类的引用指向子类的对象
  8. 代理记账公司主要做什么,原来有这么多项目
  9. 传说对决显示无法连接服务器,传说对决 -Arena of Valor-总是显示无法连接网络
  10. docker启动时出现open() “/usr/local/openresty/nginx/conf/nginx.conf“ failed (13: Permission deni