part1 文件操作

3.1:文件操作

裸机->C高级->uboot和系统移植->linux应用和网络编程->驱动
嵌入式产品总过程:

第一步:linux系统在硬件上可以跑起来(系统移植工作)

第二步:基于linux系统来开发应用程序实现产品功能

第三步:基于linux做应用编程(调用linux系统api来实现应用需要完成的任务  )

文件IO

IO=input/output,文件的输入输出及读写文件。 

硬件都是以通过驱动操控的

API

API:一些由linux系统提供支持的函数,由应用层程序来使用。应用通过API来调用操作系统中的各种功能。 学习一个操作系统,就是学习使用操作系统的API

linux常用文件IO接口

open、close、write、read、lseek(移动文件指针)

文件操作的一般步骤

open文件得到文件描述符,对文件进行读写(w/r或者其他操作)操作,最后close文件。

注意点

1、对文件的操作一定要先打开,打开成功后才能进行操作。

2、读写完成后一定要关闭文件(否则可能发生文件损坏)。

3、文件平时存放在块设备中(SD卡,硬盘等)的文件系统中,未被open的称其为静态文件。open一个文件时,linux内核所做的操作包括:内核在进程中建立了一个打开文件的数据结构,记录下我们打开的文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内存中的特定地方管理存放(动态文件)

4、打开文件后,对文件读写操作都是对内存中的动态文件的操作,而不是静态文件。对动态文件读写后,内存中的动态文件和块设备中的静态设备不同步,当我们close动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件

5、常见现象:文件越大open越慢。完成一半的文件如果没有保存,重启后文件内容丢失。

6、内核这样设计原因

块设备存在读写限制(NnadFlash,SD卡等块设备的读写特征:没办法改部分,只能整个块设备全部改),本身不灵活。内存可以按字节为单位来操作,而且可以随机操作(内存-ARM,random)。

文件描述符

1、文件描述符实质是一个数字,在一个进程中表示一个特定的含义,当open一个文件时,操作系统在内存中构建一些数据结构来表示这个动态文件,返回给应用程序一个文件描述符(数字),这个数字就和内存中的维护动态文件的数据结构绑定,此后应用程序要操作此动态文件,只需要用此动态描述符进行区分

2、简而概之:文件描述符就是一个程序区分打开的多个文件的数字。

3、文件描述符的作用域就是当前进程中。

 简单的文件读写示例
打开文件和关闭文件

linux中的文件描述符fd的合法范围是0或者一个正整数,不可能是个负数。

open返回的fd程序必须记录号,后面文件的操作都要依靠fd值对应此文件,关闭也需要fd来指定关闭其文件,如果关闭前,fd丢失,文件无法关闭也无法读写。

实时查看man手册

当我们写程序时,很多API原型不记得,可以实时查询,使用man手册

man 1 xx查linux shell命令,man 2 xx查询API,man 3 xxx查询库函数

读取文件内容
读文件

ssize_t read(int fd, void *buf, size_t count); fd表示指定的文件,buf表示应用程序提供的一段内存缓存区,用来存储读出的内容。count是我们要读取的字节数(想读取的字节数)

返回值ssize_t类型是内容用typedef重定义的类型(int),表示成功读取的字节数。(实际读取的字节数)

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

int main( void )
{
    int fd = -1; //fd = file descriptor,文件描述符
    char buf[100]={0};//定义缓冲区
    int ret = -1;//接受read返回值
    //打开文件
    fd = open("a.txt",O_RDWR);//
    if(-1 == fd)//有时候也写成 fd<0
    {
        printf("文件打开错误\n");
    }else
    {
        printf("文件打开成功,fd = %d\n",fd);
    }
    //读写文件
    ret = read(fd ,buf ,20);
    if(ret < 0)
    {
        printf("read失败\n");

        return -1;
    }else
    {
        printf("实际读取了%d字节.\n",ret);
        printf("文件内容是:[%s].\n",buf);
    }
    //关闭文件
    close(fd);
    return 0;
}

//运行如果中文出现乱码,点击终端的上方“终端”将编码设置成GB2312

写文件

ssize_t write(int fd, const void *buf, size_t count);
将buf里的count个字节写入

ret = write(fd,wbuf,strlen(wbuf));
    if(ret < 0)
    {
        printf("写入失败\n");

        return -1;
    }else
    {
        printf("写入成功,写入了%d个字符\n",ret);
    }

写入12字节,但是读出却是0(但是读出成功了)。

open函数的flag属性
O_RDONLY O_WRONLY O_RDWR  

linux中文件有读写权限,我们在打开权限时,可以进行权限说明(O_RDONLY只读,O_WRONLY只写,O_RDWR可读可写)

当我们附带了权限后,打开的文件只能按照此权限来操作。

打开存在并有内容的文件:O_APPEND O_TRUNC
向已经有内容的文件写入 

丢弃原内容至空

O_TRUNC

fd = open("a.txt" , O_WRONLY | O_TRUNC);

O_APPEND  新内容跟在原内容后面

默认不使用O_APPENDO_TRUNC时就是文件内容保持不变

open一个文件时,如果文件不存在就会打开错误。

O_CREATE

打开并不存在的文件(创建+打开)

O_CREATEO_EXCL结合使用:如果文件已经存在就会报错,不会去覆盖它。

open可以给予第三个参数mode(flag必须包含O_CREAT)来指定创建文件的权限。mode有四个数字组成例如(0666)表示可读可写不可执行的文件 

O_NONBLOCK

以非阻塞的方式打开文件

阻塞(函数当前条件不具备,不能立刻返回)与非阻塞(调用函数立即返回,不一定已经完成任务)

只用在设备文件,不用在普通文件。

O_SYNC

write 阻塞等待底层(操作系统中负责实验OPEN,WRITE这些操作的代码,包括OS中读写硬盘等底层硬件的代码)完成写入才返回应用层硬件不需要等待,直接将内容写入硬盘。

O_SYNC时write将内容写入底层缓存区即返回,底层在合适的时候将buf中的内容一次性同步到硬盘中。

退出进程

exit ,_exit , _Exit

当前面步骤操作失败导致后面操作无法进行时,应在前面错误检测中结束整个程序。

退出程序方式:

1、在main中使用return,程序正常终止 return 0,异常终止 return -1。 

2、正式终止进程使用exit或者_exit或者_Exit之一

errno与perror函数

errno就是error number。linux系统中对各种常见错误做了编号,当函数执行错误时,会返回一个特定的errno编号来说明哪里错误

errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟发生了一个什么错误。

perror就是print error,perror内部会读取errno并将其转换成对应的信息字符串并打印。

fd = open("a.txt",O_RDONLY);
    if(-1 == fd)//有时候也写成 fd<0
    {
        printf("文件打开错误\n");
    }else
    {
        printf("文件打开成功,fd = %d\n",fd);
    }
    //读写文件
    //写文件
    ret = write(fd,wbuf,strlen(wbuf));
    if(ret < 0)
    {
        printf("写入失败\n");
        perror("wrirte失败: ");
        _exit(-1);
    }else
    {
        printf("写入成功,写入了%d个字符",ret);
    }

read与write的count  

count与返回值的关系,count表示想要去写入或者读出的字节数,返回值是实际写入或读出的字节数,实际<=想要的(没完成任务或者不够 )、

文件IO效率和标准IO 

文件IO指的就是open,close等API函数构成的一套用来读写文件的体系。但是其效率不是最高的。 

应用层C语言库函数提供了一些用来做文件读写的函数列表,叫做标准IO。标准IO由一系列的C库函数构成(fopen、fclose、fwrite、fread、)。标准IO函数其实是由文件IO封装来的。(fopen内部调用的还是open)。标准IO封装后主要是为了在应用层添加了一个缓冲机制,这样我们通过fwrite写入的内容不是直接进入内核的buf而是进入标准IO库自己维护的buf中,然后标准IO库自己根据操作系统单词write的最佳count来选择好的时机完成write到内核的buf(内核的buf在根据硬盘的特性来选择好的时机写入硬盘中 )

linux如何管理文件
硬盘中静态文件和inod(i节点):记录没有被打开的文件的数据结构 

文件存放在硬盘中,以固定的形式存放叫静态文件  

一块硬盘中可以分为两大区域:一个是硬盘内容管理表项,另一个是真正存储内容的区域,OS访问硬盘时先查表,找到要访问文件的信息,再用这个信息去找存储的扇区,找到需要的文件

操作系统最初拿到的信息是文件名,最终得到的是文件内容。第一步查询管理表,此表以文件为单位记录了各个文件的各种信息,每个文件有一个信息列表(inode,i节点,一个结构体,里面的元素记录文件的信息。)、

硬盘管理是以文件为单位的,一个文件对应一个inode,。每个inode有一个数字编号,对应一个结构体,记录各种信息。

内存中被打开的文件和vnod(v节点) :管理一个被打开的文件的结构体

程序中打开的文件属于某个进程,每个进程都有一个数据结构来记录进程的所有信息(进程信息表),其中有一个指针指向一个文件管理表中记录了当前进程打开的所有文件和相关信息,文件管理表中用来索引各个打开的文件的index就是文件描述符fd,最终找到的是一个已经被打开的文件的管理结构体(vnode)。

一个vnode记录了被打开文件的各种信息,只要知道了一个文件的fd,很容易找到此文件的vnode并对其进行操作。

文件与流的概念

流(stream),文件被读出或写入时只能一个字符进行 ,那么一个文件中N多个字符被挨个一次读出/写入时,这些字符就构成了一个字符流。

流式动态的,流一般与IO相关,也被叫做IO流,文件操作时就形成了一个IO流

lseek、

off_t lseek(int fd, off_t offset, int whence);      offset偏移量,whence偏移 起始点

文件指针,我们读写的所有文件都是动态文件。动态文件在内存中的形态就是文件流。

在动态文件中,通过文件指针来表征这个正在操作的位置。所谓文件指针,就是文件管理表里的一个指针,其实就是vnode里的一个元素

 文件指针表示我们当前正在操作文件流的哪个位置。linux系统用lseek来访问文件指针。

大开文件时,默认文件指针在文件流的开始,这个时候写入时从文件头开始,write与read函数自带可以移动文件指针。

用lseek计算文件长度

int main(int argc,char *argv[])
{
    int fd = -1; //fd = file descriptor,文件描述符
    int ret = -1;//接受read返回值
    //打开文件
    if(argc != 2)
    {
        printf("usage: %s filename\n",argv[0]);

        perror("文件打开失败:");
        _exit(-1);
    }
    fd = open(argv[1],O_RDONLY);//

    if(-1 == fd)//有时候也写成 fd<0
    {
        printf("文件打开错误\n");
        _exit(-1);
    }else
    {
        printf("文件打开成功,fd = %d\n",fd);
    }
    //此时文件指针指向文件开头
    //用lseek将文件指针移动到末尾,返回值就是i文件指针距离文件开头的偏移量
    ret = lseek(fd,0,SEEK_END);
    printf("文件长度是:%d字节\n",ret);

    return 0;
}

用lseek构建空洞文件

文件中有一段是空的文件。

打开一个文件后,用lseek往后跳过一段,再write写入一段 ,就会形成空洞文件

对多线程共同操作文件极其有用。创建一个大文件时,如果从头开始一次构建时间会很长。将文件分成多段,一起构建会大大缩短时间。

多次打开同一文件与O_APPEND

重复打开同一文件读取

while(1)
    {
       memset(buf, 0, sizeof(buf));
        ret = read(fd1, buf, 2);
        if(ret<0)
        {
            printf("read失败\n");
            _exit(-1);
        }else
        {
            //printf("实际读取了%d字节\n",ret);
            printf("fd1:[%s].\n",buf);
        }
        sleep(1);
        memset(buf, 0, sizeof(buf));
        ret = read(fd2, buf, 2);
        if(ret<0)
        {
            printf("read失败\n");
            _exit(-1);
        }else
        {
            //printf("实际读取了%d字节\n",ret);
            printf("fd2:[%s].\n",buf);
        }
    }

fd1与fd2分别读,open打开同一个文件时,文件指针相互独立,文件指针在动态文件的文件管理表,在linux进程中不同fd对应不同的独立文件管理表。

重复打开同一文件写入

默认情况下是分别写。

当需要接续写的时候,在open时加入O_APPEND。O_APPEND对文件的读写是原子的

原子操作:操作一旦开始,不可以被打断,必须操作结束后,其它代码才可以调度运行。

文件共享的实现方式

文件共享:同一个文件(同一个pathname,同一个inode,静态文件)被多个独立的读写体(多个文件描述符)同时操作。

可以通过文件共享来实现多线程同时操作同一个文件,减少文件读写时间。

方式一:同一个进程中多次使用open打开同一个文件。

方式二:在不同进程中使用open去打开同一个文件(fd的数字可以相同,也可以不同)。

方式三:linux系统中提供了dup和dup2两个API让进程复制fd

文件共享的核心关注点在于:分别读/写还是接续读/写

操作系统规定,fd从0开始,依次增加。fd存在最大限制。linux中文件描述符表是个数组,fd是index,文件表指针式value

open文件时,内核会在文件描述符表中挑选一个最小的未被使用过的数字返回。

fd中的0、1、2已经默认被系统占用,用户得到最小的fd为3,已被占用的文件为stdin,stdout,stderr(标准输入,标准输出,标准错误)。

stdin对应键盘,stdout对应LCD显示器

dup与dup2

dup会返回一个新的文件描述符(当前未被使用的最小fd)。不能指定复制后得到的fd。

dup返回的fd和原来的oldfd都指向oldfd打开的动态文件。写入文件时是接续写。

dup的缺陷

不能指定分配的新文件描述符的数字。dup2修复了此缺点。

标准输出的重定位

close(1)关闭stdout, printf输出到标准输出的内容看不见了,使用dup重新分配得到1的fd,此时oldfd打开的文件与1的标准输出通道绑定了。printf的内容就会进入到oldfd的文件中。

    close(1);
    fd2 = dup(fd1);

dup2(int oldfd,int newfd)

与dup的作用是一样的,但是可以指定新fd的数字。fd与oldfd指向同一个打开的文件,交叉写入的时候结果是接续写

命令行的重定位

将ls,pwd等命令的输出重定位到一个文件中。linux终端支持重定位符合‘>’(例:ls > 1.txt)。其实现原理:open+close+dup。

fcntl(fd,cmd,arg)

多功能文件管理的工具箱,接收两个参数和一个变参。fd表示操作哪个文件,cmd表示要进行哪个命令操作,变参用来传递参数配合cmd使用。

cmd样式:F_XXX。参照man手册

F_DUPFD的作用,复制fd ,从可用的fd里找到一个大于等于arg的最小数字作为fd。

 用fcntl模拟dup2

标准IO库

标准IO库是C库函数,文件IO是linux系统的API

C语言函数库由API封装的,库函数内部也是通过调用API来完成操作的。

API在不同操作系统不可通用,C库函数在不同操作系统中几乎一致,C库函数有可移植性。

库函数在性能和易用性上更好。

常见IO库函数

 fopen,fclose,fwrite,fread,ffulsh、fseek

fwrite(a,b,c,d);   a:内容,b:每个数占几个字节,c:几个元素,d:文件的fd,返回元素个数

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值