📃个人主页:island1314
🔥个人专栏:Linux—登神长阶
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
前言 🚀
🔥 每个系统都有自己的专属函数,我们习惯称其为系统函数。系统函数并不是内核函数,因为内核函数是不允许用户使用的,系统函数就充当了二者之间的桥梁,这样用户就可以间接的完成某些内核操作了。
如:open、close、lseek、read、write这些系统IO函数又被称为不带缓冲的IO (unbuffered IO)。术语不带缓冲指的是每个read和write都调用内核中的一个系统调用,因此也常叫做系统IO,与之相对应的还有标准IO(fopen、fread、fwrite、fclose等)。
应用层程序编写如下:
- 直接调用系统层接口IO(即open、 read、 write 等函数)
- 另一种则是调用后面C库的接口IO(即fopen、 fread、 fwrite 等 函数)间接地调用系统调用层接口。
**1. 系统 IO **
** 1.1 **open 函数 - 流打开
其实这个函数 我们之前在这篇博客里【Linux】基础 IO(文件描述符fd & 缓冲区 & 重定向)
提过的,大家可以去看看,以及下面的 close 和 write 也有说明
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* @description : 调用open可以打开或创建一个文件
* @param - path : 指定打开或创建的文件的文件名
* @param - flags : 指定文件的打开方式,选项后面有说明
* @param - mode : 在创建新文件的时候才需要指定这个参数的值,用于指定新文件的权限(常用值0664==rw-rw-r--);
* @return : 若成功,返回内核分配的文件描述符(大于0),失败返回-1;
*/
int open(const char *path, int flags, mode_t mode);
flags参数指定了文件打开方式等信息。用下列一个或多个宏常量进行“或 | ”运算构成flags参数:
(这些常量在头文件<fcntl.h>中定义)。
以下为必选属性 ,在这五个宏常量属性中必须指定一个且只能指定一个
flags说明O_RDONLY以只读方式打开文件O_WRONLY以只写方式打开文件O_RDWR.以读写方式打开文件O_EXEC只执行打开O_SEARCH只搜索打开(应用于目录)
以下为可选属性 , 可以和上边的属性一起使用。
flags说明O_APPEND新数据追加到文件尾部,不会覆盖文件的原来内容O_CREAT若此文件不存在则创建它,后面必须跟mode参数指定该新文件的访问权限。如果文件存在什么也不做
1.2 write 函数 - 流写入
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
//返回值:若成功,返回已写的字节数(带符号整型);若出错,返回-1
fd:指定需要偏移操作的文件描述符
buf:字符串
count:长度
返回值:通常与参数count的值相同,否则表示出错。
write 出错的一个常见原因:① 磁盘已写满,② 超过了一个给定进程的文件长度限制。
- 注意:对于普通文件,写操作从文件的当前偏移量处开始
- 如果在打开该文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。
1.3 read 函数 - 流读取
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//返回值:若成功,返回读的字节数(带符号整型);若已到文件尾,返回0;出错,返回-1
fd:指定需要偏移操作的文件描述符
buf:字符串
count:长度
有多种情况可使实际读到的字节数少于要求读的字节数:
- 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前有30个字节,而要求读100个字节,则read返回30。下一次再调用read时,它将返回 0 (文件尾端)
- 当从终端设备读时,通常一次最多读一行
- 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
- 当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。
- 当从某些面向记录的设备(如磁带〉读时,一次最多返回一个记录。
- 当一信号造成中断,而已经读了部分数据量时。读操从文件的当前偏移量处开始。在成功返问之前,该偏移量将增加实际读到的字节数。
1.4 close 函数 - 流关闭
#include <unistd.h>
/*
* @description : 调用close可以关闭一个已打开的文件
* @param - fd : 指定关闭的文件的描述符;
* @return : 若成功,返回0,失败返回-1;
*/
int close(int fd);
关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
当一个进程终止时,如果不调用close()手动关闭打开的文件,内核将自动关闭它所有的打开文件
1.5 lseek 函数 - 定位流
每个打开文件都有一个与其相关联的 “当前文件偏移量”
它通常是一个非负整数,用以度量从文件开始处计算的字节数。
通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
- 按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0
当然也可以调用 lseek 显式地为一个打开文件设置偏移量。
#include <sys/types.h>
#include <unistd.h>
/*
* @description : 调用lseek函数可以移动文件指针,也可以通过这个函数进行文件的拓展。
* @param - fd : 指定需要偏移操作的文件描述符;
* @param - offset : 指定偏移量,需要和第三个参数配合使用
* @param - whence : 通过这个参数指定函数实现什么样的功能:
* SEEK_SET: 从文件头部开始偏移 offset 个字节
* SEEK_CUR: 从当前文件指针的位置向后偏移 offset 个字节
* SEEK_END: 从文件尾部向后偏移 offset 个字节
* @return : 若成功,返回文件指针从头部开始计算总的偏移量;出错,返回-1
*/
off_t lseek(int fd, off_t offset, int whence);
对参数offset的解释与参数whence的值(符号常量)有关:
- 若whence是 SEEK_SET,则将该文件的偏移量设置为距文件开始处 offset个字节
- 若whence是 SEEK_CUR,则将该文件的偏移量设置为其当前值加 offset,offset可为正或负
- 若whence是 SEEK_END,则将该文件的偏移量设置为文件长度加 offset,offset可为正或负
1.6 综合样例
使用代码打开当前路径下的“bite”文件(如果文件不存在在创建文件),向文件当中写入“i like linux!”.
#include <stdio.h>
#include <unistd.h>//是close, write这些接口的头文件
#include <string.h>
#include <fcntl.h>//是 O_CREAT 这些宏的头文件
#include <sys/stat.h>//umask接口头文件
int main()
{
//将当前进程的默认文件创建权限掩码设置为0--- 并不影响系统的掩码,仅在当前进程内生效
umask(0);
//int open(const char *pathname, int flags, mode_t mode);
int fd = open("./bite", O_CREAT|O_RDWR, 0664);
if(fd < 0) {
perror("open error");
return -1;
}
char *data = "i like linux\n!";
//ssize_t write(int fd, const void *buf, size_t count);
ssize_t ret = write(fd, data, strlen(data));
if (ret < 0) {
perror("write error");
return -1;
}
//off_t lseek(int fd, off_t offset, int whence);
lseek(fd, 0, SEEK_SET);
char buf[1024] = {0};
//ssize_t read(int fd, void *buf, size_t count);
ret = read(fd, buf, 1023);
if (ret < 0) {
perror("read error");
return -1;
}else if (ret == 0) {
printf("end of file!\n");
return -1;
}
printf("%s", buf);
close(fd);
return 0;
}
运行结果如下:
**2. 标准 IO **
**2.1 **fopen 函数 - 流打开
#incldue<stdio.h>
FILE * fopen(const char *pathname, const char *method);
功能:用于打开文件
参数:
- pathname:被打开文件的文件路径以及文件名。
- method:打开文件的方式。
具体方式如下:
- “r" 或 ”rb" 以只读方式打开文件。
- “w" 或 ”wb" 以写方式打开文件,新内容会覆盖原本内容。
- “a” 或 “ab” 以写方式打开文件,新内容追加在文件末尾。
FILE *fp;//定义一个指向FILE结构的指针
// 在当前路径用可读可写打开一个“ccc.txt”的文件,如果不存在则创建它
fp=fopen("./ccc.txt","w+");
返回值
- 成功:它返回一个指向FILE结构的指针,该结构代表这个新创建的流(文件顺利打开后,指向该流的文件指针就会被返回)
- 失败:它就会返回一个空指针,errno会提示问题的性质(如果文件打开失败,则返回NULL,并把错误代码存在errno中)
当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针( 文件指针)。
该对象通常是一个结构,它包含了标准IO库为管理该流需要的所有信息(流),
包括用于实际IO 的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。
- 为了引用一个流,需将 FILE 指针作为参数传递给每个标准IO函数*
2.2 fwrite 函数 - 流写入
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
- ptr – 是一个指针,对fwrite来说,是要输出数据的地址
- size – 这是要读取的每个元素的大小,以字节为单位
- nmemb – 这是元素的个数,每个元素的大小为 size 字节
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流
返回值
- 如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。
2.3 fread 函数 - 流读取
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
- ptr – 是一个指针,对fread来说,它是读入数据的存放地址。
- size – 这是要读取的每个元素的大小,以字节为单位。
- nmemb – 这是元素的个数,每个元素的大小为 size 字节。
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
返回值
- 成功读取的元素总数会以 size_t nmemb对象返回,size_t 对象是一个整型数据类型。
- 如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾
2.4 fclose 函数 - 流关闭
int fclose( FILE *file )
返回值:
- 对于输出流,fclose函数会在文件关闭前刷新缓冲区,
- 如果它执行成功,fclose返回0
在该文件被关闭之前,冲洗缓冲中的输出数据。
- 缓冲区中的任何输入数据被丢弃。如果标准I/O库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
- 当一个进程正常终止时(直接调用exit函数,或从main函数返回),
则所有带未写缓冲数据的标准IO流都被冲洗,所有打开的标准IO流都被关闭
2.5 fseek 函数 - 定位流
int fseek(FILE *stream, long offset, int whence);
参数:
- stream: 文件指针。
- offset: 偏移量,就是相当于当前位置,向左(右)移动几位。正数表示右向偏移,负数表示左向偏移。
- whence: 定义文件中哪里开始偏移,取值可为:SEEK_CUR(当前位置)、 SEEK_END (文件结尾)或 SEEK_SET(文件开头)。
- 其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2。
返回值:
- 如果执行成功,函数返回0。如果执行失败,函数返回一个非0值。
2.6 综合样例
使用代码打开当前路径下的“bite”文件(如果文件不存在在创建文件),向文件当中写入“linux so easy!”.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE *fp = fopen("./byte", "wb+");
if(fp == NULL){
perror("fopen Error");
return -1;
}
fseek(fp, 0, SEEK_SET);//跳转读写位置到,从文件起始位置开始偏移0个字节
char *data = "linux so easy!\n";
size_t ret = fwrite(data, 1, strlen(data), fp);
if(ret != strlen(data)){
perror("fwrite Error");
return -1;
}
fseek(fp, 0, SEEK_SET);
char buf[1024] = {0};
//因为设置读取块大小位1,块个数为1023因此fread返回值为实际读取到的数据长度
ret = fread(buf, 1, 1023, fp);
if (ret == 0) {
if (ferror(fp)) //判断上一次IO操作是否正确
printf("fread error\n");
if (feof(fp)) //判断是否读取到了文件末尾
printf("read end of file!\n");
return -1;
}
printf("%s", buf);
fclose(fp);
return 0;
}
运行结果如下:
3. 区别 🔖
(1)缓冲机制
- 系统 I/O:- 通常不使用缓冲,直接进行数据传输,这可能导致性能较低,因为每个 I/O 操作都涉及系统调用。
- 标准 I/O:- 采用缓冲机制,能提高 I/O 性能,尤其是在频繁读取或写入时。标准 I/O 会将数据存储在内存中,减少对系统调用的直接需求。
(2)灵活性和可移植性
- 系统 I/O:- 通常依赖于特定操作系统的实现,可能不具有跨平台的可移植性。
- 标准 I/O:- 由 C 标准库定义,具有较高的可移植性,可以在不同的平台上使用相同的代码。
(3)错误处理
- 系统 I/O:- 需要手动检查返回值和设置
errno
来处理错误。 - 标准 I/O:- 提供了更方便的错误处理机制,可以使用
ferror()
等函数检查错误状态。
4. 总结 📖
- 使用场景: 如果需要底层控制和优化性能,可以使用系统 I/O;如果希望简化开发过程,使用标准 I/O 更为合适。
- 复杂性: 标准 I/O 提供更高的抽象和易用性,适合大多数常规应用;系统 I/O 则适用于对性能和资源管理有特殊要求的场合。
【★,°*:.☆( ̄▽ ̄)/$:*.°★ 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!**
版权归原作者 IsLand1314~ 所有, 如有侵权,请联系我们删除。