linux 下的文件IO基础

2019-09-23

关键字:Linux 文件类型、Linux 文件IO、Linux 标准IO、C 语言的流


 

文件的本质就是一组数据的集合。计算机在日常生活中几乎都是被应用于处理数据方面的。而在 linux 操作系统中,将一切都视为是 “文件”。

 

1、常见文件类型

 

Linux 中常见的文件类型及其标识符有以下七种:

1、常规文件 : -

2、目录文件 : d

3、字符设备文件 : c

一种特殊的文件,代表着一个设备。如:键盘、鼠标、摄像头等。

4、块设备文件 : b

 与上面的字符设备文件一样,都是用于代表一个外部设备的映射。块设备文件与字符设备文件的区别就是传输数据时的大小不同。块设备文件是以固定长度来传送数据的,而字符设备文件则是不定长度的。

5、管道文件 : p

一般用于进程间通信。

6、套接字文件 : s

一般用于进程间通信。既可以实现网络通信,也可以实现本地通信。

7、符号链接文件 : l

类似于快捷链接。分软链接与硬链接两种。

 

2、文件IO的基础概念

 

祼机开发

直接通过代码来操作硬件的开发模式就称为“祼机开发”。换句话说,祼机开发通常是指在无操作系统的情况下进行软件开发的开发模式。同样地,可以引申出“祼机”的概念就是指无操作系统的硬件电路设备系统。

 

系统调用

与上面的祼机开发相对应,另一种操作硬件的方式是要先经过操作系统的。操作系统会把所有硬件设备资源封装起来,不允许应用程序直接操作,而是开放出一些接口给应用程序调用,经由操作系统间接地来控制硬件设备。这些由操作系统开放出来的用于控制硬件的接口就被称为“系统调用”。从另一种角度来说,操作系统的封装对硬件还能起到保护作用。

 

3、标准IO

 

标准IO就是提供给普通应用程序操作文件的支援库。它由 ANSI C标准定义。标准IO已在所有主流操作系统上实现,所以它的兼容性很好。标准IO中将文件封装成 FILE 结构体,又称之为“流”。流严格来讲需要区分“二进制流”与“文本流”。由于操作系统与硬件设备的通信是需要一定的资源开销的,为了提升运行效率、节约资源,标准IO设置了缓冲机制。所谓的缓冲就是指应用程序在进行读写操作时,先将数据暂存于内存中,当达到既定的条件后才将这些数据一起与硬件进行通信。

标准IO的缓冲机制有三种:

1、全缓冲;

将所有IO数据都暂存于内存中,直至既定的内存空间耗尽才发生实际的系统调用。

2、行缓冲;

将所有的IO数据都缓冲到内存中,直至遇到 '\n' 字符才发生实际的系统调用。我们的标准输入输出( scanf(), printf() )就是行缓冲模式的,只要在调用时没有遇到换行符,就一直将数据缓存在内存中,除非程序结束运行。我们有时会遇到程序在运行过程中发生段错误时,在错误之前有些打印没有打出来的情况,这就是因为这些要打印的数据被缓存到内存中了,在等待换行符的过程中遇到段错误强制退出,就导致了打印信息没打出来。

3、无缓冲。

如题。

 

标准IO预定了三个流,它们会在程序运行时自动打开,它们的名称与进程号分别为:

1、标准输入流 : 0 :STDIN_FILENO :stdin

2、标准输出流 : 1 :STDOUT_FILENO :stdout

3、标准错误流 : 2 :STDERR_FILENO :stderr

 

标准IO中操作文件流的方式:

打开一个流:

FILE *fopen(const char *path, const char *mode);

 

使用 fopen 创建的文件的权限默认是 0666,但是实际生成的文件的权限还要考虑到 Linux 中的 umask 才能最终决定。

 

关闭一个流:

int fclose(FILE *stream);

当关闭成功的时候会返回值 0。否则会返回 EOF,并设置相应的 errno。

当一个流执行 fclose 时,它会自动将缓存中的所有数据都刷写到对应硬件中,并释放缓冲区。

当一个程序正常终止时,会自动关闭所有打开的流。

 

处理错误流:

标准IO中预定义了一个整型变量用于指示不同类型的错误,它就是 errno 整型变量。如果我们需要在应用程序中引用到这个 errno,则可以进行如下声明:

extern int errno;

还有一个可以打印错误信息的输出函数接口:void perror(const char *msg); 这个函数会首先打印出参数 msg,然后再打印出当前的 errno 及其对应的错误信息。可以说是错误指示利器了。

还有一个函数,它可以返回指定错误号所代表的意思: char *strerror(int errno);

 

标准IO中操作对象流的方式:

所谓的操作对象流其实就是按既定的格式从流中读取或写出数据。这个既定的格式我们可以简单理解成就是一个自定义的结构体。通过这种方式可以很方便地按照任意需求从流中读写数据。

 

这种操作对象流的函数主要有两个:

size_t fread(void *ptr, size_t size, size_t n, FILE *fp);

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);

这两个函数仅对第二、第三个参数作一下解释。第二个参数就是由我们自定义的“结构体”,其实也就是一次要读取多少个字节的数据的意思。通常可以使用 sizeof(xxx) 来代替。而第三个参数就是我们希望读取多少个 size_t 的数据,通常使用 sizeof(xxx)*n 来代替。这两个函数的返回值都是实际进行读或写的数据量,即实际进行了多少个 size_t 字节的数据的读写。当读写出错时会返回 EOF,当读写达到末尾时返回 0。

 

刷新流:

前面有提到,标准IO的输入与输出都是有缓冲机制的,只有在流数据达到既定的条件时才会发生实际的系统调用过程。但标准IO同时也提供了供应用程序手动将缓冲区中的数据刷写到系统调用的接口。它就是

int fflush(FILE *fp);

当我们调用这个函数时,处于写缓冲区中的数据就会被立即刷写到系统调用中去。刷写成功时返回值 0,失败时返回值 EOF。同时要注意,这个刷写只对写缓冲有效。

 

如果我们非得刷新读缓冲区,那么可以通过 fgetc 函数读取到一个无用的地址中消费掉来曲线实现。

 

定位流:

标准IO提供的用于定位流的函数有三个:

long ftell(FILE *stream);

long fseek(FILE *stream, long offset, int whence);

long rewind(FILE *stream);

ftell 就是返回指定流当前的读写位置。

fseek 则是将指定流的读写位置定位到 whence 位置中进行 offset 偏移后的位置处。whence 参数里可以任意填写整数,也可以使用标准IO中提供的三个宏:SEEK_SET, SEEK_CUR, SEEK_END。这三个宏分别表示流的起始位置、流的当前位置、流的结尾位置。offset 可正可负,用于控制往前往后偏移。

rewind 就是将指定流的读写位置直接定位到起始位置。

 

检测流:

标准IO提供了两个函数用于给应用程序判断指定的流是否出错或到达末尾。

int ferror(FILE *steam);

int feof(FILE *stream);

这两个函数的返回值都是一个 “逻辑值”,当返回值 1 时表示逻辑真,返回值 0 时表示逻辑假。

 

格式化输出:

int printf(const char *fmt, ...);

int fprintf(FILE *stream, const char *fmt, ...);

int sprintf(char *s, const char *fmt, ...);

第一个函数最常用了,将指定格式化的字符串输出到标准输出流里。

第二个函数其实是第一个函数的升级版,它将输出流的控制权交由应用程序来决定。它的作用是将格式化后的字符串输出到指定的流里。

第三个函数则是将格式化的字符串输出到内存中的字符串中。

 

4、文件IO

 

文件IO是一种使用更广泛的IO接口。文件IO是遵循 POSIX 协议的一组函数,它的使用范围更广泛,能够支持多种文件格式。文件IO的核心概念是“文件描述符”,即 fd。

 

在 Linux 系统中,标准IO只是文件IO的一个分支而已。

 

文件IO的接口都被封装在 fcntl.h 中。

 

打开流:

int open(const char *path, int flags, ...);

当打开成功时返回文件的描述符号。出错时返回 EOF。

这个函数的参数是可变的,参数个数为 2 个或 3 个。当打开一个文件时,只需要使用到前面两个参数。当创建文件时,则需要使用到三个参数。

flags 参数一般使用接口中定义的宏,可选的类型有:

O_RDONLY : 只读

O_WRONLY :只写

O_RDWR : 读写

O_CREAT : 如果文件不存在就创建一个新的文件。使用这个参数时必须要填写第三个参数。

O_EXCL : 用于测试指定文件是否存在。通常与 O_CREATE 配合使用。当新建文件时文件已存在,则会设置错误信息 errno。

O_NOCTTY : 

O_TRUNC : 如果文件已存在,则打开时先删除掉已有数据。

O_APPEND : 追加。

第三个参数是一个整型,使用文件权限表示法来表示,如 0666, 0777, 0755 等。

 

关闭流:

int close(int fd);

执行成功时返回值 0,失败时返回值 EOF。

 

读写流:

#include <unistd.h>

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

不解释了。返回值是成功读取到的字节数。当返回值为 0 时表示读到末尾。出错时返回 EOF。

 

#include <unistd.h>

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

不解释了。

 

定位:

#include <unistd.h>

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

与标准IO中的定位接口类似。这个函数是定位当前的读写位置。执行成功时返回的就是指定文件描述符下的当前的读写位置。出错时返回 EOF。

 

访问目录:

#include <dirent.h>

DIR *opendir(const char *path);

DIR 是一个结构体类型,描述的是打开的目录文件类型。打开成功时返回的是相应的目录流指针,失败时返回的是 NULL。

 

#include <dirent.h>

struct dirent *readdir(DIR *dirp);

读取某个目录下的内容。执行成功时返回的是目录项内容。这个函数一次只返回一个目录项,这就意味着通常需要在一个循环中多次调用这个函数,直至这个函数返回 NULL 时才表示读完了目录中的所有内容。

 

#include <dirent.h>

int closedir(DIR *dirp);

不解释。

 

修改文件的访问权限:

#include <sys/stat.h>

int chmod(const char *path, mode_t mode);

int fchmod(int fd, mode_t mode);

两个函数的功能完全一样。都是修改指定文件的访问权限。

 

获取文件的属性:

#include <sys/stat.h>

int stat(const char *path, struct stat *buf);

int lstat(const char *path, struct stat *buf);

int fstat(int fd, struct stat *buf);

第一个函数返回的是实际文件的属性,即如果传入的文件是一个符号链接,那么返回的是这个符号链接指向的真实文件的属性。

第二个函数则可以返回符号链接本身的属性,当然也可以获得普通文件的属性。

第三个函数与第一个功能一样,只是用法不同。

 

关于 struct stat 结构体:

它就是专用于存放文件属性的结构体类型。它的结构成员有:

mode_t st_mode -- 用于存放类型和访问权限。

uid_t st_uid -- 文件所有者的ID。

uid_t st_git -- 文件所有者所属组的ID。

off_t st_size -- 文件的大小。

time_t st_mtime -- 文件最后修改时间。

...

 

关于文件类型 mode_t 结构体:

这个结构体是一个 32 位的整型变量。关于文件类型的记录应该按以下规则计算:

st_mode & 0170000

S_ISREG -- 0100000

S_ISDIR -- 0040000

S_ISCHR -- 0020000

S_ISBLK -- 0060000

S_ISFIFO -- 0010000

S_ISLNK -- 0120000

S_ISSOCK -- 0140000

 

文件访问权限是保存在低 8 位的。

 


 

posted @ 2019-09-23 23:10  大窟窿  阅读(444)  评论(0编辑  收藏  举报