Linux内核的VFS子系统:
文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读或写一个文件时,使用open或creat返回的文件描述符标识该文件,将其作为参数传递给read或write。
按照惯例,UNIX系统shell使用文件描述符0与进程的标准输入相关联,文件描述符1与标准输出相关联,文件描述符2与标准出错相关联。这是各种shell以及很多应用程序使用的惯例,而与UNIX内核无关,如果不遵守这种惯例,那么很多UNIX系统应用程序就不能正常工作。
在依从POSIX的应用程序中,幻数0、1、2应当替换成符号常量STDIN_FILENO,
STDOUT_FILENO,
STDERR_FILENO。
这些常量都定义在头文件<unistd.h>中。
文件描述符的变化范围0~OPEN_MAX。早期的UNIX系统实现采用的上限值是19(允许每个进程最多打开20个文件),但现在很多系统则将其增至63个。
OPEN函数
调用open函数可以打开或创建一个文件。
#include<fcntl.h>
int open(const char *pathname, int oflag,...)
我们将第三个参数写为...,ISO C用这种方法表明余下参数的数量及其类型根据具体的调用会有所不同。对于open函数而言,仅当创建新文件时才使用第三个参数。在函数原型中将此参数放置在注释中。
pathname是要打开或创建文件的名字。oflag参数可用来说明此函数的多个选项。用下列一个或多个常量进行“或”运算构成oflag参数:
O_RDONLY //只读打开
O_WRONLY //只写打开
O_RDWR //读、写打开
在这三个常量中必须指定一个且只能指定一个。下列常数则是可选择的。
O_APPEND //每次写时都追加到文件的尾端
O_CREAT //若此文件不存在,则创建它。使用时,需要第三个参数
O_EXCL //如果同时指定了O_CREAT,而文件已经存在,则会出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件,这使测试和创建者两者成为一个原子操作。
O_TRUNC //如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0。
O_NOCTTY //如果pathname指的终端设备,则不将该设备分配作为此进程的控制终端。
O_NONBLOCK //如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次操作和后续的I/O操作设置非阻塞模式。
O_DSYNC //使每次write等待物理I/O操作完成,但是如果写操作并不影响读取刚写入的数据。则不等待文件属性被更新。
O_RSYNC //使每一个以文件描述符作为参数的read操作等待,直到任何对文件同一部分进行的未决写操作都完成。
O_SYNC //使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O。
由open返回的文件描述符一定是最小的未用描述符数值。这一点被某些应用程序在 标准输入,标准输出,标准出错输出上打开新的文件。例如,一个应用程序可以先关闭标准输出(通常是文件描述符1),然后打开另一个文件,这行打开操作前就能了解到该文件一定会在文件描述符1上打开。
文件名和路径名截短
如果NAME_MAX是14,而我们却试图在当前目录中创建一个其文件名包含15个字符的新文件,此时会发生什么哪?按照传统,早期的系统V版本允许这种使用方法,但总是将文件名截短为14个字符,而且不给出任何信息,而BSD类的系统则返回出错状态,并将errno设置为ENAMETOOLONG。无声无息地截短文件名会引起问题,而且他不仅仅影响到创建新文件。如果NAME_MAX是14,并且存在一个其文件名恰好就是14个字符的文件,那么以pathname作为其参数的任意函数都无法确定该文件的原始名是什么? 其原因是这些函数无法判断该文件名是否被截短过。
在POSIX.1种,常量_POSX_NO_TRUNC决定了是要截短过茶国内的文件名或路径名,还是返回一个出错。
creat函数
也可调用creat函数创建一个新文件
#include<fcntl.h>
int creat (const char *pathname, mode_t mode)
此函数相当于
open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode)
create的不足之处是它以只写方式打开所创建的文件。在提供open的新版本之前如果要创建一个临时文件,并要写该文件,然后又读该文件,则必须先调用creat、close、open。现在则可用下列方式调用open
open(pathname, O_RDWR|O_CREAT|O_TRUNC, mode)
close函数
可调用close函数关闭一个打开的文件:
#include<unistd.h>
int close(int filedes)
关闭一个文件时还会释放该进程加在该文件上的所有记录锁
当一个进程终止时,内核自动关闭它所有打开的文件。很多程序都利用了这一功能而不显示地用close关闭打开文件。
lseek函数
每个打开的文件都有一个与其相关联的“当前文件偏移量”。它通常是一个非负数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。按系统的默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
可以调用lseek显示地位一个打开的文件设置其偏移量。
#include<unistd.h>
off_t lseek(int filedes, off_t offset, int whence);
对参数offset的解释与参数whence的值有关。
若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处的offset个字节。
若whence是SEEK_CUR,则该文件的偏移量设置为其当前值加offset,offset可为正或负。
若whence是SEEK_END,则将该文件的偏移量设置为文件长度加offset,offset可为正或负。
若lseek成功执行,则返回新的文件偏移量,为此可以用下列方式确定打开文件的当前偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
这种方法也可用来确定所涉及的文件是否可以设置偏移量。如果文件描述符引用的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。
实例:程序用于测试能否对其标准输入设置偏移量。
#include"apue.h"
int main()
{
if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("cannot seek/n");
else
printf("seek OK/n");
exit(0);
}
通常,文件的偏移量应当是一个非负整数,但是,某些设备也可能允许负的偏移量。但对于普通文件,则其偏移量必须是非负值。因为偏移量可能是负值,所以在比较lseek的返回值时应当谨慎,不要测试它是否小于0,而要测试它是否等于-1。
lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。然后,该偏移量用于下一个读或写操作。
文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被读为0。
文件中的空洞并不要求在磁盘上占用储存区。具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于原文件尾端和新开始写位置之间的部分则不需要分配磁盘块。
实例 创建一个具有空洞的文件
#include"apue.h"
#include"fcnl.h"
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int main()
{
int fd;
if ((fd = creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error");
if (write (fd, buf1, 10) != 10)
err_sys("buf1 write error");
if (lseek(fd,16384, SEEK_SET) == -1)
err_sys("lseek error");
if (write(fd, buf2, 10) != 10)
err_sys("buf2 write error");
exit(0);
}
read函数
调用read函数从打开文件中读数据。
#include<unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);
有多种情况可使实际读到的字节数少于要求读的字节数:
读普通文件时,在读到要求字节数之前已经到达了文件尾端。例如,若在到达文件尾端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将回0。
当从终端设备读时,通常一次最多读一行
当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
当从某些面向记录的设备(例如磁盘)读时,一次最多返回一个记录。
当某一信号草成终端,而已经读了部分数据量时。读操作从文件的当前偏移量出开始,在成功返回之前,该偏移量将增加实际独到的字节数
write函数
调用write函数向打开的文件写数据。
#include"unistd.h"
ssize_t write(int filedes, const void *buf, size_t nbytes)
对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。
I/O的效率
程序清单3-3中的程序使用read和write函数负值一个文件。关于该程序应注意下列各点:
它从标准输入读,写至标准输出,这就假定在执行本程序之前,这些标准输入、输入已由shell安排好。确实,所有常用的UNIX系统shell都提供一种方法,他在标准输入上打开文件用于读,在标准输出上创建一个文件。这使得程序不必自行打开输入和输出文件。
很多应用程序假定标准输入是文件描述符0,标准输出是文件描述符1。本实例中则使用STDIN_FILENO和STDOUT_FILENO.
为考虑进程终止时,UNIX系统内核会关闭该进程的所有打开文件描述符,所以此实例并不会关闭输入和输出的文件。
对UNIX系统内核而言,文本文件和二进制代码文件并无区别,所以本实例对这两种文件都能工作
实例 将标准输入复制到标准输出
#include"apue.h"
#define BUFFSIZE 4096
int main()
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf ,BUFFSIZE)) >0)
if ( write(STDOUT_FILENO,buf, n) !=n)
err_sys("write error");
if (n <0)
err_sys("read error");
exit(0);
}
我们没有回答的一个问题是如何选取BUFFSIZE值。在回答此问题之前,让我们先用各种不同的BUFFSIZE值来运行此程序。
用程序读文件,其标准输出被重新定向到/dev/null上。此测试所用的文件时Linux ext2文件系统,其块长为4096字节。系统CPU时间的最小值出现在BUFFSIZE为4096处,继续增加缓冲区几乎没有影响。
大多数文件系统为改善其性能都采用某种预读技术,当检测到正进行顺序读取时,系统就试图读入鼻应用程序所要求的更多数据,并假想应用程序很快就会读这些数据。当BUFFSIZE为128KB后,预读停止了,这对读操作的性能产生了影响。
文件共享
UNIX系统支持在不同进程间共享打开的文件。在介绍dup之前,先要说明这种共享。为此先介绍内核用于所有I/O的数据结构。
内核使用三种数据结构表示打开的文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符向关联的是:
文件描述符标志。
指向一个文件表的指针。
(2)内核为所有打开文件维持一张文件表。每个文件表项包含:
文件状态标志(读、写、添写、同步和非阻塞等)。
当前文件偏移量。
指向该文件v节点表项的指针。
(3)每个打开文件(或设备)都有一个v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针。对于大多数文件,v节点还包含了该文件的i节点。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可提供使用的。例如,i节点包含了 文件的所有者 、文件长度、文件所在的设备、指向文件实际数据块在磁盘上所在位置的指针等等。
我们忽略了某些实现细节,但这并不影响我们的讨论。例如,打开文件描述符表可存放在用户空间。而非进程表中。这些表也可以用多种方式实现,不必一定是数组;例如,可将他们实现为结构的链表。
图3-1显示了一个进程的三张表之间的关系。该进程有两个不同的打开文件:一个文件打开为标准输入,另一个打开为标准输出。从UNIX系统的早期版本以来,这三张表之间的基本关系一直保持至今。这种安排对于在不同进程之间共享文件的方式非常重要。
如果两个独立进程各自打开了同一个文件,则有图3-2所示的安排。我们假定第一个进程在文件描述符3上打开该文件,而另一个进程则在文件描述符4上打开该文件。打开该文件的每个进程都得到一格文件表项,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排是每个进程都有它自己的对该文件的当前偏移量。
给出了这些结构后,现在对前面所述的操作作进一个说明。
在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。如果这使当前文件偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量。
如果用O_APPEND标志打开了一个文件。则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作时,在文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。这就使得每次写的数据添加到文件的当前端处。
若一个文件用lseek定位到卫检当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。
lseek函数只修改文件表项中的当前文件偏移量,没有进行任何i/O操作。
可能有多个文件描述符项指向同一个文件表项。 //后续介绍
注意,文件描述符标志和文件状态标志在作用域方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。 //后续介绍
本节上面所述的一切对于多个进程读同一个文件都能正常工作。每个进程都有它自己的文件表项,其中也有自己的当前文件偏移量,。但是,当多个进程写同一个文件时,则可能产生预期不到的效果。为了说明如何避免这种情况,需要理解原子操作的概念。
原子操作
添写至一个文件
考虑一个进程,他要将数据添加到一个文件尾端。早期的UNIX系统版本并不支持open的O_APPEND选项,所以程序被编写为下列形式:
if(lseek(fd,0L,2) < 0)
err_sys(lseek error);
if(write(fd , buf, 100) !=100)
err_sys("write error");
对单个进程而言,这段程序能正常工作,但若有多个进程同时使用这种方法将数据添加到用一个文件,则会擅胜问题。(例如,若此程序由多个进程同时执行,各自将消息添加到一个日志文件中,就会产生这种情况)。
假定有两个独立的进程A和B都对同一个文件进行添加操作。每个进程都己打开了该文件,但未使用O_APPEND标志。此时,各数据结构之间的关系如图3-2所示。每个进程都有它自己的文件表项,但是共享一个v节点项。假定进程A调用了lseek,他将进程A的该文件当前偏移量设置为1500字节。然后内核切换进程是进程B运行。进程B执行lseek也将其对该文件的当前便宜设置为1500字节。然后B调用write,他将B的该文件当前文件的增至1600。因为该文件的长度已经增加了,所以内核对v节点中的当前文件长度更新为1600。然后内核又进行进程切换使进程A恢复运行。当A调用write时,就从其当前文件偏移量(1500)处将数据写到文件中去。这样也就替换了进程B刚写到该文件中的数据。
问题出在逻辑操作“定位到文件尾端处,然后写”上,他使用了两个分开的函数调用。解决问题的方法是使这2两个操作对于其他进程而言成为一个原子操作。任何一个需要多个函数调用的操作都不可能是原子操作,因为在两个函数调用之间,内核有可能会临时挂起该进程。
UNIX系统提供了一种方法使这种操作成为原子操作,该方法是在打开文件时设置O_APPEND标志。正如前一节中所述,这就使内核每次对这种文件进行写之前,都将进程的当前偏移量设置到该文件的尾端处,于是在每次写之前就不再需要调用lseek。
pread和pwrite函数
#include<unistd.h>
ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int filedes, void *buf, size_t nbytes, off_t offset);
调用pread相当于顺序调用lseek和read,但是pread又与这种顺序调用有下列重要区别:
调用pread时,无法中断其定位和读操作。
不更新文件指针。
调用pwrite相当于顺序调用lseek和write,但也与他们有类似的区别。
创建一个文件
在对open函数的O_CREAT和O_EXCL选项进行说明时,我们己见到另一个有关原子操作的例子。当同时指定这两个选项,而该文件又已经存在时,open将失败。我们曾提及检查该文件是否存在以及创建该文件这两个操作时作为一个原子操作执行的。如果没有这样一个原子操作,那么可能会编写下列程序段:
if((fd = open(pahtname, O_WRONLY)) < 0){
if(errno == ENOENT){
if((fd = creat(pathname,mode)) < 0)
err_sys(...);
}else{
err_sys(...);
}
}
如果在open和creat之间,另一个进程创建文件,那么就会引起问题。例如,若在这2两个函数调用之间,另一个进程创建了该文件,并且写进了一些数据,然后,原先的进程执行这段程序中的creat,这时,刚由另一个进程写上去的数据会被擦去。如若将这两者合并在一个原子操作中,这种问题也就不会产生。
一般而言,原子操作指的是由多步组成的操作。如果该操作原子地执行,则那么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤地一个子集。
dup和dup2函数
下面两个函数都可用来复制一个现存的文件描述符:
#include"unistd.h"
int dup(int filedes);
int dup2(int filedes, int filedes2)
有dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用filedes2参数指定新描述符。如果filedes2已经打开,则将其关闭。若filedes等于filedes2,则dup2返回filedes2,而不关闭它。
这些函数返回的新文件描述符与参数filedes共享同一个文件表项。图3-3显示了这种情况。
newfd = dup(1);
此函数开始执行时,假定下一个可用的描述符是3。因为两个描述符指向同以文件表项,所以他们共享同一文件状态以及同以当前便宜量。
每个文件描述符都有它自己的一套文件描述符标志。正如我们将在下一节中说明的那样,新描述符的执行时关闭标志总是有dup函数清除。
复制一个描述符的另一种方法是使用fcntl函数实际上调用
dup(filedes);
等效于
fcntl(filedes, F_DUPFD, 0);
而调用
dup2(filedes,filedes2);
等效于
close(filedes2);
fcntl(filedes,F_DUPFD,filedes2);
后一种情况下,dup2并不完全等同于close加上fcntl。
sync、fsync和fdatasync函数
传统的UNIX实现在内核中没有缓冲区高速缓存或叶面高速缓存,大多数磁盘I/O都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或当内核需要充用该缓冲区以便存放其他磁盘块数据时,在将该缓冲排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称为延迟写。
延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写道磁盘上。当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync函数。
#include"unistd.h"
int fsync(int filedes);
int fdatasync(int filedes);
void sync();
sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
通常称为update的系统受沪进程会周期性地调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。
fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。
fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外fsync还会同步更新文件的属性。
fcntl函数
fcntl函数可以改变已打开的文件的性质。
#include"fcntl.h"
int fcntl(int filedes, int cmd,...);
fcntl函数有5种功能:
复制一个现有的描述符(cmd = F_DUPFD)
获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD)
获得/设置文件状态标志(cmd = F_GETFL或F_SETFL)
获得/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN)
获得/设置记录锁(cmd = F_GETLK F_SETLK F_SETLKW)
我们先说明这10种cmd值中前7种我们将涉及与进程表项中各文件描述符向关联的文件描述符标志,以及每个文件表项中的文件状态标志
F_DUPFD:
复制文件描述符filedes。新文件描述符作为函数值返回。它是尚未打开的各描述符中最小值。新描述符与filedes共享同一文件表项。但是,新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符被清除(这表示该描述符在通过一个exec时仍保持有效)
F_GETFD:
对应于filedes的文件描述符标志作为函数值返回。当前只订一乐一个文件描述符标志FD_CLOEXEC
F_SETFD
对于filedes设置文件描述符标志。新标志值按第三个参数设置。
F_GETFL
对应于filedes的文件状态标志作为函数值返回。在说明open函数时,已说明了文件状态。
不幸的是,三个访问方式标志(O_RDONLY O_WRONLY O_RDWR)并不各占1位。因此首先必须用屏蔽字O_ACMODE取得访问模式位,然后将结果与这三种值中的任一种作比较。
F_SETFL
将文件状态标志设置为第三个参数的值(取为正数值)。可以更改的几个标志是:O_APPEND/O_NONBLOCK/O_SYNC/O_DYSNC/O_RSYNC/O_FSYNC/O_ASYNC。
F_GETOWN
取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。
F_SETOWN
设置接收SIGIO和SIGURG信号的进程ID或进程组ID。正的arg指定一个进程ID,负的arg表示等于arg绝对值的一个进程组ID。
fcntl使用:
来源:http://www.cnblogs.com/andtt/articles/2178875.html
功能描述:根据文件描述词来操作文件的特性。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
[描述]
fcntl()针对(文件)描述符提供控制。参数fd是被参数cmd操作(如下面的描述)的描述符。针对cmd的值,fcntl能够接受第三个参数int arg。
[返回值]
fcntl()的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
F_DUPFD 返回新的文件描述符
F_GETFD 返回相应标志
F_GETFL , F_GETOWN 返回一个正的进程ID或负的进程组ID
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).
1. cmd值的F_DUPFD :
F_DUPFD 返回一个如下描述的(文件)描述符:
·最小的大于或等于arg的一个可用的描述符
·与原始操作符一样的某对象的引用
·如果对象是文件(file)的话,则返回一个新的描述符,这个描述符与arg共享相同的偏移量(offset)
·相同的访问模式(读,写或读/写)
·相同的文件状态标志(如:两个文件描述符共享相同的状态标志)
·与新的文件描述符结合在一起的close-on-exec标志被设置成交叉式访问execve(2)的系统调用
实际上调用dup(oldfd);
等效于
fcntl(oldfd, F_DUPFD, 0);
而调用dup2(oldfd, newfd);
等效于
close(oldfd);
fcntl(oldfd, F_DUPFD, newfd);
2. cmd值的F_GETFD和F_SETFD:
F_GETFD 取得与文件描述符fd联合的close-on-exec标志,类似FD_CLOEXEC。如果返回值和FD_CLOEXEC进行与运算结果是0的话,文件保持交叉式访问exec(),否则如果通过exec运行的话,文件将被关闭(arg 被忽略)
F_SETFD 设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定,应当了解很多现存的涉及文件描述符标志的程序并不使用常数 FD_CLOEXEC,而是将此标志设置为0(系统默认,在exec时不关闭)或1(在exec时关闭)
在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值,然后按照希望修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。
3. cmd值的F_GETFL和F_SETFL:
F_GETFL 取得fd的文件状态标志,如同下面的描述一样(arg被忽略),在说明open函数时,已说明
了文件状态标志。不幸的是,三个存取方式标志 (O_RDONLY , O_WRONLY , 以及O_RDWR)并不各占1位。(这三种标志的值各是0 , 1和2,由于历史原因,这三种值互斥 — 一个文件只能有这三种值之一。) 因此首先必须用屏蔽字O_ACCMODE相与取得存取方式位,然后将结果与这三种值相比较。
F_SETFL 设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND,O_NONBLOCK,O_SYNC 和 O_ASYNC。而fcntl的文件状态标志总共有7个:O_RDONLY , O_WRONLY , O_RDWR , O_APPEND , O_NONBLOCK , O_SYNC和O_ASYNC
可更改的几个标志如下面的描述:
O_NONBLOCK 非阻塞I/O,如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,则read或write调用将返回-1和EAGAIN错误
O_APPEND 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
O_DIRECT 最小化或去掉reading和writing的缓存影响。系统将企图避免缓存你的读或写的数据。如果不能够避免缓存,那么它将最小化已经被缓存了的数据造成的影响。如果这个标志用的不够好,将大大的降低性能
O_ASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候
4. cmd值的F_GETOWN和F_SETOWN:
F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回的是负值(arg被忽略)
F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明(arg绝对值的一个进程组ID),否则arg将被认为是进程id
5. cmd值的F_GETLK, F_SETLK或F_SETLKW: 获得/设置记录锁的功能,成功则返回0,若有错误则返回-1,错误原因存于errno。
F_GETLK 通过第三个参数arg(一个指向flock的结构体)取得第一个阻塞lock description指向的锁。取得的信息将覆盖传到fcntl()的flock结构的信息。如果没有发现能够阻止本次锁(flock)生成的锁,这个结构将不被改变,除非锁的类型被设置成F_UNLCK
F_SETLK 按照指向结构体flock的指针的第三个参数arg所描述的锁的信息设置或者清除一个文件的segment锁。F_SETLK被用来实现共享(或读)锁(F_RDLCK)或独占(写)锁(F_WRLCK),同样可以去掉这两种锁(F_UNLCK)。如果共享锁或独占锁不能被设置,fcntl()将立即返回EAGAIN
F_SETLKW 除了共享锁或独占锁被其他的锁阻塞这种情况外,这个命令和F_SETLK是一样的。如果共享锁或独占锁被其他的锁阻塞,进程将等待直到这个请求能够完成。当fcntl()正在等待文件的某个区域的时候捕捉到一个信号,如果这个信号没有被指定SA_RESTART, fcntl将被中断
当一个共享锁被set到一个文件的某段的时候,其他的进程可以set共享锁到这个段或这个段的一部分。共享锁阻止任何其他进程set独占锁到这段保护区域的任何部分。如果文件描述符没有以读的访问方式打开的话,共享锁的设置请求会失败。
独占锁阻止任何其他的进程在这段保护区域任何位置设置共享锁或独占锁。如果文件描述符不是以写的访问方式打开的话,独占锁的请求会失败。
结构体flock的指针:
struct flcok
{
short int l_type; /* 锁定的状态*/
//以下的三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET, l_start=0, l_len=0
short int l_whence; /*决定l_start位置*/
off_t l_start; /*锁定区域的开头位置*/
off_t l_len; /*锁定区域的大小*/
pid_t l_pid; /*锁定动作的进程*/
};
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置
fcntl文件锁有两种类型:建议性锁和强制性锁
建议性锁是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。
强制性锁是由内核执行的:当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。
系统默认fcntl都是建议性锁,强制性锁是非POSIX标准的。如果要使用强制性锁,要使整个系统可以使用强制性锁,那么得需要重新挂载文件系统,mount使用参数 -0 mand 打开强制性锁,或者关闭已加锁文件的组执行权限并且打开该文件的set-GID权限位。
建议性锁只在cooperating processes之间才有用。对cooperating process的理解是最重要的,它指的是会影响其它进程的进程或被别的进程所影响的进程,举两个例子:
(1) 我们可以同时在两个窗口中运行同一个命令,对同一个文件进行操作,那么这两个进程就是cooperating processes
(2) cat file | sort,那么cat和sort产生的进程就是使用了pipe的cooperating processes
使用fcntl文件锁进行I/O操作必须小心:进程在开始任何I/O操作前如何去处理锁,在对文件解锁前如何完成所有的操作,是必须考虑的。如果在设置锁之前打开文件,或者读取该锁之后关闭文件,另一个进程就可能在上锁/解锁操作和打开/关闭操作之间的几分之一秒内访问该文件。当一个进程对文件加锁后,无论它是否释放所加的锁,只要文件关闭,内核都会自动释放加在文件上的建议性锁(这也是建议性锁和强制性锁的最大区别),所以不要想设置建议性锁来达到永久不让别的进程访问文件的目的(强制性锁才可以);强制性锁则对所有进程起作用。
fcntl使用三个参数 F_SETLK/F_SETLKW, F_UNLCK和F_GETLK 来分别要求、释放、测试record locks。record locks是对文件一部分而不是整个文件的锁,这种细致的控制使得进程更好地协作以共享文件资源。fcntl能够用于读取锁和写入锁,read lock也叫shared lock(共享锁), 因为多个cooperating process能够在文件的同一部分建立读取锁;write lock被称为exclusive lock(排斥锁),因为任何时刻只能有一个cooperating process在文件的某部分上建立写入锁。如果cooperating processes对文件进行操作,那么它们可以同时对文件加read lock,在一个cooperating process加write lock之前,必须释放别的cooperating process加在该文件的read lock和wrtie lock,也就是说,对于文件只能有一个write lock存在,read lock和wrtie lock不能共存。
下面的例子使用F_GETFL获取fd的文件状态标志。
#include<fcntl.h>
#include<unistd.h>
#include<iostream>
#include<errno.h>
using namespace std;
int main(int argc,char* argv[])
{
int fd, var;
// fd=open("new",O_RDWR);
if (argc!=2)
{
perror("--");
cout<<"请输入参数,即文件名!"<<endl;
}
if((var=fcntl(atoi(argv[1]), F_GETFL, 0))<0)
{
strerror(errno);
cout<<"fcntl file error."<<endl;
}
switch(var & O_ACCMODE)
{
case O_RDONLY : cout<<"Read only.."<<endl;
break;
case O_WRONLY : cout<<"Write only.."<<endl;
break;
case O_RDWR : cout<<"Read wirte.."<<endl;
break;
default : break;
}
if (val & O_APPEND)
cout<<",append"<<endl;
if (val & O_NONBLOCK)
cout<<",noblocking"<<endl;
cout<<"exit 0"<<endl;
exit(0);
}