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):在创建管道时,需要复制文件描述符以实现数据的单向流动
相关推荐
用户97183563346616 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪18 小时前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩1 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
闪闪发亮的小星星1 天前
高斯光以及高斯光公式解释
笔记
古城小栈1 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
cqbzcsq2 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
凡人叶枫2 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++