下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 1492406

1492406.DOC

1492406

mingche_god
2018-09-09 0人阅读 举报 0 0 暂无简介

简介:本文档为《1492406DOC》,可适用于IT/计算机领域

║嵌入式应用程序开发详解第章文件IO编程║第章文件IO编程本章目标  在搭建起嵌入式开发环境之后从本章开始读者将真正开始学习嵌入式Linux的应用开发。由于嵌入式Linux是经Linux裁减而来的它的系统调用及用户编程接口API与Linux基本是一致的因此在以后的章节中笔者将首先介绍Linux中相关内容的基本编程开发主要讲解与嵌入式Linux中一致的部分然后再将程序移植到嵌入式的开发板上运行。因此没有开发板的读者也可以先在Linux上开发相关应用程序这对以后进入嵌入式Linux的实际开发是十分有帮助的。本章主要讲解文件IO相关开发经过本章的学习读者将会掌握以下内容。掌握Linux中系统调用的基本概念掌握Linux中用户编程接口(API)及系统命令的相互关系掌握文件描述符的概念掌握Linux下文件相关的不带缓存IO函数的使用掌握Linux下设备文件读写方法掌握Linux中对串口的操作熟悉Linux中标准文件IO函数的使用Linux系统调用及用户编程接口(API)由于本章是讲解Linux编程开发的第章因此希望读者更加明确Linux系统调用和用户编程接口(API)的概念。在了解了这些之后会对Linux以及Linux的应用编程有更深入地理解。系统调用所谓系统调用是指操作系统提供给用户程序调用的一组“特殊”接口用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务。例如用户可以通过进程控制相关的系统调用来创建进程、实现进程调度、进程管理等。在这里为什么用户程序不能直接访问系统内核提供的服务呢?这是由于在Linux中为了更好地保护内核空间将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态)它们分别运行在不同的级别上在逻辑上是相互隔离的。因此用户进程在通常情况下不允许访问内核数据也无法使用内核函数它们只能在用户空间操作用户数据调用用户空间的函数。但是在有些情况下用户空间的进程需要获得一定的系统服务(调用内核空间程序)这时操作系统就必须利用系统提供给用户的“特殊接口”系统调用规定用户进程进入内核空间的具体位置。进行系统调用时程序运行空间需要从用户空间进入内核空间处理完后再返回到用户空间。Linux系统调用部分是非常精简的系统调用(只有个左右)它继承了UNIX系统调用中最基本和最有用的部分。这些系统调用按照功能逻辑大致可分为进程控制、进程间通信、文件系统控制、启两个终端并且在两个终端上同时运行该程序以达到多个进程操作一个文件的效果。在这里笔者首先运行终端一请读者注意终端二中的第一句。终端一:rootlocalhostfile#fcntlwritewritelocksetbyreleaselockby终端二:rootlocalhostfile#fcntlwritewritelockalreadysetbywritelocksetbyreleaselockby由此可见写入锁为互斥锁一个时刻只能有一个写入锁存在。接下来的程序是测试文件的读取锁原理同上面的程序一样。*fcntlreadc测试文件读取锁主函数部分*#include<unistdh>#include<sysfileh>#include<systypesh>#include<sysstath>#include<stdioh>#include<stdlibh>intmain(void){intfdfd=open("hello",ORDWR|OCREAT,)if(fd<){perror("open")exit()}*给文件上读取锁*lockset(fd,FRDLCK)getchar()*给文件接锁*lockset(fd,FUNLCK)getchar()close(fd)exit()}同样开启两个终端并首先启动终端一上的程序其运行结果如下所示:终端一:rootlocalhostfile#fcntlreadlocksetbyreleaselockby终端二:rootlocalhostfile#fcntlreadlocksetbyreleaselockby读者可以将此结果与写入锁的运行结果相比较可以看出读取锁为共享锁当进程已设定读取锁后进程还可以设置读取锁。思考如果在一个终端上运行设置读取锁则在另一个终端上运行设置写入锁会有什么结果呢?select()select函数说明前面的fcntl函数解决了文件的共享问题接下来该处理IO复用的情况了。总的来说IO处理的模型有种。(阻塞IO模型:在这种模型下若所调用的IO函数没有完成相关的功能就会使进程挂起直到相关数据到才会出错返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。(非阻塞模型:在这种模型下当请求的IO操作不能完成时则不让进程睡眠而且返回一个错误。非阻塞IO使用户可以调用不会永远阻塞的IO操作如open、write和read。如果该操作不能完成则会立即出错返回且表示该IO如果该操作继续执行就会阻塞。(IO多路转接模型:在这种模型下如果请求的IO操作阻塞且它不是真正阻塞IO而是让其中的一个函数等待在这期间IO还能进行其他操作。如本节要介绍的select函数和poll函数就是属于这种模型。(信号驱动IO模型:在这种模型下通过安装一个信号处理程序系统可以自动捕获特定信号的到来从而启动IO。这是由内核通知用户何时可以启动一个IO操作决定的。(异步IO模型:在这种模型下当一个描述符已准备好可以启动IO时进程会通知内核。现在并不是所有的系统都支持这种模型。可以看到select的IO多路转接模型是处理IO复用的一个高效的方法。它可以具体设置每一个所关心的文件描述符的条件、希望等待的时间等从select函数返回时内核会通知用户已准备好的文件描述符的数量、已准备好的条件等。通过使用select返回值就可以调用相应的IO处理函数了。()select函数格式Select函数的语法格式如表所示。表fcntl函数语法要点所需头文件#include<systypesh>#include<systimeh>#include<unistdh>函数原型intselect(intnumfds,fdset*readfds,fdset*writefdsfdset*exeptfds,structtimeval*timeout)函数传入值numfds:需要检查的号码最高的文件描述符加readfds:由select()监视的读文件描述符集合writefds:由select()监视的写文件描述符集合exeptfds:由select()监视的异常处理文件描述符集合timeout:永远等待直到捕捉到信号或文件描述符已准备好为止具体值:structtimeval类型的指针若等待为timeout时间还没有文件描符准备好就立即返回:从不等待测试所有指定的描述符并立即返回函数返回值成功:准备好的文件描述符(:出错思考请读者考虑一下如何确定最高的文件描述符?可以看到select函数根据希望进行的文件操作对文件描述符进行了分类处理这里对文件描述符的处理主要涉及到个宏函数如表所示。表select文件描述符处理函数FDZERO(fdset*set)清除一个文件描述符集FDSET(intfd,fdset*set)将一个文件描述符加入文件描述符集中FDCLR(intfd,fdset*set)将一个文件描述符从文件描述符集中清除FDISSET(intfd,fdset*set)测试该集中的一个给定位是否有变化一般来说在使用select函数之前首先使用FDZERO和FDSET来初始化文件描述符集在使用了select函数时可循环使用FDISSET测试描述符集在执行完对相关后文件描述符后使用FDCLR来清楚描述符集。另外select函数中的timeout是一个structtimeval类型的指针该结构体如下所示:structtimeval{longtvsec*second*longtvunsec*andmicroseconds*}可以看到这个时间结构体的精确度可以设置到微秒级这对于大多数的应用而言已经足够了。()使用实例由于Select函数多用于IO操作可能会阻塞的情况下而对于可能会有阻塞IO的管道、网络编程本书到现在为止还没有涉及。因此本例主要表现了如何使用select函数而其中的IO操作是不会阻塞的。本实例中主要实现将文件hello里的内容读出并将此内容每隔s写入hello中去。在这里建立了两个描述符集其中一个描述符集inset是用于读取文件内容另一个描述符集inset是用于写入文件的。两个文件描述符fds和fds分别指向这一文件描述符。在首先初始化完各文件描述符集之后就开始了循环测试这两个文件描述符是否可读写由于在这里没有阻塞所以文件描述符处于准备就绪的状态。这时就分别对文件描述符fds和fsd进行读写操作。该程序的流程图如图所示。*selectc*#include<fcntlh>#include<stdioh>#include<unistdh>#include<stdlibh>#include<timeh>intmain(void){intfdscharbufinti,rc,maxfdfdsetinset,insetstructtimevaltv*首先按一定的权限打开hello文件*if((fds=open("hello",ORDWR|OCREAT,))<)perror("openhello")*再按一定的权限打开hello文件*if((fds=open("hello",ORDWR|OCREAT,))<)perror("openhello")if((rc=write(fds,"Hello!n",)))printf("rc=dn",rc)lseek(fds,,SEEKSET)*取出两个文件描述符中的较大者*maxfd=fds>fdsfds:fds*初始化读集合inset并在读集合中加入相应的描述集*FDZERO(inset)FDSET(fds,inset)*初始化写集合inset并在写集合中加入相应的描述集*FDZERO(inset)FDSET(fds,inset)tvtvsec=tvtvusec=*循环测试该文件描述符是否准备就绪并调用select函数对相关文件描述符做对应操作*while(FDISSET(fds,inset)||FDISSET(fds,inset)){if(select(maxfd,inset,inset,,tv)<)perror("select")else{if(FDISSET(fds,inset)){rc=read(fds,buf,)if(rc>){bufrc=''printf("read:sn",buf)}elseperror("read")}if(FDISSET(fds,inset)){rc=write(fds,buf,)if(rc>){bufrc=''printf("rc=d,write:sn",rc,buf)}elseperror("write")sleep()}}}exit()}图select实例流程图读者可以将以上程序交叉编译并下载到开发板上运行。以下是运行结果:root(none)#selectrc=read:Hello!rc=,write:Hello!rc=,write:Hello!rc=,write:Hello!…root(none)#cathelloHello!root(none)#cathelloHello!Hello!…可以看到使用select可以很好地实现IO多路复用在有阻塞的情况下更能够显示出它的作用。嵌入式Linux串口应用开发串口概述用户常见的数据通信的基本方式可分为并行通信与串行通信两种。(并行通信是指利用多条数据传输线将一个资料的各位同时传送。它的特点是传输速度快适用于短距离通信但要求传输速度较高的应用场合。(串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单利用简单的线缆就可实现通信降低成本适用于远距离通信但传输速度慢的应用场合。串口是计算机一种常用的接口常用的串口有RSC接口。它是于年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准它的全称是“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。该标准规定采用一个DB芯引脚的连接器或芯引脚的连接器其中芯引脚的连接器如图所示。SCX内部具有个独立的UART控制器每个控制器都可以工作在Interrupt(中断)模式或者DMA(直接内存访问)模式。同时每个UART均具有字节的FIFO(先入先出寄存器)支持的最高波特率可达到Kbps。UART的操作主要可分为以下几个部分:资料发送、资料接收、产生中断、产生波特率、Loopback模式、红外模式以及自动流控模式。串口参数的配置读者在配置超级终端和minicom时也已经接触到过一般包括波特率、起始位数量、数据位数量、停止位数量和流控协议。在此可以将其配置为波特率、起始位b、数据位b、停止位b和无流控协议。在Linux中所有的设备文件一般都位于“dev”下其中串口一、串口二对应的设备名依次为“devttyS”、“devttyS”可以查看在“dev”下的文件以确认。在本章中已经提到过在Linux下对设备的操作方法与对文件的操作方法是一样的因此对串口的读写就可以使用简单的“read”“write”函数来完成所不同的是只是需要对串口的其他参数另做配置下面就来详细讲解串口应用开发的步骤。图引脚串行接口图串口设置详解本节主要讲解设置串口的主要方法。如前所述设置串口中最基本的包括波特率设置校验位和停止位设置。串口的设置主要是设置structtermios结构体的各成员值如下所示:#include<termiosh>structtermio{unsignedshortciflag*输入模式标志*unsignedshortcoflag*输出模式标志*unsignedshortccflag*控制模式标志*unsignedshortclflag*本地模式标志*unsignedcharcline*linediscipline*unsignedcharcccNCC*controlcharacters*}在这个结构中最为重要的是ccflag通过对它的赋值用户可以设置波特率、字符大小、数据位、停止位、奇偶校验位和硬件流控等。另外ciflag和ccc也是比较常用的标志。在此主要对这个成员进行详细说明。ccflag支持的常量名称如表所示。其中设置波特率为相应的波特率前加上‘B’由于数值较多本表没有全部列出。表ccflag支持的常量名称CBAUDB波特率(放弃DTR)……续表B波特率B波特率B波特率B波特率B波特率B波特率B波特率B波特率EXTA外部时钟率EXTB外部时钟率CSIZE数据位的位掩码CS个数据位CS个数据位CS个数据位CS个数据位CSTOPB个停止位(不设则是个停止位)CREAD接收使能PARENBPARODD校验位使能使用奇校验而不使用偶校验HUPCL最后关闭时挂线(放弃DTR)CLOCAL本地连接(不改变端口所有者)LOBLK块作业控制输出CNETCTSRTS硬件流控制使能在这里对于ccflag成员不能直接对其初始化而要将其通过“与”、“或”操作使用其中的某些选项。输入模式ciflag成员控制端口接收端的字符输入处理。ciflag支持的变量名称如表所示。表ciflag支持的常量名称INPCK奇偶校验使能IGNPAR忽略奇偶校验错误PARMRK奇偶校验错误掩码ISTRIP除去奇偶校验位续表IXON启动出口硬件流控IXOFF启动入口软件流控IXANY允许字符重新启动流控IGNBRK忽略中断情况BRKINT当发生中断时发送SIGINT信号INLCR将NL映射到CRIGNCR忽略CRICRNL将CR映射到NLIUCLC将高位情况映射到低位情况IMAXBEL当输入太长时回复ECHOccc包含了超时参数和控制字符的定义。ccc所支持的常用变量名称如表所示。表ccc支持的常量名称VINTR中断控制对应键为CTRLCVQUIT退出操作对应键为CRTLZVERASE删除操作对应键为Backspace(BS)VKILL删除行对应键为CTRLUVEOF位于文件结尾对应键为CTRLDVEOL位于行尾对应键为Carriagereturn(CR)VEOL位于第二行尾对应键为Linefeed(LF)VMIN指定了最少读取的字符数VTIME指定了读取每个字符的等待时间下面就详细讲解设置串口属性的基本流程。.保存原先串口配置首先为了安全起见和以后调试程序方便可以先保存原先串口的配置在这里可以使用函数tcgetattr(fdoldtio)。该函数得到与fd指向对象的相关参数并将它们保存于oldtio引用的termios结构中。该函数还可以测试配置是否正确、该串口是否可用等。若调用成功函数返回值为若调用失败函数返回值为(其使用如下所示:if(tcgetattr(fd,oldtio)!=){perror("SetupSerial")return}.激活选项有CLOCAL和CREADCLOCAL和CREAD分别用于本地连接和接受使能因此首先要通过位掩码的方式激活这两个选项。newtioccflag|=CLOCAL|CREAD.设置波特率设置波特率有专门的函数用户不能直接通过位掩码来操作。设置波特率的主要函数有:cfsetispeed和cfsetospeed。这两个函数的使用很简单如下所示:cfsetispeed(newtio,B)cfsetospeed(newtio,B)一般地用户需将输入输出函数的波特率设置成一样的。这几个函数在成功时返回失败时返回(。.设置字符大小与设置波特率不同设置字符大小并没有现成可用的函数需要用位掩码。一般首先去除数据位中的位掩码再重新按要求设置。如下所示:optionsccflag=~CSIZE*maskthecharactersizebits*optionsccflag|=CS.设置奇偶校验位设置奇偶校验位需要用到两个termio中的成员:ccflag和ciflag。首先要激活ccflag中的校验位使能标志PARENB和是否要进行偶校验同时还要激活ciflag中的奇偶校验使能。如使能奇校验时代码如下所示:newtioccflag|=PARENBnewtioccflag|=PARODDnewtiociflag|=(INPCK|ISTRIP)而使能偶校验时代码为:newtiociflag|=(INPCK|ISTRIP)newtioccflag|=PARENBnewtioccflag=~PARODD.设置停止位设置停止位是通过激活ccflag中的CSTOPB而实现的。若停止位为则清除CSTOPB若停止位为则激活CSTOPB。下面是停止位是时的代码:newtioccflag=~CSTOPB.设置最少字符和等待时间在对接收字符和等待时间没有特别要求的情况下可以将其设置为如下所示:newtiocccVTIME=newtiocccVMIN=.处理要写入的引用对象由于串口在重新设置之后在此之前要写入的引用对象要重新处理这时就可调用函数tcflush(fdqueueselector)来处理要写入引用的对象。对于尚未传输的数据或者收到的但是尚未读取的数据其处理方法取决于queueselector的值。这里queueselector可能的取值有以下几种。(TCIFLUSH:刷新收到的数据但是不读。(TCOFLUSH:刷新写入的数据但是不传送。(TCIOFLUSH:同时刷新收到的数据但是不读并且刷新写入的数据但是不传送。如在本例中所采用的是第一种方法:tcflush(fd,TCIFLUSH).激活配置在完成全部串口配置之后要激活刚才的配置并使配置生效。这里用到的函数是tcsetattr它的函数原型是:tcsetattr(fd,OPTION,newtio)这里的newtio就是termios类型的变量OPTION可能的取值有以下三种:(TCSANOW:改变的配置立即生效。(TCSADRAIN:改变的配置在所有写入fd的输出都结束后生效。(TCSAFLUSH:改变的配置在所有写入fd引用对象的输出都被结束后生效所有已接受但未读入的输入都在改变发生前丢弃。该函数若调用成功则返回若失败则返回(。如下所示:if((tcsetattr(fd,TCSANOW,newtio))!=){perror("comseterror")return}下面给出了串口配置的完整的函数。通常为了函数的通用性通常将常用的选项都在函数中列出这样可以大大方便以后用户的调试使用。该设置函数如下所示:intsetopt(intfd,intnSpeed,intnBits,charnEvent,intnStop){structtermiosnewtio,oldtio*保存测试现有串口参数设置在这里如果串口号等出错会有相关的出错信息*if(tcgetattr(fd,oldtio)!=){perror("SetupSerial")return}bzero(newtio,sizeof(newtio))*步骤一设置字符大小*newtioccflag|=CLOCAL|CREADnewtioccflag=~CSIZE*设置停止位*switch(nBits){case:newtioccflag|=CSbreakcase:newtioccflag|=CSbreak}*设置奇偶校验位*switch(nEvent){case'O':奇数newtioccflag|=PARENBnewtioccflag|=PARODDnewtiociflag|=(INPCK|ISTRIP)breakcase'E':偶数newtiociflag|=(INPCK|ISTRIP)newtioccflag|=PARENBnewtioccflag=~PARODDbreakcase'N':无奇偶校验位newtioccflag=~PARENBbreak}*设置波特率*switch(nSpeed){case:cfsetispeed(newtio,B)cfsetospeed(newtio,B)breakcase:cfsetispeed(newtio,B)cfsetospeed(newtio,B)breakcase:cfsetispeed(newtio,B)cfsetospeed(newtio,B)breakcase:cfsetispeed(newtio,B)cfsetospeed(newtio,B)breakcase:cfsetispeed(newtio,B)cfsetospeed(newtio,B)breakdefault:cfsetispeed(newtio,B)cfsetospeed(newtio,B)break}*设置停止位*if(nStop==)newtioccflag=~CSTOPBelseif(nStop==)newtioccflag|=CSTOPB*设置等待时间和最小接收字符*newtiocccVTIME=newtiocccVMIN=*处理未接收字符*tcflush(fd,TCIFLUSH)*激活新配置*if((tcsetattr(fd,TCSANOW,newtio))!=){perror("comseterror")return}printf("setdone!n")return}串口使用详解在配置完串口的相关属性后就可以对串口进行打开、读写操作了。它所使用的函数和普通文件读写的函数一样都是open、write和read。它们相区别的只是串口是一个终端设备因此在函数的具体参数的选择时会有一些区别。另外这里会用到一些附加的函数用于测试终端设备的连接情况等。下面将对其进行具体讲解。.打开串口打开串口和打开普通文件一样使用的函数同打开普通文件一样都是open函数。如下所示:fd=open("devttyS",ORDWR|ONOCTTY|ONDELAY)可以看到这里除了普通的读写参数外还有两个参数ONOCTTY和ONDELAY。(ONOCTTY标志用于通知Linux系统这个程序不会成为对应这个端口的控制终端。如果没有指定这个标志那么任何一个输入(诸如键盘中止信号等)都将会影响用户的进程。(ONDELAY标志通知Linux系统这个程序不关心DCD信号线所处的状态(端口的另一端是否激活或者停止)。如果用户指定了这个标志则进程将会一直处在睡眠状态直到DCD信号线被激活。接下来可恢复串口的状态为阻塞状态用于等待串口数据的读入。可用fcntl函数实现如下所示:fcntl(fd,FSETFL,)再接着可以测试打开文件描述符是否引用一个终端设备以进一步确认串口是否正确打开如下所示:isatty(STDINFILENO)该函数调用成功则返回若失败则返回。这时一个串口就已经成功打开了。接下来就可以对这个串口进行读、写操作。下面给出了一个完整的打开串口的函数同样写考虑到了各种不同的情况。程序如下所示:*打开串口函数*intopenport(intfd,intcomport){char*dev={"devttyS","devttyS","devttyS"}longvdisableif(comport==)串口{fd=open("devttyS",ORDWR|ONOCTTY|ONDELAY)if(==fd){perror("Can'tOpenSerialPort")return()}}elseif(comport==)串口{fd=open("devttyS",ORDWR|ONOCTTY|ONDELAY)if(==fd){perror("Can'tOpenSerialPort")return()}}elseif(comport==)串口{fd=open("devttyS",ORDWR|ONOCTTY|ONDELAY)if(==fd){perror("Can'tOpenSerialPort")return()}}*恢复串口为阻塞状态*if(fcntl(fd,FSETFL,)<)printf("fcntlfailed!n")elseprintf("fcntl=dn",fcntl(fd,FSETFL,))*测试是否为终端设备*if(isatty(STDINFILENO)==)printf("standardinputisnotaterminaldevicen")elseprintf("isattysuccess!n")printf("fdopen=dn",fd)returnfd}.读写串口读写串口操作和读写普通文件一样使用read、write函数即可。如下所示:write(fd,buff,)read(fd,buff,)下面两个实例给出了串口读和写的两个程序的main函数部分这里用到的函数有前面讲述到的openport和setopt函数。*写串口程序*#include<stdioh>#include<stringh>#include<systypesh>#include<errnoh>#include<sysstath>#include<fcntlh>#include<unistdh>#include<termiosh>#include<stdlibh>*读串口程序*intmain(void){intfdintnread,icharbuff="Hellon"if((fd=openport(fd,))<){打开串口perror("openporterror")return}if((i=setopt(fd,,,'N',))<){设置串口perror("setopterror")return}printf("fd=dn",fd)fd=nread=read(fd,buff,)读串口printf("nread=d,sn",nread,buff)close(fd)return}读者可以将该程序在宿主机上运行然后用串口线将目标板和宿主机连接起来之后将目标板上电这样就可以看到宿主机上有目标板的串口输出。rootlocalhostfile#receivefcntl=isattysuccess!fdopen=setdonefd=nread=,…另外读者还可以考虑一下如何使用select函数实现串口的非阻塞读写具体实例会在后面的实验中给出。标准IO开发本章前面几节所述的文件及IO读写都是基于文件描述符的。这些都是基本的IO控制是不带缓存的。而本节所要讨论的IO操作都是基于流缓冲的它是符合ANSIC的标准IO处理这里有很多函数读者已经非常熟悉了(如printf、scantf函数等)因此本节中仅简要介绍最主要的函数。标准IO提供流缓冲的目的是尽可能减少使用read和write调用的数量。标准IO提供了种类型的缓冲存储。(全缓冲。在这种情况下当填满标准IO缓存后才进行实际IO操作。对于驻在磁盘上的文件通常是由标准IO库实施全缓冲的。在一个流上执行第一次IO操作时通常调用malloc就是使用全缓冲。(行缓冲。在这种情况下当在输入和输出中遇到新行符时标准IO库执行IO操作。这允许我们一次输出一个字符(如fputc函数)但只有写了一行之后才进行实际IO操作。当流涉及一个终端时(例如标准输入和标准输出)典型地使用行缓冲。(不带缓冲。标准IO库不对字符进行缓冲。如果用标准IO函数写若干字符到不带缓冲的流中则相当于用write系统的用函数将这些字符写全相比较的打开文件上。标准出错况stderr通常是不带缓存后这就使得出错信息可以尽快显示出来而不管它们是否含有一个新行字符。在下面讨论具体函数时请读者注意区分这种不同的情况。打开和关闭文件.打开文件()函数说明打开文件有三个标准函数分别为:fopen、fdopen和freopen。它们可以以不同的模式打开但都返回一个指向FILE的指针该指针以将对应的IO流相绑定了。此后对文件的读写都是通过这个FILE指针来进行。其中fopen可以指定打开文件的路径和模式fdopen可以指定打开的文件描述符和模式而freopen除可指定打开的文件、模式外还可指定特定的IO流。()函数格式定义fopen函数格式如表所示。表fopen函数语法要点所需头文件#include<stdioh>函数原型FILE*fopen(constchar*path,constchar*mode)函数传入值path:包含要打开的文件路径及文件名mode:文件打开状态(后面会具体说明)函数返回值成功:指向FILE的指针失败:这里的mode类似于open中的flag可以定义打开文件的具体权限等表说明了fopen中mode的各种取值。表mode取值说明r或rb打开只读文件该文件必须存在r+或r+b打开可读写的文件该文件必须存在w或wb打开只写文件若文件存在则文件长度清为即会擦些文件以前内容。若文件不存在则建立该文件w或w+b打开可读写文件若文件存在则文件长度清为即会擦些文件以前内容。若文件不存在则建立该文件a或ab以附加的方式打开只写文件。若文件不存在则会建立该文件如果文件存在写入的数据会被加到文件尾即文件原先的内容会被保留a或a+b以附加方式打开可读写的文件。若文件不存在则会建立该文件如果文件存在写入的数据会被加到文件尾后即文件原先的内容会被保留注意在每个选项中加入b字符用来告诉函数库打开的文件为二进制文件而非纯文字文件。不过在Linux系统中会自动识别不同类型的文件而将此符号忽略。fdopen函数格式如表所示。表fdopen函数语法要点所需头文件#include<stdioh>函数原型FILE*fdopen(intfd,constchar*mode)函数传入值fd:要打开的文件描述符mode:文件打开状态(后面会具体说明)函数返回值成功:指向FILE的指针失败:freopen函数格式如表所示。表freopen函数语法要点所需头文件#include<stdioh>函数原型FILE*freopen(constchar*path,constchar*mode,FILE*stream)函数传入值path:包含要打开的文件路径及文件名mode:文件打开状态(后面会具体说明)stream:已打开的文件指针函数返回值成功:指向FILE的指针失败:.关闭文件()函数说明关闭标准流文件的函数为fclose这时缓冲区内的数据写入文件中并释放系统所提供的文件资源。()函数格式说明freopen函数格式如表所示。表fclose函数语法要点所需头文件#include<stdioh>函数原型intfclose(FILE*stream)函数传入值stream:已打开的文件指针函数返回值成功:失败:EOF.使用实例文件打开关闭的操作都比较简单这里仅以fopen和fclose为例代码如下所示:*fopenc*#include<stdioh>main(){FILE*fpintc*调用fopen函数*if((fp=fopen("exist","w"))!=){printf("opensuccess!")}fclose(fp)}读者可以尝试用其他文件打开函数进行练习。文件读写.读文件()fread函数说明在文件流打开之后可对文件流进行读写等操作其中读操作的函数为fread。()fread函数格式fread函数格式如表所示。表fread函数语法要点所需头文件#include<stdioh>函数原型sizetfread(void*ptr,sizetsize,sizetnmemb,FILE*stream)函数传入值ptr:存放读入记录的缓冲区size:读取的记录大小nmemb:读取的记录数stream:要读取的文件流函数返回值成功:返回实际读取到的nmemb数目失败:EOF.写文件()fwrite函数说明fwrite函数是用于对指定的文件流进行写操作。()fwrite函数格式fwrite函数格式如表所示。表fwrite函数语法要点所需头文件#include<stdioh>函数原型sizetfwrite(constvoid*ptr,sizetsize,sizetnmemb,FILE*stream)函数传入值ptr:存放写入记录的缓冲区size:写入的记录大小nmemb:写入的记录数stream:要写入的文件流函数返回值成功:返回实际写入到的nmemb数目失败:EOF这里仅以fwrite为例简单说明:*fwritec*#include<stdioh>intmain(){FILE*streamchars={'a','b','c'}*首先使用fopen打开文件之后再调用fwrite写入文件*stream=fopen("what","w")i=fwrite(s,sizeof(char),nmemb,stream)printf("i=d",i)fclose(stream)}运行结果如下所示:rootlocalhostfile#writei=rootlocalhostfile#catwhatabc输入输出文件打开之后根据一次读写文件中字符的数目可分为字符输入输出、行输入输出和格式化输入输出下面分别对这种不同的方式进行讨论。.字符输入输出字符输入输出函数一次仅读写一个字符。其中字符输入输出函数如表和表所示。表字符输出函数语法要点所需头文件#include<stdioh>函数原型intgetc(FILE*stream)intfgetc(FILE*stream)intgetchar(void)函数传入值stream:要输入的文件流函数返回值成功:下一个字符失败:EOF表字符输入函数语法要点所需头文件#include<stdioh>函数原型intputc(intc,FILE*stream)intfputc(intc,FILE*stream)intputchar(intc)函数返回值成功:字符c失败:EOF这几个函数功能类似其区别仅在于getc和putc通常被实现为宏而fgetc和fputc不能实现为宏因此函数的实现时间会有所差别。下面这个实例结合fputc和fgetc将标准输入复制到标准输出中去。*fputc*#include<stdioh>main(){intc*把fgetc的结果作为fputc的输入*fputc(fgetc(stdin),stdout)}运行结果如下所示:rootlocalhostfile#filew(用户输入)w(屏幕输出).行输入输出行输入输出函数一次操作一行。其中行输入输出函数如表和表所示。表行输出函数语法要点所需头文件#include<stdioh>函数原型char*gets(char*s)charfgets(char*s,intsize,FILE*stream)函数传入值s:要输入的字符串size:输入的字符串长度stream:对应的文件流函数返回值成功:s失败:表行输入函数语法要点所需头文件#include<stdioh>函数原型intputs(constchar*s)intfputs(constchar*s,FILE*stream)函数传入值s:要输出的字符串stream:对应的文件流函数返回值成功:s失败:这里以gets和puts为例进行说明本实例将标准输入复制到标准输出如下所示:*getsc*#include<stdioh>main(){chars*同上例把fgets的结果作为fputs的输入*fputs(fgets(s,,stdin),stdout)}运行该程序结果如下所示:rootwwwyul#fileThisisstdin(用户输入)Thisisstdin(屏幕输出).格式化输入输出格式化输入输出函数可以指定输入输出的具体格式这里有读者已经非常熟悉的printf、scanf等函数这里就简要介绍一下它们的格式。如下表~表所示。表格式化输出函数所需头文件#include<stdioh>函数原型intprintf(constchar*format,…)intfprintf(FILE*fp,constchar*format,…)intsprintf(char*buf,constchar*format,…)函数传入值format:记录输出格式fp:文件描述符buf:记录输出缓冲区函数返回值成功:输出字符数(sprintf返回存入数组中的字符数)失败:表格式化输出函数所需头文件#include<stdargh>#include<stdioh>函数原型intvprintf(constchar*format,valistarg)intvfprintf(FILE*fp,constchar*format,valistarg)intvsprintf(char*buf,constchar*format,valistarg)函数传入值format:记录输出格式fp:文件描述符arg:相关命令参数函数返回值成功:存入数组的字符数失败:表格式化输入函数所需头文件#include<stdioh>函数原型intscanf(constchar*format,…)intfscanf(FILE*fp,constchar*format,…)intsscanf(char*buf,constchar*format,…)函数传入值format:记录输出格式fp:文件描述符buf:记录输入缓冲区函数返回值成功:输出字符数(sprintf返回存入数组中的字符数)失败:由于本节的函数用法比较简单并且比较常用因此就不再举例了请读者需要用到时自行查找其用法。实验内容文件读写及上锁.实验目的通过编写文件读写及上锁的程序进一步熟悉Linux中文件IO相关的应用开发并且熟练掌握open、read、write、fcntl等函数的使用。.实验内容该实验要求首先打开一个文件然后将该文件上写入锁并写入hello字符串。接着在解锁后再将该文件上读取锁并读取刚才写入的内容。最后模拟多进程同时读写一个文件时的情况。.实验步骤()画出实验流程图该实验流程图如图所示。()编写代码该实验源代码如下所示其中用到的lockset函数可参见第节。*exprc实验一源码*#include<unistdh>#include<sysfileh>#include<systypesh>#include<sysstath>#include<stdioh>#include<stdlibh>#include<stringh>voidlockset(intfd,inttype)intmain(void){intfd,nwrite,nread,lenchar*buff="Hellon"charbufrlen=strlen(buff)fd=open("hello",ORDWR|OCREAT,)if(fd<){perror("open")exit()}*加上写入锁*lockset(fd,FWRLCK)if((nwrite=write(fd,buff,len))==len){printf("writesuccessn")}getchar()*解锁*lockset(fd,FUNLCK)getchar()*加上读取锁*lockset(fd,FRDLCK)lseek(fd,,SEEKSET)if((nread=read(fd,bufr,len))==len){printf("read:sn",bufr)}getchar()*解锁*lockset(fd,FUNLCK)getchar()close(fd)exit()}()首先在宿主机上编译调试该程序如下所示:rootlocalhostprocess#gccexprc–oexpr()在确保没有编译错误后使用交叉编译该程序如下所示:rootlocalhostprocess#armlinuxgccexprc–oexpr()将生成的可执行程序下载到目标板上运行。.实验结果此实验在目标板上的运行结果如下所示:root(none)#exprwritelocksetbywritesuccessreleaselockbyreadlocksetbyread:Helloreleaselockby另外在本机上可以开启两个终端同时运行该程序。实验结果会和这两个进程运行过程具体相关希望读者能具体分析每种情况。下面列出其中一种情况:终端一:rootlocalhostfile#exprwritelocksetbywritesuccessreleaselockbyreadlocksetbyread:Helloreleaselockby终端二:rootlocalhostfile#exprwritelockalreadysetbywritelocksetbywritesuccessreleaselockbyreadlocksetbyread:Helloreleaselockby多路复用式串口读写.实验目的通过编写多路复用式串口读写进一步理解select函数的作用同时更加熟练掌握Linux设备文件的读写方法。.实验内容完成串口读写操作这里设定从串口读取消息时使用select函数发送消息的程序不需要用select函数只发送“Hello”消息由接收端接收。.实验步骤()画出流程图下图是读串口的流程图写串口的流程图与此类似。()编写代码分别编写串口读写程序该程序中用到的openport和setopt函数请参照节所述。写串口程序的代码如下所示:*写串口*#include<stdioh>#include<stringh>#include<systypesh>#include<errnoh>#include<sysstath>#include<fcntlh>#include<unistdh>#include<termiosh>#include<stdlibh>intmain(void){intfdintnwrite,icharbuff="Hellon"*打开串口*if((fd=openport(fd,))<){perror("openporterror")return}*设置串口*if((i=setopt(fd,,,'N',))<){perror("setopterror")return}printf("fd=dn",fd)*向串口写入字符串*nwrite=write(fd,buff,)printf("nwrite=dn",nwrite)close(fd)return}读串口程序的代码如下所示:*读串口*#include<stdioh>#include<stringh>#include<systypesh>#include<errnoh>#include<sysstath>#include<fcntlh>#include<unistdh>#include<termiosh>#include<stdlibh>intmain(void){intfdintnread,nwrite,icharbufffdsetrd*打开串口*if((fd=openport(fd,))<){perror("openporterror")return}*设置串口*if((i=setopt(fd,,,'N',))<){perror("setopterror")return}*利用select函数来实现多个串口的读写*FDZERO(rd)FDSET(fd,rd)while(FDISSET(fd,rd)){if(select(fd,rd,,,)<)perror("select")else{while((nread=read(fd,buff,))>){printf("nread=d,sn",nread,buff)}}close(fd)return}()接下来把第一个写串口程序交叉编译再把第二个读串口程序在PC机上编译分别得到可执行文件write和read。()将写串口程序下载到开发板上然后连接PC和开发板的串口。首先运行读串口程序再运行写串口程序。.实验结果发送端的运行结果如下所示:root(none)#writefcntl=isattysuccess!fdopen=setdonefd=nwrite=接收端的运行结果如下所示:rootlocalhostfile#readfcntl=isattysuccess!fdopen=setdonefd=nread=,Hello!读者还可以尝试修改select函数选项例如设定一个等待时间查看程序的运行结果。本章小结本章首先讲解了系统调用、用户函数接口(API)和系统命令之间的关系和区别这也是贯穿本书的一条主线本书的讲解就是从系统命令、用户函数接口(API)到系统调用为顺序一层层深入进行讲解的希望读者能有一个较为深刻的认识。接着本章主要讲解了嵌入式Linux中文件IO相关的开发在这里主要讲解了不带缓存IO函数的使用这也是本章的重点。因为不带缓存IO函数的使用范围非常广泛在有很多情况下必须使用它这也是学习嵌入式Linux开发的基础因此读者一定要牢牢掌握相关知识。其中主要讲解了open、close、read、write、lseek、fcntl和select等函数这几个函数包括了不带缓存IO处理的主要部分并且也体现了它的主要思想。接下来本章讲解了嵌入式Linux串口编程。这其实是Linux中设备文件读写的实例由于它能很好地体现前面所介绍的内容而且在嵌入式开发中也较为常见因此对它进行了比较详细地讲解。之后本章简单介绍了标准IO的相关函数希望读者也能对它有一个总体的认识。最后本章安排了两个实验分别是文件使用及上锁和多用复用串口读写。希望读者能够认真完成。思考与练习使用select函数实现个串口的通信:串口接收数据串口和串口向串口发送数据。�EMBEDVisioDrawing���图实验节流程图�EMBEDVisioDrawing���实验节流程图vsd��开始�调用FDISSET测试inset和inset是否有变化打开文件hello和hello并在hello中写入“Hello!”并lseek�分别对fds、fds调用FDZERO、FDSET初始化调用select读hello写hello结束�暂停秒doc用户空间内核空间系统调用系统命令用户编程接口APIvsd��开始�打开串口�设置串口select等待读串口结束vsd��开始�Open文件加写入锁并写入hello解锁加读取锁并读取hello解锁结

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/43

1492406

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利