wblyuyang

博客园 首页 新随笔 联系 订阅 管理

文件操作的系统调用接口:
 文件是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.

posted on 2012-11-05 18:15  wblyuyang  阅读(5788)  评论(0编辑  收藏  举报