Liunx系统编程:系统层面上的文件IO接口

目录

一. 如何在系统层面上理解文件

二. 语言层面上的文件IO函数

三. Linux操作系统提供的IO接口

3.1 open接口 -- 打开文件

3.2 close接口 -- 关闭文件

3.3 write接口 -- 向文件中写内容

3.4 read接口 -- 从文件中读取内容

四. 总结


一. 如何在系统层面上理解文件

在Linux操作系统层面,可以认为,只要能进行input写入或output读取的任何的任何设备,都可以被理解为文件,我们可以从狭义和广义两个方面认识文件的概念:

  • 狭义上的文件:磁盘文件
  • 广义上的文件:任何可以进行IO操作的设备,包括显示器、键盘、声卡、网卡、磁盘、光盘、音响、磁带、光盘等。

换句话说就是:Linux下一切皆文件

文件 = 内容 + 属性,对文件的操作无外乎就是对内容的操作和对属性的操作。因此,就算一个文件的内容为空,那么它的属性信息也要存储在磁盘中,也要占用存储空间

通过Linux操作系统的ls -l (可简写为ll) 指令,可以查看文件的属性信息,包括文件的类型、权限信息、链接数、拥有者、所属组、文件内容占用空间大小、最近一次修改的时间、文件名。

图1.1 ls -l指令查看文件信息

本质上,只有操作系统有权利对文件进行读写操作。用户对于文件的任何操作,其本质是都是调用操作系统的相关IO接口来完成的,只不过为了方便不同用户的使用,操作系统及各种编程语言,会通过图形化界面、库函数等方式,封装文件的IO接口。

如果我们希望通过代码来访问文件,那么操作流程依次为:写代码 -> 编译源代码 -> 载入内存运行,创建进程 -> 访问文件。因此,我们还可以认为,访问文件的操作本质是由进程来完成的

二. 语言层面上的文件IO函数

C/C++均提供了相应的库函数/类,来实现的文件得IO操作

  • C语言读文件函数:fgets、fgetc、fread、fscanf等。
  • C语言写文件函数:fputs、fputc、fwrite、fprintf等。
  • C++写文件类:std::ofstream,通过流插入运算符 << 来写文件。
  • C++读文件类:std::ifstream,通过流提取运算符 >> 来读文件。

这些语言层面的IO函数,在底层都封装了操作系统提供的IO接口。那么,既然操作系统已经提供了IO接口,为什么各种语言语言还要提供自己的IO函数呢?这可以从以下两个方面来理解:

  1. 语言层面的IO函数相对于系统接口使用难度较低。
  2. 语言具有跨平台的性能,而如果直接使用系统接口,那么代码就不具有跨平台性。

提问:库函数怎样针对不同的OS平台,实现不同版本的IO函数呢?答:一般采用条件编译#ifdef/#endif的方法来实现,在库函数中将全部平台下的IO接口都进行封装,实现不同底层原理的IO函数,再根据运行的平台,来对库函数代码选取一部分进行编译。

不同的语言,会提供不同的IO接口函数,但是,在同一操作系统平台下,系统IO的接口都是一样的,无论语言层面的IO接口再怎么变化,在底层都需要封装系统IO接口。

三. Linux操作系统提供的IO接口

3.1 open接口 -- 打开文件

用于打开文件的系统接口(可能创建文件),有下面两种格式: 

  • int open(const char* file, int flag)
  • int open(const char* file, int flag, int mode) 

使用open函数,要包含三个头文件: 

其中,file的意义为文件打开的路径,flag为打开方式的标识方法,mode为如果要创建文件的话文件的起始权限。

关于file,就是带有路径的文件的文件名。flag类似于C语言的open函数中"w"、"a"、"r"等打开方式控制的标识,主要的flag和对应的功能以下几种:

  • O_RDONLY -- 以只读方式打开文件。
  • O_WRONLY -- 以只写的方式打开文件。
  • O_RDWR -- 读写方式打开文件。
  • O_CREAT -- 如果指定的文件不存在,那就创建它。注意O_WRONLY与C语言的"w"不同的是,在open中只声明O_WRONLY并不会在文件不存在的时候创建文件。
  • O_APPEND -- 追加写入文件的内容,而不覆盖文件原有内容。
  • O_TRUNC -- 清空文件的内容后写文件。如果仅仅声明O_WRONLY,那么仅仅是在文件起始位置开始写数据,写多少内容覆盖文件原来的多少内容,不会情况原有内容。

如果我们同时需要两种flag的功能,那么应该使用按位或操作符,来同时对两个位置进行标记。如:我们希望open以只写的方式打开某个文件的同时在文件不存在的时候创建它,那么flag的传参方式为O_WRONLY|O_CREAT。

mode为文件创建的起始权限,以8进制的方式传参,如果我们希望创建文件的起始权限为拥有者、所属组和其它人都是rw-,那么在调用open时就应该给mode传0666。

但是,实际创建文件的起始权限,要经过系统的文件权限掩码过滤相应权限。Linux系统默认的文件掩码一般为0002,即:其它人不能拥有写文件的权限。那么,我们就可以在创建的进程的文件中,使用umask(0000),将该进程中的文件掩码暂时改为0000,不对任何权限进行过滤,这样就可以按照希望的方式给定文件的起始权限。

open接口的返回值:a.如果文件打开成功,就返回被打开的文件的文件描述符。b.如果文件打开失败,那么就返回-1。

再使用open之后,应当通过if判断open函数的返回值,再确保文件打开成功之后,再使用文件。

代码3.1:文件打开

#include    
#include    
#include    
#include    
#include    
    
int main()    
{    
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT);   //以只写的方式打开log1.txt文件,如果文件不存在就创建    
    if(fd1 < 0)  //判断打开是否成功    
    {    
        perror("open fd1");    
        return 1;    
    }    
    
    //下面省略打开文件是否成功的判断    
    int fd2 = open("log2.txt", O_RDONLY);   //以只读方式打开log2.txt文件    
    int fd3 = open("log3.txt", O_WRONLY|O_APPEND);   //以追加写的方式打开log3.txt文件    
    int fd4 = open("log4.txt", O_WRONLY|O_TRUNC);    //清空文件原有内容再写入    
    
    umask(0000);   //在此之后,权限掩码为0000    
    int fd5 = open("log5.txt", O_WRONLY|O_CREAT, 0666);   //给定权限0666创建文件    
    
    close(fd1);    
    close(fd2);    
    close(fd3);    
    close(fd4);    
    close(fd5);                                                                                                                                                                                                                               
    
    return 0;    
} 

3.2 close接口 -- 关闭文件

close用于关闭某个被open接口打开的文件,其函数原型为:

  • int close(int fd) -- 其中fd为文件描述符,关闭文件成功返回0,失败返回-1。

使用close函数需要包含的头文件: 

close一般配合open的返回值使用,如果传入错误的文件描述符,那么close就会失败。

3.3 write接口 -- 向文件中写内容

  • 函数原型:ssize_t wirte(int fd, const void* ptr, size_t count)
  • 函数功能:将从ptr位置处开始的count个字节的内容,写入到fd所对应的文件中去。
  • 函数返回值:返回实际写入到文件中的bytes数。

使用write函数需要包含的头文件:

代码3.2:写文件

#include    
#include    
#include    
#include    
#include    
#include    
    
int main()    
{    
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_TRUNC);    
    if(fd1 < 0)    
    {    
        perror("open fd1");    
        return 1;    
    }    
    
    const char* s1 = "hello world\n";    
    //这里不需要strlen(s) + 1
    //字符串末尾以\0结尾是语言层面的规则,与文件无关
    write(fd1, s1, strlen(s1));
    write(fd1, s1, strlen(s1));
    write(fd1, s1, strlen(s1));

   int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND|O_TRUNC); 
   if(fd2 < 0)
   {
       perror("open fd2");
       return 2;
   }

   const char* s2 = "hello Linux\n";
   write(fd2, s2, strlen(s2));
   write(fd2, s2, strlen(s2));
   write(fd2, s2, strlen(s2));

   close(fd1);
   close(fd2);                                                                                                                                                                                                                                
                                                                                                                              
   return 0;                                                                                                                  
}

3.4 read接口 -- 从文件中读取内容

  • ssize_t read(int fd, void *buff, size_t count)
  • 从fd确定的文件后缀读取count个字符,到buff所指向的内存空间中去。
  • 函数返回值:a.如果成功从文件中读取到内容,就返回实际读取到的内容的bytes数  b.如果读到了文件的末尾,就返回0。

代码3.3:read读取文件内容

  #include    
  #include    
  #include    
  #include    
  #include    
  #include    
      
  int main()    
  {    
      int fd = open("log1.txt", O_RDONLY);   //只读方式打开文件    
      if(fd < 0)  //检查文件是否打开成功    
      {    
          perror("open");    
          return 1;    
      }    
      
      char buff[100];   //记录存储从文件中读取到的内容    
      memset(buff, '\0', 100);  //空间内容初始化                                                                                                                                                                                              
      
      ssize_t ret = read(fd, buff, 100);   //读取100字节的内容到buff中        
      printf("ret = %d\n", ret);    
      printf("buff:%s\n", buff);    
      
      close(fd);    
      return 0;    
  }  

四. 总结

  • 在操作系统层面,一切可以进行IO操作的设备都被视为文件。从狭义上讲文件就是磁盘文件,从广义上讲文件包含磁盘、显示器、键盘、鼠标、网卡、声卡等诸多设备。
  • 每一种编程语言都会提高特定的IO接口,对系统IO接口进行封装,从而方便用户的使用、实现语言的跨平台性能。
  • Linux操作系统提供了open、write、read、close等系统调用接口,用于文件IO操作。

你可能感兴趣的:(Linux系统和网络,linux,运维,服务器)