关闭

文件I/O

标签: UNIXLinux系统编程
140人阅读 评论(0) 收藏 举报
分类:

文件 I/O


1.什么是文件I/O?

简单的说文件I/O就是对文件的读写,但由于要考虑到linux支持多进程多线程、文件权限、读写效率等诸多因素,简单的读写操作也变得颇有讲究。

2.文件描述符

文件描述符就是一个文件的身份证,通过它系统才可以对文件进行操作。文件描述符是一个非负整数,当打开一个已有的文件或者创建一个新文件时,内核回想进城返回一个文件描述符,当要读写一个文件时,需要将文件描述符传递给相应的读写函数。有三个文件描述符是系统shell默认的:0-标准输入;1-标准输出;2-标准错误。由于这些数字缺乏可读性,系统设置了三个常量与之对应:STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO。

3.关于文件读写的一些函数

open或openat:打开或创建一个文件

函数原型

  1. #include<fcntl.h>
  2. int open(const char* pathname, int flags, ...../*mode_t mode*/)
  3. int openat( int fd ,const char* pathname , int flags ,...../*mode_t mode*/)

参数解释 
pathname:打开或创建文件的路径,当pathname是绝对路径时open和openat两个函数是等效的,openat的参数fd被忽略。当pathname是相对路径时,open()函数是在当前目录下打开或创建一个文件,而openat函数则避免了这样的缺陷,它可以通过指定fd,在其他目录下打开或创建文件。fd参数指定相对路径所在的起始地址,比如如果要在/etc/目录下打开或创建文件,fd可以通过打开/etc/目录来获取。这样新打开或创建的文件就可以在/etc/目录下而不是当前目录。 
flags:这个参数主要是指定打开的方式,系统定义的一些常量用来描述这些方式这里给出一部分常量以及它的解释,完整的内容可通过man手册查询。 
O_RDONLY:以只读的方式打开; 
O_WRONLY:以只写的方式打开; 
O_RDWR:以读写的方式打开; 
O_EXEC:以只执行的方式打开; 
O_SEARCH:以搜索的方式打开(应用于目录) 
以上五种方式是互斥的,这五种方式必须选一种且只能选一种。 
下面还有一些常量是可选的 
O_APPEND:每次写时追加到文件尾部; 
O_CLOEXEC:把FD_CLOEXEC设置为文件描述符标志,在下面介绍dup函数时有应用; 
O_CREAT:若文件不存在则创建,当flags中使用此方式,就得说明第三个参数mode,mode设置了新创建文件的访问权限,系统也为文件的访问权限定义了一些常量。 
返回值 
open和openat函数返回文件描述符,该描述符是系统最小且尚未使用的描述符数值。若失败则返回-1.

creat函数:创建一个新文件。

函数原型

  1. #include<fcntl.h>
  2. int creat(char* pathname,mode_t mode);

参数解释与open函数一样。 
其实完全可以用open实现creat函数,并且更加灵活,creat相当于

  1. int open (char* pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);

creat函数有一个缺陷,就是它以只写的方式创建文件,若想对新创建的文件进行读操作那么必须先close文件,再以可读的方式打开,可用open函数避免这个问题

  1. int open(char* pathname, O_RDWR|O_CREAT|O_TRUNC,mode);

返回值 
成功则返回文件描述符,若失败返回-1.

lseek函数:为一个已打开的文件设置偏移量。

当对文件进行读写操作时,需要一个读写的起始地址,这个起始地址就是文件偏移量,读写操作都是从文件偏移量处开始。偏移量会随着读写的进行改变,读写几个字节它就增加几个字节。当打开一个文件除非指定以O_APPEND的方式,否则文件偏移量被设为0。lseek函数可以显示地设置文件偏移量,这样读写文件时程序将更具灵活性。 
函数原型

  1. #include<unistd.h>
  2. off_t lseek(int fd,off_t offset,int whence);

参数说明 
fd:文件描述符; 
offset:偏移量; 
whence:参考位置。 
当whence为SEEK_SET时,文件偏移量设置为距文件开始处offset个字节; 
当whence为SEEK_END时,文件偏移量设置为文件长度加上offset,offset可正可负; 
当whence为SEEK_CUR时,文件偏移量为当前位置加offset,offset可正可负。 
返回值 
成功返回新的文件偏移量,失败则返回-1。

函数read:从打开的文件中读数据

函数原型

  1. #include<unistd.h>
  2. ssize_t read(int fd,void* buf,size_t count);

功能说明:从文件描述符为fd的文件中读出count字节的数据并存放在buf中。若read成功则返回读到的字节数,若已经到达文件末尾则返回0,若出错返回-1. 
有以下几种情况读到的实际字节数小于count: 
在未读够count字节但已经到达文件末尾时返回实际读到的字节数。下一次读时返回0. 
从终端设备读时,通常一次最多读一行。 
从网络读时,由于网络缓冲机制可能会造成返回值小于count。 
从管道或FIFO读时,若管道包含字节数小于count,则返回实际字节数。 
从一些面向记录的设备中读时,一次最多返回一个记录。

write函数

函数原型

  1. #include<unistd.h>
  2. ssize_t write(int fd ,void* buf,size_t count);

功能说明:从buf中向文件描述符为fd的文件写count字节的数据,若成功则返回写入的字节数,若出错返回-1,返回值通常和count相等,否则表示出错。

原子操作

原子操作指有多步组成的操作,原子操作在执行时,要么执行完所有的步骤,要么就一步都不执行。为什么要引入原子操作这个概念呢? 
下面写了一个简单的测试程序,观察一下它的执行结果,体会一下原子操作的必要性。

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. #include<fcntl.h>
  5. int main(void)
  6. {
  7. char buf[3]={'a','b','c'};
  8. char tmp[3]={'q','w','e'};
  9. int num;
  10. int fd;
  11. int fd1;
  12. pid_t pid;
  13. pid=fork();
  14. if(pid==0)
  15. {
  16. if((fd=open("yy",O_RDWR))<0)
  17. {
  18. printf("open error");
  19. exit(-1);
  20. }
  21. if(lseek(fd,0,SEEK_END)<0)
  22. {
  23. printf("lseek error");
  24. exit(-1);
  25. }
  26. sleep(2);
  27. if(write(fd,tmp,3)!=3)
  28. {
  29. printf("write error");
  30. exit(-1);
  31. }
  32. }
  33. else{
  34. sleep(1);
  35. if((fd1=open("yy",O_RDWR))<0)
  36. {
  37. printf("open error");
  38. exit(-1);
  39. }
  40. if(lseek(fd1,0,SEEK_END)<0)
  41. {
  42. printf("lseek error");
  43. exit(-1);
  44. }
  45. if(write(fd1,buf,3)!=3)
  46. {
  47. printf("write error");
  48. exit(-1);
  49. }
  50. }
  51. }

在这个程序中有两个进程对同一个文件进行写操作,由于是先定位,再写这种模式,子进程先定位但还未写文件偏移量为0,子进程被挂起,执行父进程,父进程定位紧接着写入abc,这时文件尾发生了改变。然后子进程继续执行但它不知道文件尾发生了改变,子进程以为自己还是写在文件尾,这样会导致子进程写的qwe会覆盖父进程写的abc。最终yy文件中只保存qwe。

函数dup和dup2

函数原型

  1. #include<unistd.h>
  2. int dup(in fd);
  3. int dup2(int fd, int fd2);

功能说明:这两个函数都是复制一个现有的文件描述符。dup函数返回的新描述符是当前可用描述符中最小的一个。而dup2函数可以通过参数fd2指定新的描述符,如果fd2已经打开就关闭它,如果fd等于fd2则返回返回fd2,不关闭。否则fd2的FD_CLOEXEC文件描述符标志就被清除。这样fd2在进程调用exec时是打开状态。

函数 fcntl

函数原型

  1. #include<fcntl.h>
  2. int fcntl(int fd,int cmd,...../*int arg*/);

功能说明: 
复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)。 
获取/设置文件描述符(cmd=F_GETFD或F_SETFD)。 
获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)。 
获取/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)。 
获取/设置记录锁(cmd=F_GETLK或F_SETLKW)。 
详细用法参见man手册。 
这里用fcntl函数模拟一下dup和dup2

  1. dup(fd)

等效于

  1. fcntl(fd,F_DUPFD,0);
  1. dup2(fd,fd2);

等效于

  1. close(fd2);
  2. fcntl(fd,F_DUPFD,fd);

下面是通过指定cmd参数获取文件状态的程序

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<fcntl.h>
  4. #include<stdlib.h>
  5. int main(int argc,char** argv)
  6. {
  7. int fd;
  8. int var;
  9. if(argc!=2)
  10. {
  11. printf("usage: a.out <descriptor#>");
  12. exit(-1);
  13. }
  14. int file=atoi(argv[1]);
  15. if((var=fcntl(file,F_GETFL,0))<0)
  16. {
  17. printf("fcntl error for fd:%d\n",file);
  18. exit(-1);
  19. }
  20. switch(var & O_ACCMODE)
  21. {
  22. case O_RDONLY:
  23. printf("read only");
  24. break;
  25. case O_WRONLY:
  26. printf("write only");
  27. break;
  28. case O_RDWR:
  29. printf("read write");
  30. break;
  31. default:
  32. printf("unknown access mode");
  33. }
  34. if(var & O_APPEND)
  35. printf(", append");
  36. if(var & O_NONBLOCK)
  37. printf(", noblocking");
  38. if(var & O_SYNC)
  39. printf(", synchronous writes");
  40. putchar('\n');
  41. exit(0);
  42. }
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1620次
    • 积分:63
    • 等级:
    • 排名:千里之外
    • 原创:5篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档