文件IO(文件的概念,读,写等操作)

目录

1.文件的概念

2.各类文件

3.文件操作

4.系统IO


1.文件的概念

在 Linux 中,有一句经典的话叫做:一切皆文件。这句话是站在内核的角度说的,因为在内核中所有的设备 (除了网络接口) 都一律使用 Linux 独有的虚拟文件系统 (VFS) 来管理。这样做的最终目的,是将各种不同的设备用“文件”这个概念加以封装和屏蔽,简化应用层编程的难度。文件,是 Linux 系统最重要的两个抽象概念之一 (另一个是进程) 。

2.各类文件

在 Linux 中,文件总共被分成了 7 种,他们分别是:

1,普通文件 (regular) :存在于外部存储器中,用于存储普通数据。

2, 目录文件 (directory) :用于存放目录项,是文件系统管理的重要文件类型。 3,管道文件 (pipe) :一种用于进程间通信的特殊文件,也称为命名管道 FIFO。 4,套接字文件 (socket) :一种用于网络间通信的特殊文件。

5,链接文件 (link) :用于间接访问另外一个目标文件,相当于 Windows 快捷方式。 6,字符设备文件 (character) :字符设备在应用层的访问接口。

7,块设备文件 (block) :块设备在应用层的访问接口。

下面是一张全家

注意到,每个文件信息的最左边一栏,是各种文件类型的缩写,从上到下依 次是:

b (block) 块设备文件

c (character) 字符设备文件

d (directory) 目录文件

l (link) 链接文件 (软链接)

p (pipe) 管道文件 (命名管道)

- (regular) 普通文件

s (socket) 套接字文件 (Unix 域/本地域套接字)

 其中,块设备文件和字符设备文件,是 Linux 系统中块设备和字符设备的访问节点,在 内核中注册了某一个设备文件之后,还必须在/dev/下为这个设备创建一个对应的节点文件 (网络接口设备除外) ,作为访问这个设备的入口。目录文件用来存放目录项,是实现文件 系统管理的最重要的手段。链接文件指的是软链接,是一种用来指向别的文件的特殊文件, 其作用类似于 Windows 中的快捷方式,但他有更加有用的功能,比如库文件的版本管理。 普通文件指的是外部存储器中的文件,比如二进制文件和文本文件。套接字文件指的是本机 内进程间通信用的 Unix 域套接字,或称本地域套接字。

3.文件操作

对一个文件的操作有两种不同的方式,既可以使用由操作系统直接提供的编程接口   (API),即系统调用,也可以使用由标准 C 库提供的标准 IO 函数,他们的关系如图 4-3 所 示。

图 4-3  标准 IO 和系统 IO 的位置 

在 Linux 操作系统中,应用程序的一切行为都依赖于这个操作系统,但是操作系统的内 部函数应用层的程序是不能直接访问的,因此操作系统 OS 提供了大约四五百个接口函数, 叫做“系统调用接口”,好让应用程序通过他们使用内核提供的各种服务,上图中用红色标 注的那一层,就是这所谓的系统调用接口,这几百个函数是非常精炼的 (Windows 系统的 接口函数有数千个) ,他们以功能的简洁单一为美,以健壮稳定为美,但考虑到用户可能需 要用到更加丰富的功能,因此就开发了很多库,其中最重要的也是应用程序必备的库就是标 准 C 库,库里面的很多函数实际上都是对系统调用函数的进一步封装而已,用个比喻来讲 就是:OS 的系统调用接口类似于菜市场,只提供最原始的肉菜,而库的函数接口相当于饭 馆,对肉菜进行了加工,提供风味各异品种丰富的更方便食用的佳肴。

在几百个 Linux 系统调用中,有一组函数是专门针对文件操作的,比如打开文件、关闭 文件、读写文件等,这些系统调用接口就被称为“系统 IO”,相应地,在几千个标准 C 库 函数中,有一组函数也是专门针对文件操作的,被称为“标准 IO”,他们是工作在不同层 次,但都是为应用程序服务的函数接口。

4.系统IO

要对一个文件进行操作,先必须“打开”他,打开两个字之所以加上双引号,是因为 这是代码级别的含义,并非图形界面上所理解的“双击打开”一个文件,代码中打开一个文件意味着获得了这个文件的访问句柄 (即 file descriptor,文件描述符 fd) ,同时规定了之后访问这个文件的限制条件

我们使用以下系统 IO 函数来打开一个文件:

功能

打开一个指定的文件并获得文件描述符,或者创建一个新文件

头文件

#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);

参数

pathname:即将要打开的文件

flags

O_RDONLY:只读方式打开文件

这三个参数互斥

O_WRONLY:只写方式打开文件

O_RDWR:读写方式打开文件

O_CREAT:如果文件不存在,则创建该文件。

O_EXCL:如果使用 O_CREAT 选项且文件存在,则返回错误消息。

O_NOCTTY:如果文件为终端,那么终端不可以作为调用 open()系统调 用的那个进程的控制终端。

O_TRUNC:如文件已经存在,则删除文件中原有数据。

O_APPEND:以追加方式打开文件。

mode

如果文件被新建,指定其权限为 mode  (八进制表示法)

返回值

成功

大于等于 0 的整数 (即文件描述符)

失败

- 1

备注

表 4-1 函数 open( )的接口规范

使用系统调用 open( )需要注意的问题有:

1,flags 的各种取值可以用位或的方式叠加起来,比如创建文件的时候需要满足这样的 选项:读写方式打开,不存在要新建,如果存在了则清空他。那么此时指定的 flags 的取值 应该是:O_RDWR | O_CREAT | O_TRUNC。

2,mode 是八进制权限,比如 0644,或者 0755 等。

3,它可以用来打开普通文件、块设备文件、字符设备文件、链接文件和管道文件,但 只能用来创建普通文件,每一种特殊文件的创建都有其特定的其他函数。

4,其返回值就是一个代表这个文件的描述符,是一个非负整数。这个整数将作为以后任何系统 IO 函数对其操作的句柄,或称入口

以下的系统 IO 函数用来关闭一个文件

功能

关闭文件并释放相应资源

头文件

#include <unistd.h>

原型

int close(int fd);

参数

fd:即将要关闭的文件的描述符

返回值

成功

0

失败

- 1

备注

重复关闭一个已经关闭了的文件或者尚未打开的文件是安全的。

表 4-2 函数 close( )的接口规范

系统调用 close( )相对来讲简单得多,只需要提供已打开的文件描述即可。一般来讲, 当我们使用完一个文件之后,需要及时对其进行关闭,以防止内核为继续维护它而付出不必 要的代价。下面是一个简单的使用了这两个函数的示例代码:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(void)   
{
     int fd = open("a.txt", O_CREAT|O_TRUNC|O_WRONLY, 0644);
     printf("fd: %d\n", fd);

     close(fd);
     return 0;
}

代码中第 8 行使用 open( )打开了一个文件a.txt,打开模式是只读,并且存在就打开不 存在就创建,从程序的执行结果来看,获得的文件描述符 fd 等于 3,这是因为 0、1 和 2 三 个描述符在程序一开始运行时就已经被默认打开了,他们分别代表了标准输入、标准输出和 标 准 出 错 ,事 实 上 ,在代码 中 我 们 经 常 使 用 STDIN_FILENO 、 STDOUT_FILENO 和 STDERR_FILENO 来替代 0、1 和 2。

  

图 4-4  文件描述符与文件

上面的示意图表示,每一个被打开的文件 (键盘、显示器都是文件) 都会有一个非负的 描述符来对应他们,一个文件还可以被重复打开多次,每打开一次也都会有一个描述符对应, 并且可以有不同的模式。

么,这个所谓的文件描述符究竟是什么玩意儿呢?其实他是一个数组的下标值,在 4.1 节中提到过,在内核中打开的文件是用 file 结构体来表示的,每一个结构体都会有一个 指针来指向他们,这些指针被统一存放在一个叫做 fd_array 的数组当中,而这个数组被存 放在一个叫做 files_struct 的结构体中,该结构体是进程控制块 task_struct 的重要组成部分。 他们的关系如图 4-5 所示

图 4-5 文件描述符的本质

 接下来是文件的读写接口:

功能

从指定文件中读取数据

头文件

#include <unistd.h>

原型

ssize_t read(int fd, void *buf, size_t count);

参数

fd:从文件fd 中读数据

buf:指向存放读到的数据的缓冲区

count:想要从文件 fd 中读取的字节数

返回值

成功

实际读到的字节数

失败

- 1

备注

实际读到的字节数小于等于 count

功能

将数据写入指定的文件

头文件

#include <unistd h>

原型

ssize_t write(int fd, const void *buf, size_t count);

参数

fd:将数据写入到文件 fd 中

buf:指向即将要写入的数据

count:要写入的字节数

返回值

成功

实际写入的字节数

失败

- 1

备注

实际写入的字节数小于等于 count

表 4-3 函数 read( )和 write( )的接口规范

下面通过一个示例展示他们的用法,这是示例实现一个简单的功能:将指定的一个件 的内容拷贝到另一个指定的文件中去, 目前暂时只支持普通文件的拷贝。代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

define SIZE 1024

int main(int argc, char **argv)
{
int fd_from, fd_to;

      if(argc != 3) 15         {
           printf("Uage: %s <src> <dst>", argv[0]);
           exit(1);
      }
      //  以只读方式打开源文件,以只写方式打开目标文件
      fd_from = open(argv[1], O_RDONLY);
      fd_to = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC, 0644); 22
      char buf[SIZE];
      char *p;
      int nread, nwrite;

      while(1) 
       {
             nread = read(fd_from, buf, SIZE); 30
             if(nread == 0)
                    break;
             write(fd_to, buf, nread);
       }

      close(fd_from);
      close(fd_to); 39
      return 0;
}

上述代码中的第 27 - 35 行是关键,程序循环地从 fd_from 中读取数据,放置到 buf 中, 然后将数据写入fd_to 中去。当 read( )返回 0 时代表已经读完,退出循环并且关闭两个文 件描述符。

在读写文件的时候有个偏移量的概念,即当前读写的位置,这个位置可 以获取,也可以人为调整,用到的系统 IO 接口如下

功能

调整文件位置偏移量

头文件

#include <sys/types.h>

#include <unistd.h>

原型

off_t lseek(int fd, off_t offset, int whence);

参数

fd:要调整位置偏移量的文件的描述符

offset:新位置偏移量相对基准点的偏移

whence:基准点

SEEK_SET:文件开头处

SEEK_CUR:当前位置

SEEK_END:文件末尾处

返回值

成功

新文件位置偏移量

失败

- 1

备注

                               表 4-3 函数 lseek( )的接口规范

注意,lseek( )只对普通文件凑效,特殊文件是无法调整偏移量的。下面通过一个示例 来使用这个接口:创建一个空洞文件。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
      int fd = open("file", O_RDWR|O_CREAT|O_TRUNC, 0644); 16
      write(fd, "abc", 3);              // 写入”abc”
      lseek(fd, 100, SEEK_CUR); // 定位到 100 个字节之后
      write(fd, "xyz", 3);              // 写入”xyz”

      close(fd);
      return 0;
}

注意上述代码的第 17 - 19 行,首先写入”abc”三个字符,然后直接将文件位置偏移量人 为调整到“当前位置往后 100 个字节”处,然后再写入xyz”三个字符,这时文件file 中就 包含了一个大小为 100 个字节的空洞,头尾分别有三个字节,整个文件的大小为 106 个字 节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值