嵌入式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 
#include 
#include 
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 
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 
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 
int close(int fd);

2.5 lseek: reposition read/write file offset

#include 
#include 
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);

2.6 ioctl:control device

#include 
int ioctl(int fd, unsigned long request, ...);

函数说明:

fd:表示文件描述符;

request:表示与驱动程序交互的命令, 用不同的命令控制驱动程序输出我们
需要的数据;

… :表示可变参数 arg, 根据 request 命令, 设备驱动程序返回输出的数据。

返回值: 打开成功返回文件描述符,失败将返回-1。

ioctl 的作用非常强大、灵活。不同的驱动程序内部会实现不同的 ioctl,APP 可以使用各种 ioctl 跟驱动程序交互:可以传数据给驱动程序,也可以从驱动程序中读出数据。
 

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

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

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

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

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

挺简单的,直接上代码了

#include 
#include 
#include 
#include 
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

嵌入式Linux入门-Linux文件IO讲解并实现copy程序_第1张图片

 使用我们自己的copy命令

./copy test1 test2

嵌入式Linux入门-Linux文件IO讲解并实现copy程序_第2张图片

成功。 

你可能感兴趣的:(嵌入式Linux入门,嵌入式,arm,stm32,单片机,物联网)