005 嵌入式Linux应用开发——文件操作

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语言中用于处理字符串数据转换和解析,非常适用于从配置文件中读取数据或解析用户输入等场景

  1. 除了基本的格式说明符,sscanf还支持更复杂的格式,如:
    宽度限定符%5s(读取最多5个字符的字符串)、集合操作如%[a-z](匹配小写字母a到z中的任意字符)、%[^,]会读取字符串中从当前位置开始,直到遇到第一个逗号为止的所有字符。(% 是格式说明符的开始标志,[^,]表示除了逗号外的任何字符)
  2. 返回值
    • sscanf函数返回成功匹配和赋值的参数个数。这可以用来检查读取操作是否成功。
  3. 高级用法
    • 使用%*可以跳过某些数据不进行读取。
    • 结合正则表达式,sscanf可以实现更复杂的字符串解析。
  4. 示例
cs 复制代码
sscanf函数的原型是
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内部调用机制

  1. 用户层调用
    • 应用程序调用如openread等函数。
  2. glibc库函数
    • glibc内的函数设置触发异常的原因,并调用汇编指令swisvc来触发异常。
  3. 异常处理
    • CPU发现异常后,调用Linux内核中的异常处理函数,根据异常原因处理。
  4. 系统调用表
    • 内核根据系统调用号在sys_call_table数组中查找并调用相应的系统调用处理程序。
  5. 文件描述符
    • 每个打开的文件在进程中有唯一的文件描述符,文件描述符表记录了文件描述符与文件结构体file的对应关系。
  6. 文件操作
    • 通过文件描述符进行读写操作时,内核根据文件描述符找到对应的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

区别

  1. dup:总是返回当前可用文件描述符中数值最小的那个。
  2. dup2 :允许用户指定新的文件描述符的值。如果指定的newfd已经打开,则先关闭它。
  3. dup3 :增加了对O_CLOEXEC标志的支持,可以控制文件描述符在执行exec类系统调用时是否自动关闭。

典型用法

  1. 重定向标准输入/输出/错误:通过将标准输入、输出或错误的文件描述符复制到其他文件描述符,实现重定向。
  2. 实现管道(Pipe):在创建管道时,需要复制文件描述符以实现数据的单向流动
相关推荐
声网14 分钟前
a16z 最新 Voice AI 报告:语音将成为关键切入点,而非最终产品本身丨 Voice AI 学习笔记
人工智能·笔记·学习
云边有个稻草人1 小时前
DeepSeek与人工智能的结合:探索搜索技术的未来
人工智能·笔记·科技·算法·deepseek
吴声子夜歌2 小时前
Linux运维——文件内容查看编辑
java·linux·运维
一晌小贪欢3 小时前
Python办公笔记——将csv文件转Json
笔记·python·json·python办公·python读取csv
m0_748237153 小时前
Linux(CentOS)安装 MySQL
linux·mysql·centos
小镇敲码人3 小时前
【Linux网络编程】之配置阿里云安全组
linux·网络·阿里云
Golinie3 小时前
【Linux网络编程】谈谈网络编程中的select、poll、epoll、Reactor、Proactor模型(下)
linux·网络·reactor·epoll·io多路复用
张同学的IT技术日记3 小时前
线性代数于工程应用中的实践:以代码实例拆解相似性度量问题的求解逻辑
开发语言·笔记·python·学习·线性代数·工程应用
张同学的IT技术日记3 小时前
线性代数于工程应用中的实践:以代码实例拆解图像平滑问题的求解逻辑
开发语言·笔记·python·学习·线性代数·工程应用
汇能感知3 小时前
摄像头模块烟火检测
经验分享·笔记·科技