赞
踩
摘要
介绍了原子操作的概念
指定O_EXCL 标志调用 open(),这确保了调用者就是文件的创建者。
指定 O_APPEND 标志来调用 open(),还确保了多个进程在对同一文件追加数据时不会覆盖彼此的输出。
系统调用 fcntl()可以执行许多文件控制操作,其中包括:修改打开文件的状态标志、复制文件描述符。使用 dup()和 dup2()系统调用也能实现文件描述符的复制功能。
研究了文件描述符、打开文件句柄和文件 i-node 之间的关系,并特别指出这 3 个对象各自包含的不同信息。文件描述符及其副本指向同一个打开文件句柄,所以也将共享打开文件的状态标志和文件偏移量。
之后描述的诸多系统调用,是对常规 read()和 write()系统调用的功能扩展。pread()和pwrite()系统调用可在文件的指定位置处执行 I/O 功能,且不会修改文件偏移量。readv()和writev()系统调用实现了分散输入和集中输出的功能。preadv()和 pwritev()系统调用则集上述两对系统调用的功能于一身。
使用 truncate() 和 ftruncate()系统调用,既可以丢弃多余的字节以缩小文件大小,又能使用填充为 0 的文件空洞来增加文件大小。
本章还简单介绍了非阻塞 I/O 的概念,后续章节中还将继续讨论。LFS 规范定义了一套扩展功能,允许在 32 位系统中运行的进程来操作无法以 32 位表示的大文件。
运用虚拟目录/dev/fd 中的编号文件,进程就可以通过文件描述符编号来访问自己打开的文件,这在 shell 命令中尤其有用。
mkstemp()和 tmpfile()函数允许应用程序去创建临时文件
所有执行I/O操作的系统调用都以文件描述符,来指代打开的文件.针对每个进程文件描述符都自成一套
大多数程序都期望使用3种文件描述符
文件描述符 | 用途 | POSIX名称 | stdio流 |
---|---|---|---|
1 | 标准输入 | STDIN_FILENO | stdin |
2 | 标准输出 | STDOUT_FILENO | stdout |
3 | 标准错误 | STDERR_FILENO | stderr |
在程序中指代这些文件描述符时,可以使用数字(012)表示,或采用<unistd.h>定义的标准名称
编程语言和软件包通常会利用 I/O 函数库对它们进行间接调用
函数打开pathname所标识的文件,并返回文件描述符
用以在后续函数中指代打开的文件.如果文件不存在,open()函数可以创建之,这取决于对位掩码参数 flags 的设置。flags 参数还可指定文件的打开方式:只读、只写亦或是读写方式。mode 参数则指定了由 open()调用创建文件的访问权限,如果 open()函数并未创建文件,那么可以忽略或省略 mode 参数
flags参数表示打开文件所采用的操作,我们需要注意的是:必须指定以下三个常量的一种,且只允许指定一个
O_RDONLY:只读模式
O_WRONLY:只写模式
O_RDWR:可读可写
以下的常量是选用的,这些选项是用来和上面的必选项进行按位或起来作为flags参数。
S_IRWXU 00700 用户(文件所有者)有读、写和执行权限
S_IRUSR 00400 用户有读权限
S_IWUSR 00200 用户有写权限
S_IXUSR 00100 用户有执行权限
S_IRWXG 00070 组有读、写和执行允许
S_IRGRP 00040 组有读权限
S_IWGRP 00020 组有写权限
S_IXGRP 00010 组有执行权限
S_IRWXO 00007 其他人已读、写、执行允许
S_IROTH 00004 其他人有读权限
S_IWOTH 00002 其他人有写权限
S_IXOTH 00001 其他人有执行权限
O_EXCL 表示如果要创建的文件已存在,则出错,同时返回 -1,并且修改 errno 的值。
O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)
以下三个常量同样是选用的,它们用于同步输入输出
O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
O_RSYNC read 等待所有写入同一区域的写操作完成后再进行
O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O
3、mode:
mode参数表示设置文件访问权限的初始值,和用户掩码umask有关,比如0644表示-rw-r–r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,有以下几点
文件权限由open的mode参数和当前进程的umask掩码共同决定。
第三个参数是在第二个参数中有O_CREAT时才作用,如果没有,则第三个参数可以忽略
调用从 fd 所指代的打开文件中读取至多 count 字节的数据,并存储到 buffer 中。read()调用的返回值为实际读取到的字节数。如果再无字节可读(例如:读到文件结尾符 EOF 时),则返回值为 0
调用buffer中count字节的数据写入由fd所指向的以打开文件中.write()调用的返回值为实际写入文件的字节数,可能小于count.
在文件所有操作完成后,调用close()释放文件描述符fd以及与之相关的核心资源
使用I/O系统调用 #include<sys/stat.h> #include<fcntl.h> #include"tlpi_hdr.h" #ifndef BUF_SIZE #define BUF_SIZE 1024 #endif int main(int argc,char ** argv) { int inputFd,outputFd,openFlags; mode_t filePerms; ssize_t numRead ; char buf[BUF_SIZE]; if(argc != 3||strcmp(argv[1],"--help")==0) usageErr("%s old-file new-file \n ",argv[0]); inputFd=open(argv[1],O_RDONLY); if(inputFd == -1) errExit ("opening file %s ",argv[1]); openFlags = O_CREAT | O_WRONLY | O_TRUNC;//创建|只写|截断 filePerms = S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; outputFd = open(argv[2],openFlags,filePerms); if(optputFd == -1) errExit("opening file %s" ,argv[2]); while ((numRead=read(inputFd,buf,BUF_SIZE))>0 ) if(write(outputFd,buf,numRead)!=numRead) fatal("couldn't write whole buffer"); if(numRead == -1) errExit("read"); if(close(inputFd)==-1) errExit("close input"); if(close(outputFd)==-1) errExit("close output"); exit(EXIT_SUCCESS); }
要打开的文件由参数pathname来标识 如果pathname是一符号连接,会对其进行解引用 调用成功open()返回一文件标识符,用于在后续函数调用中指代该文件. 若发生错误,则返回-1, 并将errno设置为相应的错误标志.
flag为掩码,用于指定文件的访问模式
访问模式 (flag)
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
count参数指定最多能读取的字节数.(size_t数据类型属于无符号整数类型)buffer参数提供用来存放输入数据的内存缓冲区地址,缓冲区至少拥有count字节.
如果 read()调用成功,将返回实际读取的字节数,如果遇到文件结束(EOF)则返回 0,如果出现错误则返回-1。ssize_t 数据类型属于有符号的整数类型,用来存放(读取的)字节数或-1(表示错误)。
当 read()应用于其他文件类型时,比如管道、FIFO、socket 或者终端,在不同环境下也会出现 read()调用读取的字节数小于请求字节数的情况。例如,默认情况下从终端读取字符,一遇到换行符(\n),read()调用就会结束。在后续章节论及其他类型文件时,会再次针对这些情况进行探讨。
read()能够从文件中读取任意序列的字节。有些情况下,输入信息可能是文本数据,但在其他情况下,又可能是二进制整数或者二进制形式的 C语言数据结构。read()无从区分这些数据,故而也无法遵从 C 语言对字符串处理的约定,在字符串尾部追加标识字符串结束的空字符。如果输入缓冲区的结尾处需要一个表示终止的空字符,必须显式追加
因此 由于表示字符串终止的空字符需要一个字节的内存,所以缓冲区的大小至少要比预计读取的最大字符串长度多出 1 个字节
write()调用的参数含义与 read()调用相类似。buffer 参数为要写入文件中数据的内存地址,count参数为欲从 buffer 写入文件的数据字节数,fd 参数为一文件描述符,指代数据要写入的文件。
如果 write()调用成功,将返回实际写入文件的字节数,该返回值可能小于 count 参数值。这被称为“部分写”。对磁盘文件来说,造成“部分写”的原因可能是由于磁盘已满,或是因为进程资源对文件大小的限制。
close()系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。当一进程终止时,将自动关闭其已打开的所有文件描述符
对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个 read()或 write()操作的文件起始位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0。
SEEK_SET 将文件偏移量设置为从文件头部起始点开始的 offset 个字节。
SEEK_CUR 相对于当前文件偏移量,将文件偏移量调整 offset 个字节 。
SEEK_END 将文件偏移量设置为起始于文件尾部的 offset 个字节。也就是说,offset 参数应该从文件最后一个字节之后的下一个字节算起。
curr = lseek(fd ,0 ,SEEK_CUR)
程序样例 #include <sys/stat.h> #include <fcntl.h> #include <ctype.h> #include "tlpi_hdr.h" int main(int argc ,char** argv) { size_t len ; off_t offset; int fd , ap ,j; char *buf; ssize_t numRead,numWritten; if(argc < 3|| strcmp(argv[1],"--help")==0) usageErr("%s file{r<length>|R<lenfth>|w<string>|s<offset>}...\n",argv[0]); fd = open(argv[1],O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if(fd==-1) errExit("open"); for(ap=2;ap<argc;ap++) { switch(argv[ap][0]){ case 'r': case 'R': len = getLong(&argv[ap][1],GN_ANY_BASE,argv[ap]); buf= malloc(len); if(buf==NULL) errExit("malloc"); numRead = read(fd,buf,len); if(numRead==-1) errExit("read"); if(numRead==0) { printf("%s:end-of-file \n",argv[ap]); } else{ printf("%s: ",argv[ap]); for(j=0;j<numRead;j++) { if(argv[ap][0]=='r') printf("%c",isprint((unsigned char)buf[j])?buf[j]:'?'); else printf("%02x ",(unsigned int)buf[j]); } printf("\n"); } free(buf); break; case 'w': numWritten=write(fd,&argv[ap][1],strlen(&argv[ap][1])); if(numWritten == -1) errExit("write"); printf("%s:wrote %ld bytes\n",argv[ap],(long)numWritten); break; case 's': offset = getLong(&argv[ap][1],GN_ANY_BASE,argv[ap]); if(lseek(fd,offset,SEEK_SET)==-1) errExit("lseek"); printf("%s : seek succeeded\n",argv[ap]); break; default: cmdLineErr("Argument must start with [rRws]:%s\n",argv[ap]) } } exit(EXIT_SUCCESS) ; }
将某一系统调用所要完成的各个动作做为不可中断的操作,一次性加以执行.是许多系统调用的一正常执行的必要条件 .
函数原型:
fcntl()有5种功能 :
描述: fcntl()针对文件描述符提供控制,参数fd是被参数cmd操作的描述符;
针对cmd的值,fcntl能接受第三个参数(arg)
针对一个打开的文件,只有通过 open()或后续 fcntl()的 F_SETFL 操作,才能对该文件的状态标志进行设置
判定文件的访问模式有一点复杂,这是因为 O_RDONLY(0)、O_WRONLY(1)和 O_RDWR(2)这 3 个常量并不与打开文件状态标志中的单个比特位对应。因此,要判定访问模式,需使用掩码 O_ACCMODE 与 flag 相与,将结果与 3 个常量进行比对,
int flag,accessMode ;
flags=fcntl(fd,F_GETFL);
if(flags==-1) errExit("fcntl");
accessMode=flags&O_ACCMODE
if(accessMode==O_WRONLY || accessMode == O_RDWR)
printf("file is witable\n" );
内核对所有打开的文件维护都有一个系统级的描述表格 (有时也称为-打开文件表),并将表中的各个条目称为打开文件句柄.一个打开文件句柄存储了于一个打开文件的所有信息
两个不同的文件描述符,若指向同一打开文件句柄,将共享同一文件偏移量.因此,若通过其中的
dup/dup2函数:
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
当调用dup函数时 内核在进程中创建一个新的文件描述符,此描述符是当前文件描述符的最小值这个文件描述符指向oldfd拥有的所有的文件表项.
dup()和dup2()的区别就是可以用newfd指定新描述符的数值,若newfd已经打开 则先将其关闭(忽略关闭该过程的所有错误)
如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项
实际上,
dup(oldfd) == fcntl(oldfd, F_DUPFD,0);
dup2(oldfd, newfd) == close(oldfd);fcntl(oldfd, F_DUPFD,newfd);
并非文件指针所在,而在调用时的输入的偏移量处
定义:>
#include <unistd.h>
ssize_t pread(int fd,void *buf,size_t count,off_t offset);
ssize_t pwrite(int fd ,void *buf,size_t count,off_t offset);
系统调用pread()和pwrite()完成read()和write()类似的操作,
只是前两者会在offset参数指定的位置进行IO操作,而非始于文章偏移量处,并且它们不会改变文章的偏移量
pread()函数相当于将如下调用列入同一原子操作:
off_t orig;
orig = lseek(fd,0,SEEK_CUR);//SEEK_CUR -> 指文件当前偏移量
lseek(fd,offset,SEEK_SET); //SEEK_SET -> 文件头部启始的offset偏移量
s = read(fd ,buf,len);
lseek(fd,orig,SEEK_SET); //返回最初的偏移量
多线程应用为这些系统调用提供了用武之地。正如第 29 章所述,进程下辖的所有线程将共享同一文件描述符表。这也意味着每个已打开文件的文件偏移量为所有线程所共享。当调用pread()或 pwrite()时,多个线程可同时对同一文件描述符执行 I/O 操作,且不会因其他线程修改文件偏移量而受到影响。如果还试图使用 lseek()和 read()(或 write())来代替 pread()(或pwrite()),那么将引发竞争状态 .
readv(),writev()函数系统调用分别实现了分散输入和集中输出的功能.
#include <sys/uio.h>
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
ssize_t readv(int fd,const struct iovec * iov,int iovcnt);
// returns the number of bytes read, 0 on EOF, -1 on error
ssize_t writev(int fd,const struct iovec * iov,int iovcnt);
// Returns the number of bytes writen ,-1 on error
readv和writev函数用于在一次函数调用中读、写多个非连续缓冲区。有时也将这两个函数称为散布读(scatter read)和聚集写(gather write)。
ssize_t readv(int fd,const struct iovec * iov,int iovcnt);
// returns the number of bytes read, 0 on EOF, -1 on error
ssize_t writev(int fd,const struct iovec * iov,int iovcnt);
// Returns the number of bytes writen ,-1 on error
preadv() / pwritev() ,
将分散输入/集中输出和于指定文件偏移量处的 I/O 二者集于一身
#define _BSD_SOURCE
#include <sys/uio.h>
ssize_t preadv(int fd,const struct iovec *iov,int iovcnt ,off_t offset);
// returns the number of bytes read, 0 on EOF, -1 on error
ssize_t pwritev(int fd,const struct iovec * iov,int iovcnt,off_t offset);
// returns the number of bytes write ,-1 on error
preadv()和 pwritev()系统调用所执行的任务与 readv()和 writev()相同,但执行 I/O 的位置将由 offset 参数指定(类似于 pread()和 pwrite()系统调用)。
对于那些既想从分散-集中 I/O 中受益,又不愿受制于当前文件偏移量的应用程序(比如,多线程的应用程序)而言,这些系统调用(preadv(),pwritev())恰好可以派上用场。
readv和writev,相比于read和write函数只是多了对多个buffer操作的能力。当要将一个连续区域数据写入文件时,使用write()即可,但当要将多个不同区域数据写入文件时,则使用writev()更为方便。
truncate()和ftruncate()系统调用将文件大小设置为length参数指定的值
#include<nuistd.h>
int truncate(const char *pathname,off_t length);
int ftruncate(int fd,off_t length);
// both return 0 on success ,or -1 on error
若文件当前长度大于length,调用将丢弃超出部分,若小于length,调用将在文件尾部添加一系列空字节或一个文件空洞.
两个系统调用之间的差别在于如何指定操作文件.
在打开文件时指定O_NINBLOCK 目的:
管道,FIFO,套接字,设备(终端,伪终端)都支持非阻塞状态。(因为无法通过 open()来获取管道和套接字的文件描述符,所以要启用非阻塞标志,就必须使用 5.3 节所述 fcntl()的F_SETFL 命令。)
FIFO:http://t.csdn.cn/wBFuZ
通常来说存放文件偏移量的数据类型off_t实际上是一个有符号的长整形; 在X86-32 ~ 即32位体系架构中,将文件大小限制在2^31-1个字节(2GB)的限制下
然而,32位的UNIX实现有处理超过2GB大小的需求 则提出了LFS的增强特性
要使用过渡型LFS API 必须在编译程序时定义_LARGEFILE64_SOURCE 功能测试宏,该定义可以通过命令行指定,也可以定义于源文件中包含所有头文件之前的位置
该 API 所属函数具有处理 64 位文件大小和文件偏移量的能力。这些函数与其 32 位版本命名相同,只是尾部缀以 64 以示区别。其中包括:fopen64()、open64()、lseek64()、truncate64()、stat64()、mmap64()和 setrlimit64()
对于于定义的值,展示其值的方式是将其转化为long类型 使用%ld
但对于 LFS功能的off_t,则需要将其转换为longlong类型 使用%lld
对于每个进程,内核都提供一个特殊的虚拟目录/dev/fd.该目录包含"/dev/fd/n"形式的文件名,其中n是与进程中的打开文件描述符相对应的编号.因此,例如,/dev/fd/0则对应的是该进程的标准输入
下面两行代码是等价的
fd=open("/dev/fd/1", O_WRONLY);
fd=dup(1);
在为open()设置参数时,注意将其设置为与原描述相同的访问模式 . 这一场景下,在flag标志下设置中引入其他标志诸如O_CREAT,是无意义的.
!!! /dev/fd 实际上是一个符号链接,链接到 Linux 所专有的/proc/self/fd 目录。后者又是 Linux特有的/proc/PID/fd 目录族的特例之一,此目录族中的每一目录都包含有符号链接,与一进程所打开的所有文件相对应。
没看太懂
程序中很少会使用/dev/fd 目录中的文件。其主要用途在 shell 中。许多用户级 shell 命令将文件名作为参数,有时需要将命令输出至管道,并将某个参数替换为标准输入或标准输出。
出于这一目的,有些命令(例如,diff、ed、tar 和 comm)提供了一个解决方法,使用“-”符号作为命令的参数之一,用以表示标准输入或输出(视情况而定)。所以,要比较 ls 命令输出的文件名列表与之前生成的文件名列表,命令就可以写成:
$ ls | diff -oldfilelist
这种方法有不少问题。首先,该方法要求每个程序都对“-”符号做专门处理,但是许多程序并未实现这样的功能,这些命令只能处理文件,不支持将标准输入或输出作为参数。其次,有些程序还将单个“-”符解释为表征命令行选项结束的分隔符。
使用/dev/fd 目录,上述问题将迎刃而解,可以把标准输入、标准输出和标准错误作为文件名参数传递给任何需要它们的程序。所以,可以将前一个 shell 命令改写成如下形式:
$ ls | diff /dev/fd/0/ oldfilelist
方便起见,系统还提供了 3 个符号链接:/dev/stdin、/dev/stdout 和/dev/stderr,分别链接到/dev/fd/0、/dev/fd/1 和/dev/fd/2
很多程序需要创建一些临时文件,仅供其在运行期间使用,程序终止后即删除.
例如: mkstemp()和tmpfile()。
#include <stdlib.h>
int mkstemp(char *template);
!!! 模板参数采用路径名形式,其中最后 6 个字符必须为 XXXXXX。这 6 个字符将被替换,以保证文件名的唯一性,且修改后的字符串将通过 template 参数传回。因为会对传入的 template参数进行修改,所以必须将其指定为字符数组,而非字符串常量.
文件拥有者对mkstemp()函数建立的文件拥有读写权限(其他用户没有任何权限),且打开文件时使用了O_EXCL标志,以保证调用者以独占的方式访问文件.
通常打开临时文件不久,程序就会使用unlink系统调用将其删除
int fd;
char templateHahahahaha[]="/tmp/somesthingXXXXXX";
fd = mkstemp(templateHahahahaha);
if(fd==-1) errExit("mkstemp");
printf("Generated filename was: %s\n",templateHahahahaha);
unlink(templateHahahahaha);
if(close(fd)==-1) errExit("close");
#include<stdio.h>
FILE *tmpfile(void)
tmpfile()函数执行成功,将返回一个文件流供 stdio 库函数使用。文件流关闭后将自动删除临时文件。为达到这一目的,tmpfile()函数会在打开文件后,从内部立即调用 unlink()来删除该文件名
本章介绍了原子操作的概念,这对于一些系统调用的正确操作至关重要。
特别是,指定O_EXCL 标志调用 open(),这确保了调用者就是文件的创建者。
而指定 O_APPEND 标志来调用 open(),还确保了多个进程在对同一文件追加数据时不会覆盖彼此的输出。
系统调用 fcntl()可以执行许多文件控制操作,其中包括:修改打开文件的状态标志、复制文件描述符。使用 dup()和 dup2()系统调用也能实现文件描述符的复制功能。
本章接着研究了文件描述符、打开文件句柄和文件 i-node 之间的关系,并特别指出这 3 个对象各自包含的不同信息。文件描述符及其副本指向同一个打开文件句柄,所以也将共享打开文件的状态标志和文件偏移量。
之后描述的诸多系统调用,是对常规 read()和 write()系统调用的功能扩展。pread()和pwrite()系统调用可在文件的指定位置处执行 I/O 功能,且不会修改文件偏移量。readv()和writev()系统调用实现了分散输入和集中输出的功能。preadv()和 pwritev()系统调用则集上述两对系统调用的功能于一身。
使用 truncate() 和 ftruncate()系统调用,既可以丢弃多余的字节以缩小文件大小,又能使用填充为 0 的文件空洞来增加文件大小。
本章还简单介绍了非阻塞 I/O 的概念,后续章节中还将继续讨论。LFS 规范定义了一套扩展功能,允许在 32 位系统中运行的进程来操作无法以 32 位表示的大文件。
运用虚拟目录/dev/fd 中的编号文件,进程就可以通过文件描述符编号来访问自己打开的文件,这在 shell 命令中尤其有用。
mkstemp()和 tmpfile()函数允许应用程序去创建临时文件
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。