创客学院
Linux下支持两种IO接口
1、文件IO(open/close/read/write)——POSIX标准 2、标准IO(fopen/fclose/fread/fwrite)——ANSI标准
两者为相互依赖的关系
概念
对设备(如硬盘、内存、网卡、键盘)中的数据进行读写 input、output
IO作用
Linux“下一切皆文件”,磁盘、硬件、网络设备等都被虚拟成了相应的文件 Linux对这些文件的操作都通过IO接口来完成的。
Linux内核中IO操作的实现机制
Linux系统内部会将文件系统,设备驱动、网络协议等复杂的操作实现好,对应用层只提供简单的应用接口,即API(application interface)应用接口
一般计算机分三层——硬件-操作系统-APP
Linux内部的VFSC(虚拟文件系统层)层对不同文件系统,设备驱动、网络协议等接口做了标准化,这样在应用层就可以通过统一的接口操作不同的设备。 驱动——直接读写硬盘 在一个操作系统下,文件系统的种类也可以很多
华清创客学院
概念:
一组相关数据的集合
文件类型:
7种
-
常规文件 r
-
目录文件 d
-
字符设备文件 c
-
块设备文件 b
-
管道文件 p
-
套接字文件 s
-
符号链接文件 I
强调:操作系统不同,所支持的文件类型也不同
标准I/O
介绍
标准I/O由ANSI C标准定义
主流操作系统上都实现了C库
标准I/O通过缓冲机制减少系统调用,实现更高的效率
无缓冲
有缓冲
标准I/O-流
FILE
标准IO用一个结构体类型来存放打开的文件的相关信息
标准I/O的所有操作都是围绕FILE来进行
流(stream)
FILE又被称为流(stream)
文本流/二进制流
-
Windows
二进制流:换行符 ←→ '\n'
文本流:换行符 ←→ '\r'\n'
-
Linux
换行符 ←→ '\n'
标准I/O-流的缓冲类型
-
全缓冲
当流的缓冲区无数据或无空间时才执行实际I/O操作
-
行缓冲
当在输入和输出中遇到换行符('\n')时,进行I/O操作
当流和一个终端关联时,典型的行缓冲
-
无缓冲
数据直接写入文件,流不进行缓冲
标准I/O-stdin,stdout,stderr
标准I/O-打开流
下列函数可用于打开一个标准I/O流:
FILE *fopen(const char *path,const char *mode);
成功时返回流指针;出错时返回NULL
// 函数原型 FILE *fopen(const char *path, const char *mode); // 参数解释: // path: 要打开的文件的路径名(包括文件名)。 // mode: 打开文件的模式,例如 "r" 表示读取,"w" 表示写入,"a" 表示追加等。 // 返回值: // 如果文件成功打开,返回指向该文件的 FILE 指针。 // 如果文件打开失败,返回 NULL。
标准I/O-fopen-mode参数
标准I/O-fopen-示例
标准I/O-fopen-新建文件权限
-
fopen()创建的文件访问权限是0666(rw-rw-rw-)
-
Linux系统中umask设定会影响文件的访问权限,其规则为(0666 &~umask)
-
用户可以通过umask函数修改相关设定
-
如果希望umask不影响文件访问权限,该如何设定?
标准I/O-处理错误信息
extern int errno; void perror(const char *s); char *strerror(int errno);
-
errno 存放错误号
-
perror 先输出字符串s,再输出错误号对应的错误信息
-
strerror 根据错误号返回对应的错误信息
标准I/O-处理错误信息-示例1
标准I/O-处理错误信息-示例2
标准I/O-关闭流
int fclose(FILE *stream);
-
fclose()调用成功返回0,失败返回EOF,并设置errno
-
流关闭时自动刷新缓冲中的数据并释放缓冲区
-
当一个程序正常终止时,所有打开的流都会被关闭
-
流一旦关闭后就不能执行任何操作
标准I/O-思考和练习
程序中能够打开的文件或文件或流的个数有限制,如何测试?
思路:循环打开流,成功则计数器累加,直到出错为止
答案:1021 + stdin + stdout + stderr = 1024
在大多数Unix和Linux系统中,每个进程默认都有一个文件描述符表,该表的大小通常限制为1024个条目。这些条目用于跟踪进程打开的所有文件、套接字和其他I/O资源。
-
stdin
(标准输入)通常是文件描述符0。 -
stdout
(标准输出)通常是文件描述符1。 -
stderr
(标准错误)通常是文件描述符2。
因此,如果您尝试打开1021个额外的文件或资源,并且已经使用了stdin、stdout和stderr,那么总共会有1024个文件描述符被使用,这达到了默认限制。 请注意,这个限制是可以通过系统配置进行调整的。例如,在Linux上,可以使用ulimit -n命令来查看或设置这个限制。在某些系统上,这个限制可能更高或更低,取决于系统管理员的设置和硬件资源。 如果您的程序在尝试打开第1022个文件时失败,并报告达到了打开文件的限制,那么这通常意味着您的程序或系统已经达到了文件描述符的限制。此时,您可能需要调整系统配置以增加这个限制,或者优化您的程序以减少同时打开的文件数量。
#include <stdio.h> #include <stdlib.h> //这个头文件包含了各种常用的函数,如内存分配(malloc、calloc、realloc、free)、程序终止(exit、abort)、随机数生成(rand、srand)等。 #include <string.h> //这个头文件包含了处理字符串的函数,如复制字符串(strcpy、strncpy)、连接字符串(strcat、strncat)、比较字符串(strcmp、strncmp)、查找字符串中的字符(strchr、strrchr)等。 #include <errno.h> //这个头文件定义了全局变量errno,它用于在调用系统调用或库函数出错时存储错误码。errno的值可以通过特定的错误码宏(如ENOENT、EACCES等)来检查,以确定发生了哪种错误。 #include <unistd.h> //这个头文件主要包含了POSIX操作系统API,它提供了对操作系统服务的访问,如进程管理(fork、exec系列函数)、文件I/O(read、write、lseek)、进程间通信(管道、信号、共享内存等)、用户账户控制(getuid、setuid等)。 #define MAX_FILES_TO_OPEN 1000 // 假设我们最多尝试打开1000个文件 #define BASE_FILENAME "file" // 基础文件名 #define FILE_EXTENSION ".txt" // 文件扩展名 #define BUFFER_SIZE 1024 // 足够的缓冲区大小来构造文件名 int main() { FILE *fp; int file_no; int success_count = 0; // 记录成功打开的文件数 char filename[BUFFER_SIZE]; // 创建临时文件 for (file_no = 1; file_no <= MAX_FILES_TO_OPEN; ++file_no) { snprintf(filename, BUFFER_SIZE, "%s%d%s", BASE_FILENAME, file_no, FILE_EXTENSION); fp = fopen(filename, "w"); // 以写模式打开文件来创建它 if (fp != NULL) { fclose(fp); // 关闭文件,此时文件已经被创建 } else { perror("Failed to create temporary file"); return EXIT_FAILURE; } } // 现在尝试打开这些文件 for (file_no = 1; file_no <= MAX_FILES_TO_OPEN; ++file_no) { snprintf(filename, BUFFER_SIZE, "%s%d%s", BASE_FILENAME, file_no, FILE_EXTENSION); fp = fopen(filename, "r"); // 以只读方式打开文件 if (fp != NULL) { // 如果文件成功打开,则增加成功计数并关闭文件 success_count++; fclose(fp); printf("Successfully opened file: %s\n", filename); } else { // 如果文件打开失败,检查错误原因 if (errno == EMFILE || errno == ENFILE) { printf("Reached the limit of open files: errno = %d\n", errno); } else { printf("Failed to open file: %s, errno = %d\n", filename, errno); } break; // 退出循环 } } // 清理临时文件 for (file_no = 1; file_no <= MAX_FILES_TO_OPEN; ++file_no) { snprintf(filename, BUFFER_SIZE, "%s%d%s", BASE_FILENAME, file_no, FILE_EXTENSION); remove(filename); // 删除文件 } printf("Successfully opened %d files.\n", success_count); return 0; }
标准I/O-读写流
流支持不同的读写方式:
-
读写一个字符:fgetc()/fputc()一次读/写一个字符
-
读写一行:fgets()和fputs()一次读/写一行
-
读写若干个对象:fread()/fwrite()每次读/写若干个对象,而每个对象具有相同的长度
标准I/O-按字符输入
下列函数用来输入一个字符:
#include<stdio.h> int fgetc(FILE *stream); int getc(FILE *stream); int getchar(void);
-
成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)
-
getchar()等同于fgetc(stdin)
标准I/O-fgetc-示例1
标准I/O-fgetc-示例2
这段代码的主要功能是统计并输出给定文件的大小(以字节为单位)。它接受一个命令行参数(文件名),然后打开一个文件,逐个字符地读取文件内容,并计数。最后,输出文件的总字节数。
// 函数:统计文件大小 #include<stdio.h> int main(int argc, const char *argv[]) { /* // 注释掉的代码,用于从标准输入(stdin)读取一个字符并打印 int ch; ch = fgetc(stdin); // 从标准输入读取一个字符 printf("%c\n",ch); // 打印读取到的字符 */ FILE *fp; // 定义文件指针fp int ch, count = 0; // 定义字符变量ch和计数变量count,初始化为0 // 判断是否成功打开文件,如果打开失败则输出错误信息并返回-1 if((fp = fopen(argv[1],"r")) == NULL) { perror("fopen"); // 输出fopen函数调用的错误信息 return -1; // 返回-1表示程序异常退出 } // 循环读取文件内容,直到遇到文件结束符EOF while((ch = fgetc(fp)) != EOF) { count++; // 每读取一个字符,计数器加1 } // 输出文件的总字节数 printf("total %d bytes\n", count); // 关闭文件,并返回0表示程序正常退出 fclose(fp); // 关闭文件 return 0; }
标准I/O-按字符输出
下列函数用来输出一个字符:
#include<stdio.h> int fput(int c,FILE *stream); int putc(int c,FILE *stream); int putchar(int c);
-
成功时返回写入的字符;出错时返回EOF(-1)
-
putchar()等同于fputc(c, stdout)
标准I/O-fputc-示例1
标准I/O-fputc-示例2
这段代码的主要功能是:接受一个命令行参数(文件名),然后以写入模式("w")打开这个文件。如果文件不存在,它会被创建。接着,程序会将小写英文字母从'a'到'z'逐个写入到这个文件中。如果文件打开失败,程序会输出错误信息并返回-1。最后,程序正常退出时返回0。
#include<stdio.h> int main(int argc, const char *argv[]) { FILE *fp; // 定义文件指针fp int ch; // 定义字符变量ch,用于存储要写入文件的字符 // 以写入模式("w")打开文件,如果文件不存在则创建它 // argv[1]是命令行参数中的文件名 if((fp = fopen(argv[1],"w")) == NULL) { perror("fopen"); // 如果打开文件失败,则调用perror函数输出错误信息 return -1; // 并返回-1表示程序异常退出 } // 使用for循环,从字符'a'开始到字符'z'结束,逐个写入文件 for(ch = 'a'; ch <= 'z'; ch++) { fputc(ch, fp); // 使用fputc函数将字符ch写入到文件指针fp所指向的文件中 } // 关闭文件,释放文件指针 fclose(fp); return 0; // 程序正常执行完毕,返回0 }
标准I/O-思考和练习
如何利用fgetc/fputc实现文件的复制?
-
通过命令行参数传递源文件和目标文件名
-
通过fgetc返回值判断是否读到文件末尾
这段代码的功能是复制一个文件的内容到另一个文件。它接受两个命令行参数:第一个是源文件的名称,第二个是目标文件的名称。程序首先检查是否提供了足够的参数,然后尝试打开源文件和目标文件。如果任一文件打开失败,程序会输出错误信息并退出。如果文件打开成功,程序会逐个字符地从源文件读取内容,并写入到目标文件中。最后,程序关闭这两个文件并正常退出。
#include<stdio.h> int main(int argc, const char *argv[]) { FILE *fps, *fpd; // 定义两个文件指针,fps指向源文件,fpd指向目标文件 int ch; // 定义一个整型变量,用于存储从源文件读取的字符 // 检查命令行参数数量是否足够 if(argc < 3) { printf("Usage : %s <src_file> <dst_file>\n",argv[0]); // 如果参数不足,输出使用方法,并返回-1表示程序异常退出 return -1; } // 以只读模式打开源文件 if((fps = fopen(argv[1],"r")) == NULL){ perror("fopen src file"); // 如果打开源文件失败,输出错误信息 return -1; // 并返回-1表示程序异常退出 } // 以写入模式打开目标文件 if((fpd = fopen(argv[2],"w")) == NULL){ perror("fopen dst file"); // 如果打开目标文件失败,输出错误信息 return -1; // 并返回-1表示程序异常退出 } // 循环读取源文件的内容,直到文件结束(EOF) while((ch = fgetc(fps)) != EOF) { fputc(ch,fpd); // 将读取到的字符写入目标文件 } // 关闭源文件 fclose(fps); // 关闭目标文件 fclose(fpd); // 程序正常结束,返回0 return 0; }
后续待更新……