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

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);
}

问题:如何选择合适的缓冲区大小?

  使用文件系统的块大小(通常为40968192

  具体数字在后面章节描述

  测试数据略

 

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已经打开,它将首先被关闭(fildesfildes2相等时除外)

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

  dupdup2对于实现输入/输出重定向非常有用

 
12、函数sync()、fsync()、fdatasync() --数据同步
int fsync ( int fildes );
int fdatasync ( int fildes );
void sync (void);
说明:

  sync仅对修改过的内核缓冲区排队,并不等待数据写入磁盘

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

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

 

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/Oioctl的最大用途

后续章节详述
 
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 平台并不支持
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值