Linux文件操作(文件I/O--描述符概念+IO操作)

Linux文件操作

  • 文件I/O
    • 文件描述符的概念
    • 文件I/O操作
      • 打开一个文件
      • 打开文件出错
      • 关闭一个文件
      • 创建一个新文件
      • 文件定位
      • 文件截短
      • 清空文件
      • 文件读写操作
      • 文件同步

文件I/O

文件描述符的概念

在Linux环境下每-一个磁盘文件在打开时都会在内核中建立一个文件表项。该文件表项包括文件的状态信息、存储文件内容的缓冲区,以及当前文件的读写位置等。当同一个磁盘文件打开两次时会创建两个这样的文件表项,读写该文件时只影响到该文件表项中的文件读写位置。这些文件表项共同保存在内核中的一个数组里,该数组就是文件表。每一个进程在内核中有保存一个整型数组,该数组中每个元素是文件表的下标。因此使用该数组下标就可以引用打开的文件表项了。该数组下标就是文件描述符,每个进程中的文件表下标的数组就是文件描述符数组。当进程需要引用文件时,只需要引用这个文件描述符就可以了。因此文件描述符可以说是进程与打开文件的一个桥梁。通过这个桥梁进程才能够读写文件。

文件I/O操作

打开一个文件

open函数原型:

#include
int open(const char* pathname,int oflag, .../*mode_t mode*/);

参数说明:

  • pathname:指定要打开的文件,有两种方式指定
    • 文件的绝对路径名(或者相对路径名),结合 . 、… 使用;
    • 文件的文件名,操作系统只在当前工作目录找;
  • oflag:指定打开文件的选项,其值是一个位向量,通常进行或操作构成oflag参数
    • 这些常量值由宏表示,定义在fcntl.h中;
    • O_RDONLY: 只读,值为0;
      O_WRONLY: 只写,值为1;
      O_RDWR: 读写,值为2;
      注:以上三个选项是互斥的
      O_APPEND:追加写
      O_CREAT:如果文件不存在则创建,此时需要第三个参数
      O_EXCL:为了解决O_CREAT时写错文件名,当这两个标志一起的时候,则没有文件时创建文件, 有这个文件时会报错提醒我们
      O_TRUNC:丢弃原内容,添加新内容(只要打开,就丢弃了)
      O_NOTTY:如果文件是终端设备,则不把此控制终端分配给调用open函数的进程
      O_NONBLOCK:如果指定文件是命名管道、块设备或者字符设备,将此设备设置为非阻塞
      注:O_DSYNC\O_RSYNC\O_SYNC和文件同步输出有关
  • mode:表示创建文件的访问权限
    • mode使用4个数字来指定权限的
    • 当然,创建新文件会受到umask文件权限掩码的影响
    • 譬如一般创建一个可读可写不可执行的文件就用0666
  • 返回值:返回值是打开文件的文件描述符
    • 默认的文件描述符:
      在这里插入图片描述
    • 如果打开失败,则返回-1,并且设置errno变量
  • 文件描述符详述
    • 文件描述符的本质是一个数字,这个数字本质上是进程表中文件描述符表的一个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。
    • 操作系统规定,fd从0开始依次增加。fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以当时一个进程最多允许打开20个文件。linux中文件描述符表是个数组(不是链表),所以这个文件描述符表其实就是一个数组,fd是index,文件表指针是value
    • 当我们去open时,内核会从文件描述符表中挑选一个最小的未被使用的数字给我们返回。也就是说如果之前fd已经占满了0-9,那么我们下次open得到的一定是10.(但是如果上一个fd得到的是9,下一个不一定是10,这是因为可能前面更小的一个fd已经被close释放掉了);
    • fd中0、1、2已经默认被系统占用了,因此用户进程得到的最小的fd就是3了
    • linux内核占用了0、1、2这三个fd是有用的,当我们运行一个程序得到一个进程时,内部就默认已经打开了3个文件,这三个文件对应的fd就是0、1、2。这三个文件分别叫stdin、stdout、stderr。也就是标准输入、标准输出、标准错误
    • 标准输入一般对应的是键盘(可以理解为:0这个fd对应的是键盘的设备文件),标准输出一般是LCD显示器(可以理解为:1对应LCD的设备文件)
    • printf函数其实就是默认输出到标准输出stdout上了。stdio中还有一个函数叫fpirntf,这个函数就可以指定输出到哪个文件描述符中

打开文件出错

  • 常见的打开文件错误:
    • 打开一个不存在的文件
    • 打开文件的方式是文件权限不允许的
  • 排查错误
    • 检查errno,或者通过strerror打印具体错误信息
    • 使用perror打印错误信息

关闭一个文件

close函数原型:

#include
int close(int fd);

参数说明:

  • 在一个进程结束运行时,系统会隐式地关闭该进程打开的所有文件描述符,其结果和在程序中显式地调用close函数-样。但是作为-种良好的编程习惯,open函数和close函数是要配对的。也就是说,打开的文件最好要显式地关闭
  • 函数返回值:成功关闭返回1,失败返回-1;
  • 同open函数-样,close 函数同样很少出错,因此,在一般的编程过程中都不对close函数的返回值进行检查。但是在有些极限情况下检查close 函数的返回值是十分必须的,例如,close函数关闭的文件是一个远程的网络文件时,这时就需要检查close函数是否成功执行。由于close函数在关闭的时候会将内存缓冲区中的内容写到外存上,因此,关闭一个网络文件相当于与对端进行一次数据通信,这时出错的概率是很大的,因为网络中随时可能导致数据包的丢失

创建一个新文件

creat函数原型:

#include
int creat(const cahr* pathname,mode_t mode);

参数说明:

  • 参数与open类似
  • 成功返回1,失败返回-1
  • 使用open函数的creat也可以创建新文件

文件定位

lseek函数原型:

#include
off_t lseek(int filedes,off_t offset,int whence);

参数说明:

  • filedes:打开文件的文件描述符
  • offset 和 whence:
    当whence是SEEK_SET时,表示将该文件的文件偏移量设置为距文件开始位置offset个字节;
    当whence是SEEK_CUR时,表示将该文件的文件偏移量设置为当前文件偏移位置增加offset个字节,offset 的值可以是一个负数;
    当whence是SEEK END时,表示将该文件的文件偏移量设置为当前文件结尾位置增加ofet个字节,ofet 的值可以是一个负数;
    注意:第三个参数只能是上面三个宏代表的整型值中的一个
  • 返回值:lseek如果成功执行,其返回值是所设置的新的偏移量,否则返回-1。因为文件偏移量可能为负值,所以检查lseek函数是否出错时,不能检查其返回值是否小于0,而应检查其返回值是否是-1.根据其返回值的特性,可以得出打开文件的当前偏移量。
  • 注意:套接字文件和管道文件这些特殊的文件是不能够进行文件定位的

文件截短

truncate函数原型:

#include
int truncate(const char* pathname,off_t length);

参数说明:

  • pathname:文件路径
  • length:表示将文件截短的字节数,超过该字节数的部分将被系统放弃,如果文件的实际大小小于指定的值,系统会自动扩展文件,这时文件的实际末尾和拓展的新文件末尾形成一个文件空洞
  • 返回值:成功截短,返回0,失败返回-1

ftruncate函数原型:

#include
int ftruncate(int filedes,off_t length);

参数说明:

  • filedes:打开文件的文件描述符
  • length:需要截短的长度
  • 返回值:成功截短,返回0,失败返回-1

清空文件

方法:(1)使用truncate截短为0;(2)使用 open打开并添加 O_TRUNC选项

文件读写操作

read函数原型

#include
ssize_t read(int filedes,void *buf,size_t nbytes);

参数说明:

  • buf:缓冲区,将文件内容读到缓冲区
  • nbytes:需要读取的字节数
  • 返回值:实际读出的字节数
    read函数的返回值是实际读出字节数,其值可能有以下3种情况:
    1、从指定文件中读入nb个字节,返回值和参数nb相等,这是所遇到最多的情况;
    2、文件剩余字节数小于nb,返回值是实际读出的字节数,如果文件已经到达末尾则
    返回值为0;
    3、读操作出现错误(例如,文件没有以读方式打开),返回值为-1。
    如果所要读取的文件太大,应采用分批读入文件的方法。
  • 注:read函数不会在读入的内容后面添加‘\0’ 结束符,这一点和C语言函数库中提供的读文件函数是不同的,如果需要,则必须由用户手动添加,添加代码如下:
#define MAX 100
char buf[MAX];//缓冲区
int bytes;//读入的字节数
bytes=read(fd,buf,MAX-1);//最多读入MAX-1个字节,留一个给'\0'
buf[bytes]='\0';//添加'\0'结束符

write函数原型:

#include
ssize_t write(int filedes,void *buf,size_t nbytes);

参数说明:

  • buf:缓冲区,需要写到文件的缓冲区
  • nbytes:需要写入的字节数
  • 返回值:实际写入的字节数,失败返回-1
  • 注意:对于write 函数来讲,常见的出错原因是外存没有足够的存储空间或者文件长度超过了进程文件长度的限制

应用实例:文件复制
功能说明:打开两个文件,对于目标文件如果其不存在,则创建该文件。顺序读取源文件内容并将其写入到目标文件中,实现文件的复制。

#include
#include
#include
#include#define MAX 10int main(int argc,char *argv[])
{int in,out;int n;char buf[MAX];//打开源文件和目标文件if(argc != 3){perror("函数使用格式为:file_copy 目标文件名 源文件名");exit(1);}out=open(argv[1],O_WRONLY | O_CREAT | O_TRUNC);in=open(argv[2],O_RDONLY);if(-1 == in){perror("fail to open");exit(1);}if(-1 == out){perror("fail to open");exit(1);}//读写文件while((n = read(in,buf,MAX))>0){if((write(out,buf,n))!= n){perror("fail to write");exit(1);}}if(n<0){perror("fail to read");exit(1);}printf("copy done\n");close(in);close(out);return 0;}

文件同步

问题描述:由于内核缓冲区机制,文件的写操作会由于缓冲的缘故致使输出延时,所以在一段时间内, 会导致内存中的文件内容和外存上的文件内容不一致.
文件同步: 为了避免该种情况,用户可以指定系统在缓冲区并未填满的时候将文件内容回写到磁盘上。这种操作叫做文件同步,Linux 提供以下函数用于文件同步操作,其函数原型如下:

#include
int fsync(int filedes);
int fdatasync(int filedes);
void sync(void);

函数说明:

  • sync:此函数操作最简单,将所有打开的文件写回到磁盘上,将文件中修改过内容的盘块放入系统队列后就返回,并不等待盘块实际写入外存,因此,该函数能够加快文件同步速度,但是并不能保证真正的文件同步;
  • fsync:fsync函数可以确保文件的实际写出,该函数会阻塞直到修改的盘块写到外存后才返回,成功返回0,如出错则返回-1。 fsync 函数是需要保持同步文件的描述符。fsync 函数可保证文件内容及时更新,并且不用频繁做打开关闭文件的操作。
  • fdatasync:fdatasync函数和sync函数类似,但不同的是fdatasync函数只更新文件的数据部分,而不更新文件的属性部分。fdatasync函数的参数和返回值与fsync函数的意义相同。而sync函数将所有打开文件的内容回写到磁盘上,所以该函数不需要任何参数,也不返回任何值。

应用实例:
功能描述:下面实例演示了使用fsync 函数进行文件的同步操作。该程序首先打开一个文件,之后每隔5秒钟向文件中输出一行字符。输出字符之后程序调用fsyme函数将输出的内容回写到磁盘上。用户可以打开该文件,观察输出的信息是否即时写回到了磁盘文件上。

#include
#include
#include
#includeint main(void)
{int fd;int i;//打开文件,可写fd=open("test.txt",O_RDWR);if(-1 == fd){perror("fail to open");exit(1);}i=0;while(i<3){sleep(5);printf("hello\n");if(-1 == write(fd,"hello\n",6)){perror("fail to write");exit(1);}//同步文件if(fsync(fd) == -1){perror("fail to fsync");exit(1);}i++;}close(fd);return 0;
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部