当前位置: 首页 > news >正文

嵌入式Linux入门-Linux文件IO讲解并实现copy程序

嵌入式Linux入门学习教程汇总:嵌入式Linux教程—裸机、应用、驱动完整教程目录

在Linux系统中,一切都是“文件”:普通文件、驱动程序、网络通信等等。所有的操作,都是通过“文件IO”来操作的。

IO就是input和output,文件IO就是文件的读写。文件没有打开时是存放在块设备中的文件系统里的,这样的文件叫做静态文件

操作一个文件一般是先打开(open)一个文件,得到这个文件的文件描述符,然后对文件进行读写(read/write)或其他操作,最后关闭(close)文件。

当我们open一个文件的时候,内核在进程中建立一个打开文件的数据结构来记录我们打开的文件,然后在内存中申请一段内存,将静态文件的内容读取到内存中特定地址管理存放,此时内存中的就是动态文件

之后的读写等操作都是针对这份内存中的动态文件,当close关闭动态文件后,内核将内存中动态文件的内容同步更新到静态文件中。

文件描述符的概念:一个程序打开一个文件就会得到一个文件描述符(整数),该数字用来区分一个程序打开的多个文件。文件描述符的作用域就是当前进程。

1.Linux七种文件类型

普通文件类型
Linux中最多的一种文件类型, 包括 纯文本文件(ASCII);二进制文件(binary);数据格式的文件(data);各种压缩文件.第一个属性为 [-]

目录文件
就是目录, 能用cd 命令进入的。第一个属性为 [d],例如 [drwxrwxrwx]

块设备文件
块设备文件 : 就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。第一个属性为 [b]

字符设备
字符设备文件:即串行端口的接口设备,例如键盘、鼠标等等。第一个属性为 [c]

套接字文件
这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。第一个属性为 [s],最常在 /var/run目录中看到这种文件类型

管道文件
FIFO也是一种特殊的文件类型,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写。第一个属性为 [p]

链接文件
类似Windows下面的快捷方式。第一个属性为 [l],例如 [lrwxrwxrwx]

Linux中文件扩展名
windows里通过扩展名来区分文件类型的。linux里文件扩展名和文件类型没有关系。但为了容易区分和兼容用户使用windows的习惯,还是会用扩展名来表示文件类型。举例如下:
● 源码.tar、.tar.gz、.tgz、.zip、.tar.bz表示压缩文件,创建命令一般为tar,gzip,zip等。
● .sh表示shell脚本文件,通过shell语言开发的程序。
● .pl表示perl语言文件,通过perl语言开发的程序。
● .py表示python语言文件,通过python语言开发的程序。
● .html、.htm、.php、.jsp、.do表示网页语言的文件。
● .conf表示系统服务的配置文件。
● .rpm表示rpm安装包文件。
 

2.Linux常用文件IO接口(API)

2.1 open :open and possibly create a file·

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

返回值:open函数的返回值如果操作成功,它将返回一个文件描述符,如果操作失败,它将返回-1。

pathname:在open函数中第一个参数pathname是指向想要打开的文件路径名,或者文件名。我们需要注意的是,这个路径名是绝对路径名。文件名则是在当前路径下的。

flags:flags参数表示打开文件所采用的操作,我们需要注意的是:必须指定以下三个常量的一种,且只允许指定一个

  • O_RDONLY:只读模式
  • O_WRONLY:只写模式
  • O_RDWR:可读可写

以下的常量是选用的,这些选项是用来和上面的必选项进行按位或起来作为flags参数。

  • O_APPEND 表示追加,如果原来文件里面有内容,则这次写入会写在文件的最末尾。
  • O_CREAT 表示如果指定文件不存在,则创建这个文件
  • O_EXCL 表示如果要创建的文件已存在,则出错,同时返回 -1,并且修改 errno 的值。
  • O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
  • O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
  • O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)

以下三个常量同样是选用的,它们用于同步输入输出

  • O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
  • O_RSYNC read 等待所有写入同一区域的写操作完成后再进行
  • O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/Omode:

mode:表示设置文件访问权限的初始值,和用户掩码umask有关,比如0644表示-rw-r–r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。

文件权限由open的mode参数和当前进程的umask掩码共同决定。

第三个参数是在第二个参数中有O_CREAT时才作用,如果没有,则第三个参数可以忽略

mode 的 具体 参数:

S_IRWXU
00700 允许 文件 的 属主 读 , 写 和 执行 文件
S_IRUSR (S_IREAD)
00400 允许 文件 的 属主 读 文件
S_IWUSR (S_IWRITE)
00200 允许 文件 的 属主 写 文件
S_IXUSR (S_IEXEC)
00100 允许 文件 的 属主 执行 文件
S_IRWXG
00070 允许 文件 所在 的 分组 读 , 写 和 执行 文件
S_IRGRP
00040 允许 文件 所在 的 分组 读 文件
S_IWGRP
00020 允许 文件 所在 的 分组 写 文件
S_IXGRP
00010 允许 文件 所在 的 分组 执行 文件
S_IRWXO
00007 允许 其他 用户 读 , 写 和 执行 文件
S_IROTH
00004 允许 其他 用户 读 文件
S_IWOTH
00002 允许 其他 用户 写 文件
S_IXOTH
00001 允许 其他 用户 执行 文件

2.2 read : read from a file descriptor

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

返回值:读到文件末尾返回0;正常则返回读到的字节数;当有错误发生时则返回-1,错误代码存入errno中。

fd:文件描述符,buf:读取数据存放位置,count:需要读取的数据大小

2.3 write : write to a file descriptor

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。

fd:文件描述符,buf:待写入数据,count:待写入数据大小

2.4 close :close a file descriptor

#include <unistd.h>
int close(int fd);

2.5 lseek: reposition read/write file offset

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

(1)文件指针:当我们要对一个文件进行读写时,一定需要先打开文件,所以我们读写的所有文件都是动态文件。动态文件在内存中的形态就是文件流的形式。

(2)文件流很长,里面有很多个字节。那我们当前正在操作的是哪个位置?GUI模式下的软件用光标来识别这个当前正在操作的位置,这个给人看的

(3)在动态文件中,通过文件指针来表征这个正在操作的位置。所谓文件指针,就是我们文件管理表这个结构体里面的一个指针。所以文件指针其实是vnode中的一个元素。这个指针表示当前我们正在操作文件流的那个位置。这个指针不能被直接访问,Linux系统用lseek函数来访问这个文件指针。

(4)当我们打开一个空文件时,默认情况下文件指针指向文件流的开始。所以这个时候去write时写入就是从文件开头开始的。write和read函数本身自带移动文件指针的功能,所以当我write了n个字节后,文件指针会自动向后移动n位。如果需要人为的随意更改文件指针,那就只能通过lseek函数了

(5)read和write函数都是从当前文件指针处开始操作的,所以当我们用lseek显示的将文件指针移动后,那么再去read/write时就是从移动过后的位置开始的。

返回值:成功返回当前位置到开始的长度,失败返回-1并设置errno

fd:文件描述符,offset:偏移量

whence:位置
SEEK_SET:The offset is set to offset bytes. offset为0时表示文件开始位置
SEEK_CUR:The offset is set to its current location plus offset bytes. offset为0时表示当前位置
SEEK_END:The offset is set to the size of the file plus offset bytes. offset为0时表示结尾位置
 

lseek返回的是偏移量,所以如下其实就是返回文件长度

ret = lseek(fd,0,SEEK_END);

3. 应用层io进入内核发生了什么

这里不作深入讲解,只需知道基本内容:

Linux应用程序调用的open()、read()等函数时,触发内部(32位cpu:swi,64位cpu:svc)指令,触发异常(在触发异常时还会传入不同的参数给内核,根据参数来分辨),然后调用内核中对应的sys_open()、sys_read()等函数。

会分辨目标文件到底是七种文件中的哪一种,例如普通文件,就去调用文件系统对应的驱动代码,读取块设备上的文件。例如目标文件是我们想驱动的字符设备文件,就会去调用们自己写的字符设备驱动程序中的read(),write()等。

4.小练习;自己写个copy复制文件

挺简单的,直接上代码了

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char **argv)
{
	int src;
	int dst;
	int buf[1024];
	int read_len,write_len;
	if(argc!=3)
	{
		printf("Input Error\n");
		return -1;
	}
	src=open(argv[1],O_RDONLY);
	if(src<=0)
	{
		printf("open %s error\n",argv[1]);
		return -1;
	}
	dst=open(argv[2],O_RDWR|O_CREAT,S_IRWXU);
	if(dst<=0)
	{
		printf("open %s error\n",argv[2]);
		return -1;
	}
	while(1)
	{
		read_len=read(src,buf,1024);
		if(read_len<0)
		{
			printf("read %s error\n",argv[1]);
			return -1;
		}
		if(read_len>0)
		{
			write_len=write(dst,buf,read_len);
			if(write_len<0)
			{
				printf("read %s error\n",argv[1]);
				return -1;
			}
		}
		else
		break;
	}
	close(src);
	close(dst);
	return 0;
}

把源代码编译

gcc -o copy copy.c

使用vi命令随便写点东西

vi test1

 使用我们自己的copy命令

./copy test1 test2

成功。 

相关文章:

  • 1.6-02:陶陶摘苹果
  • Java项目:SSM学生选课管理系统
  • [MySQL数据库部署及初始化相关]
  • 0901(044天 集合框架08 树TreeMap)
  • 【数据结构】栈(stack)
  • 【MyBatis笔记09】MyBatis中常用的几个动态SQL标签
  • Apache Geode<1.15.0 不受信任的反序列化漏洞
  • GitLab 中 GitHub 导入 API 存在远程代码执行漏洞
  • 什么是生成器 — 一篇文章让你看懂
  • 国内近五年人工智能教育的研究热点及趋势——基于多维尺度和社会网络分析的方法
  • QGraphicsItem鼠标拖动旋转(五)
  • win7连接打印机0x0000011b错误的解决办法
  • 四、RocketMq本地集群搭建:多master-slaver异步
  • pcan二次开发文档 | PEAK-System Documentation
  • R语言数据分组聚合实战:使用aggregate函数对mtcars数据通过两个分类变量进行数据分组聚合、并计算分组的均值、使用na.rm删除异常值
  • ComponentOne 2017 V2版本正式发布
  • Date型的使用
  • Git初体验
  • PHP那些事儿
  • react 代码优化(一) ——事件处理
  • React16时代,该用什么姿势写 React ?
  • VuePress 静态网站生成
  • web标准化(下)
  • 安卓应用性能调试和优化经验分享
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 如何学习JavaEE,项目又该如何做?
  • 手写一个CommonJS打包工具(一)
  • 数据可视化之 Sankey 桑基图的实现
  • 想使用 MongoDB ,你应该了解这8个方面!
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • Prometheus VS InfluxDB
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • #预处理和函数的对比以及条件编译
  • (07)Hive——窗口函数详解
  • (4)Elastix图像配准:3D图像
  • (C)一些题4
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (一)WLAN定义和基本架构转
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转)memcache、redis缓存
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • ./configure,make,make install的作用
  • .net 怎么循环得到数组里的值_关于js数组
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • @Transient注解
  • [20171102]视图v$session中process字段含义
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [BZOJ 3282] Tree 【LCT】