Linux中的文件读写操作详解及示例代码 | 嵌入式Linux应用开发篇 - 01

寻求更清爽的阅读体验,请访问我的个人博客:

1. Linux中“一切皆文件”

在Linux系统中,一切皆文件,文件类型根据其表示的意义,分为:

  • 普通文件
  • 设备文件:代表一个具体的硬件设备
  • 管道文件、FIFO文件:具有特殊意义的文件,用于进程间通信;
  • 套接字文件:用于网络通信;

所有这些文件都可以用一套API来操作,最基本的四个API是:

  • 打开:open
  • 读文件:read
  • 写文件:write
  • 关闭:close

在使用这些API操作文件的时候,需要传入文件标识符 fd(file descriptor),文件标识符的本质就是在进程中代码某一个具体文件的整数,在使用 open 函数打开一个文件时被唯一分配,一般情况下,fd的值如果从0开始分配,如果 fd 为负数,则表示文件打开失败或者操作失败。

需要注意,fd的值在使用open打开文件时被唯一分配,在使用close关闭文件时会被回收。举个例子,比如第一次打开文件时分配的值fd = 0,如果该文件被关闭,下次新打开一个文件时,fd依然为0,但如果之前打开的文件没有被close掉,则下次新打开一个文件时,fd递增为1,一直递增到系统设定的同时打开文件的最大值为止,这个最大值可以使用ulimit -n查看,一般为1024。

另外,文件描述符的值0、1、2被系统占用,在桌面Linux系统上表示:

  • fd = 0:表示标准输入(stdin),对应系统的键盘
  • fd = 1:表示标准输出(stdout),对应系统的显示器
  • fd = 2:表示标准错误输出(stderr),对应系统的显示器

而在嵌入式Linux系统中,一般不存在显示器和键盘,都是用串口交互,所以这三个值对应的设备如下:

  • fd = 0:表示标准输入(stdin),对应系统的控制台串口
  • fd = 1:表示标准输出(stdout),对应系统的控制台串口
  • fd = 2:表示标准错误输出(stderr),对应系统的控制台串口

2. Linux C库提供的文件操作API

2.1. 头文件

在使用文件操作API时,必须首先包含以下头文件:

#include <sys/types.h>		//定义了一些常用数据类型,比如size_t
#include <fcntl.h>			//定义了open、creat等函数,以及表示文件权限的宏定义
#include <unistd.h>			//定义了read、write、close、lseek等函数
#include <errno.h>			//与全局变量errno相关的定义
#include <sys/ioctl.h>		//定义了ioctl函数 

2.2. open — 打开文件

操作文件之前必须要打开文件,获取文件描述符fd,该函数原型如下:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags,mode_t mode);

① 参数含义如下:

  • pathname:文件路径+文件名称
  • flags:文件打开方式
  • mode:打开文件的权限
  • 返回值int:打开成功则返回文件描述符,打开失败则返回-1,同时设置全局变量errno的值来表示错误原因;

② flags标志的值可以使用在<fcntl.h>的宏定义:

  • O_RDONLY:只读
  • O_WRONLY:只写
  • O_RDWR:可读可写(常用)
  • O_CREAT:如果要打开的文件不存在,则创建新文件
  • O_EXCL:如果使用O_CREAT时文件已经存在,则返回错误消息
  • O_TRUNC:如果文件已经存在,且成功打开,则删除文件中原来的全部数据
  • O_APPEND:以追加写入方式打开文件,打开之后文件指针指向文件末尾

这些打开方式可以使用|操作符,一起使用,比如:

O_RDWR | O_CREAT

③ mode的值表示创建新文件时设置的权限,用8进制数来表示,也可以使用<fcntl.h>中定义的宏定义来表示,但是八进制数比较方便。

3bit的八进制数分别对应linux中文件的三个权限:

rwxrwxrwx

第一个rwx是文件拥有者的权限,第二个rwx是文件拥有者所在用户组的权限,第三个rwx是其它用户组的权限;

每一个rwx都对应一个8进制数,比如0x7就表示rwx三项权限全有,0x0就表示rwx三个权限全没有,比如:

  • 0700:所属用户有rwx权限,当前用户组和其他用户组的用户没有任何权限;
  • 0664(常用):所属用户有rw-权限(-表示没有此项对应权限),当前用户组的其它用户拥有rw-权限,其它用户组的用户只有r–权限。

2.3. read — 读取文件

函数原型如下:

ssize_t read(int fd, void *buf, size_t count);

函数参数含义如下:

  • fd:文件描述符
  • buf:用来接收所读数据的缓冲区
  • count:请求读取的字节数
  • 返回值:读取成功则返回读取的字节数,读取到文件尾则返回0,读取失败则返回-1,同时设置全局变量errno的值来表示错误原因;

2.4. write — 写入文件

函数原型如下:

ssize_t write(int fd, const void *buf, size_t count);

函数参数含义如下:

  • fd:文件描述符
  • buf:存放待写入数据的缓冲区
  • count:请求写入的字节数
  • 返回值:写入成功则返回实际写入的字节数,写入失败则返回-1,同时设置全局变量errno的值来表示错误原因;

2.5. close — 关闭文件

API原型如下:

int close(int fd);

函数参数fd表示要关闭文件的文件描述符。

如果关闭成功,返回0,否则返回-1,同时设置全局变量 errno 报告具体错误的原因。

3. 文件操作示例程序

范例实现功能:

打开当前目录下的text.txt文件,如果不存在则创建,首先写入一个字符串,然后关闭文件,再重新打开读取该文件中的内容并打印。

范例代码:

/**
 * @brief  文件读写API使用示例程序
 * @author Mculover666
 * @note   首先写入文件,接着读取文件
*/

#include <sys/types.h>		//定义了一些常用数据类型,比如size_t
#include <fcntl.h>			//定义了open、creat等函数,以及表示文件权限的宏定义
#include <unistd.h>			//定义了read、write、close、lseek等函数
#include <errno.h>			//与全局变量errno相关的定义
#include <sys/ioctl.h>		//定义了ioctl函数 
#include <stdio.h>

int main(void)
{
    int fd = -1;
    int res = 0;

    char filename[]  = "test.txt";
    char write_dat[] = "Hello World!";
    char read_buf[128] = {0};

    /* 写入文件操作示例 */
    //1. 打开文件
    fd = open(filename, O_RDWR | O_CREAT, 0664);
    if(fd < 0)
    {
        printf("%s file open fail,errno = %d.\r\n", filename, errno);
        return -1;
    }

    //2. 读取内容
    res = write(fd, write_dat, sizeof(write_dat));
    if(res < 0)
    {
        printf("write dat fail,errno = %d.\r\n", errno);
        return -1;
    }
    else
    {
        printf("write %d bytes:%s\r\n", res, write_dat);
    }

    //3. 关闭文件
    close(fd);

    /* 读取文件数据示例 */
    //1. 打开文件
    fd = open(filename, O_RDONLY);
    if(fd < 0)
    {
        printf("%s file open fail,errno = %d.\r\n", filename, errno);
        return -1;
    }

    //2. 写入内容
    res = read(fd, read_buf, sizeof(read_buf));
    if(res < 0)
    {
        printf("read dat fail,errno = %d.\r\n", errno);
        return -1;
    }
    else
    {
        printf("read %d bytes:%s\r\n", res, read_buf);
    }

    //3. 关闭文件
    close(fd);

    return 0;
}

编译:

gcc 01-file_test.c -o 01-file_test.o

运行:

4. 移植到嵌入式Linux开发板上运行测试

4.1. 优化程序 —— fsync

嵌入式Linux系统通常采用 Flash 存储器, write()有个问题 —— 将数据提交到系统内部缓存之后就返回,所以当它返回之后,嵌入式系统如果突然掉电,则文件数据也会写入失败。

所以,在嵌入式系统中使用这些API时,应该在write函数之后,用 fsync()函数把修改过的文件数据立马写入闪存中,避免数据丢失

fsync()函数的功能原型在<unistd.h>中,原型如下:

int fsync(int fd);

fsync()调用后,会直到文件已修改过的数据全部写入磁盘后才返回,成功返回 0,失败返回-1, 同时设置全局变量 errno 报告具体错误的原因。

在示例程序的write操作之后添加代码:

//写入之后立马同步数据
res = fsync(fd);
if(res < 0)
{
    printf("fsync fail,errno = %d.\r\n", errno);
    return -1;
}

4.2. 移植到开发板上运行

① 使用交叉编译工具重新编译程序:

arm-none-linux-gnueabi-gcc 01-file_test.c -o 01-file_test_arm.o

② 基于NFS文件系统,将可执行文件传到开发板上

cp 01-file_test_arm.o /nfs_root/

③ 开发板上执行该程序测试:

./01-file_test_arm.o

开发板上执行结果如下:

接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。

  • 11
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 嵌入式Linux应用开发第五章示例3是一个关于文件操作代码示例。该示例主要涉及到如何在Linux嵌入式系统使用文件操作的相关函数。 首先,在示例代码引入了几个与文件操作相关的头文件,包括<fcntl.h>、<sys/types.h>和<sys/stat.h>。这些头文件定义了一些文件打开、读写和关闭等函数以及文件的相关数据类型。 接下来,代码示例定义了一个用于存储文件路径的字符串变量path和一个用于存储文件描述符的整型变量fd。在示例,path被指定为了一个具体的文件路径,这个路径可以根据实际情况进行修改。fd用于存储打开的文件的描述符。使用函数open()来打开文件,并将返回的文件描述符保存到变量fd。在代码示例,函数open()的第一个参数为文件路径,第二个参数为打开文件的方式,第三个参数为文件的权限设置。 接下来,代码示例使用函数read()来读取打开的文件的内容,并将读取到的内容存储到一个用于存储读取结果的字符数组buf。函数read()的第一个参数为文件描述符,第二个参数为存储读取结果的缓冲区地址,第三个参数为缓冲区的大小。 最后,在代码示例的结尾部分,使用函数close()来关闭文件,传入文件描述符作为参数。 总结而言,该示例代码主要演示了在嵌入式Linux系统如何进行文件操作,包括打开文件、读取文件内容和关闭文件操作。通过学习该示例,可以了解到Linux系统文件操作相关的函数和头文件的使用方法。 ### 回答2: 嵌入式Linux应用开发第五章示例3代码主要涉及嵌入式设备使用动态库的开发过程。该示例使用了一个简单的动态库,主要实现了一个计算两个整数和的函数。 首先,在示例代码,我们首先需要编写一个动态库的C源文件,可以为该文件命名为libadd.c。该源文件定义了一个函数add,用于计算两个整数的和。然后,我们使用gcc编译器将该源文件编译为一个动态库,使用的命令是:gcc -shared -o libadd.so libadd.c。编译后的动态库文件为libadd.so。 接下来,在示例代码,我们需要编写一个可执行文件的C源文件,可以为该文件命名为main.c。在该源文件,我们通过使用dlopen函数动态加载libadd.so动态库,并通过dlsym函数获取动态库的add函数的地址。然后,我们可以调用add函数来计算两个整数的和,并输出结果。最后,我们使用gcc编译器将该源文件与动态库链接为一个可执行文件,使用的命令是:gcc -o main main.c -ldl。编译后的可执行文件为main。 最后,在示例代码,第三个函数是main函数,其通过调用add函数来计算两个整数的和,并输出结果。 通过以上步骤,我们成功实现了嵌入式设备动态库的使用。使用动态库可以提供代码的复用性,减少可执行文件的大小,并且方便进行代码的更新和维护。在实际应用,我们可以根据需要编写更多的动态库,并将其集成到嵌入式系统,以实现各种功能和服务。 ### 回答3: 第五章示例3代码是一个嵌入式Linux应用开发示例程序。这个示例程序主要展示了如何在Linux系统上使用C语言编写一个简单的应用程序,以及如何利用Linux内核提供的接口对系统进行控制和管理。 代码的主要结构是一个无限循环,循环不断读取用户输入的按键值,然后根据按键值执行相应的操作代码使用了Linux系统提供的头文件和库函数来实现输入和输出操作。 首先,在程序开始的部分,设置了一些初始化的参数。包括定义了按键值的变量和输入输出设备的文件描述符。 接着,在无限循环,使用read函数读取用户输入的按键值,并将其存储在按键变量。然后,通过switch语句对按键进行判断和处理。 代码示例操作比较简单,主要是根据不同的按键值打印相应的提示信息。例如,按下“1”键会打印“Hello World”等等。 最后,在代码的结尾处,关闭了打开的输入输出设备的文件描述符。 总的来说,这个示例程序展示了如何在嵌入式Linux系统进行应用开发和控制,以及如何利用Linux内核提供的接口进行输入和输出操作。这个示例程序比较简单,但是可以作为学习和理解嵌入式Linux应用开发的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mculover666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值