不带缓存的文件IO操作.docx
- 文档编号:16028957
- 上传时间:2023-07-10
- 格式:DOCX
- 页数:26
- 大小:23.84KB
不带缓存的文件IO操作.docx
《不带缓存的文件IO操作.docx》由会员分享,可在线阅读,更多相关《不带缓存的文件IO操作.docx(26页珍藏版)》请在冰点文库上搜索。
不带缓存的文件IO操作
不带缓存的文件I/O操作
本节主要介绍不带缓存的文件I/O操作,主要用到5个函数:
open、read、write、lseek
和close。
这里的不带缓存是指每一个函数都只调用系统中的一个函数。
这些函数虽然不是
ANSIC的组成部分,但是是POSIX的组成部分。
6.3.1open和close
(1)open和close函数说明
open函数是用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权
限等各种参数。
close函数是用于关闭一个打开文件。
当一个进程终止时,它所有已打开的文件都由内核
自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。
(2)open和close函数格式
open函数的语法格式如表6.1所示。
表6.1open函数语法要点
所需头文件
#include
#include
#include
续表
函数原型intopen(constchar*pathname,flags,intperms)
pathname被打开的文件名(可包括路径名)
O_RDONLY:
只读方式打开文件
O_WRONLY:
可写方式打开文件
O_RDWR:
读写方式打开文件
O_CREAT:
如果该文件不存在,就创建一个新的文件,并用第三个参
数为其设置权限
O_EXCL:
如果使用O_CREAT时文件存在,则可返回错误消息。
这一
参数可测试文件是否存在
O_NOCTTY:
使用本参数时,如文件为终端,那么终端不可以作为调
用open()系统调用的那个进程的控制终端
O_TRUNC:
如文件已经存在,并且以只读或只写成功打开,那么会先
全部删除文件中原有数据
flag:
文件
打开的方
式
O+APPEND:
以添加方式打开文件,在打开文件的同时,文件指针指
向文件的末尾
函数传入值
perms被打开文件的存取权限,为8进制表示法
函数返回值
成功:
返回文件描述符
失败:
-1
在open函数中,flag参数可通过“|”组合构成,但前3个函数不能相互组合。
perms是
文件的存取权限,采用8进制表示法,相关内容读者可参见第2章。
close函数的语法格式如下表6.2所示。
表6.2close函数语法要点
《嵌入式Linux应用程序开发详解》——第6章、文件IO编程
所需头文件#include
函数原型intclose(intfd)
函数输入值fd:
文件描述符
函数返回值0:
成功
-1:
出错
(3)open和close函数使用实例
下面实例中的open函数带有3个flag参数:
O_CREAT、O_TRUNC和O_WRONLY,
这样就可以对不同的情况指定相应的处理方法。
另外,这里对该文件的权限设置为0600。
其
源码如下所示:
/*open.c*/
#include
#include
#include
#include
#include
#include
intmain(void)
{
intfd;
/*调用open函数,以可读写的方式打开,注意选项可以用“|”符号连接*/
if((fd=open("/tmp/hello.c",O_CREAT|O_TRUNC|O_WRONLY,0600))<0){
perror("open:
");
exit
(1);
}
else{
printf("Openfile:
hello.c%d\n",fd);
}
if(close(fd)<0){
perror("close:
");
exit
(1);
}
else
printf("Closehello.c\n");
exit(0);
}
[root@(none)1]#./open
Openfile:
hello.c3
Closehello.c
[root@(none)tmp]#ls-l|grephello.c
-rw-------1rootroot0Dec400:
59hello.c
经过交叉编译后,将文件下载到目标板,则该可执行文件运行后就能在目录/tmp下新建
一个hello.c的文件,其权限为0600。
注意
open函数返回的文件描述符一定是最小的未用文件描述符。
由于一个进程在启动时自动打开了
0、1、2三个文件描述符,因此,该文件运行结果中返回的文件描述符为3。
读者可以尝试在
调用open函数之前,加依据close(0),则此后在open函数时返回的文件描述符为0(若关闭文
件描述符1,则在执行时会由于没有标准输出文件而无法输出)。
6.3.2read、write和lseek
(1)read、write和lseek函数作用
read函数是用于将指定的文件描述符中读出数据。
当从终端设备文件中读出数据时,通
常一次最多读一行。
write函数是用于向打开的文件写数据,写操作从文件的当前位移量处开始。
若磁盘已满
或超出该文件的长度,则write函数返回失败。
lseek函数是用于在指定的文件描述符中将文件指针定位到相应的位置。
(2)read和write函数格式
read函数的语法格式如下表6.3所示。
表6.3read函数语法要点
所需头文件#include
函数原型ssize_tread(intfd,void*buf,size_tcount)
fd:
文件描述符
函数传入值buf:
指定存储器读出数据的缓冲区
count:
指定读出的字节数
函数返回值
成功:
读到的字节数
0:
已到达文件尾
-1:
出错
在读普通文件时,若读到要求的字节数之前已到达文件的尾部,则返回的字节数会小于
希望读出的字节数。
write函数的语法格式如下表6.4所示。
表6.4write函数语法要点
所需头文件#include
函数原型ssize_twrite(intfd,void*buf,size_tcount)
《嵌入式Linux应用程序开发详解》——第6章、文件IO编程
fd:
文件描述符
函数传入值buf:
指定存储器写入数据的缓冲区
count:
指定读出的字节数
函数返回值
成功:
已写的字节数
-1:
出错
在写普通文件时,写操作从文件的当前位移处开始。
lseek函数的语法格式如下表6.5所示。
表6.5lseek函数语法要点
所需头文件
#include
#include
函数原型off_tlseek(intfd,off_toffset,intwhence)
fd:
文件描述符
函数传入值offset:
偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可
负(向前移,向后移)
续表
SEEK_SET:
当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:
当前位置为文件指针的位置,新位置为当前位置加上
偏移量
whence:
当前位置
的基点SEEK_END:
当前位置为文件的结尾,新位置为文件的大小加上偏
移量的大小
函数返回值
成功:
文件的当前位移
-1:
出错
(3)函数使用实例
该示例程序首先打开上一节中创建的文件,然后对此文件进行读写操作(记得要将文件
打开属性改为可读写,将文件权限也做相应更改)。
接着,写入“Hello!
I'mwritingtothisfile!
”,
此时文件指针位于文件尾部。
接着在使用lseek函数将文件指针移到文件开始处,并读出10
个字节并将其打印出来。
程序源代码如下所示:
/*write.c*/
#include
#include
#include
#include
#include
#include
#include
#defineMAXSIZE
intmain(void)
{
inti,fd,size,len;
char*buf="Hello!
I'mwritingtothisfile!
";
charbuf_r[10];
len=strlen(buf);
/*首先调用open函数,并指定相应的权限*/
if((fd=open("/tmp/hello.c",O_CREAT|O_TRUNC|O_RDWR,0666))<0){
perror("open:
");
exit
(1);
}
else
printf("openfile:
hello.c%d\n",fd);
/*调用write函数,将buf中的内容写入到打开的文件中*/
if((size=write(fd,buf,len))<0){
perror("write:
");
exit
(1);
}
else
printf("Write:
%s\n",buf);
/*调用lsseek函数将文件指针移到文件起始,并读出文件中的10个字节*/
lseek(fd,0,SEEK_SET);
if((size=read(fd,buf_r,10))<0){
perror("read:
");
exit
(1);
}
else
printf("readformfile:
%s\n",buf_r);
if(close(fd)<0){
perror("close:
");
exit
(1);
}
else
printf("Closehello.c\n");
exit(0);
}
[root@(none)1]#./write
openfile:
hello.c3
《嵌入式Linux应用程序开发详解》——第6章、文件IO编程
Write:
Hello!
I'mwritingtothisfile!
readformfile:
Hello!
I'm
Closehello.c
[root@(none)1]#cat/tmp/hello.c
Hello!
I'mwritingtothisfile!
6.3.3fcntl
(1)fcntl函数说明
前面的这5个基本函数实现了文件的打开、读写等基本操作,这一节将讨论的是,在文
件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情况,这时,
Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
文件锁包括建议性锁和强制性锁。
建议性锁要求每个上锁文件的进程都要检查是否有锁
存在,并且尊重已有的锁。
在一般情况下,内核和系统都不使用建议性锁。
强制性锁是由内
核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读
写操作。
采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在Linux中,实现文件上锁的函数有lock和fcntl,其中flock用于对文件施加建议性锁,
而fcntl不仅可以施加建议性锁,还可以施加强制锁。
同时,fcntl还能对文件的某一记录进行
上锁,也就是记录锁。
记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在
文件的同一部分建立读取锁。
而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的
某个部分上建立写入锁。
当然,在文件的同一部分不能同时建立读取锁和写入锁。
注意
fcntl是一个非常通用的函数,它还可以改变文件进程各方面的属性,在本节中,主要介绍它建
立记录锁的方法,关于它其他用户感兴趣的读者可以参看fcntl手册。
(2)fcntl函数格式
用于建立记录锁的fcntl函数格式如表6.6所示。
表6.6fcntl函数语法要点
所需头文件
#include
#include
#include
函数原型intfcnt1(intfd,intcmd,structflock*lock)
fd:
文件描述符
F_DUPFD:
复制文件描述符
F_GETFD:
获得fd的close-on-exec标志,若标志未设置,则文件经过exec
函数之后仍保持打开状态
F_SETFD:
设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位
决定
F_GETFL:
得到open设置的标志
函数传入值
cmd
F_SETFL:
改变open设置的标志
F_GETFK:
根据lock描述,决定是否上文件锁
F_SETFK:
设置lock描述的文件锁
F_SETLKW:
这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。
如果存在其他锁,则调用进程睡眠;如果捕捉到信号则睡眠中断
F_GETOWN:
检索将收到SIGIO和SIGURG信号的进程号或进程组号
F_SETOWN:
设置进程号或进程组号
Lock:
结构为flock,设置记录锁的具体状态,后面会详细说明
函数返回值
成功:
0
-1:
出错
这里,lock的结构如下所示:
Structflock{
shortl_type;
off_tl_start;
shortl_whence;
off_tl_len;
pid_tl_pid;
}
lock结构中每个变量的取值含义如表6.7所示。
表6.7lock结构变量取值
F_RDLCK:
读取锁(共享锁)
l_typeF_WRLCK:
写入锁(排斥锁)
F_UNLCK:
解锁
l_stat相对位移量(字节)
SEEK_SET:
当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:
当前位置为文件指针的位置,新位置为当前位置加上偏移量
l_whence:
相对位移
量的起点(同lseek
的whence)。
SEEK_END:
当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小
l_len加锁区域的长度
小技巧
为加锁整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明
为0。
(3)fcntl使用实例
下面首先给出了使用fcntl函数的文件记录锁函数。
在该函数中,首先给flock结构体的
对应位赋予相应的值。
接着使用两次fcntl函数分别用于给相关文件上锁和判断文件是否可以
上锁,这里用到的cmd值分别为F_SETLK和F_GETLK。
这个函数的源代码如下所示:
《嵌入式Linux应用程序开发详解》——第6章、文件IO编程
/*lock_set函数*/
voidlock_set(intfd,inttype)
{
structflocklock;
lock.l_whence=SEEK_SET;//赋值lock结构体
lock.l_start=0;
lock.l_len=0;
while
(1){
lock.l_type=type;
/*根据不同的type值给文件上锁或解锁*/
if((fcntl(fd,F_SETLK,&lock))==0){
if(lock.l_type==F_RDLCK)
printf("readlocksetby%d\n",getpid());
elseif(lock.l_type==F_WRLCK)
printf("writelocksetby%d\n",getpid());
elseif(lock.l_type==F_UNLCK)
printf("releaselockby%d\n",getpid());
return;
}
/*判断文件是否可以上锁*/
fcntl(fd,F_GETLK,&lock);
/*判断文件不能上锁的原因*/
if(lock.l_type!
=F_UNLCK){
/*/该文件已有写入锁*/
if(lock.l_type==F_RDLCK)
printf("readlockalreadysetby%d\n",lock.l_pid);
/*该文件已有读取锁*/
elseif(lock.l_type==F_WRLCK)
printf("writelockalreadysetby%d\n",lock.l_pid);
getchar();
}
}
}
下面的实例是测试文件的写入锁,这里首先创建了一个hello文件,之后对其上写入锁,
最后释放写入锁。
代码如下所示:
/*fcntl_write.c测试文件写入锁主函数部分*/
#include
#include
#include
#include
#include
#include
intmain(void)
{
intfd;
/*首先打开文件*/
fd=open("hello",O_RDWR|O_CREAT,0666);
if(fd<0){
perror("open");
exit
(1);
}
/*给文件上写入锁*/
lock_set(fd,F_WRLCK);
getchar();
/*给文件接锁*/
lock_set(fd,F_UNLCK);
getchar();
close(fd);
exit(0);
}
为了能够使用多个终端,更好地显示写入锁的作用,本实例主要在PC机上测试,读者
可将其交叉编译,下载到目标板上运行。
下面是在PC机上的运行结果。
为了使程序有较大
的灵活性,笔者采用文件上锁后由用户键入一任意键使程序继续运行。
建议读者开启两个终
端,并且在两个终端上同时运行该程序,以达到多个进程操作一个文件的效果。
在这里,笔
者首先运行终端一,请读者注意终端二中的第一句。
终端一:
[root@localhostfile]#./fcntl_write
writelocksetby4994
releaselockby4994
终端二:
[root@localhostfile]#./fcntl_write
writelockalreadysetby4994
《嵌入式Linux应用程序开发详解》——第6章、文件IO编程
writelocksetby4997
releaselockby4997
由此可见,写入锁为互斥锁,一个时刻只能有一个写入锁存在。
接下来的程序是测试文件的读取锁,原理同上面的程序一样。
/*fcntl_read.c测试文件读取锁主函数部分*/
#include
#include
#include
#include
#include
#include
intmain(void)
{
intfd;
fd=open("hello",O_RDWR|O_CREAT,0666);
if(fd<0){
perror("open");
exit
(1);
}
/*给文件上读取锁*/
lock_set(fd,F_RDLCK);
getchar();
/*给文件接锁*/
lock_set(fd,F_UNLCK);
getchar();
close(fd);
exit(0);
}
同样开启两个终端,并首先启动终端一上的程序,其运行结果如下所示:
终端一:
[root@localhostfile]#./fcntl2
readlocksetby5009
releaselockby5009
终端二:
[root@localhostfile]#./fcntl2
readlocksetby5010
releaselockby5010
读者可以将此结果与写入锁的运行结果相比较,可以看出,读取锁为共享锁,当进程5009
已设定读取锁后,进程5010还可以设置读取锁。
思考
如果在一个终端上运行设置读取锁,则在另一个终端上运行设置写入锁,会有什么结果呢?
6.3.4select
(1)select函数说明
前面的fcntl函数解决了文件的共享问题,接下来该处理I/O复用的情况了。
总的来说,I/O处理的模型有5种。
∙阻塞I/O模型:
在这种模型下,若所调用的I/O函数没有完成相关的功能就会使进程
挂起,直到相关数据到才会出错返回。
如常见对管道设备、终端设备和网络设备进行读写时
经常会出现这种情况。
∙非阻塞模型:
在这种模型下,当请求的I/O操作不能完成时,则不让进程睡眠,
而且返回一个错误。
非阻塞I/O使用户可以调用不会永远阻塞的I/O操作,如open、write
和read。
如果该操作不能完成,则会立即出错返回,且表示该I/O如果该操作继续执行
就会阻塞。
∙I/O多路转接模型:
在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,
而是让其中的一个函数等待,在这期间,I/O还能进行其他操作。
如本节要介绍的select函数
和poll函数,就是属于这种模型。
∙信号驱动I/O模型:
在这种模型下,通过安装一个信号处理程序,系统可以自动
捕获特定信号的到来,从而启动I/O。
这是由内核通知用户何时可以启动一个I/O操作
决定的。
∙异步I/O模型:
在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通
知内核。
现在,并不是所有的系统都支持这种模型。
可以看到,select的I/O多路转接模型是处理I/O复用的一个高效的方法。
它可以具体设
置每一个所关心的文件描述符的条件、希望等待的时间等,从select函数返回时,内核会通
知用户已准备好的文件描述符的数量、已准备好的条件等。
通过使用select返回值,就可以
调用相应的I/O处理函数了。
(2)select函数格式
Select函数的语法格式如表6.8所示。
表6.8fcntl函数语法要点
所需头文件
#include
#include
《嵌入式Linux应用程序开发详解》——第6章、文件IO编程
#include
函数原型intselect(intnumfds,fd_set*readfds,fd_set*writefds,fd_set*exeptfds,structtimeval
*timeout)
numfds:
需要检查的号码最高的文件描述符加1
readfds:
由select()监视的读文件描述符集合
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 缓存 文件 IO 操作