?个人主页:island1314
?个人专栏:Linux—登神长阶
⛺️ 欢迎关注:?点赞 ??留言 ?收藏 ? ? ?
前言 ?
? 每个系统都有自己的专属函数,我们习惯称其为系统函数。系统函数并不是内核函数,因为内核函数是不允许用户使用的,系统函数就充当了二者之间的桥梁,这样用户就可以间接的完成某些内核操作了。
如:open、close、lseek、read、write这些系统IO函数又被称为不带缓冲的IO (unbuffered IO)。术语不带缓冲指的是每个read和write都调用内核中的一个系统调用,因此也常叫做系统IO,与之相对应的还有标准IO(fopen、fread、fwrite、fclose等)。
应用层程序编写如下:
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);//返回值:若成功,返回已写的字节数(带符号整型);若出错,返回-1fd:指定需要偏移操作的文件描述符buf:字符串count:长度
返回值:通常与参数count的值相同,否则表示出错。
write 出错的一个常见原因:① 磁盘已写满,② 超过了一个给定进程的文件长度限制。
1.3 read 函数 - 流读取
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count); //返回值:若成功,返回读的字节数(带符号整型);若已到文件尾,返回0;出错,返回-1fd:指定需要偏移操作的文件描述符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 函数 - 定位流
每个打开文件都有一个与其相关联的 “当前文件偏移量”
它通常是一个非负整数,用以度量从文件开始处计算的字节数。
通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
当然也可以调用 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 的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。
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 则适用于对性能和资源管理有特殊要求的场合。【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!