• Linux 文件操作的系统调用接口


    文件操作的系统调用接口:
     文件是Linux系统中的重要概念。它不仅仅是对普通文件的操作接口,也是设备通信、进程间通信、网络通信的重要编程接口。因

    此文件操作的相关调用也是Linux内核提供的最重要的编程接口。
     
     本节将重点叙述如下几个常用的文件操作系统调用。
     open:打开文件。
     read:从已打开的文件中读取数据。
     write:向已打开的文件中写入数据。
     close:关闭已打开的文件。
     ioctl:向文件传递控制信息或发出控制命令。
     
     对文件的操作工程一般是这样的:先打开文件,内核对打开的文件进行管理,打开成功后应用程序将获得文件描述符;然后应用程

    序使用文件描述符对文件进行读写操作;当全部操作完毕后,应用程序需要将文件关闭以释放用于管理打开文件的内存。
     文件描述符是一个取值从0开始的整数。内核默认一个进程同时打开的文件数有一个上限,也就是文件描述符取值的上限,一般是

    1024。
     每个进程在启动后就默认有三个打开的文件描述符0,1,2,如果启动程序时没有进行重定向,则文件描述符0关联到标准输入,1关

    联到标准输出,2关联到标准错误输出。在C库函数中可以使用以下几个宏来表示这几个文件描述符:
     #define STDIN_FILENO   0
     #define STDOUT_FILENO  1
     #define STDERR_FILENO  2


    打开文件:
     在访问文件之前,首先应打开文件。可以使用open或creat函数来打开文件,它们的接口头文件及函数原型如下:
     #include <sys/types.h>
     #include <sys/stat.h>
     #include <fcntl.h>
     int open(const char *pathname, int flags);
     int open(const char *pathname, int flags, mode_t mode);
     int creat(const char *pathname, mode_t mode);

     其各个参数及返回值的含义解释如下.
     ◆ pathname:要打开的文件名称。
     ◆ flags:标志位,指定打开文件的操作方式及打开时的一些行为。
     ◆ mode: 用于指定新文件的权限标志位。
     ◆ 返回值:操作成功则返回文件描述符,否则返回-1并设置标量errno的值。

     flags参数有以下几个基本的取值。
     ◆ O_RDONLY:以只读方式打开文件。
     ◆ O_WRONLY:以只写方式打开文件。
     ◆ O_RDWR:以读写方式打开文件。

     这几个标志位指定打开文件的操作方式,它们是互斥的,不能同时使用,但可以与下述标志用按位或的方式组合起来使用。
     ◆ O_CREAT:如果被打开的文件不存在,则自动创建这个文件。
     ◆ O_EXCL:如果O_CREAT标志已经使用,那么当由pathname参数指定的文件已经存在时open函数返回失败。如果pathname给出的是 

      一个符号链接,无论它指向的文件是否存在,对open函数的调用都会返回失败。
     ◆ O_NOCITY:如果被打开的文件是一个终端设备文件(如/dev/tty),它不会成为这个进程的控制终端。
     ◆ O_TRUNC:如果被打开的文件存在并且是以可写的方式打开的,则清空文件原有内容。
     ◆ O_APPEND:新写入的内容将被附加在文件原来的内容之后,即打开后文件的读写位置被置于文件尾。
     ◆ O_NONBLOCK:被打开的文件将以非阻塞的方式进行操作。
     ◆ O_NDELAY:同O_NONBLOCK。
     ◆ O_SYNC:被打开的文件将以同步I/O的方式进行操作,即任何写操作都会先被同步到硬件设备上。同步完成后,对写函数的调用 

      才返回。
     ◆ O_NOFOLLOW:如果pathname是一个符号链接,则对open函数的调用将返回失败。
     ◆ O_DIRECTORY:如果pathname不是目录,则对open函数的调用将返回失败。

     需要注意的是,open函数有两个原型,其中一个多出了个参数mode,它用于指定创建的新文件的访问权限。如果打开时使用了

    O_CREAT标志创建新文件,则一般都要给出mode参数,它的一些常用取值如表所示,这些值可以用按位或的方式组合使用。新文件的所属用

    户和所属组则是创建它的进程的所属用户和所属组。
     权限标志定义  对应的八进制形式  含义
     S_IRWXU   00700    文件所属用户有读写和执行权限
     S_IRUSR(S_IREAD) 00400    文件所属用户有读权限
     S_IWUSR(S_IWRITE) 00200    文件所属用户有写权限
     S_IXUSR(S_IEXEC) 00100    文件所属用户有执行权限
     S_IRWXG   00070    组内用户有读写和执行权限
     S_IRGRP   00040    组内用户有读权限
     S_IWGRP   00020    组内用户有写权限
     S_IXGRP   00010    组内用户有执行权限
     S_IRWXO   00007    其他用户有读写和执行权限
     S_IROTH   00004    其他用户有读权限
     S_IWOTH   00002    其他用户有写权限
     S_IXOTH   00001    其他用户有执行权限

     鉴于在调用open函数时,O_WRONLY,O_CREAT,O_TRUNC三个标志位经常组合使用,因此由一个专门的函数creat来实现。如下:
     creat(pathname, mode); 
     实际上等价于:
     open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);
     
     这两个函数在打开文件成功时将返回一个文件描述符,可用于随后的read/write或其他对文件的操作使用。两个不同的进程打开同

    一个文件是允许的,但它们得到的文件描述符一般是不同的。如果它们都对文件进行写操作,就会出现数据不一致的情况,也就是最后写入

    的可能覆盖先前其他进程写入的内容,这就涉及到进程间数据共享和同步的概念了。
     如果打开操作失败,这两个函数会返回-1,并将errno变量设置为一个合适的错误值。

    从文件读取数据:
     文件打开后就可以进行读写操作了。读操作的接口头文件及函数原型如下:
     #include <unistd.h>
     ssize_t read(int fd, void *buf, size_t count);

     其各个参数及返回值的含义解释如下:
     ◆ fd:要读取的文件描述符。
     ◆ buf:指向读取到的数据要放入的缓冲区。(buf指向的内存空间必须事先分配好)
     ◆ count:要读取的字节数(缓冲区大小)。
     ◆ 返回值:读取到的字节数,失败返回-1,并设置标量errno的值。

     这里的size_t型实际上就是无符号整型,而ssize_t型就是有符号整型。
     这个函数将从fd代表的文件的当前读写位置读取不超过count个字节到buf指向的内存中,并返回读到的字节数。

     对于普通文件来说,读操作完成后,文件的读写位置会向后移动,移动的长度就是读取的字节数,下一次读操作将从新的读写位置

    开始。
     read函数的返回值小于指定的count是可能的,并不是错误。出现这种情况有各种原因,比如,文件本身可供读取的字节数比count

    小或者read系统调用被信号打断等。read系统调用看似简单,但实际上对于各种可能情况的处理是比较复杂的,因为I/O操作有很多异常情

    况要考虑到。下面列出了read系统调用中可能遇到的情况及其处理方法。
     ◆ 调用返回值等于count,读取的数据存放在buf指向的内存中,结果与预期一致。
     ◆ 调用返回一个大于0小于count的值,读取的字节数存放在buf指向的内存中。出现这种情况可能是一个信号打断了读取过程,或 

      在读取中发生了一个错误,或读取的有效字节数大于0但不足count个,或在读入count个字节前文件已经结束。如果读取 

      的过程被信号打断则可以再次进行读取。
     ◆ 调用返回0,说明文件已结束,没有可以读入的数据。
     ◆ 调用阻塞,说明没有可读的数据,这种情况下如果以非阻塞方式操作文件,那么会立即返回错误。
     ◆ 调用返回-1,并且errno变量被设置为EINTR,表示在读入有效字节前收到一个信号,这种情况可以重新进行读操作。
     ◆ 调用返回-1,并且errno变量被设置为EAGAIN,这说明是在非阻塞方式下读文件,并且没有可读的数据。
     ◆ 调用返回-1,并且errno变量被设置为非EINTR或EAGAIN的值,表示有其他类型的错误发生,必须根据具体情况进行处理。

     由于有各种异常情况的存在,为了从文件中可靠的读取指定的字节数,就必须对这些情况进行处理,必要时需重新进行读操作。这

    对于设备文件、管道或socket来说尤其有意义。例如,用下面的代码能够可靠的从文件中读取指定的字节数(假设文件是以阻塞的方式进行

    操作的):
     ssize_t ret;
     while(len != 0 && (ret = read(fd,buf,len)) != 0)
     {
      if(ret == -1)
      {
       if(errno == EINTR) continue;
       perror("read");
       break; 
      }
      len -= ret;
      buf += ret; 
     }
     这里把操作放在循环中,当一次读操作没有得到len个字节时,将调整len和buf的值继续进行操作。如果读操作返回-1,说明有错

    误发生,这时如果错误码是EINTR,说明只是被信号打断,可以继续读,其他情况则被认为是严重的错误,不能再继续读。
     以上是以阻塞方式读文件的例子,这时,如果文件是一个设备文件并且设备没有可读的数据,进程将进入睡眠状态不再继续执行,

    或者说阻塞在read系统调用处,直到设备有了可读的数据才会被唤醒继续执行。很多时候我们需要进程能够立刻返回以处理其他的事物,那

    么就需要采用非阻塞的方式来操作文件,举例如下:
     ssize_t nr;
     start:
     nr = read(fd, buf, len);
     if(nr == -1)
     {
      if(errno == EINTR)  goto start;
      if(errno == EAGAIN)  
      {
       /*处理其他事物,在恰当时再调用read*/
      }
      else
      {
       /*有错误发生,处理错误*/
      }
     } 
     可以看到,在采用非阻塞的方式读文件时,如果读操作返回-1,我们必须检查错误码是否为EINTR或EAGAIN,如果是EINTR,可以再

    次进行读操作,而如果是EAGAIN,表示要读取的(设备)文件现在没有可供读取的数据,因此进程可以继续处理其他事物,而后在恰当的时

    机再来读取这个文件。
      
    写数据到文件:
     向打开文件中写入数据的接口头文件及函数原型如下:
     #include <unistd.h>
     ssize_t write(int fd, const void *buf, size_t count);

     其各个参数及返回值的含义解释如下。
     ◆ fd:要写入的文件的描述符。
     ◆ buf:指向要写入的数据所存放的缓冲区。
     ◆ count:要写入的字节数。
     ◆ 返回值:实际写入的字节数,失败则返回-1,并设置变量errno的值。

     这个函数会从fd所代表的文件当前读写位置开始,把buf指向的内存中最多count个字节写入文件。写入成功则返回写入的字节数,

    并更新文件的读写位置。
     write系统调用返回大于0而小于count的值是合法的,并不表示有错误发生。
     ◆ 调用返回值等于count,说明数据全部写入成功。
     ◆ 调用返回一个大于0小于count的值,说明部分数据没有写入。这可能是因为写入过程被信号打断,或者底层的设备暂时没有足 

      够的空间存放写入的数据。
     ◆ 调用阻塞,说明暂时不能写入数据,这种情况下如果以非阻塞方式操作文件,那么会立即返回错误。
     ◆ 调用返回-1,并且errno变量被设置为EINTR,表示在写入一个有效字节前,收到一个信号,应用程序可以再次进行写操作。
     ◆ 调用返回-1,并且errno变量被设置为EAGAIN,说明是在非阻塞方式下写文件但文件暂时不能写入数据。
     ◆ 调用返回-1,并且errno变量被设置为EBADF,表示给定的文件描述符非法,或者文件不是以写方式打开。
     ◆ 调用返回-1,并且errno变量被设置为EFAULT,表示buf是无效的指针。
     ◆ 调用返回-1,并且errno变量被设置为EFBIG,表示写入的数据超过了最大的文件尺寸,或者超过了允许的文件读写位置。
     ◆ 调用返回-1,并且errno变量被设置为EPIPE,说明写入时发生了数据通道断层的错误,这种情况只在文件是管道或者是socket 

      的情况下发生。在这种情况下,进程还将收到一个SIGPIPE信号,信号的默认处理程序是使进程退出。
     ◆ 调用返回-1,并且errno变量被设置为ENOSPC,说明底层设备没有足够的空间。
     
     写文件举例如下:
     ssize_t ret;
     while(len != 0 && (ret = write(fd, buf, len)) != 0)
     {
      if(ret == -1)
      {
       if(errno == EINTR) continue;
       perror("write"); 
       break;
      } 
      len -= ret;
      buf += ret;
     }
     这里假定写操作是以阻塞的方式进行的。如果以非阻塞方式进行写操作,则当函数返回-1时,必须检测errno变量的值是否为

    EAGAIN,以决定能否再进行写操作。
     

    发送控制命令:
     在Linux系统上,那些不能被抽象为读和写的文件操作统一由ioctl操作代表。ioctl操作用于向文件发送控制命令,这些命令不能

    被视为是输入输出流的一部分,而只是影响文件的操作方式。对于设备文件来说,ioctl操作常用于修改设备的参数。
     ioctl系统调用的接口头文件及函数原型如下:
     #include <sys/ioctl.h>
     int ioctl(int fd, int request, ...);
     
     ◆ fd:要操作的文件描述符。
     ◆ request:代表要进行的操作,不同的(设备)文件有不同的定义。
     ◆ 可变参数:取决于request参数,通常是一个指向变量或结构体的指针。
     ◆ 返回值:成功返回0,有效ioctl操作返回其他非负值,错误返回-1。

     ioctl能够进行的操作根据fd所代表的文件的具体类型而变化,非常繁多。下面举一个例子,使用TIOCGWINSZ命令获得终端的窗口

    大小,如下:
     

    /*文件名:console_size.c*/
     /*说明:使用ioctl获得控制台窗口的大小*/
     #include <stdio.h>
     #include <stdlib.h>
     #include <unistd.h>
     #include <sys/ioctl.h>
     
     int main(void)
     {
                 struct winsize size;
                 /*判断标准输出是否为tty设备,防止输出被重定向的情况*/
                 if(!isatty(STDOUT_FILENO) < 0) return -1;
                 /*获得窗口大小*/
                if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0)
               {
                        perror("ioctl TIOCGWINSZ error");
                        return -1; 
               }
               /*输出结果*/
               printf("rows is %d, columns is %d\n", size.ws_row, size.ws_col);
    
              return 0;
     }

     
    关闭文件:
     程序完成对文件的操作后,要使用close系统调用将文件关闭,其接口头文件与函数原型如下:
     #include <unistd.h>
     int close(int fd);
     
     fd是要关闭的文件的描述符,返回值在操作成功的情况下是0,否则是-1.

  • 相关阅读:
    VC++ error C2248: “CObject::CObject”: 无法访问 private 成员(在“CObject”类中声明)
    VC++ 在使用 CImage 的Draw 输入一个图像时,有时候会造成图像失真严重,解决的方法如下
    VC++ 中CDC与HDC的区别以及二者之间的转换
    BASE64编码和解码(VC源代码) 并 内存加载 CImage 图像
    VC 使用OnCtlColor函数来改变控件颜色(引用)
    VC++ 对话框程序响应键盘消息的处理方法的说明(非常重要)
    VC++ 迭代器 iterator, const_iterator, const iterator
    VC++ 解决在鼠标移动时,光标闪烁的问题。其实本质是 ON_SETCURSOR的用法
    SQL练习题-50道SQL练习题及答案与详细分析
    Windows Essentials Movie Maker 安装失败报错 ——问题解决
  • 原文地址:https://www.cnblogs.com/wblyuyang/p/2755625.html
Copyright © 2020-2023  润新知