1. 标准 C 的 I/O
1.1 常用函数和结构体
- char *fgets(char *s, int size, FILE *stream); // 整行输入
- int printf(const char *format, ...);
- int fprintf(FILE *stream, const char *format, ...);
- size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- typedef struct iobuf{
- int cnt; // 剩余的字节数
- char *ptr; // 下一个字符的位置
- char *base; // 缓冲区的位置
- int flag; // 文件访问模式
- int fd; // 文件描述符
- }FILE;
标准 I/O 函数都是带缓存的.
stdin: 标准输入, 针对键盘
stdout: 标准输出, 针对屏幕
stderr: 标准出错, 针对屏幕
三个都是 FILE 类型的结构体指针, 成为流指针
1.2 标准 C 的 IO 缓存类型
1. 全缓存
要求填满整个缓冲区后才进行 I/O 系统调用操作, 对于磁盘文件通常使用全缓存访问.
2. 行缓存
涉及一个终端时(例如标准输入和标准输出), 使用行缓存;
行缓存满自动溢出;
碰到换行符自动输出.
行缓存案例 line_buffer.c
3. 无缓存
标准错误流 stderr 通常是不带缓存的, 这使得错误信息能够尽快的显示出来.
2. 文件 I/O 系统调用
2.1 常用函数
打开文件: open()
创建文件: create()
关闭文件: close()
读取文件: read()
写入文件: write()
文件定位: lseek()
这些不带缓存的函数都是内核提供的系统调用, 他们不是 ANIC C 的组成部分, 是 POSIX 的组成部分.
系统调用与 C 库的差异:
标准库函数: 遵循 ISO 标准, 基于流的 I/O, 对文件指针 (FILE 结构体) 进行操作.
系统调用: 兼容 POSIX 标准, 基于文件描述符的 I/O, 对文件描述符进行操作.
2.2 文件描述符
对于内核而言, 所有打开文件都由文件描述符引用. 文件描述符是一个非负整数, 当打开一个现存文件或创建一个新文件时, 内核向进程返回一个文件描述符. 当读, 写一个文件时, 用 open 或 create 返回的文件描述符标识该文件, 将其作为参数传给 read 或 write.
在 POSIX 应用程序中, 整数 0,1,2 被替换成符号常数 STDIN_FILENO,STDOUT_FILENO 和 STDERR_FILENO. 这些常数都定义在头文件 < unistd.h > 中.
文件描述符的范围是 0-OPEN_MAX.Linux 为 1024
2.3 文件描述符和文件指针之间的转换
标准文件指针:
- stdin 0
- stdout 1
- stderr 2
文件指针和文件描述符之间的转换函数:
- FILE *fdopen(int fd, const char *mode); // 文件描述符 =>文件指针(fd=>FILE*)
- int fileno(FILE *stream); // 文件指针 =>文件描述符(FILE *=>fd)
2.4 常用 I/O 系统调用函数说明
(1)open 函数
- #include <sys/types.h> // 头文件标准路径 / usr/include
- #include <sys/stat.h>
- #include <fcntl.h>
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
返回: 若成功返回文件描述符, 出错返回 - 1
功能: 打开或创建一个文件
参数: pathname: 要打开或创建的文件路径
flags: 用来说明此函数的多个选择项, O_RDONLY 只读, O_WRONLY 只写, O_RDWR 读写
mode: 新建文件的访问权限, 对于 open 函数而言, 仅当创建新文件时才使用第三个参数.
注: 用下列一个或多个常数进行或运算构成 flags 参数(这些常数定义在 < fcntl.h > 头文件中)
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以读写方式打开文件
O_APPEND 以追加模式打开文件, 每次写时都追加到文件的尾端, 但在网络文件系统进行操作时没有保证
O_CREAT 如果指定的文件不存在, 则按照 mode 参数指定的文件权限来创建文件
O_EXCL 如果同时指定了 O_CREAT, 而文件已经存在, 则出错. 这可测试一个文件是否存在, 但在网络文件系统进行操作时没有保证
O_DIRECTORY 如果参数 pathname 不是一个目录, 则 open 出错
O_TRUNC 如果此文件存在, 而且为只读或只写成功打开, 否则将其长度截短为 0
O_NONBLOCK 如果 pathname 指的是一个 FIFO, 一个块特殊文件或一个字符特殊文件, 则此选择项为此文件的本次打开操作和后续的 I/O 操作设置为非阻塞方式
(2)create 函数
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- int create(const char *pathname, mode_t mode);
返回: 若成功为只写打开的文件描述符, 若出错为 - 1
此函数等效于 open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode);
create 的一个不足之处是它以只写方式打开所创建的文件.
(3)close 函数
- #include <unistd.h>
- int close(int fd);
返回: 若成功返回 0, 若出错返回 - 1
功能: 关闭一个打开的文件.
(4)read 函数
- #include <unistd.h>
- ssize_t read(int fd, void *buf, size_t count);
返回: 读到的字节数, 若已到文件尾为 0, 若出错为 - 1
功能: 从文件中读取数据.
参数: fd: 读取文件的文件描述符指针
buf: 存放读取数据的缓存
count: 要求读取一次数据的字节数
有多种情况可使实际读到的字节数小于要求读字节数
读普通文件时, 当读到要求字节数之前已达到了文件尾端
当从终端设备读时, 通常一次最多读一行
当从网络读时, 网络中的缓冲机制可能造成返回值小于所要求读的字节数
某些面向记录的设备, 例如磁带, 一次最多返回一个记录
进程由于信号造成中断
读操作从文件的当前位移量处开始, 在成功返回之前, 该位移量增加实际读得的字节数.
(5)write 函数
- #include <unistd.h>
- ssize_t write(int fd, const void *buf, size_t count);
返回: 若成功返回写的字节数, 若出错返回 - 1
功能: 向打开的文件中写数据
参数: fd: 写入文件的文件描述符
buf: 存放待写数据的缓存
count: 要求写入一次数据的字节数
若返回值通常与 count 的值不同, 表示出错
write 出错的一个常见原因: 磁盘已写满或者超过了对一个给定进程的文件长度限制
对于普通该文件, 写操作从文件的当前位移量处开始. 如果在打开该文件时, 指定了 O_APPEDN 选择项, 则在每次写操作之前, 将文件位移量设置在文件的当前结尾处, 在一次成功写之后, 该文件位移量增加实际写的字节数.
(5)lseek 函数
- #include <sys/types.h>
- #include <unistd.h>
- off_t lseek(int fd, off_t offset, int whence);
返回: 若成功则返回新的文件偏移量(绝对偏移量: 相对起始位置), 若出错返回 - 1
功能: 定位一个已打开的文件
参数: fd: 已打开文件的文件描述符
offset: 位移量
whence: 定位的位置, 基准点. SEEK_SET: 将该文件的位移量设置为距文件开始处 offset 个字节; SEEK_CUR: 将文件的位移量设置为其当前值加 offset,offset 可正可负; SEEK_END: 将该文件的位移量设置为文件长度加 offset,offset 可正可负, 正数表示一个空洞文件.
lseek 也可用来确定所涉及的文件是否可以设置位移量, 如果文件描述符引用的是一个管道或 FIFO, 则 lseek 返回 - 1, 并将 errno 设置为 EPIPE.
每个打开的文件都有一个与其相关联的 "当前文件偏移量". 它是一个非负整数, 用以度量从文件开始处计算的字节数. 通常读, 写操作都从文件偏移量处开始, 并使偏移量加所读或写的字节数. 按系统默认, 当打开一个文件时, 除非指定 O_APPEND 选择项, 否则该位移量被设置为 0.
文件读写 buffer 的大小一般设置为磁盘块的大小, 磁盘块的大小可以通过命令查看:
df -k 查看分区
sudo tune2fs -l /dev/sda1 其中有个信息是: Block size: 4096
(6)dup 和 dup2 函数
- #include <unistd.h>
- int dup(int oldfd);
- int dup2(int oldfd, int newfd);
返回: 成功返回新文件描述符, 出错返回 - 1
功能: 文件描述符的复制
参数: oldfd: 原先的文件描述符; newfd: 新的文件描述符
说明: 由 dup 返回的新文件描述符一定是当前可用文件描述符中的最小数值.
用 dup2 则可以用 newfd 参数指定新描述符的数值, 如果 newfd 已经打开, 则先将其关闭, 若 oldfd 等于 newfd, 则 dup2 返回 newfd, 而不关闭它.
在进程间通信时可用来改变进程的标准输入和标准输出设备.
cat <输入重定向文件> 输出重定向文件>> 追加输出重定向文件
dup2 复制过程:
(7)fcntl 函数
- #include <unistd.h>
- #include <fcntl.h>
- int fcntl(int fd, int cmd);
- int fcntl(int fd, int cmd, long arg);
- int fcntl(int fd, int cmd, struct float *lock);
返回: 若成功则依赖于 cmd, 若出错返回 - 1
功能: 可以改变已经打开的文件性质
常见的功能: 复制一个现存的描述符, 新文件描述符作为函数值返回(cmd=F_DUPFD);
获取 / 设置文件描述符标志(cmd=F_GETFD 或 F_SETFD);
获取 / 设置文件状态标志(cmd=F_GETFL 或 F_SETFL);
获取 / 设置文件锁 (cmd=F_SETLK,cmd=F_GETLK,F_SETLKW) 第三个参数为 struct flock 结构体.
cmd 的常见取值:
F_DUPFD: 复制文件描述符, 新的文件描述符作为函数返回值返回
F_GETFD/F_SETFS: 获取 / 设置文件描述符, 通过第三个参数设置(arg)
F_GETFL/F_SETFL: 获取 / 设置文件状态标志, 通过第三个参数设置(arg), 可以更改的几个标志是: O_APPEDN,O_NONBLOCK,O_SYNC,O_ASYNC(O_RDONLY,O_WRONLY 和 O_RDWR 不适用)
给文件上锁: cmd:F_SETLK(非阻塞式),F_GETLK 和 F_SETLKW(阻塞式)
- struct flock{
- short l_type;
- off_t l_start;
- short l_whence;
- off_t l_len;
- pid_t l_pid;
- };
l_type: 锁类型, F_RDLCK(共享读锁),F_WRLCK(独占性写锁)或 F_UNLCK(解锁一个区域).
l_start,l_whence: 要加锁或解锁的区域的起始地址, 由 l_start 和 l_whence 两者决定, l_start 是相对位移量, l_whence 则决定相对位移量的起点.
l_len: 表示区域的长度.
(9)ioctl 函数
- #include <unistd.h>
- #include <sys/ioctl.h>
- int ioctl(int fd, int request, ...);
返回: 若成功则为其他值, 出错返回 - 1
说明: I/O 操作的杂物箱. 不能用其他函数表示的 I/O 操作通常都能用 ioctl 表示, 终端 I/O 是 ioctl 的最大使用方面, 主要用于设备的 I/O 控制.
来源: http://www.bubuko.com/infodetail-3326007.html