UNIX环境高级编程--文件及标准I/O(阅读笔记)(原创)
由 王宇 原创并发布 :
第3章文件I/O
3.1引言
文件I/O函数--打开文件、读文件、写文件等。UNIX系统中的大多数文件I/O只需用到5个函数:open、read、write、lseek以及close。
术语不带缓冲指的是每个read和write都调用内核中的一个系统调用。 这些不带缓冲的I/O函数不是ISO C的组成部分,但是,它们是POSIX.1和SingleUNIXSpecification的组成部分
本章将进一步讨论在多个进程间如何共享文件,以及所涉及的内核数据结构:dup、fcntl sysnc fsysnc ioctl
3.2文件描述
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读或写一个文件时,使用open或creat返回的文件描述符标识该文件,将其作为参数传递给read或write
按照惯例,UNIX系统shell使用文件描述符0(STDIN_FILENO )与进程的标准输入相关联,文件描述符1(STDOUT_FILENO )与标准输出相关联,文件描述符2(STDERR_FILENO)与标准出错输出相关联。这些常量定义在<unistd.h>中
文件描述符的变化范围0~OPEN_MAX
3.3 open函数 :打开或创建一个文件
函数声明:
参数:mode:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
O_EXCL:如果同时指定了O_CREAT,而文件已经存在,则会出错。用此可以测试一个文件是否存在,如果不存在,则创建文件,这使测试和创建两者成为一个原子操作
O_TRUNC:如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0
O_NOCTTY:如果pathname指的是终端设备,则不将该设备分配作为此进程的控制终端
O_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置为非阻塞模式
O_DSYNC:使每次write等待物理I/O操作完成,但是如果写操作并不影响读取刚写入的数据,则不等待文件属性被更新
O_RSYSC:使每一个以文件描述符作为参数的read操作等待,直至任何对文件同一部分进行的未决写操作都完成
O_SYNC:使每次write等待物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O.(写时,同步更新文件属性)
返回值:返回的文件描述符一定是最小的未用描述符数值 ,否则返回-1 。
3.4 create函数 :创建一个新文件
函数声明:
参数:同open的参数等效
不足之处以只写方式打开所创建的文件。
返回值:
3.5 close函数 :关闭一个打开的文件
函数声明:
3.6 lseek函数
每个打开的文件都有一个与其相关联的“当前文件偏移量”(currentfileoffset).它通常是一个非负整数,用以度量从文件开始处计算的字节数
按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0
函数声明:
3.7 read函数 :从打开文件中读数据
函数声明:
参数:
返回值:如read成功,则返回读到的字节数,如已到达文件结尾,则返回0
3.8 write函数 :向打开的文件写数据
函数声明:
参数:
返回值:其返回值通常与参数n bytes的值相同,否则表示出错。
3.9 I/O的效率
程序清单:3-3
它从标准输入读,写至标准输出,这就假定在执行本程序之前,这些标准输入、输出已经由shell安排好
很多应用程序假定标准输入是文件描述符0(STDIN_FILENO),标准输出是文件描述符1(STDOUT_FILENO)
进程终止时,UNIX系统内核会关闭该进程的所有打开的文件描述符,所以本程序没有关闭输入和输出
对UNIX系统内核而言,文本文件和二进制文件并无区别
**如何选取BUFFSIZE值 ,表3-2
此测试所用的文件系统是Linux ext2文件系统,其块长为4096字节 ,系统CPU时间的最小值出现在BUFFSIZE为4096
3.10 文件共享
UNIX系统支持在不同进程间共享打开的文件
内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
图3-1
图3-2
参考《深入理解linux内核》文件
3.11 原子操作
[1]添写至一个文件
问题出在逻辑操作“定位到文件尾端处,然后写”上,它使用了两个分开的函数调用。解决问题的方法是使这两个操作对于其他进程而言成为一个原子操作。
UNIX系统提供了一种方法使这种操作成为原子操作,该方法是在打开文件时设置O_APPEND标志。正如前一节中所述,这就使内核每次对这种文件进行写之前,都将进程的当前偏移量设置到该文件的尾端处,于是在每次写之前就不再需要调用lseek
[2]pread和pwrite函数
Single UNIX Specification包含了XSI扩展,该扩展允许原子性定位搜索(seek)和执行I/O
pread和pwrite函数格式:
[3]创建一个文件
一般而言,原子操作(atomicoperation)指的是由多步组成的操作,如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
3.12 dup和dup2函 数:用来复制一个现存的文件描述符
函数定义
图3-3**
复制文件描述符可以用作重定向。复制一个文件描述符,具有两个,关闭一个,另一个不被关闭。复制与打开同一个文件,获得文件描述符的区别是,复制具有同一个文件表,而打开具有不同的文件表
3.13 sync、fsync和fdatasync函数
当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式被称为延迟写
为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync、fdatasync 三个函数
函数定义:
sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束
fsync函数只对文件描述符filesets指定的单一文件起作用,并且等待写磁盘结束,然后返回。
fdatasync函数类似于fsync函数,但它只影响文件的数据部分,而除数据外,fsync还会同步更新文件的属性
3.14 fcntl函数:改变已打开文件的性质
函数定义:
fcntl有5种功能:
复制一个现有的描述符:cmd=F_DUPFD
获得、设置文件描述符:cmd=F_GETFDF_SETFD
获得、设置文件状态标志:cmd=F_GETFLF_SETFL
获得、设置异步I/O所有权:cmd=F_GETOWNF_SETOWN
获得、设置记录锁:cmd=F_GETLKF_SETLKF_SETLKW
程序清单:3-43-5
3.15 ioctl函数
函数定义:
3.16 /dev/fd
较新的系统都提供名为/dev/fd的目录,其目录项是名为0,1,2等的文件,打开文件/dev/fd/n等效于复制描述符n
fd=open("/dev/fd/0",mode);等效fd=dup(0);
第4章文件和目录
4.1引言
4.2stat、fstat和lstat函数
函数定义:
stat函数 就返回与此命名文件有关的信息结构
fstat函数 获取已在描述符filedes上打开文件的有关信息
lstat函数 与stat类似,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息
struct stat结构体
4.3文件类型
普通文件
目录文件
块特殊文件
字符特殊文件
FIFO
套接字
符号链接
4.4设置用户ID和设置组ID
与一个进程相关联的ID有6个或更多,表4-4**
实际用户ID和实际组ID标识我们究竟是谁
有效用户ID,有效阻ID以及附加组ID决定了我们的文件访问权限
保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效阻ID的副本
通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID
每个文件都有一个所有者和组所有者,所有者由stat结构中的st_uid成员表示,组所有者则由st_gid成员表示
4.5文件访问权限
所有文件类型都有访问权限,很多人认为只有普通文件有访问权限,这是一种误解
每个文件有9个访问限位,可将它们分为三类,见表4-5
chmod(1)命令用于修改这9个权限位
使用方式汇总:
第一个规则是,我们用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限 。例如:打开文件:/usr/include/stdio.h需要对目录/、/usr和/usr/include具有执行权限
对于一个文件的读权限决定了我们是否能够打开该文件进行读操作
对于一个文件的写权限决定了我们是否能够打开该文件进行写操作
为了在open函数中对一个文件指定O_TRUNC标志,必须对该文件具有写权限
为了在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限
为了在一个目录中删除一个现有文件,必须对该目录具有写权限和执行权限
如果用6个exec函数中的任何一个执行某个文件,都必须对该文件具有执行权限。
进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试 :
(1)若进程的有效用户ID是0(超级用户),则允许访问
(2)若进程的有效用户ID等于文件的所有者ID,那么:若所有者适当的访问权限被设置,则允许访问,否则拒绝访问。
(3)若进程的有效组ID或进程的附加组ID之一等于文件的组ID,那么:若组适当的访问权限被设置,则允许访问,否则拒绝访问。
(4)若其他用户适当的访问权限位被设置,则允许访问,否则拒绝访问
4.6新文件和目录的所有权
新文件的用户ID设置为进程的有效用户ID
(1)新文件的组ID可以是进程的有效组ID
(2)新文件的组ID可以是它所在目录的组ID
4.7access函数
当用open函数打开一个文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。有时,进程也希望按其实际用户ID和实际组ID来测试其访问能力
access函数是按实际用户ID和实际组ID进行访问权限测试的
函数定义:表4-6
4.8umask函数: 为进程设置文件模式创建屏蔽字,并返回以前的值(屏蔽权限)
函数定义:
进程创建一个新文件或新目录时,就一定会使用文件模式创建屏蔽字
4.9chmod和fchmod函数:更改现有文件的访问权限
函数定义:
chmod函数在指定的文件上进程操作,而fchmod函数则对已打开的文件进行操作
为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限
4.10粘住位
4.11chown、fchown和lchown函数:更改文件的用户ID和组ID
函数定义:
4.12文件长度
stat结构成员st_size表示以字节为单位的文件长度。此字段只对普通文件、目录文件和符号链接有意义
大多数UNIX系统提供字段st_balsize和st_blocks.其中,第一个是对文件I/O较合适的块长度,第二个是所分配的实际512字节数量
文件中的空洞:是由所设置的偏移量超过文件尾端,并写了某些数据后造成的
4.13文件截短
需要在文件尾端处截去一些数据以缩短文件。
函数定义:
4.14文件系统
为了说明文件链接的概念,先要介绍UNIX文件系统的基本结构。同时,了解i节点和指向i节点的目录项之间的区别也是很有益的。
我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统。图4-1**
i节点是固定长度的记录项,它包含有关文件的大部分信息
图:4-2较详细的柱面组的i节点和数据块
节点号相同,不同文件名
图:4-3
4.15link、unlink、remove和rename
任何一个文件可以有多个目录项指向其i节点,创建一个指向现有文件的链接的方法是使用link函数
函数定义:
删除一个现有的目录项,可以调用unlink函数:
函数定义:
remove函数解除对一个文件或目录的链接
函数定义:
4.16符号链接
符号链接是指向一个文件的间接指针,它与上一节所述的硬链接有所不同,硬链接直接指向文件的i节点。引入符号链接的原因是为了避开硬链接的一些限制:
硬链接通常要求链接和文件位于同一文件系统中
只有超级用户才能创建指向目录的硬链接
对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中的另一个位置
4.17synlink和readlink函数: 创建和读取符号链接
函数定义:
4.18文件的时间
对每个文件保持有三个时间字段,表4-10
注意修改时间(st_time)和更改状态时间(st_ctime)之间的区别。修改时间是文件内容最后一次被修改的时间。更改状态时间是该文件的i节点最后一次被修改的时间
4.19utime函数: 一个文件的访问和修改时间可以用utime函数更改
函数定义:
4.20mkdir和rmdir函数: 创建和删除目录
函数定义:
4.21读目录
对某个目录具有访问权限的任一用户都可读该目录,但是,为了防止文件系统产生混乱,只有内核才能写目录
函数定义:
4.22chdir、fchdir和getcmd函数: 更改获得当前目录
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点
函数定义:
4.23设备特殊文件
st_dev和st_rdev这两个字段经常引起混淆,有关规则:
每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t.主设备号标识设备驱动程序,有时编码为与通信的外设板;此设备号标识特定的子设备
我们通常可以使用两个宏即major和minor来访问主、次设备号,大多数实现都定义了这两个宏
系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及其对应的i节点
只有字符特殊文件和块特殊文件才有st_rdev值,此值包含实际设备的设备号
4.24文件访问权限位小结
表4-12文件访问权限位小结
第5章标准I/O库
5.1引言
5.2流和FILE对象
所有I/O函数都是针对文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。而对于标准I/O库,它们的操作则是围绕流进行的。当用标准I/O库打开或创建一个文件时,我们已使一个流与一个文件相关联。
对于ASCII字符集,一个字符用一个字节表示.对于国际字符集,一个字符可用多个字节表示。标准I/O文件流可用于单字节或多字节("宽")字符集。流的定向决定了所读、字符集是单字节还是多字节。当一个流最初被创建时,它并没有定向。如若在未定向的数据流上使用一个多字节I/O函数,则将该流的定向设置为宽定向的。若在为定向的流上使用一个单字节I/O流函数,则将该流的定向设置为字节定向的。只有两个函数可以改变流的定向。freopen函数清除一个流的定向;fwide函数设置流的定向
函数定义:
当打开一个流时,标准I/O函数fopen返回一个指向FIFE对象 的指针。该对象通常是一个结构,它包含了标准I/O库为管理流所需要的所有信息。包括:用于实际I/O的文件描述符,指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等。
5.3标准输入、标准输出和标准出错
对一个进程预定义了三个流 ,并且这三个流可以自动地被进程使用,它们是:标准输入、标准输出和错误输出 。
文件描述符:STDIN_FILENO STDOUT_FILENO STDERR_FILENO
标准I/O流通过预定义文件指针stdinstdout和stderr加以引用<stdio.h>
5.4缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数
标准I/O提供了三个类型的缓冲 :
(1)全缓冲 :在填满标准I/O缓冲区后才进行实际I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区。
术语冲洗(flush):说明标准I/O缓冲区的写操作。缓冲区可由标准I/O例程自动冲洗,或者可以调用fflush冲洗一个流。
flush有两种意思:
在标准I/O库方面,flush意味着将缓冲区的内容写到磁盘上。
在终端驱动程序方面,flush表示丢弃已经存在缓冲区中的数据
(2)行缓冲 :当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。
行缓存的两个限制:
因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行I/O操作。
任何时候只要通过标准I/O库要求从一个不带缓冲区的流,或者一个行缓冲区的流得到输入数据,那么就会造成冲洗所有行缓冲区输出流。
(3)不带缓冲 :标准I/O库不对字符进行缓冲存储
标准出错流stderr通常是不带缓冲区的,这就使得出错信息可以尽快显示出来,而不管它们是否有一个换行符
IOS C要求缓冲特征:
当且仅当标准输入和标准输出并不涉及交互设备时,它们才是全缓冲的。
标准出错绝不会是全缓冲的
标准出错是不带缓冲的
如若是涉及终端设备的其他流,则它们是行缓冲的;否则是全缓冲的
两个更改缓冲区类型函数定义:
setbuf函数打开或关闭缓冲机制
setvbuf可以精确地制定缓冲区的类型,mode:
_IOFBF全缓冲
_IOLBF行缓冲
_IONBF不带缓冲
fflush:强制冲洗流
函数定义:
5.5打开流
函数定义:
fopen打开一个指定的文件
freopen在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该留。若该流已经定向,则freopen清楚该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出和标准出错
fdopen获取一个现有的文件描述符,并是一个标准的I/O流与该描述符相结合。此函数常用于创建管道和网络通信通道函数返回的描述符。
表5-2
表5-3
fclose函数定义:
当一个进程正常终止时,则所有带未写缓冲数据的标准I/O流都会被冲洗,所有打开的标准I/O流都会被关闭
5.6读和写流
一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择 ,对其进行读写操作:
(1)每次一个字符的I/ O,一次读和写一个字符,如果流是带缓冲的,则标准I/O函数会处理所有缓冲。
(2)每次一行的I/O ,如果想要一次读或写一行,则使用fgets和fputs。每行都以一个换行符结束。当调用fgets时,应说明能处理的最大行数
(3)直接I/O, fread和fwrite函数支持这种类型的I/O,每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中每次读或写一个结构。
输入函数
可用于一次读一个字符
函数定义:
函数getchar等价于getc(stdin)。前两个函数的区别是getc可被实现为宏,而fgetc不能实现为宏。这意味着:
(1)getc的参数不应道具有副作用的表达式
(2)因为fgetc()一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传递给另一个函数。
(3)调用fgetc所需的时间很可能长于调用getc,因为调用函数所需的时间长于调用宏。
这三个函数在返回下一个字符时,会将其unsigned char类型转换为int类型。说明为不带符号的理由是,如果最高位为1也不会使返回值为负。要求整数返回值的理由是,这样就可以返回所以可能的返回值再加上一个出错或已到达文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一个负值,其值经常是-1.
注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值,为了区分这两种不同的情况,必须调用ferror和feof
函数定义:
从流中读取数据以后,可以调用ungetc将字符再压回流中
函数定义:
压送回流中的字符以后又可以从流中读出,但读出字符的顺序与压送回的顺序相反。应当了解,虽然ISOc允许实现支持任何次数的回送,但它要求实现提供一次只送回一个字符。我们不能期望一次能送回多个字符。
输出函数
函数定义:
与输入函数一样,putchar(c)等效于putc(c,stdin),putc可以实现为宏,fputc不能实现为宏
5.7每次一行I/O
两个函数实现每次输入一行的功能:
函数定义:
这两个函数都制定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流中读。
对于fgets必须制定缓冲区的长度n。此函数一直读到下一个换行符为止,但是不超过n-1个字符,读入的字符被送到缓冲区。该缓冲区以null字符结尾。如若该行的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null结尾。对fgets的下一次调用会继续读入该行。
gets是一个不推荐使用的函数。其问题是调用者在使用gets时不能指定缓冲区的长度。这样就可能造成缓冲区溢出,写到缓冲区之后的存储空间中,从而产生不可预料的后果。
gets与fgets另一个区别是,gets并不将换行符存入缓冲区中。
即使IOSC要求实现提供gets,但请使用fgets,而不要使用gets。
fputs和puts提供每次输入一行的功能:
函数定义:
fputs将一个以null符终止的字符串写到指定的流、尾端的终止符null不写出。注意,这并不一定是每次输出一行,因为它并不要求每次在null之前一定是一个换行符。通常,在null之前是一个换行符,但并不要求总是如此。
puts将一个以null符终止的字符串写到标准输出,终止符不写出。但是,puts然后又将一个换行符写到标准输出。
puts并不像它所对应的gets那样不安全,但是我们还是应该避免使用它,以免需要记住它在最后是否添加了一个换行符。
如果总是使用fgets和fputs,那么就会熟知在每行终止处我们必须自己处理换行符
5.8标准I/O的效率
程序清单:5-15-1
系统CPU时间几乎相同,原因是所有这些程序对内核提出的读、写请求数基本相同。注意,使用标准I/O的一个好处是无需考虑缓冲及最佳I/O长度的选择。在使用fgets时需要考虑最佳行长,但是与选择最佳I/O长度比较,这要方便的多。
使用getc和putc的版本与使用fgetc和fputc的版本在文本空间长度方面大体相同。??
5.9二进制I/O
函数定义:
(1)读或写一个二进制数组:例如,将浮点组的第2-5个元素写至一个文件中
float data[10];
if(fwrite(&data[2],sizeof(float),4,fp)!=4) perror("fwriteerror!");
(2)读或写一个结构,例如,
struct{
short count;
long total;
char name[NAMESIZE];
}item;
if((fwrite(&item,sizeof(item),1,fp))!=1) perror("fwriteerror!");
fread和fwrite返回读或写的对象数,对于读,如果出错或到达文件尾端,则返回数字可以少于nobj(n)。在这种情况下,应该调用ferror或feof以判断究竟应该属于哪一种情况。对于写,如果返回值少于所要求的nobj,则出错。
使用二进制I/O的基本问题是,它只能用于读在同一系统上已写的数据。
5.10定位流
有三种方法定位I/O流:
(1)ftell和fseek函数,这两个函数自V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
(2)ftello和fseeko函数,SingleUNIXSpecification引入了它们,可以使文件的偏移量不必一定使用长整型,它们使用了off_t数据类型替代了长整型。
(3)fgetpos和fsetpos函数,这两个函数是由IOSC引入的。它们使用一个抽象数据类型fpos_t记录文件的位置。这种数据类型可以定义为记录一个文件位置所需的长度。
函数定义:
对于一个二进制文件,其文件位置指示器是从文件开始位置开始度量,并以字节为计量单位。ftell用于二进制文件时,其返回值就是这个字节的位置。
SET_SET:表示从文件的开始位置开始
SET_CUR:表示从文件的当前位置开始
SET_END:表示从文件的尾端开始
5.11格式化I/O
(1)格式化输出:执行格式化输出处理的是4个函数
函数定义:
printf将格式化数据写到标准输出
fprintf写至指定的流
sprintf将格式化的字符送入数组buf中,它在该数组的尾端自动加一个null字节,但该字节不包含在返回值中。
注意,sprintf函数可能造成由buf指定的缓冲区的溢出。调用者有责任确保缓冲区足够大。为了解决这种缓冲区问题,引入了snprintf函数,在该函数中,缓冲区长度是一个显示参数,超过缓冲区尾端写入的任何字符都会被丢失。如果缓冲区足够大,snprintf函数就会返回写入缓冲区的字符数。
转换说明以字符%开始,除转换说明外,格式字符中的其他字符将按原样,不经任何修改的复制输出。
一个转换说明格式有4个可选部分 :
%[flags][fldwidth][precision][lenmodifier]convtype
[flags]:表5-5
[fldwidth]:说明转换的最小字段宽度。如果转换的字符较少,则用空格填充它。字符宽度是一个非负十进制数,或是一个型号(*)
[precision]:说明整型转换后最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转换后最大字符数。精度是一个句点(.),后接一个可选的非负十进制整数或一个星号(*)
宽度和精度字段两者皆可为*
[lenmodifier]:说明参数长度:表5-6
convtype:不是可选。它控制如何解释参数:表5-7
下列4中printf族的变体类:
函数定义:
(2)格式化输入:三个scanf函数
函数定义:
一个转换说明有三个可选部分:
%[*][fldwidth][lenmodifier]convtype
可选的前导星号(*)用于抑制转换。按照转换说明的其余部分对输入进行转换,但转换的结果并不存放在参数中。
[fldwidth]:说明最大宽度。
[lenmodifier]要用转换结果初始化的参数大小。由printf函数族支持的长度修饰符同样得到scanf函数族的支持
5.12实现细节
标准I/O库最终都要调用I/O例程。每个标准I/O流都有一个与其相关的文件描述符,可以对一个流调用fileno函数以获其描述符
函数定义:
为了了解你所使用的系统的标准I/O库的实现,最好从头文件<stdio.h> 开始
5.13临时文件
IOS C标准I/O库提供了两个函数以帮助创建临时文件:
函数定义:
tempnam函数产生一个与现有文件名不同的一个有效路径名字符串
tmpfile创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件
5.14标准I/O的替代软件
标准I/O库并不完善。
标准I/O库的一个不足之处是效率不高 ,这与它需要复制的数据有关。当时每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准I/O缓冲之间,第二次是在标准I/O缓冲区和用户程序中的行缓冲区之间。快速I/O库(AT&Tfio(3)) 避免了这一点。其方法是使读一行的函数返回指向该行的指针,而不是将该行复制到另一个缓冲区中
Korn和Vo[1991] 说明了标准I/O库的另一个替代版本:sfio推广了I/O流,使其不仅可以代表文件,也可以代表存储区:可以编写处理模块,并以栈方式将其压入I/O流,这样就可以改变一个流的操作;较好的异常处理等。
许多标准I/O库的实现可用于C函数库中,这种C函数库是为内存较小的系统(如嵌入式)设计的
第6章系统数据文件和信息
6.1引言
UNIX系统的正常运行需要大量与系统有关的数据文件 ,例如:口令文件/etc/passwd组文件/etc/group就是经常由多种程序使用的两个文件
由于历史原因,这些数据文件都是ASCII文本文件 ,并且使用标准I/O库读这些文件
6.2口令文件
/etc/passwd文件格式注意 :
通常有一个用户名为root的登录项,其用户ID是0(超级用户)
加密口令字段包括了一个占位字符
口令文件项中的某些字段可能是空的
shell字段包括了一个可执行程序名,它被用作该用户的登录shell。若字段为空,则取系统默认值,通常是/bin/sh。阻止一个用户登录时,此字段为:/dev/null;阻止登录其他的两种方法:/bin/false和/bin/true
使用nobody用户名的目的是,使任何人都可登录至系统
提供finger(1)命令的某些UNIX系统支持注释字段中的附加信息
POSIX定义了两个获取口令文件项的函数:getpwuid()getpwnam()
函数定义:
查看整个口令文件:getpwent() setpwent() endpwent()
函数定义:
6.3阴影口令
加密口令是经单向加密算法处理过的用户口令副本。因为此算法是单向的,所以不能从加密口令猜测到原来的口令。
难以获得原始资料,现在,某些系统将加密口令存放在另一个通常称为阴影口令(shadowpassword)的文件中,该文件至少要包括用户名和加密口令(etc/shadow)
阴影口令文件不应是一般用户可以读取的。但有少数几个程序需要存取加密口令,例如:login(1)和passwd(1)这些程序常常是设置用户ID为root的程序
另一组函数可用于访问阴影口令文件:
函数定义:
6.4组文件
<grp.h>/etc/group
POSIX定义的函数来查看组名或数值组ID
函数定义
需要搜索组文件,则需要使用另外几个函数:
函数定义
6.5附加组ID
在V7中,每个用户在任何时候都只属于一个组。4.2BSD引入了附加组ID(supplementary groupID)的概念,我们不仅可以属于口令文件记录项中组ID所对应的组,也可以属于多达16个另外的组。文件访问权限检查相应被修改为:不仅将进程的有效组ID与文件的组ID相比较,而且也将所有附件组ID与文件组ID相比较。
使用附加组的优点是不必显示地经常更改组。一个用户会参加多个项目,因此也就要同时属于多个组。
获取和设置附加组ID,三个函数:
函数定义
6.6实现的区别
表6-4
6.7其他数据文件
BSD网络软件有一个记录各网络服务器所提供服务的数据文件(/etc/services),有一个记录协议信息的数据文件(/etc/protoclols),还有一个则是记录网络信息的数据文件(/etc/networks)
一般情况下,对于每个文件至少有三个函数:
(1)get函数
(2)set函数
(3)end函数
6.8登录账号记录
大多数UNIX系统都提供下列两个数据文件:utmp文件,它记录当前登录进系统的各个用户;wtmp文件,它跟踪各个登录和注销事件
6.9系统标识
POSIX定义了uname函数 ,它返回与当前主机和操作系统有关的信息
函数定义:
BSD派生的系统提供了gethostname函数,它返回主机名,该名字通常就是TCP/IP网络上主机的名字
函数定义:
6.10时间和日期例程
time函数返回当前时间和日期:
函数定义
与time函数相比,gettimeofday提供了更高的分辨率(最高为微妙级)这对某些应用很重要
函数定义
localtime和gettime之间的区别是:localtime将日历时间转换成本地时间,而gettime将日历时间转换为国际时间
函数定义
函数mktime以本地时间的年、月、日等作为参数,将其转换为time_t值
函数定义
asctime和ctime函数产生大家都熟悉的26字节的字符串,这与data(1)命令的系统默认输出形式类似
函数定义:
strftime,它是非常复杂的类似printf的时间值函数