01 文件 IO
文件 IO的分类
Unit它有很多衍生版本。那么我能不能够写出一个应用程序开让它既可以在Linux上运行,也可以在其他unix或者它扩展的内容上运行了。可以的,
只要你这个应用程序使用同一套接口就可以了,这套接口叫做posix接口,又名++系统IO++。
特性/名称 | 系统IO | 标准IO(底层依旧系统IO) |
---|---|---|
提供者 | Linux操作系统 | C标准库 |
缓存机制 | 无缓存,直接与内核交互 | 有缓存,减少系统调用次数 |
跨平台性 | 特定于Linux或Unix平台 | 跨平台,可在不同操作系统上使用 |
文件描述符/指针 | 使用文件描述符 | 使用FILE结构体指针 |
功能强大性 | 功能强大,可访问各种文件类型 | 功能相对较弱,通常用于普通文件 |
适用场景 | 实时性要求高的硬件设备操作 | 大量数据操作,如多媒体和文本文件 |
系统调用 | 每次操作都直接调用内核 | 通过缓存机制减少系统调用 |
性能 | 频繁系统调用可能降低性能 | 缓存机制提高IO效率 |
API函数 | open, read, write, close等 | fopen, fread, fwrite, fclose等 |
文件 IO函数使用
使用open函数 ++打开、创建文件++
在Linux系统中,open
函数是一个系统调用,用于打开或创建文件。其原型如下:
cs
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname
:待打开/创建文件的路径名。flags
:用于指定文件的打开/创建模式,如O_RDONLY
(只读)、O_WRONLY
(只写)、O_RDWR
(读写)等。mode
:当使用O_CREAT
标志创建新文件时,用于指定文件的访问权限
以下是一个使用open
函数在Linux系统中打开和创建的示例:
cs
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
int fd;
fd = open("example.txt", O_RDWR | O_CREAT, 0644);//return 文件句柄
if (fd < 0) {
// 处理错误
perror("Error opening file");
return -1;
}
// 文件成功打开
return 0;
}
保护措施:
umask限制权限
对book用户:
对root用户:
使用write函数写文件
- 写入文件 :使用
write
函数向文件写入数据。 - 读取文件 :使用
read
函数从文件读取数据。
cs
#include <unistd.h>
char *data = "Hello, World!";
size_t num_bytes = write(fd, data, strlen(data));
if (num_bytes == -1) {
// 处理错误
perror("Error writing to file");
exit(1);
}
使用read
系统调用读取文件内容
cs
char buf[BUFSIZE];
ssize_t numread = read(fd, buf, BUFSIZE);
每次读取时,内核会读取上一次保留的位置,并且基于此往后读取,读取完再记录当前位置......
效果演示
使用close
函数关闭文件
cs
if (close(fd) == -1) {
// 处理错误
perror("Error closing file");
exit(1);
}
以下是详细的示例代码:
cs
#include <unistd.h>
int main() {
int fd;
char buf[] = "Hello, World!";
char readBuf;
// 打开文件
fd = open("example.txt", O_RDWR | O_CREAT, 0644);
if (fd < 0) {
// 处理错误
return -1;
}
// 写入数据
write(fd, buf, sizeof(buf));
// 读取数据
lseek(fd, 0, SEEK_SET); // 将文件指针移至0位置(文件开头)
read_number=read(fd, readBuf, sizeof(readBuf));
// 打印读取的数据
printf("Read data: %s\n", readBuf);
// 关闭文件
close(fd);
return 0;
}
实际应用
查看原始数据的16进制:
(使用编译器)
(使用命令行)
知识点补充:
**
sscanf
**在C语言中用于处理字符串数据转换和解析,非常适用于从配置文件中读取数据或解析用户输入等场景
- 除了基本的格式说明符,
sscanf
还支持更复杂的格式,如:
宽度限定符%5s
(读取最多5个字符的字符串)、集合操作如%[a-z]
(匹配小写字母a到z中的任意字符)、%[^,]会读取字符串中从当前位置开始,直到遇到第一个逗号为止的所有字符。(%
是格式说明符的开始标志,[^,]
表示除了逗号外的任何字符)- 返回值
sscanf
函数返回成功匹配和赋值的参数个数。这可以用来检查读取操作是否成功。- 高级用法
- 使用
%*
可以跳过某些数据不进行读取。- 结合正则表达式,
sscanf
可以实现更复杂的字符串解析。- 示例
cssscanf函数的原型是 int sscanf(const char *str, const char *format,...); 这个函数从字符串str中按照format指定的格式(format字符串可以包含多种格式说明符,如%s(字符串)、%d(整数)、%f(浮点数)等。)读取数据,并将结果存储在后续的变量参数中 假设有一个字符串"Jack 201.75",可以使用sscanf解析出名字、年龄和身高: char name; int age; float height; sscanf("Jack 201.75", "%s %d %f", name, &age, &height);
忘记了strcpy是前面的复制到后面还是++后面的复制到前面++......哈哈哈
完整代码:
cs
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
/* 返回值: n表示读到了一行数据的数据个数(n >= 0)
* -1(读到文件尾部或者出错)
*/
static int read_line(int fd, unsigned char *buf)
{
/* 循环读入一个字符 */
/* 如何判断已经读完一行? 读到0x0d, 0x0a */
unsigned char c;
int len;
int i = 0;
int err = 0;
while (1)
{
len = read(fd, &c, 1);
if (len <= 0)
{
err = -1;
break;
}
else
{
if (c != '\n' && c != '\r')
{
buf[i] = c;
i++;
}
else
{
/* 碰到回车换行 */
err = 0;
break;
}
}
}
buf[i] = '\0';
if (err && (i == 0))
{
/* 读到文件尾部了并且一个数据都没有读进来 */
return -1;
}
else
{
return i;
}
}
void process_data(unsigned char *data_buf, unsigned char *result_buf)
{
/* 示例1: data_buf = ",语文,数学,英语,总分,评价"
* result_buf = ",语文,数学,英语,总分,评价"
* 示例2: data_buf = "张三,90,91,92,,"
* result_buf = "张三,90,91,92,273,A+"
*
*/
char name[100];
int scores[3];
int sum;
char *levels[] = {"A+", "A", "B"};
int level;
if (data_buf[0] == 0xef) /* 对于UTF-8编码的文件,它的前3个字符是0xef 0xbb 0xbf */
{
strcpy(result_buf, data_buf);
}
else
{
sscanf(data_buf, "%[^,],%d,%d,%d,", name, &scores[0], &scores[1], &scores[2]);
//printf("result: %s,%d,%d,%d\n\r", name, scores[0], scores[1], scores[2]);
//printf("result: %s --->get name---> %s\n\r", data_buf, name);
sum = scores[0] + scores[1] + scores[2];
if (sum >= 270)
level = 0;
else if (sum >= 240)
level = 1;
else
level = 2;
sprintf(result_buf, "%s,%d,%d,%d,%d,%s", name, scores[0], scores[1], scores[2], sum, levels[level]);
//printf("result: %s", result_buf);
}
}
/*
* ./process_excel data.csv result.csv
* argc = 3
* argv[0] = "./process_excel"
* argv[1] = "data.csv"
* argv[2] = "result.csv"
*/
int main(int argc, char **argv)
{
int fd_data, fd_result;
int i;
int len;
unsigned char data_buf[1000];
unsigned char result_buf[1000];
if (argc != 3)
{
printf("Usage: %s <data csv file> <result csv file>\n", argv[0]);
return -1;
}
fd_data = open(argv[1], O_RDONLY);
if (fd_data < 0)
{
printf("can not open file %s\n", argv[1]);
perror("open");
return -1;
}
else
{
printf("data file fd = %d\n", fd_data);
}
fd_result = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd_result < 0)
{
printf("can not create file %s\n", argv[2]);
perror("create");
return -1;
}
else
{
printf("resultfile fd = %d\n", fd_result);
}
while (1)
{
/* 从数据文件里读取1行 */
len = read_line(fd_data, data_buf);
if (len == -1)
{
break;
}
//if (len != 0)
// printf("line: %s\n\r", data_buf);
if (len != 0)
{
/* 处理数据 */
process_data(data_buf, result_buf);
/* 写入结果文件 */
//write_data(fd_result, result_buf);
write(fd_result, result_buf, strlen(result_buf));
write(fd_result, "\r\n", 2);
}
}
close(fd_data);
close(fd_result);
return 0;
}
02 文件IO内部调用机制
文件IO内部调用机制
- 用户层调用
- 应用程序调用如
open
、read
等函数。
- 应用程序调用如
- glibc库函数
- glibc内的函数设置触发异常的原因,并调用汇编指令
swi
或svc
来触发异常。
- glibc内的函数设置触发异常的原因,并调用汇编指令
- 异常处理
- CPU发现异常后,调用Linux内核中的异常处理函数,根据异常原因处理。
- 系统调用表
- 内核根据系统调用号在
sys_call_table
数组中查找并调用相应的系统调用处理程序。
- 内核根据系统调用号在
- 文件描述符
- 每个打开的文件在进程中有唯一的文件描述符,文件描述符表记录了文件描述符与文件结构体
file
的对应关系。
- 每个打开的文件在进程中有唯一的文件描述符,文件描述符表记录了文件描述符与文件结构体
- 文件操作
- 通过文件描述符进行读写操作时,内核根据文件描述符找到对应的
file
结构体,进行文件操作
- 通过文件描述符进行读写操作时,内核根据文件描述符找到对应的
dup函数的使用
dup
函数用于复制++文件描述符++。这在处理文件IO操作时非常有用,尤其是在需要多个文件描述符指向同一个文件的情况下
使用场景:打开同一个文件两次时,得到的文件句柄会不一样
但可以使用dep、dup2、dup3可以实现打开同一个文件时,使用同一个文件结构体
dup
函数:
用于复制一个现有的文件描述符。系统会分配一个新的、未用过的、值为最小的文件描述符,并使其指向与oldfd
相同的文件。
- 原型 :
int dup(int oldfd);
- 返回值:成功时返回新的文件描述符,失败时返回-1
dup2
函数:
与dup
类似,但允许用户指定返回的文件描述符的值。如果newfd
已经打开,则先关闭它。
- 原型 :
int dup2(int oldfd, int newfd);
- 返回值 :成功时返回
newfd
,失败时返回-1
dup3
函数:没有讲到
在dup2
的基础上增加了O_CLOEXEC
标志的支持,用于控制文件描述符在执行exec
类系统调用时是否自动关闭。
- 原型 :
int dup3(int oldfd, int newfd, int flags);
- 返回值 :成功时返回
newfd
,失败时返回-1
区别
dup
:总是返回当前可用文件描述符中数值最小的那个。dup2
:允许用户指定新的文件描述符的值。如果指定的newfd
已经打开,则先关闭它。dup3
:增加了对O_CLOEXEC
标志的支持,可以控制文件描述符在执行exec
类系统调用时是否自动关闭。
典型用法
- 重定向标准输入/输出/错误:通过将标准输入、输出或错误的文件描述符复制到其他文件描述符,实现重定向。
- 实现管道(Pipe):在创建管道时,需要复制文件描述符以实现数据的单向流动