第6章文件IO编程Word文档下载推荐.docx
《第6章文件IO编程Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《第6章文件IO编程Word文档下载推荐.docx(97页珍藏版)》请在冰豆网上搜索。
但并不是所有的函数都一一对应一个系统调用,有时,一个API函数会需要几个系统调用来共同完成函数的功能,甚至还有一些API函数不需要调用相应的系统调用(因此它所完成的不是内核提供的服务)。
在Linux中,用户编程接口(API)遵循了在UNIX中最流行的应用编程界面标准——POSIX标准。
POSIX标准是由IEEE和ISO/IEC共同开发的标准系统。
该标准基于当时现有的UNIX实践和经验,描述了操作系统的系统调用编程接口(实际上就是API),用于保证应用程序可以在源代码一级上在多种操作系统上移植运行。
这些系统调用编程接口主要是通过C库(libc)实现的。
6.1.3系统命令
以上讲解了系统调用、用户编程接口(API)的概念,分析了它们之间的相互关系,那么,读者在第2章中学到的那么多的Shell系统命令与它们之间又是怎样的关系呢?
系统命令相对API更高了一层,它实际上是一个可执行程序,它的内部引用了用户编程接口(API)来实现相应的功能。
它们之间
的关系如图6.1所示。
6.2Linux中文件及文件描述符概述
图6.1系统调用、A
系统命令之间的关
在Linux中对目录和设备的操作都等同于文件的操作,因此,大大简化了系统对
不同设备的处理,提高了效率。
Linux中的文件主要分为4种:
普通文件、目录文件、
链接文件和设备文件。
那么,内核如何区分和引用特定的文件呢?
这里用到了一个重要的概念——文件描述符。
对于Linux而言,所有对设备和文件的操作都是使用文件描述符来进行的。
文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。
当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;
当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
通常,一个进程启动时,都会打开3个文件:
标准输入、标准输出和标准出错处理。
这3个文件分别对应文件描述符为0、1和2(也就是宏替换STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,鼓励读者使用这些宏替换)。
基于文件描述符的I/O操作虽然不能移植到类Linux以外的系统上去(如Windows),但它往往是实现某些I/O操作的惟一途径,如Linux中低级文件操作函数、多路I/O、TCP/IP套接字编程接口等。
同时,它们也很好地兼容POSIX标准,因此,可以很方便地移植到任何POSIX平台上。
基于文件描述符的I/O操作是Linux中最常用的操作之一,希望读者能够很好地掌握。
6.3底层文件I/O操作
本节主要介绍文件I/O操作的系统调用,主要用到5个函数:
open()、read()、write()、lseek()和close()。
这些函数的特点是不带缓存,直接对文件(包括设备)进行读写操作。
这些函数虽然不是ANSIC的组成部分,但是是POSIX的组成部分。
6.3.1基本文件操作
(1)函数说明。
open()函数用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
close()函数用于关闭一个被打开的文件。
当一个进程终止时,所有被它打开的文
件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件。
read()函数用于将从指定的文件描述符中读出的数据放到缓存区中,并返回实际读入的字节数。
若返回0,则表示没有数据可读,即已达到文件尾。
读操作从文件的当前指针位置开始。
当从终端设备文件中读出数据时,通常一次最多读一行。
write()函数用于向打开的文件写数据,写操作从文件的当前指针位置开始。
对磁盘文件进行写操作,若磁盘已满或超出该文件的长度,则write()函数返回失败。
lseek()函数用于在指定的文件描述符中将文件指针定位到相应的位置。
它只能用在可定位(可随机访问)文件操作中。
管道、套接字和大部分字符设备文件是不可定位的,所以在这些文件的操作中无法使用lseek()调用。
(2)函数格式。
open()函数的语法格式如表6.1所示。
表6.1open()函数语法要点
所需头文件
#include<
sys/types.h>
/*提供类型pid_t的定义*/
sys/stat.h>
fcntl.h>
函数原型
intopen(constchar*pathname,intflags,intperms)
函数传入值
pathname
被打开的文件名(可包括路径名)
flag:
文件打开的方式
O_RDONLY:
以只读方式打开文件
O_WRONLY:
以只写方式打开文件
O_RDWR:
以读写方式打开文件
O_CREAT:
如果该文件不存在,就创建一个新的文件,并用第
三个参数为其设置权限
O_EXCL:
如果使用O_CREAT时文件存在,则可返回错误消息。
这一参数可测试文件是否存在。
此时open是原子操作,防止多个进程同时创建同一个文件
O_NOCTTY:
使用本参数时,若文件为终端,那么该终端不会
成为调用open()的那个进程的控制终端
O_TRUNC:
若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为0。
O_APPEND:
以添加方式打开文件,在打开文件的同时,文件
指针指向文件的末尾,即将写入的数据添加到文件的末尾
perms
被打开文件的存取权限
可以用一组宏定义:
S_I(R/W/X)(USR/GRP/OTH)
其中R/W/X分别表示读/写/执行权限
USR/GRP/OTH分别表示文件所有者/文件所属组/其他用户
例如,S_IRUSR|S_IWUSR表示设置文件所有者的可读可写属性。
八进制表示法中600也表示同样的权限
函数返回值
成功:
返回文件描述符失败:
1
在open()函数中,flag参数可通过“|”组合构成,但前3个标志常量(O_RDONLY、
O_WRONLY以及O_RDWR)不能相互组合。
perms是文件的存取权限,既可以用宏
定义表示法,也可以用八进制表示法。
close()函数的语法格式表6.2所示。
表6.2close()函数语法要点
所需头文件#include<
unistd.h>
函数原型intclose(intfd)函数输入值fd:
文件描述符
函数返回值0:
成功
1:
出错
read()函数的语法格式如表6.3所示。
表6.3read()函数语法要点
所需头文件#include<
函数原型ssize_tread(intfd,void*buf,size_tcount)
fd:
buf:
指定存储器读出数据的缓冲区count:
指定读出的字节数成功:
读到的字节数
0:
已到达文件尾
在读普通文件时,若读到要求的字节数之前已到达文件的尾部,则返回的字节数
会小于希望读出的字节数。
write()函数的语法格式如表6.4所示。
表6.4write()函数语法要点
ssize_twrite(intfd,void*buf,size_tcount)
指定存储器写入数据的缓冲区
count:
指定读出的字节数
已写的字节数
在写普通文件时,写操作从文件的当前指针位置开始。
lseek()函数的语法格式如表6.5所示。
表6.5lseek()函数语法要点
off_tlseek(intfd,off_toffset,intwhence)
offset:
偏移量,每一读写操作所需要移动的距离,单位是字节,可正可负
(向前移,向后移)
whence:
当前位置的基点
SEEK_SET:
当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:
当前位置为文件指针的位置,新位置为当前位置加上偏移量
SEEK_END:
当前位置为文件的结尾,新位置为文件的大小
加上偏移量的大小
文件的当前位移
(3)函数使用实例。
下面实例中的open()函数带有3个flag参数:
O_CREAT、O_TRUNC和O_WRONLY,这样就可以对不同的情况指定相应的处理方法。
另外,这里对该文件的权限设置为0600。
其源码如下所示:
下面列出文件基本操作的实例,基本功能是从一个文件(源文件)中读取最后
10KB数据并到另一个文件(目标文件)。
在实例中源文件是以只读方式打开,目标文件是以只写方式打开(可以是读写方式)。
若目标文件不存在,可以创建并设置权限的初始值为644,即文件所有者可读可写,文件所属组和其他用户只能读。
读者需要留意的地方是改变每次读写的缓存大小(实例中为1KB)会怎样影响运行效率。
/*copy_file.c*/
stdlib.h>
stdio.h>
#defineBUFFER_SIZE1024/*每次读写缓存大小,影响运行效率*/
#defineSRC_FILE_NAME"
src_file"
/*源文件名*/
#defineDEST_FILE_NAME"
dest_file"
/*目标文件名文件名*/
#defineOFFSE10240/*复制的数据大小*/
intmain()
{
intsrc_file,dest_file;
unsignedcharbuff[BUFFER_SIZE];
intreal_read_len;
/*以只读方式打开源文件*/
src_file=open(SRC_FILE_NAME,O_RDONLY);
/*以只写方式打开目标文件,若此文件不存在则创建该文件,访问权限值为644*/
dest_file=open(DEST_FILE_NAME,O_WRONLY|O_CREAT,
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if(src_file<
0||dest_file<
0)
printf("
Openfileerror\n"
);
exit
(1);
}
/*将源文件的读写指针移到最后10KB的起始位置*/
lseek(src_file,-OFFSET,SEEK_END);
/*读取源文件的最后10KB数据并写到目标文件中,每次读写1KB*/
while((real_read_len=read(src_file,buff,sizeof(buff)))>
write(dest_file,buff,real_read_len);
close(dest_file);
close(src_file);
return0;
$./copy_file
$ls-lhdest_file
-rw-r--r--1davidroot10K14:
06dest_file
open()函数返回的文件描述符一定是最小的未用文件描述符。
由于一个进程在
启动时自动打开了0、1、2三个文件描述符,因此,该文件运行结果中返回的注意文件描述符为3。
读者可以尝试在调用open()函数之前,加一句close(0),则此后在调用open()函数时返回的文件描述符为0(若关闭文件描述符1,则在程
序执行时会由于没有标准输出文件而无法输出)。
6.3.2文件锁
(1)fcntl()函数说明。
前面的这5个基本函数实现了文件的打开、读写等基本操作,本小节将讨论的是,在文件已经共享的情况下如何操作,也就是当多个用户共同使用、操作一个文件的情况,这时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
文件锁包括建议性锁和强制性锁。
建议性锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。
在一般情况下,内核和系统都不使用建议性锁。
强制性锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。
采用强制性锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在Linux中,实现文件上锁的函数有lockf()和fcntl(),其中lockf()用于对文件施加建议性锁,而fcntl()不仅可以施加建议性锁,还可以施加强制锁。
同时,fcntl()还能对文件的某一记录上锁,也就是记录锁。
记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。
而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。
当然,在文件的同一部分不能同
时建立读取锁和写入锁。
fcntl()是一个非常通用的函数,它可以对已打开的文件描述符进行各种操作,
注意不仅包括管理文件锁,还包括获得和设置文件描述符和文件描述符标志、文件描述符的复制等很多功能。
在本节中,主要介绍建立记录锁的方法。
(2)fcntl()函数格式。
用于建立记录锁的fcntl()函数格式如表6.6所示。
表6.6fcntl()函数语法要点
函数原型intfcnt1(intfd,intcmd,structflock*lock)
函数传入值fd:
cmd
F_DUPFD:
复制文件描述符
F_GETFD:
获得fd的close-on-exec标志,若标志未设置,则文件经过exec()
函数之后仍保持打开状态
F_SETFD:
设置close-on-exec标志,该标志由参数arg的FD_CLOEXEC
位决定
F_GETFL:
得到open设置的标志
F_SETFL:
改变open设置的标志
F_GETLK:
根据lock参数值,决定是否上文件锁
F_SETLK:
设置lock参数值的文件锁
F_SETLKW:
这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。
在无法获取锁时,会进入睡眠状态;
如果可以获取锁或者捕捉到信号则会
返回
lock:
结构为flock,设置记录锁的具体状态
这里,lock的结构如下所示:
structflock
shortl_type;
off_tl_start;
shortl_whence;
off_tl_len;
pid_tl_pid;
lock结构中每个变量的取值含义如表6.7所示。
表6.7lock结构变量取值
l_type
F_RDLCK:
读取锁(共享锁)
F_WRLCK:
写入锁(排斥锁)
F_UNLCK:
解锁
l_stat
相对位移量(字节)
l_whence:
相对位移量的起点(同lseek的whence)
当前位置为文件指针的位置,新位置为当前位置加上偏移量
当前位置为文件的结尾,新位置为文件的大小加上偏移量的大
小
l_len
加锁区域的长度
小技巧为加锁整个文件,通常的方法是将l_start设置为0,l_whence设置为
SEEK_SET,l_len设置为0。
(3)fcntl()使用实例
下面首先给出了使用fcntl()函数的文件记录锁功能的代码实现。
在该代码中,首先给flock结构体的对应位赋予相应的值。
接着使用两次fcntl()函数,分别用于判断文件是否可以上锁和给相关文件上锁,这里用到的cmd值分别为F_GETLK和F_SETLK
(或F_SETLKW)。
用F_GETLK命令判断是否可以进行flock结构所描述的锁操作:
若可以进行,则flock结构的l_type会被设置为F_UNLCK,其他域不变;
若不可行,则l_pid被设置为拥有文件锁的进程号,其他域不变。
用F_SETLK和F_SETLKW命令设置flock结构所描述的锁操作,后者是前者的
阻塞版。
文件记录锁功能的源代码如下所示:
/*lock_set.c*/
intlock_set(intfd,inttype)
structflockold_lock,lock;
lock.l_whence=SEEK_SET;
lock.l_start=0;
lock.l_len=0;
lock.l_type=type;
lock.l_pid=-1;
/*判断文件是否可以上锁*/
fcntl(fd,F_GETLK,&
lock);
if(lock.l_type!
=F_UNLCK)
/*判断文件不能上锁的原因*/
if(lock.l_type==F_RDLCK)/*该文件已有读取锁*/
Readlockalreadysetby%d\n"
lock.l_pid);
elseif(lock.l_type==F_WRLCK)/*该文件已有写入锁*/
Writelockalreadysetby%d\n"
/*l_type可能已被F_GETLK修改过*/
/*根据不同的type值进行阻塞式上锁或解锁*/
if((fcntl(fd,F_SETLKW,&
lock))<
Lockfailed:
type=%d\n"
lock.l_type);
return