您的位置:首页 > 编程语言 > C语言/C++

UNIX环境C语言编程(2)-文件IO

2015-01-27 15:59 399 查看
1、系统限制与功能选项

•分为两类:编译时、运行时
  编译时限制定义于/usr/include/sys/limits.h

  如:CHAR_BIT //char类型包含几个bit

  编译时选项定义于/usr/include/unistd.h

  如:_POSIX_JOB_CONTROL //是否支持任务控制

•运行时限制与选项的获取,相关调用:
  long sysconf(int name) //形如_SC_OPEN_MAX

  long pathconf(const char *pathname,int
name)

  long fpathconf(int
fildes,int name) //形如_PC_PATH_MAX

 

2、函数open() --打开或创建文件

•int open(const char *pathname, int oflag, ... /* mode_t mode */ );
•oflag可以由多个选项“或”组合,必须且只能指定3个选项中的一个:
  O_RDONLY 
//只读

  O_WRONLY 
//只写

  O_RDWR  //读写

  其他常用选项

  O_APPEND 
//追加

  O_CREAT  //文件不存在时创建

  O_EXCL  //如果同时指定O_CREAT且文件已存在,出错

  O_TRUNC  //截断,文件内容清空

 

3、函数creat() --创建文件

•int creat(const char *pathname, mode_t mode);
•等价于
  open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

•不足
  通过creat()创建的文件是“只写”的

  建议使用上面等价的open语句创建文件

 

4、函数close() --关闭文件

•int close(int
fildes);
•进程终止时自动关闭所有打开的文件描述符
 

5、函数lseek() --偏移定位

•off_t lseek(int
fildes,off_t offset,int whence);
•offset的解析依赖于whence的取值
  SEEK_SET 
//相对于文件开头,设置

  SEEK_CUR 
//相对于当前偏移,offset可正可负

  SEEK_END 
//相对于文件结尾,offset可正可负

•lseek(fd, 0, SEEK_CUR); //获取当前偏移
•对于普通文件,偏移必须是非负值;某些设备允许负值的偏移
•例子:创建一个包含空洞的文件
#include <stdio.h>
#include <fcntl.h>

char    buf1[] = "abcdefghij";
char    buf2[] = "ABCDEFGHIJ";

int main(void)
{
int fd;

/* 创建文件 */
if( (fd = creat("file.hole", 0777)) < 0 )
{
perror("creat");
exit(1);
}

/* 写入10个字节 */
if( write(fd, buf1, 10) != 10 )
{
perror("buf1 write");
exit(1);
}
/* offset now = 10 */

/* 设置文件偏移,越过文件结尾
* 因为位移量可能是负值,所以在比较 lseek的返回值时应当谨慎,
* 不要测试它是否小于0,而要测试它是否等于-1
*/
if( lseek(fd, 16384, SEEK_SET) == -1 )
{
perror("lseek");
exit(1);
}
/* offset now = 16384 */

/* 再次写入10个字节 */
if( write(fd, buf2, 10) != 10 )
{
perror("buf2 write");
exit(1);
}
/* offset now = 16394 */

exit(0);
}


6、函数read() --读取数据

•ssize_t
read(int
fildes, void *buf,size_t
nbytes);
•以下情形中,实际读取的字节数少于指定读取的字节数:
  读取普通文件时,且在读取指定字节数之前已到达文件结尾;

  读取终端设备时,通常每次只能读取一行;

  从网络读取时,缓冲机制的作用;

  读取管道或FIFO时,且管道中数据少于指定读取的字节数;

  读取面向记录的设备时,例如磁带,每次只能读取一个记录大小;

  读取部分数据后被信号中断;

 

7、函数write() --写入数据

•ssize_t
write(int
fildes,const void *buf,size_t
nbytes);
•返回值通常与参数nbytes的值相同,否则表示出错
  常见原因是:磁盘已满,或者超过当前进程的文件长度限制

•如果打开文件时指定了O_APPEND选项,则每次写入之前,将文件偏移设置在文件的当前结尾
•成功写入之后,文件偏移增加实际写入的字节数
 
8、I/O效率

•回顾一下概述(1)中引用的一个例子
#include <stdio.h>
#include <unistd.h>

int main(void)
{
int  n;
char buf[BUFSIZ];

/* 读取标准输入 */
while( (n = read(STDIN_FILENO, buf, BUFSIZ)) > 0 )
{
/* 写入标准输出 */
if( write(STDOUT_FILENO, buf, n) != n )
{
perror("write");
exit(-1);
}
}

if( n < 0 )
{
perror("read");
}

exit(0);
}


•问题:如何选择合适的缓冲区大小?
  使用文件系统的块大小(通常为4096或8192)

  具体数字在后面章节描述

  测试数据略

 

9、文件共享

内核中关于打开文件的数据结构



 

•每次写入之后,文件表中当前偏移增加相应的字节
  如果当前偏移大于文件大小,V-节点表中文件大小设置为当前偏移

•如果使用O_APPEND选项打开文件
  文件表的文件状态标志中设置相应标志

  写入之前,文件表中当前偏移首先被设置为V-节点表中当前文件大小

•如果使用lseek定位至文件结尾
  仅仅将文件表中当前偏移设置为V-节点表中当前文件大小

  注意:与O_APPEND选项打开文件不同

•lseek并不产生实际的I/O操作

 
10、原子操作

•追加写入文件
  lseek(fd, 0L, SEEK_END); //position to EOF

  write(fd,buf, 100); //and
write

  存在问题:并非原子操作

•替代方案:pread()与pwrite()函数
  ssize_t pread(intfildes, void
*buf,size_t
nbytes,off_t offset);

  原子操作,并不移动当前文件偏移

•如果文件不存在,创建文件
  if( open(…) < 0 )creat(…); //存在问题:并非原子操作

  正确做法示例:open (…, O_WRONLY | O_CREAT | O_EXCL, mode);

 
11、函数dup()与dup2() --复制描述符

•int dup(int
fildes);
•int dup2(int
fildes,int fildes2);
•说明:
  dup返回当前可用的最小描述符;

  dup2可以指定目标描述符fildes2,如果fildes2已经打开,它将首先被关闭(fildes与fildes2相等时除外)

  调用返回后,存在两个等价的文件描述符

  dup与dup2对于实现输入/输出重定向非常有用

 
12、函数sync()、fsync()、fdatasync() --数据同步

•int fsync(int
fildes);
•int fdatasync(int
fildes);
•void sync(void);
•说明:
  sync仅对修改过的内核缓冲区排队,并不等待数据写入磁盘

  fsync只作用于单个文件,等待数据确实写入磁盘后返回

  fdatasync与fsync类似,但是只影响文件的数据部分,并不维护文件属性

 

13、函数fcntl() --修改文件特性

•int fcntl(int
fildes,int
cmd, ... /*int
arg */ );
•功能概述:
  复制描述符,cmd = F_DUPFD,与dup()类似

  获取/设置描述符标志,F_GETFD / F_SETFD

  获取/设置状态标志,F_GETFL / F_SETFL

  获取/设置异步I/O属主,F_GETOWN / F_SETOWN

  获取/设置记录锁,F_GETLK / F_SETLK / F_SETLKW

•例子:获取文件状态标志
#include <stdio.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
int val;

if( argc != 2 )
{
printf("usage: %s <descriptor#>\n", argv[1]);
exit(0);
}

/* 获取文件状态标志 */
if( (val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0 )
{
printf("fcntl error for fd %d\n", atoi(argv[1]));
exit(0);
}

/* 检查访问模式 */
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:
printf("unknown access mode");
break;
}

/* 检查其他标志 */
if( val & O_APPEND )
printf(", append");
if( val & O_NONBLOCK )
printf(", nonblocking");

#if defined(O_SYNC)
if( val & O_SYNC )
printf(", synchronous writes");
#endif

#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC)
if( val & O_FSYNC )
printf(", synchronous writes");
#endif

putchar('\n');
exit(0);
}

/*
执行示例:
$ ./a.out 0 < /dev/tty
read only
$ ./a.out 1 > temp.foo
$ cat temp.foo
write only
$ ./a.out 2 2>>temp.foo
write only, append
$ ./a.out 5 5<>temp.foo
read write
*/

14、函数ioctl() --包罗万象的IO操作

•int ioctl(int
fildes,int request, ...);
•ioctl函数是I/O操作的杂物箱
  不能用本章中其他函数表示的I/O操作通常都能用ioctl表示

  终端I/O是ioctl的最大用途

•后续章节详述
 
15、/dev/fd目录

•一些系统提供/dev/fd目录,其目录项是名为0、1、2等的文件
•打开文件/dev/fd/n等效于复制描述符n(假定描述符n是打开的)
  fd =open("/dev/fd/0",
mode);

  等价于

  fd =dup(0);

•/dev/fd主要由shell程序使用,可以使用路径名参数来处理标准输入和标准输出
  cat file1 /dev/fd/0 >file.out

•AIX平台并不支持

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unix 编程 c语言 linux io