1.典型的嵌入式产品开发的顺序
1、让Linux系统在硬件上跑起来(系统移植工作)
2、基于Linux系统来开发应用程序实现产品功能(应用编程属于这一步骤)
基于Linux做应用编程其实就是调用API来实现应用需要完成的一些任务。
注意:此课程是降低难度版
主要目的:使用linux内核提供的API和c库函数来实现一定的功能
层次:APP+OS 操作系统本身有一部分是驱动,下面是硬件
读写文件的案例中,文件是从硬盘中放着,当打开文件即将其读取到内存,这些硬件需要驱动。操作系统中有文件系统fs,
保证我们用目录+文件名的方式来访问他,给我们提供了一些访问接口,从而用这些“门”去操控硬件和文件。
目录
3.2常用文件IO函数详解(使用man命令查询函数具体功能)
(1)、open函数(E:\Linux\3.AppNet\1.fileio\3.2)
(2)、close函数----关闭文件描述符fd指向的动态文件,并存储文件和刷新缓存。
(3)、read函数---Read成功返回读取的字节数,失败返回-1
write函数---Write成功返回写入的字节数,失败返回-1
3.6 lseek函数(E:\Linux\3.AppNet\1.fileio\3.4)
(1)一个进程中两次打开同一个文件,然后分别读取,看结果会怎么样
(1) 一个进程中2个打开同一个文件,得到fd1和fd2.然后看是分别写还是接续写?
六、文件描述符的复制 E:\Linux\3.AppNet\1.fileio\3.6
3.dup2函数实例 E:\Linux\3.AppNet\1.fileio\3.6\file1.c
七、fcntl函数 E:\Linux\3.AppNet\1.fileio\3.7
2.2、第二个参数 int cmd:控制命令选项,用来控制修改什么样的性质,对于cmd的设置选择如下:
一、文件IO概念
(1)IO就是input/output,输入/输出。文件IO的意思就是读写文件。
(2) linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件IO操作。不带缓存的属于直接调用系统调用(system call)的方式,高效完成文件输入输出。它以文件标识符(整型)作为文件唯一性的判断依据。这种操作不是ASCI标准的,与系统有关,移植有一定的问题。而带缓存的是在不带缓存的基础之上封装了一层,维护了一个输入输出缓冲区,使之能跨OS,成为ASCI标准,称为标准IO库。不带缓存的方式频繁进行用户态 和内核态的切换,高效但是需要程序员自己维护;带缓冲的方式因为有了缓冲区,不是非常高效,但是易于维护。由此,不带缓冲区的通常用于文件设备的操作,而带缓冲区的通常用于普通文件的操作。
二、文件操作的主要接口API
2.1、什么是操作系统API
(1)API是一些函数,这些函数是由linux系统提供支持的,由应用层程序来使用。
(2)应用层程序通过调用API来调用操作系统中的各种功能,来干活。
(3)学习一个操作系统,其实就是学习使用这个操作系统的API。
(1)今天我们要使用linux系统来读写文件,手段就是学习linux系统API中和文件IO有关的几个。
2.2、linux常用文件IO接口
(1)open、close、write、read、lseek
三、文件操作的一般步骤
3.1在linux下对文件操作的一般步骤
在linux系统中要操作一个文件,一般是先open打开一个文件,得到一个文件描述符,然后对文件进行读写操作(或其他操作),最后close关闭文件即可
实时查man手册
(1)当我们写应用程序时,很多API原型都不可能记得,所以要实时查询,用man手册
(2)man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查库函数
例如我们使用到的open、close、write、read函数,在linux下查阅使用方法即可。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
int fd=-1; //fd file descriptor,文件描述符
char buf[100]={0};
char writebuf[20]="are you ok";
int ret=-1;
//第一步:打开文件
fd=open("a.txt",O_RDWR);
if(-1 == fd)
{
printf("文件打开错误\n");
}
else
{
printf("文件打开成功,fd=%d.\n",fd);
}
//第二步:读写文件
//写文件
ret=write(fd,writebuf,strlen(writebuf));
if(-1==ret)
{
printf(" 写入失败\n");
}
else
{
printf(" 实际写入了%d字节.\n",ret);
printf(" 文件内容是: [%s] .\n",writebuf);
}
//读文件
ret=read(fd,buf,20);
if(-1==ret)
{
printf(" 读取失败\n");
}
else
{
printf(" 实际读取了%d字节.\n",ret);
printf(" 文件内容是: [%s] .\n",buf);
}
//第三步:关闭文件
close(fd);
return 0;
}
3.2常用文件IO函数详解(使用man命令查询函数具体功能)
(1)、open函数(E:\Linux\3.AppNet\1.fileio\3.2)
#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。
|
O_RDONLY //只读打开
O_WRONLY //只写打开
O_RDWR //读、写打开
O_APPEND //每次写时都追加到文件的尾端
O_CREAT //若此文件不存在,则创建它。使用时,需要第三个参数mode
O_EXCL //如果同时指定了O_CREAT,而文件已经存在,则会出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件。
O_TRUNC //如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0。
O_NONBLOCK //如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次操作和后续的I/O操作设置非阻塞模式。只用于设备文件,不能用于普通文件。
O_SYNC //使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O。
|
使用四个数字指定创建文件的权限,与linux的权限设置相同,如0755
譬如一般创建一个可读可写不可执行的文件就用0666
(2)、close函数----关闭文件描述符fd指向的动态文件,并存储文件和刷新缓存。
#include <unistd.h>
int close(int fd);
(3)、read函数---Read成功返回读取的字节数,失败返回-1
write函数---Write成功返回写入的字节数,失败返回-1
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
count和返回值的关系。count参数表示我们想要写或者读的字节数,返回值表示实际完成的要写或者读的字节数。实现的有可能等于想要读写的,也有可能小于(说明没完成任务)
3.3、打印错误信息
errno就是error number。
如果程序代码中包含 #include <errno.h>,函数调用失败的时候,系统会自动用用错误代码填充errno这个全局变量,取读errno全局变量可以获得失败原因。
函数调用失败是否会设置errno全局变量由函数决定,并不是所有函数调用失败都会设置errno变量。
#include <stdio.h>
void perror(const char *s);
perror ( )用来将上一个函数发生错误的原因输出到标准错误(stderr),参数s 所指的字符串会先打印出,后面再加上错误原因字符串。
3.4文件IO效率和标准IO
(1)文件IO就指的是我们当前在讲的open、close、write、read等API函数构成的一套用来读写文件的体系,这套体系可以很好的完成文件读写,但是效率并不是最高的。
(2)应用层C语言库函数提供了一些用来做文件读写的函数列表,叫标准IO。标准IO由一系列的C库函数构成(fopen、fclose、fwrite、fread),这些标准IO函数其实是由文件IO封装而来的(fopen内部其实调用的还是open,fwrite内部还是通过write来完成文件写入的)。标准IO加了封装之后主要是为了在应用层添加一个缓冲机制,这样我们通过fwrite写入的内容不是直接进入内核中的buf,而是先进入应用层标准IO库自己维护的buf中,然后标准IO库自己根据操作系统单次write的最佳count来选择好的时机来完成write到内核中的buf(内核中的buf再根据硬盘的特性来选择好的实际去最终写入硬盘中)。
3.5linux系统如何管理文件
硬盘中的静态文件和inode(i节点) |
(1)文件平时都放在硬盘中,硬盘中以一种固定的形式存放,我们称为静态文件
(2)一块硬盘中分为两大区域:一个是硬盘内容管理表,另一个是真正存储的内容区域。操作系统访问硬盘时先去读取硬盘内容管理表,从中找到我们访问的那个扇区级别的信息,然后用这个信息去查询真正的存储内容的区域,然后得到我们要的文件
(3)管理表中以文件为单位,记录了各个文件的各种信息,每一个文件有一个文件列表(我们叫inode,i节点,其实质是一个结构体,这个结构体有很多元素,每个元素记录了这个文件的一些信息,其中包括文件名,文件在硬盘上对应的扇区号,块号那些东西......
(4)硬盘管理的时候以文件为单位,每个文件一个inode,每个inode有一个数字编号,对应一个结构体,结构体记录了各种信息
内存中被打开的文件和vnode(v节点) |
(5)格式化硬盘(U盘)时发现:快速格式化和底层格式化。快速格式化非常快,格式32G只需一秒,普通格式速度慢。两者差异?
快速格式化只是删除了U盘硬盘内容管理表(inode),真正存储的内容没有动,这种格式化可能被找回。
(6)一个程序的运行就是一个进程,我们在程序中打开的文件就属于某个进程。每个进程都有一个数据结构来记录这个进程的所有信息(进程信息),表中有一个指针会指向一个文件管理表,文件管理表中记录了当前进程及其相关信息。
文件管理表中用来索引各个打开的文件的index就是文件描述符f,我们最终找到的就是一个已经被打开的文件的管理结构体vnode
(7)一个vnode中记录了一个被打开的文件的各种信息,而且我们只要知道这个文件的fd,就可以很容易找到这个文件的vnode进而对这个文件进行各操作。
文件和流的概念 |
(1)文件操作中,文件类似一个包裹,里面装了一堆字符,但是文件被读出/写入时都只能一个字符一个字符的进行,而不能一个文件中N多个字符被挨个一次读出/写入时,这些字符就构成了字符流
(2)流是动态的,不是静态的
(3)编程中提到的流的概念,一般是IO相关的。所以经常叫IO流。文件操作就构成了IO流。
简而言之就是:表表相连 :硬盘内容管理表