目录
标准IO
什么是标准IO
Stdio 标准输入输出以FILE结构体类型,通过标准库函数(如 fread、fwrite、fscanf、fprintf 等)进行操作。这些函数通常在内部使用缓冲机制,以减少对系统调用的依赖。
标准 IO 库在进程的用户态的虚拟内存维护了一个缓冲区,数据先写入缓冲区,然后再批量写入文件系统,因此在标准IO和系统IO同时执行时,系统IO更快,因为系统IO直接和内核交互。
什么是缓冲区
缓冲区是一种数据存储机制,用于在数据传输或处理过程中暂存数据,主要作用是合并系统调用,可以通过setbuf更改。
缓冲区的概念
暂存数据:缓冲区用于暂存即将被处理或传输的数据,以减少数据传输过程中的延迟和等待时间。
平滑数据流:通过缓冲区,可以将数据流从生产者平滑地传递给消费者,避免因速度不匹配而导致的数据丢失或处理延迟。
提高效率:缓冲区可以减少对硬件设备的直接访问次数,通过批量处理数据来提高数据传输和处理的效率。
缓冲区的实现
内存:缓冲区通常实现为内存中的一段连续空间。这可以是物理内存,也可以是虚拟内存。
硬件设备:
硬盘:硬盘控制器通常包含缓冲区,用于暂存读写操作的数据。
网络设备:网络接口卡(NIC)包含缓冲区,用于暂存网络数据包。
图形卡:图形处理单元(GPU)包含缓冲区,用于暂存图形渲染数据。
专用硬件:某些系统可能使用专用硬件(如FPGA或ASIC)来实现缓冲区,以提供更高的性能和更低的延迟。
操作系统和驱动程序:操作系统和设备驱动程序负责管理缓冲区的分配和使用。驱动程序通常会根据硬件设备的特性和需求来优化缓冲区的使用。
缓冲区的类型
行缓冲:换行和缓冲去满时刷新(标准输出)
全缓冲:缓冲区满的时候刷新(默认,只要不是终端设备)
无缓冲:如stderr,需要立即输出的内容
标准IO函数
1.fopen
FILE *fopen(const char *pathname, const char *mode);
const表示参数不会被修改,返回FILE指针(存储在堆上)或NULL 模式r和r+要求文件必须存在
w,w+,a,a+创建的文件权限是 0666 & ~umask 结果是 664,umask一般是0002,目的是为了防止产生文件权限过松
2.fdopen
FILE *fdopen(int fd, const char *mode);
把已有的文件描述符通过指定的方式,封装成一个FILE流
3.fclose
int fclose(FILE *stream);
关闭一个FILE流,成功返回0,失败返回EOF或者error
4.fgetc
int fgetc(FILE *stream);
返回读取的字符,将其作为无符号字符强制转换为整数,或者在文件结束或出错时返回EOF
5.fputc
int fputc(int c, FILE *stream);
字符放入流,返回值同fgetc
6.fgets
char *fgets(char *s, int size, FILE *stream);
读取到size-1或者'\n'停止,成功返回s,失败返回NULL或者error
7. fputs
int fputs(const char *s, FILE *stream);
const常量s写入流
8.fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size是读取字节数,nmemb是读取对象数,没有边界检查所以读取字节数不能大于实际字符数,返回成功读到对象个数
9.fprintf
int fprintf(FILE *stream, const char *format, ...);
指定格式输出到流(stdin,stdout,strerr等),返回值同printf
10.fscanf
int fscanf(FILE *stream, const char *format, ...);
从流中按指定格式读取,返回值同scanf
11.fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
返回成功写入c对象个数
12.fseek
int fseek(FILE *stream, long offset, int whence);
offset偏移大小,whence偏移参考系(SEEK_SET, SEEK_CUR, SEEK_END),作用是文件指针移动到参考系+—偏移的位置,返回偏移offset
13.ftell
long ftell(FILE *stream);
返回文件指针现在位置(对于文件首的偏移字节数)
14.fflush
int fflush(FILE *stream);
强制刷新流的缓冲区,如果参数为空那么刷新所有打开的流
15.rewind
void rewind(FILE *stream);
FILE流定位到首位置
16.perror
void perror(const char *s);
向stderr输出字符串s和error信息
17.strerror
char *strerror(int errnum);
输入errorno输出对应的error信息
18.tempnam
char *tempnam(cahr *s);
生成一个唯一的临时文件名,成功时,返回一个指针,指向一个字符串包含了生成的临时文件名。失败时,返回 NULL,并设置 errno 以指示错误类型。在并发下不安全
19.tmpfile
FILE *tmpfile(void);
创建并以二进制打开一个临时文件,返回匿名的FILE流
系统调用IO
什么是系统调用IO/文件IO
系统调用IO减少了缓冲机制的间接层,每次调用 write 或 read 都会直接与内核交互,但是每次系统调用都需要从用户态切换到内核态,这涉及到上下文切换,可能会有一定的开销。
标准 IO 的缓冲机制可以减少这种切换的次数,但代价是增加了内存的使用和可能的延迟。
fd文件描述符是文件IO中贯穿始终的类型,本质是一个整形数(数组的下标),而文件描述符表中存放的是指针,指针指向文件信息结构体(包含pos,count,inode等需要的信息)
fd数组中0位是stdin,1位stdout,2位stderr,因此存储fd从第三位开始(优先使用最小的),文件表述符表又称fd数组是内核的一部分,每个新进程在创建时,操作系统内核会为其在内核空间中初始化一个独立的文件描述符表。进程通过系统调用在用户空间操作这些文件描述符。
系统调用IO函数
1.open
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
当flags有O_CREAT时,用三参open,mode通常使用 S_IRUSR(用户可读)、S_IWUSR(用户可写)、S_IXUSR(用户可执行)等宏定义来设置权限。返回值是创建的文件描述符fd
2.read
ssize_t read(int fd, void *buf, size_t count);
读一个fd到buf, 返回读到的字节数,失败返回-1
3.write
ssize_t write(int fd, const void *buf, size_t count);
从const buf写入fd, 返回写入的字节数,失败返回-1
4.lseek
off_t lseek(int fd, off_t offset, int whence);
同fseek,对于某些类型的文件(如管道或终端),lseek 可能不会产生预期的结果,因为这些文件类型可能不支持随机访问。
5.truncate&ftruncate
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
如果文件原来的长度大于 length 参数指定的大小,文件将被截断到这个新的大小。如果文件原来的长度小于 length,文件将被扩展,并且新的空间将被填充为零。
6.dup&dup2
int dup(int oldfd);
int dup2(int oldfd, int newfd);
fd重定向,dup是生成fd副本定向到最小空闲fd,dup2是生成oldfd副本,关闭newfd并且放入副本,返回值是fd
SYNC
sync 为每个文件描述符(FD)维护一个内核缓冲区。
sync是一个系统调用,也是一个命令行工具,用于将系统缓冲区内的所有未写入数据强制写入到磁盘上。这通常用于确保在系统崩溃或重启之前,所有的数据都已经安全存储。
作为系统调用:sync 没有参数,调用后会刷新所有文件系统缓冲区。
作为命令行工具:在终端中运行 sync 命令也会达到同样的效果。
int fsync(int fd);
fd表示要同步的文件。系统调用,用于确保指定fd对应的文件数据被同步到磁盘上
int fdatasync(int fd);
fdatasync 是 fsync 的一个变体,它只同步文件的数据部分,而不同步元数据(如访问时间、修改时间等)。这可以提供比 fsync 更快的同步速度,因为元数据的更新通常不需要磁盘I/O操作。
FCNTL
fcntl是一个非常强大的系统调用,可以用于实现多种文件相关的控制操作.
int fcntl(int fd, int cmd, ... /* arg */ );
参数说明:
-fd:文件描述符,表示要操作的文件。
-cmd:命令,指定了要执行的操作类型。
-arg(可选):一些命令可能需要额外的参数,这些参数会通过这个参数传递。
常用命令(cmd):
-F_DUPFD:复制文件描述符,返回给定文件描述符的新文件描述符,从指定位置开始计数。
-F_GETFD:获取文件描述符的标志。
-F_SETFD:设置文件描述符的标志,如 FD_CLOEXEC。
-F_GETFL:获取文件状态标志,如 O_NONBLOCK。
-F_SETFL:设置文件状态标志。
-F_GETLK:获取记录锁信息。
-F_SETLK:设置记录锁。
-F_SETLKW:与 F_SETLK 类似,但如果锁已经被占用,则等待直到锁被释放。
-F_GETOWN:获取接收 SIGURG 信号和 out-of-band 数据的进程或线程的 ID。
-F_SETOWN:设置接收上述信号的进程或线程的 ID。
返回值:
-成功时,大多数 fcntl 命令返回一个非负值,具体值取决于命令。
-失败时,返回 -1,并设置 errno 以指示错误类型。
IOCTL
允许用户空间的程序执行或请求设备驱动程序执行非标准的操作。通常用于执行那些标准系统调用(如 read、write)无法实现的设备特定操作,例如:
-配置设备参数(如波特率、停止位等)。
-检索设备状态。
-执行设备特定的命令(如发送一个硬件重置信号)。
ioctl 的强大之处在于它的灵活性,它可以接受几乎任何类型的数据作为输入或输出,这使得它可以用于各种不同的设备和协议。
int ioctl(int fd, unsigned long request, ... /* void *arg */ );
参数说明:
-fd:文件描述符,表示要操作的设备或文件。
-request:指定了要执行的控制命令。
-arg(可选):一个可选参数,根据 request 的不同,可能需要传递一个指针来接收或发送数据。
返回值:
-成功时,返回非负值,具体值取决于操作的类型。
-失败时,返回 -1,并设置 errno 以指示错误类型。
利用IO自写getline函数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <string.h>
ssize_t K_getline(char **lineptr, size_t *n, FILE *stream) {
long int pos, len;
// 保存当前文件位置
pos = ftell(stream);
if (pos < 0) return -1; // 如果ftell失败,返回错误
// 移动到文件末尾
fseek(stream, 0, SEEK_END);
// 获取文件长度
len = ftell(stream) - pos;
// 恢复到原始位置
fseek(stream, pos, SEEK_SET);
// 为lineptr分配内存
if (*lineptr == NULL || *n < (size_t)len + 1) {
free(*lineptr); // 释放旧内存
*lineptr = malloc((len + 1) * sizeof(char)); // 分配新内存,包含'\0'位
if (*lineptr == NULL) return -1; // 如果内存分配失败,返回错误
*n = len + 1;
}
// 读取一行
if (fgets(*lineptr, (size_t)len + 1, stream) == NULL) return -1; // 如果读取失败,返回错误
// 返回读取的行的长度,不包括最后的空字符
return strlen(*lineptr);
}