APUE学习文件IOWord格式文档下载.docx
《APUE学习文件IOWord格式文档下载.docx》由会员分享,可在线阅读,更多相关《APUE学习文件IOWord格式文档下载.docx(12页珍藏版)》请在冰豆网上搜索。
这里要说下最后一个方法,当进程结束时会自动关闭文件的,但当你调用close
(1)关闭时是不刷新缓冲区的。
我们再来看下fseek()这个函数(和它相关的还有ftell()rewind()),功能就是重新定位文件的读写位置指针,如果参照<
中FILE类型定义,很容易明白这些函数改变的是哪些变量。
我们要说的是这个函数中偏移量这个参数的类型是long,long类型的变量大小是4,也就是说这个偏移量的范围:
-2^31~2^31即(-2G,2G)这样的区间,那好如果一个文件的大小大于这样的空间我们就不能操作了(rh6的镜像就近3G,超清的电影就更别提了)。
解决的办法是fseek()的一个替代函数fseeko()
这个函数与fseek()的区别仅在于偏移量这个参数的类型是off_t。
我们可以找一下这个类型的定义,在<
sys/types.h>
中有这么一段:
#ifndef__USE_FILE_OFFSET64
typedef__off_toff_t;
#else
typedef__off64_toff_t;
#endif
这一段条件编译的含义就是,如果__USE_FILE_OFFSET64定义了那么off_t的类型就是64位的否则就是32位的。
那好,我们在找__USE_FILE_OFFSET64这个宏,在<
features.h>
#ifdefined_FILE_OFFSET_BITS&
&
_FILE_OFFSET_BITS==64
#define__USE_FILE_OFFSET641
到这里我们就明白了,如果说_FILE_OFFSET_BITS这个宏的值为64,那off_t的长度就是64位,否则为32位,那好我们就可以利用这个宏使用fseeko()来操作长度大于2G的文件了,方法有两个:
1.编译时加上宏定义参数,如gcc-D_FILE_OFFSET_BITS=64-oexetest.c
2.在头文件包含前定义这个宏定义:
#define_FILE_OFFSET_BITS64
偏移量的长度为64位,范围是(-2^63,2^63)区间,目前的文件貌似没有超过这个范围的(2^63为8MT)
操作FILE*的标准库函数中,还有一些格式化读写文件(如fprintf()fscanf())、格式化读写字符串(sprintf()snprintf()sscanf())以及像getline()getdelim()tmpfile()这样非常方便的函数。
下一篇,我们再来阐述下系统调用的I/O操作
APUE学习--文件I/O
(2)
Unix环境编程2013-05-2115:
58
21人阅读
接下来,我们就来说说系统调用中的I/O操作
首先,打开文件是open()函数,这是一个变参函数(变参函数在后面找个机会仔细说说),基本参数为一个文件名字符串和一个整型的方式选项,可增加的参数为一个mode_t类型的权限参数。
文件名字符串支持相对路径(getcwd()可以获得当前进程的工作目录)和绝对路径;
方式选项是一个位图的参数(即整型数中每一位或几位表示一个含义),位图参数的最大好处是可以通过一个参数传递很多种的方式,如O_RDWRO_CREATO_TRUNC这三个宏的含义分别是读写方式、不存在创建、存在截断为0,我们可以传递某一个,也可以将这三个组合进行传递(位运算中的或运算有置1的功能)O_RDWR|O_CREAT|O_TRUNC。
我列举了一些fopen()中的方式与open()传递的方式的对应:
"
r"
-->
O_RDONLY
r+"
O_RDWR
“w”-->
O_WRONLY|O_CREAT|O_TRUNC"
w+"
O_RDWR|O_CREAT|O_TRUNC
a"
O_WRONLY|O_APPEND"
a+"
O_RDWR|O_APPEND
这里还需要说一下O_EXCL这个宏的含义,它通常是和O_CREAT一起使用的,功能是如果文件存在则打开失败(设置errno为EEXIST),这样做能确保文件时被创建的而非打开的。
当方式选项中使用O_CREAT后,可增加的权限参数,是一个三位8进制,如0666,第一个0的含义是这是一个8进制的常量,6的含义是读写权限(6的二进制为110,三位分别表示读写执行,所以6是读写),三位8进制分别对应的是对于所属用户的权限,所属组的权限,其他用户的权限。
如果我们通过这样的一个权限值创建了一个文件,我们会发现,这个文件的权限并不是我们所期望的rw-rw-rw-而是rw-r--r--,造成这种情况的原因是文件创建掩码,这个掩码将把创建的文件的权限强制去掉一部分,一般而言是022,所以会去掉后两个的写权限,通过umask
(2)这个系统调用在程序中更改掩码,也可以在终端中使用umask
(1)来更改掩码。
参数说完后还要重点说下open()函数的返回值,返回值是文件描述符,是一个整型数,这个整型数是从0开始分配的,每次分配的都是最小的未被占用的。
这个描述符应该怎样去理解呢?
内核为每个进程维护一张进程的文件表项,这个表可以看做是一个结构体数组,结构体中维护着每个打开的文件的各种信息,而文件描述符就可以理解为被打开的文件在这个数组表中的下标值。
当我们操作某个文件时传入该文件的描述符,内核就可以在该进程的文件表中找到那个被开的文件的各种信息。
之前说过,FILE等于文件描述符+缓冲区,也就是说系统调用的I/O操作是没有缓冲区的,有缓冲好还是无缓冲好,需要看实际需求,无缓冲区可以减少操作增加实时性,而缓冲区可以提高处理I/O大量数据时的吞吐量。
文件描述符和FILE*之间可以通过fdopen()fileno()两个函数实现互相转换。
如果一个open()打开的文件,通过fdopen()获得FILE*并设置缓冲区后,即使使用write()也不会有缓冲,这是因为,系统调用的I/O操作无论是打开还是读写都是无缓冲的操作(有无缓冲不在于打开的函数而在于读写操作的函数)。
与open()对应的关闭文件的函数是close(),注意对于一个文件而言,关闭一次就可以了(无论是close还是fclose()),close()和fclose()的一个区别是fclose()会刷新缓冲区(前提是得有缓冲区),而close()不会。
系统调用对普通文本文件的读写函数不像C库中那么多,基本上使用的是read()/write()(当然也有pread()/pwrite()send()/recv()sendto()/recvfrom()等等)。
这两个函数的原型为:
ssize_t
read(intfd,void*buf,size_tcount);
ssize_twrite(intfd,constvoid*buf,size_tcount);
从原型上看这两个函数的参数几乎是一样的,唯一的区别是write的buf带一个const而read不带,cosnt表示该指针指向的空间不可变,可以确定write()的第二个参数是一个入参,而read()的第二个参数为传参,事实上确实是这样,write()要讲数组中的内容写到文件中去是入参,而read()要讲文件中的内容读到数组是出参。
第一个参数是描述符不用说了,第二个参数是数组地址刚刚说过,第三个参数是一个整型的大小,size_t类型是unsignedint,表示的是最多写/读count个字节(注意单位是字节)。
函数的返回值类型是ssize_t,为signedsIze_t即有符号的int,表示的是成功读/写的字节数,至于为什么要用有符号的,这里是因为考虑到read()/write()出错的情况返回-1。
系统调用的定位函数为lseek(),它相当于C库中的fseeko()+ftello(),参数和fseeko()类似(FILE*变为文件描述符),返回值为相对于文件开头的偏移量。
需要注意的是偏移量参数的类型也是off_t。
简单提一下pread()/pwrite(),相当于lseek()+read()/write(),但这两个函数是一个原子操作(原子操作不会os的进程调度机制所打断)
系统调用中文件I/O的重定向通过dup2()实现(C库中用freopen()实现),介绍dup2()之前介绍dup()这个函数。
dup()这个函数的功能是复制一个I文件描述符,要强调的是,复制的并不是进程再打开一次文件,而是之前提到过的内核中维护的进程文件表中的一个表项,相当于多了一条途径来访问这个文件。
还能得出的结论是复制的描述符和原描述符共享读写位置,调用close()相当于断开一个访问打开的文件的途径仅当所有对应文件的所有描述符全部close()后文件才被关闭。
具体的对应关系如下:
dup2()可以实现重定向的原理是,dup2()可以指定复制后的描述符值,如果指定的值已经被占用了则先关闭再复制。
比如我们现在有一个描述符fd,现在调用dup2(fd,1);
得到的操作就是先关闭1,再复制fd且指定值为1,这样当我们调用printf()时输出的数据就传输到fd文件中了,这样就相当于讲标准输出重定向到文件中。
我们再额外介绍fcntl()函数和structfile_operations结构
fcntl()用来管理一个文件描述符,可以通过该函数对文件描述符做一些除读写以外的操作,如实现复制描述符的非原子操作(F_DUPFD),更改文件的打开方式(F_GETFLF_SETFL),获得/设置执行关闭位(F_GETFDF_SETFD执行关闭位在后面的文章中详说)、文件锁的操作(F_GETLKF_SETLKF_SETLKW)等等
structfile_operations是内核中的一个结构体,在内核代码的include/linux/fs.h中(大多数的开源linux系统中安装时可以选择是否安装内核源码,rh中是在/usr/src/kernels/2.6..../中),该结构体中定义了大量的函数指针,仔细观察这些函数指针,都是一些我们所用到的一些I/O函数的类型。
在Unix中一切皆文件,对于我们程序员也是如此,我们可以使用文件操作函数操作任何一个设备文件,那么针对不同的设备文件,我们要执行的read()/write()等函数在内核中做的实际操作肯定是不同的,在我们编写一些简单的内核设备驱动时,将这些函数指针赋上我们赋予该设备的操作就能实现一个简单驱动的编写。
APUE学习--文件I/O(3)
Unix环境编程2013-05-2211:
06
40人阅读
编程APUELinux文件
围绕着“ls-l”这个命令,功能是显示文件的详细信息
获取文件详细信息的系统调用是:
stat()lstat()fstat()一组函数,stat()和lstat()的区别是对于符号链接的处理,而与fstat()的区别是指定文件的参数是描述符还是路径
获取文件详细信息是通过第二个参数获得,传递一个structstat结构的地址,这是一个出参。
[cpp]
viewplaincopy
1.int
stat(const
char
*path,
struct
stat
*buf);
2.int
fstat(int
filedes,
3.int
lstat(const
1.struct
{
2.
dev_t
st_dev;
/*
ID
of
device
containing
file
文件系统设备号*/
3.
ino_t
st_ino;
inode
number
索引节点号
*/
4.
mode_t
st_mode;
protection
5.
nlink_t
st_nlink;
hard
links
硬链接数
6.
uid_t
st_uid;
user
owner
7.
gid_t
st_gid;
group
8.
st_rdev;
(if
special
file)
硬件设备设备号
9.
off_t
st_size;
total
size,
in
bytes
文件的大小
10.
blksize_t
st_blksize;
blocksize
for
filesystem
I/O
文件比较适合的块大小
11.
blkcnt_t
st_blocks;
blocks
allocated
以一块为512的块数
12.
time_t
st_atime;
time
last
access
最后一次访问时间*/
13.
st_mtime;
modification
最后一次修改文件内容时间
14.
st_ctime;
status
change
最后一次修改文件状态时间
15.};
这里的成员大部分都比较好理解,挑出几个来详细说明一下
什么是索引节点号(inode)?
linux文件系统中索引节点类似于windows的文件分配表,文件系统中拥有一个索引节点数组,每个索引节点对应着一个文件或目录,索引节点号就是数组的下标,每个索引节点中维护着文件的长度、权限、所属关系等等。
简单来说,文件系统是使用索引节点来管理硬盘中的所有文件的。
什么是硬链接?
一般我们直接称为链接,刚刚说到一个文件对应一个索引节点,但是一个文件名(路径)与索引节点的对应并不一定是一对一的,多个文件名(路径)可以对应同一个索引节点(即硬盘中的一个文件)。
也就是说一个文件可以有多个文件名,文件名的个数即是链接数。
创建链接的方法,可以用命令link
(1),可以用系统调用link
(2)
和硬链接对应的是软链接,也称为符号链接,对应windows系统中快捷方式,注意,并非是一个文件的另一个名字,符号链接本身是另一个文件。
st_blksize和st_blocks这两个跟块有关的成员应该怎样了理解?
首先,st_blksize是进行I/O操作的块的大小,在linux系统中为4K(页大小),而st_blocks是以512B为单位的块数。
这里要强调的一点是,给一个文件分配硬盘空间,是按I/O块为单位分配的,所以一次要至少分配4K,即st_blocks为8,但st_size是实际的大小。
st_mtime和st_ctime的区别是,mtime表示的是文件内容的修改,ctime表示的是文件状态的改变,状态改变包括内容的改变还包括诸如上面的结构中的一些属性的改变。
我们要详细说明的是st_mode这个成员,这个成员是一个位图,每一位表示了不同的信息,这些信息包含文件的类型和权限(这样做的好处是节省空间)。
获取文件类型的方法很简单,使用下面的这些宏并把st_mode传递过去即可:
1.S_ISREG(m)
is
it
a
regular
file?
2.S_ISDIR(m)
directory?
3.S_ISCHR(m)
character
device?
4.S_ISBLK(m)
block
5.S_ISFIFO(m)
FIFO
(named
pipe)?
6.S_ISLNK(m)
symbolic
link?
(Not
POSIX.1-1996.)
7.S_ISSOCK(m)
socket?
如果为真,则表示是对应的文件类型,我们主要来看下这些宏定义的原理,在<
sys/stat.h>
中可以找到这些宏定义:
现在有一个structstat的变量statres,如果这样调用S_ISREG(statres.st_mode),讲宏定义展开后得到的结果是(statres.st_mode&
00170000)==0100000
来分析一下,&
运算可以取出某些位来,&
00170000应该是将statres.st_mode的13~16位取出来其余位全部清零,再比较这几位和哪个标志相等来确定是什么类型的文件。
再来分析下确定文件权限的方法,<
linux/stat.h>
中有如下的宏定义:
1.#define
S_IRWXU
00700
2.#define
S_IRUSR
00400
3.#define
S_IWUSR
00200
4.#define
S_IXUSR
00100
5.#define
S_IRWXG
00070
6.#define
S_IRGRP
00040
7.#define
S_IWGRP
00020
8.#define
S_IXGRP
00010
9.#define
S_IRWXO
00007
10.#define
S_IROTH
00004
11.#define
S_IWOTH
00002
12.#define
S_IXOTH
00001
四个一组,共三组,分别对应着的是所属用户,所属组,其他用户的权限,操作的方法和获取文件类型类似(但没有现成的宏定义),我们只拿对于所属用户来说明。
S_IRWXU的功能和S_IFMT类似,起到掩码的作用,statres.st_mode&
S_IRWXU就能去除第7~9位,再和S_IRUSRS_IWUSRS_IXUSR比较来确定权限值。
到目前为止,我们能够获得"
ls-l"
中文件的类型、权限、链接数、大小。
接下来我们解决时间的问题,虽然structstat结构中有了三种时间,但这个时间是时间戳,即1970年1月1日0时0分0秒到指定的时间点的秒数,这里我们介绍一组函数来来进行转换。
1.time_t
time(time_t
*t);
//获取当前时间戳
2.struct
tm*
gmtime(const
*timep);
//将时间戳转换为UTC时间
3.struct
localtime(const
//讲时间戳转换为本地时间
4.time_t
mktime(struct
tm
*tm);
//将struct
tm结构还原为时间戳
5.char
*asctime(const
tm*tm);
tm表示的时间转换为字符串
6.char
*ctime(const
7.size_t
strftime(char
*s,
size_t
max,
const
*format,
tm表示的时间转换指定格式的字符串
时间的操作基本上是这样的过程:
时间戳-->
structtms时间结构-->
时间字符串。
如果是显示当前时间,就使用time()这个函数,如果处理文件的时间久使用structstat中的时间成员,转换为structtm结构的函数是gmtime()localtime(),structtm结构的定义如下(<
time.h>
):
2.{
int
tm_sec;
seconds