首页 > 系统相关 >【嵌入式Linux】<总览> 文件IO

【嵌入式Linux】<总览> 文件IO

时间:2024-06-21 20:57:03浏览次数:32  
标签:文件 include 函数 int argv fd IO Linux 总览

文章目录

前言

一、文件IO常用函数

1. open函数

2. close函数

3. write函数

4. read函数

5. dup函数

6. dup2函数

7. lseek函数

8. fcntl函数

二、文件读写细节

1. 换行符

2. 文件描述符

3. errno和perror

4. 系统IO和用户IO

5. Linux管理文件

6. 文件共享

三、文件属性

1. Linux文件类型

2. 获取文件属性

3. 读取目录文件


前言

在Linux系统中,一切皆文件。因此,掌握Linux下文件IO常用的函数、理解读写文件背后的原理至关重要。在【嵌入式Linux笔记】第三篇:Linux应用开发基础(上)中,本人已经记载了相关的文件IO的知识,但那篇只是用于快速入门,记录的知识并不全面。本篇更加细致地记录文件IO中的重要知识点,若涉及版权问题,请联系本人删除。


一、文件IO常用函数

文件IO操作流程:首先调用open函数打开文件,其次采用read/write函数进行读写操作,最后调用close函数关闭文件。

值得注意的是:调用open函数之后,内核就会在进程中建立一个打开文件的数据结构,记录打开的文件;然后内核去磁盘(块设备)中找到文件,将其加载到内存中,我们读写都是作用于内存中的文件;最后调用close函数才会将内存中的文件写入到块设备中。

这样设计的原因:块设备本身有读写限制,读写操作起来不灵活;内存以字节为单位,操作灵活方便。

1. open函数

【1】头文件:#include <sys/types.h>、#include <sys/stat.h>、#include <fcntl.h>

【2】函数原型:

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

【3】功能:打开或创建一个文件

【4】相关描述:

①pathname:指明了文件打开的地址。

②flags:必须包含以下之一:O_RDONLY, O_WRONLY, O_RDWR。当有多个参数时,用|来分隔。其余如下的flags是可选的:

  • O_APPEND:追加内容
  • O_TRUNC:截断,如果文件存在并且以只写、读写方式打开,则将其长度截断为0
  • O_CREAT:若文件不存在,则创建
  • O_EXCL:若要创建的文件已存在,则出错并返回-1,同时修改errno的值
  • O_NONBLOCK:以非阻塞方式打开设备文件
  • O_SYNC:write函数阻塞等待内容全部写入块设备中才返回
  • 注意:如果 O_APPEND | O_TRUNC 则为 O_TRUNC 的效果

③mode:只有当创建新文件时(使用了O_CREAT时)才使用,用于指定文件的访问权限,能用权限的数字表示法。

  • umask能够设置文件在创建时mode的掩码。例如,使用umask命令得到"0002"的结果,那么文件创建时模式掩码为"000 000 000 010"。其实就是other用户权限总数字-2。
  • 使用open创建文件时最终的权限结果为:"mode & ~umask"。因此,若指定权限为0777但创建文件的权限结果为"000 111 111 101"即0775.

④返回值:

  • 打开成功:是一个文件描述符,其是一个小的、非负整数。成功调用返回的文件描述符是当前未为进程打开的编号最低的文件描述符。
  • 打开失败:-1

⑤机制:调用open函数会创建一个新的打开文件描述,打开文件描述记录了文件偏移量和文件状态标志。文件描述符就是对该打开文件描述的参考。当路径名被删除或修改为其它文件,则此参考并不受影响。

【5】open函数打开文件:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
	//命令检测
	if (argc != 2) {
		printf("请输入命令:%s <filename>\n", argv[0]);
		return -1;
	}
	//打开文件
	int fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror("错误信息");
		return -1;
	}
	//执行相关操作
	printf("打开文件成功,fd=%d\n", fd);
	//关闭文件
	close(fd);
	return 0;
}

【6】open函数创建文件:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
	//命令检测
	if (argc != 2) {
		printf("请输入命令:%s <filename>\n", argv[0]);
		return -1;
	}
	//打开文件,若不存在则创建权限为777
	//但是系统保护,最终创建权限为775
	int fd = open(argv[1], O_RDWR | O_CREAT, 0777);
	if (fd < 0) {
		perror("错误信息");
		return -1;
	}
	//执行相关操作
	printf("打开文件成功,fd=%d\n", fd);
	//关闭文件
	close(fd);
	return 0;
}

2. close函数

【1】头文件:#include <unistd.h>

【2】函数原型:int close(int fd);

【3】功能:关闭文件描述符为fd的文件。

【4】返回值:关闭成功为0,关闭失败为-1


3. write函数

【1】头文件:#include <unistd.h>

【2】函数原型:ssize_t write(int fd, const void *buf, size_t count);

【3】功能:将buf中指定字节数count的内容写入文件描述符为fd的文件中。

【4】相关描述:

  • ①返回值:
    • 写入成功:返回写入的字节数
    • 写入失败:返回小于写入字节数
    • 发生错误:返回-1
  • 使用lseek函数给指定位置写入内容,是对原有位置内容进行覆盖。

【5】示例:命令行中用户输入运行命令时,给出多条字符串,将字符串写入指定文件中。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char **argv)
{
	/* 1.命令格式判定 */
	if (argc <= 2) {
		printf("请输入命令:%s <文件名> 字符串1 ...\n", argv[0]);
		return -1;
	}
	/* 2.打开文件 */
	int fd = open(argv[1], O_RDWR | O_CREAT, 0664);
	if (fd < 0) {
		perror("打开文件失败");
		return -1;
	}
	/* 3.写入字符串 */
	for (int i = 2; i < argc; ++i) {
		int writeLen = write(fd, argv[i], strlen(argv[i]));
		if (writeLen < strlen(argv[i])) {
			perror("写入失败");
			break;
		}
		write(fd, "\n", 1);//每个字符串后再换行
	}
	/* 4.关闭文件 */
	close(fd);
	return 0;
}

4. read函数

【1】头文件:#include <unistd.h>

【2】函数原型:ssize_t read(int fd, void *buf, size_t count);

【3】功能:将文件描述符为fd的文件中指定字节数count的内容读取到buf中。

【4】返回值:读取成功返回字节数,发生错误返回-1。在判定读取是否成功时,最好不要拿返回的字节数与指定的读取字节数作比较,因为有时指定的字节数会比返回的字节数大。

【5】示例:

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	/* 1.命令格式判定 */
	if (argc != 2) {
		printf("请输入如下格式:%s <文件名>\n", argv[0]);
		return -1;
	}
	/* 2.打开文件:只读 */
	int fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror("打开失败");
		return -1;
	}
	/* 3.读取文件中所有内容 */
	char readBuf[100];
	int readLen = read(fd, readBuf, sizeof(readBuf));
	if (readLen == -1) {
		perror("读取失败");
		close(fd);
		return -1;
	}
	printf("读取的文本长度:%d\n", readLen);
	printf("读取的文本内容:%s", readBuf);
	/* 4.关闭文件 */
	close(fd);
	return 0;
}

5. dup函数

【1】头文件:#include <unistd.h>

【2】函数原型:int dup(int oldfd);

【3】功能:复制。将生成的文件描述符指向传入的oldfd指向的结构体,而不会新建一个结构体。

【4】返回值:新生成的文件描述符。 

【5】示例:假设本地已有一个1.txt文件,当我们通过下列程序去读1.txt文件时,执行结果为:①fd = 3, fd2 = 4, fd3 = 5 ②fd: 1, fd2: 1, fd3 : 2

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	//命令行判定
	if (argc != 2) {
		printf("请输入下列格式:%s <文件名>\n", argv[0]);
		return -1;
	}
	//dup功能检验
	int fd = open(argv[1], O_RDONLY);	//会新建一个结构体
	int fd2 = open(argv[1], O_RDONLY);	//会新建一个结构体
	int fd3 = dup(fd);	//不新建,而是指向fd的结构体
	printf("fd = %d, fd2 = %d, fd3 = %d\n", fd, fd2, fd3);
	//读取同一个文件
	char c1, c2, c3;
	read(fd, &c1, 1);	//fd中的pos往后移动
	read(fd2, &c2, 1);	//fd2中的pos往后移动
	read(fd3, &c3, 1);	//由于fd3指向fd结构体,fd中的pos往后移动
	printf("fd: %c, fd2: %c, fd3 : %c\n", c1, c2, c3);
	//关闭文件
	close(fd);
	close(fd2);
	return 0;
}

6. dup2函数

【1】头文件:#include <unistd.h>

【2】函数原型:int dup2(int oldfd, int newfd);

【3】功能:重定向。以dup2(fd, 1)为例说明:①关闭文件句柄1的文件;②将文件句柄1指向fd对应的file结构体。因此,之后涉及文件句柄1的操作都作用于fd所指的文件。如下图所示:

【4】返回值:newfd。 一般不关注这个返回值。

【5】示例:假设我们有个1.txt文件,执行下列程序后,命令行中并未出现字符串,而字符串"fd = 3, fd = 1"出现在了1.txt文件中。

int fd = open(argv[1], O_RDWR);
int fd2 = dup2(fd, 1);
printf("fd = %d, fd2 = %d\n", fd, fd2);
close(fd);

7. lseek函数

【1】头文件:#include <sys/types.h>、#include <unistd.h>

【2】函数原型:off_t lseek(int fd, off_t offset, int whence);

【3】参数说明:

  • fd:操作的文件描述符
  • offset:相对位置的偏移量,可正可负
  • whence:表示从何处开始偏移,包含SEEK_SET (文件开始)、SEEK_CUR (当前位置)、SEEK_END (文件结尾)

【4】功能:将fd对应的文件中的文件指针pos位置偏移到对应位置。

【5】常见操作:

  • 查看文件指针当前位置:lseek(fd, 0, SEEK_CUR);
  • 查看文件共多少个字节:lseek(fd, 0, SEEK_END); 计算文件的长度、大小(以Byte为单位)
  • 跳转到文件开始位置:lseek(fd, 0, SEEK_SET);
  • 拓展文件长度:lseek(fd,1000,SEEK_END); 但是需要执行一次write操作才有效。

【6】空洞文件:

  • 当我们用lseek偏移了几个位置后再write,那么跳过的这几个位置都是空(ASCII码为0),该文件就是空洞文件。
  • 空洞文件可以用于多线程写文件,能够加快写入速度。可以将文件分成多个块,然后使用多个线程并行写入这些块。

8. fcntl函数

【1】头文件:#include <unistd.h>、#include <fcntl.h>

【2】函数原型:int fcntl(int fd, int cmd, ... /* arg */ );

【3】参数说明:

  • fd:操作的文件描述符
  • cmd:表示进行哪种操作。常用F_DUPED复制文件描述符,返回一个>=arg的文件描述符。
  • arg:配合cmd

【4】功能:多功能文件管理工具箱。

【5】示例:fd2和fd操作同一个文件。

int fd = open(argv[1], O_RDWR | O_CREAT, 0664);
int fd2 = fcntl(fd, F_DUPFD, 0);
write(fd2, "hello", strlen("hello"));

二、文件读写细节

1. 换行符

  • Windows中:换行为"\r\n"
  • Linux中:换行为"\n"
  • Mac中:换行为"\r"
  • Linux中以16进制形式查看文件的命令:hexdump -C 文件名

2. 文件描述符

  • 文件描述符是一种文件的"ID"。通过文件描述符,我们可以对其指向的文件进行读写操作。
  • 在一个进程中,每次调用open函数打开一个文件,都会生成一个新的文件描述符(不会与已有的重复)。
  • 不同进程的文件描述符是相互独立的。
  • 一个进程中默认会有三个文件描述符:0指向标准输入(键盘),1指向标准输出(命令行),2指向标准错误的输出(命令行)。
  • 文件描述符表存储的最大数量默认为1024,但是用户可以修改(视硬件配置而定)。

3. errno和perror

  • errno是一个int型的全局变量,保存最近一次的错误码。
  • perror是一个错误打印函数,其内部会读取errno的值然后打印对应的错误信息。例如:perror("错误"); -----> 当出现错误时,就会打印"错误:XXXX"。

4. 系统IO和用户IO

  • 系统IO函数:open、close、read、write、lseek等
  • 用户IO函数:fopen、fclose、fread、fwrite等
  • 用户IO函数是为应用层设计的,其内部机制依旧调用了系统IO函数,但是并不是每次读写都会调用系统IO函数去访问内核。而是维护一个缓冲区,读写都是针对该缓冲区,只有当某些条件达到时才会访问内核将缓冲区的数据写入。这样设计的目的:减少用户态和内核态的切换,提高效率。

5. Linux管理文件

  • 硬盘文件:一块硬盘分为两个区域:硬盘内容管理表、存储内容区域。管理表中每个文件对应唯一的inode,每个inode对应一个结构体,里面记录了文件的各种信息。
  • 内存文件:在一个进程中会存在一个进程信息表记录了这个进程的所有信息,其中有一个指针指向文件管理表。文件管理表记录了所有在该进程中打开的文件信息,通过文件的fd就能找到指定文件的vnode。vnode记录了打开的文件的各种信息。

6. 文件共享

  • 文件共享是指多个fd同时调用open函数打开同一个文件。
  • 三种情况:
    • 同一个进程中多次同时调用open打开同一个文件。
    • 不同进程中打开同一个文件。
    • 调用dup生成fd指向同一个文件。

三、文件属性

1. Linux文件类型

符号表示含义举例
-普通文件.txt、.c、.h
d目录
c字符设备文件(面向流)鼠标、键盘、串口
b块设备U盘、SD卡
p管道文件
s套接字文件
l软链接文件

2. 获取文件属性

2.1 stat、fstat、lstat函数:

  • Linux下输入命令"stat 文件名"可以查看文件的属性。
  • fstat与stat区别:stat是通过文件名得到文件属性信息的结构体,而fstat是通过fd得到属性信息。
  • lstat与上述两个区别:对于软连接文件,上述两个是查看软连接指向文件的属性;而lstat是查看软连接文件本身的属性。
  • 函数原型:
    • int stat(const char *pathname, struct stat *statbuf);
    • int fstat(int fd, struct stat *statbuf);
    • int lstat(const char *pathname, struct stat *statbuf);
  • 返回值:都是成功了返回0,失败返回-1

2.2 struct stat介绍:

//来自爱编程的大丙
struct stat {
    dev_t           st_dev;        	// 文件的设备编号
    ino_t           st_ino;        	// inode节点
    mode_t          st_mode;      	// 文件的类型和存取的权限, 16位整形数  -> 常用
    nlink_t         st_nlink;     	// 连到该文件的硬连接数目,刚建立的文件值为1
    uid_t           st_uid;       	// 用户ID
    gid_t           st_gid;       	// 组ID
    dev_t           st_rdev;      	// (设备类型)若此文件为设备文件,则为其设备编号
    off_t           st_size;      	// 文件字节数(文件大小)   --> 常用
    blksize_t       st_blksize;   	// 块大小(文件系统的I/O 缓冲区大小)
    blkcnt_t        st_blocks;    	// block的块数
    time_t          st_atime;     	// 最后一次访问时间
    time_t          st_mtime;     	// 最后一次修改时间(文件内容)
    time_t          st_ctime;     	// 最后一次改变时间(指属性)
};

2.3 判断文件类型和权限:

  • 文件的类型和权限保存在struct stat结构体的st_mode中。
  • 首先,我们需要使用stat函数来读取指定文件的属性,并保存至辅助的结构体变量中。
  • 其次,调用一些宏来判断文件类型。man手册的stat的例子就给出了编写方法。
  • 最后,通过位运算&来获取文件权限。详见文件的属性信息 | 爱编程的大丙 (subingwen.cn)
//这里不做健壮性判断
struct stat buf;		//辅助结构体变量
stat(argv[1], &buf);	//读取2.txt文件属性,保存至buf
//判断文件类型
if (S_ISREG(buf.st_mode)) {
	printf("%s是常规文件\n", argv[1]);
}
//获取拥有者的权限
if (buf.st_mode & S_IRUSR) {
	printf("r");
}
if (buf.st_mode & S_IWUSR) {
	printf("w");
}
if (buf.st_mode & S_IXUSR) {
	printf("x");
}

3. 读取目录文件

已有文章写得很好了,详见目录遍历 | 爱编程的大丙

标签:文件,include,函数,int,argv,fd,IO,Linux,总览
From: https://blog.csdn.net/weixin_46249470/article/details/139829305

相关文章

  • ZZULIOJ157:素数判定
    方法一:#include<stdio.h>#include<math.h>intmain(){intn,i;scanf("%d",&n);for(i=2;i<n;i++)//解题思路:一个素数只能被1和本身整除,任何数都能被1整除,所以我们将设置除数从2开始,如果除数一直递增到被除数n的前一位也就是n-1还不能被整除,那么说明输入的n为......
  • Enhancing CLIP with GPT-4: Harnessing Visual Descriptions as Prompts
    标题:用GPT-4增强CLIP:利用视觉描述作为提示源文链接:Maniparambil_Enhancing_CLIP_with_GPT-4_Harnessing_Visual_Descriptions_as_Prompts_ICCVW_2023_paper.pdf(thecvf.com)https://openaccess.thecvf.com/content/ICCV2023W/MMFM/papers/Maniparambil_Enhancing_CLIP_with......
  • JOISC 2024 Day3 T1 : Card Collection / 卡牌收集
    首先,注意到对于一组询问,我们只需要关注每个数与\((T_j,W_j)\)的相对大小关系。这一共有\(9\)种情况,于是我们直接做区间DP,设一个形如\(f(l,r,0/1/2,0/1/2)\)的状态,即可得到\(O(N^3M)\)的做法;进一步使用bitset优化可以做到\(O(\frac{N^3M}{w})\),但是无法通过(甚至\(N=20......
  • DVWA 靶场 Authorisation Bypass 通关解析
    前言DVWA代表DamnVulnerableWebApplication,是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序,旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。DVWA提供了一系列的漏洞场景和练习环境,用户可以通过......
  • 华为电脑BIOS设置系统启动顺序
        最近在华为电脑上装了Windows和Ubuntu双系统后,由于安装失误,导致每次开机后都会进入grub界面。    为了正常进入Windows和Ubuntu系统,在开机进入grub界面前,可以按F12进入bootmanager界面,在此界面下可以选择需要启动的系统。(请原谅我使用手机拍摄屏幕的方......
  • arm环境,报错 no com_alibaba_nacos_shaded_io_grpc_netty_shaded_netty_transport_nat
    大概率版本号不对,sprintcloud版本+netty版本netty版本4.1.101.Final点击查看代码<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.101.Final</version></dependency><depend......
  • 【C++】priority_queue的模拟实现与仿函数
    文章目录1.优先级队列的介绍与使用1.1介绍1.2使用2.模拟实现2.1push2.2pop2.3top、empty、size2.4迭代区间构造3.仿函数1.优先级队列的介绍与使用1.1介绍优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。......
  • 【FAS】《Application of machine learning to face Anti-spoofing detection》
    文章目录原文相关工作方法静态Gabor小波和动态LBP的融合特征基于GAN的数据增强人脸活体检测方法半监督学习用于图像修复的人脸活体检测点评原文李莉.反欺骗人脸活体图像的机器学习方法研究[D].广东工业学,2020.DOI:10.27029/d.cnki.ggdgu.2020.001204.相关......
  • Windows 系统安装 NVM(Node Version Manager)攻略
    Windows系统安装NVM(NodeVersionManager)全攻略在Windows系统中,NVM(NodeVersionManager)是一个非常实用的工具,它允许我们方便地管理和切换不同版本的Node.js。在这篇博客中,我将详细介绍Windows系统下安装NVM的步骤、使用方法以及可能出现的问题和解决办法。一、......
  • 把selection screen当子屏幕放到普通屏幕上
    REPORTzly_subscreen.TYPES:BEGINOFty_ord,object_idTYPEcrmt_object_id,process_typeTYPEcrmt_process_type,ENDOFty_ord.DATA:gt_ordTYPETABLEOFty_ord,gw_ordTYPEty_ord,gv_okTYPEsy-ucomm."定义选择屏幕子屏......