《Linux C程序设计第17章 文件IO.pdf》由会员分享,可在线阅读,更多相关《Linux C程序设计第17章 文件IO.pdf(47页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、 第 17 章 文件 I/O 计算机有两个重要的概念时间和空间。对于时间概念计算机将其抽象为进程,而对于空间概念则抽象为文件,本章开始涉及文件操作的内容。本章首先对一般文件 I/O 进行介绍,然后阐述经典的 Linux 文件系统模型,最后部分的内容介绍了基于文件描述符的操作,本章最后的两个小节则介绍两种特殊的文件符号连接和目录。17.1 文件描述符的概念 在 Linux 环境下每一个磁盘文件在打开时都会在内核中建立一个文件表项。该文件表项包括文件的状态信息、存储文件内容的缓冲区,以及当前文件的读写位置等。当同一个磁盘文件打开两次时会创建两个这样的文件表项,读写该文件时只影响到该文件表项中的文件
2、读写位置。这些文件表项共同保存在内核中的一个数组里,该数组就是文件表。每一个进程在内核中有保存一个整型数组,该数组中每个元素是文件表的下标。因此使用该数组下标就可以引用打开的文件表项了。该数组下标就是文件描述符,每个进程中的文件表下标的数组就是文件描述符数组。当进程需要引用文件时,只需要引用这个文件描述符就可以了。因此文件描述符可以说是进程与打开文件的一个桥梁。通过这个桥梁进程才能够读写文件。17.2 文件 I/O 操作 这一节主要涉及文件基本 I/O 操作,包括对一个文件的打开与关闭、读写定位等。Linux使用 open 函数打开文件,使用 close 函数关闭文件;使用 lseek 函数在
3、文件中定位;使用read 函数和 write 函数读写文件。17.2.1 打开一个文件 在 Linux 环境下使用 open 系统调用打开一个文件(在该文件不存在的情况下可以创建该文件)。Open 函数的原型如下:#include int open(const char*pathname,int oflag,/*mode_t mode*/);第 17 章 文件 I/O 509 open 函数的第 1 个参数指定要打开的文件,有两种方式指定该文件。?该文件的绝对路径名(或者相对路径名),操作系统将根据路径名寻找文件。?该文件的文件名,操作系统只在当前工作目录寻找该文件。相对路径名中可以使用“.”
4、和“.”代表当前工作目录和当前工作目录的父目录。open(./test.txt,O_RDONLY);open(./test.txt,O_RDONLY);分别表示打开当前目录下的 test.txt 文件和打开当前目录的上一级目录下的 test.txt 文件。使用“.”引用当前目录下的文件和直接使用文件名两种方式看起来差不多,但是如果在当前目录下引用多个目录,第 1 种方式的优势就显现出来了。open(./admin/test.txt,O_RDONLY);表示应用当前目录下的 admin 目录下的 test.txt 文件。open 函数的第 2 个参数指定打开文件的选项,其值是一个位向量,通常使用
5、下列一个或者多个常量进行或操作构成 oflag参数,这些常量值由宏表示,其定义在 fcntl.h 头文件中。以下最常用的 3 个选项是打开文件的模式,Linux 支持以只读、只写或者读写的方式打开一个文件。?O_RDONLY(Read only):只读打开,值为 0;?O_WRONLY(Write only):只写打开,值为 1;?O_RDWR(Read Write):读写打开,值为 2。?注意:以上 3 个选项是互斥的,也就是说,一个文件只能以上述任意一种方式打开,例如“O_RDONLY|O_WRONLY”之类的选项是没有意义的。上面 3 个选项必须选择其中的一个,下面的选项则是可选的:?O
6、_APPEND:每次写的时候都追加到文件的末尾。?O_CREAT:如此文件不存在,则创建该文件,使用此选项时,需要 open 函数的第 3 个参数。?O_EXCL:如果同时设置了 O_CREAT 选项,而文件已经存在,则使用时用此选项会导致函数返回出错。该种做法可以避免多进程的并发错误,所以说该操作是原子性的。?O_TRUNC:如果此文件存在,且以写方式成功打开,则将该文件截短为 0。本章17.2.6 小节将介绍截短概念和截短操作。?O_NOTTY:如果指定的文件是终端设备,则不把此控制终端分配给调用 open 函数的进程。?O_NONBLOCK:如果指定文件是命名管道、块设备或者字符设备,则
7、将此设备设置为非阻塞。其余的三个选项分别是 O_DSYNC、O_RSYNC 和 O_SYNC。此 3 个选项和文件同步输出有关。Linux C 程序设计大全 510 打开文件选项的讲解比较复杂,笔者安排在涉及该选项的时候再对其进行详细的讲解。读者也可以根据选项后面涉及章节的标注,直接阅读和该选项有关的内容。open 函数的第 3 个参数只有在要打开的文件并不存在,而且需要系统创建的时候才有用。这时,该参数代表创建新文件的访问权限。?注意:在创建一个新文件时,用户指定的权限会受到 umask 文件权限掩码的影响。该函数的返回值是打开文件的文件描述符,此文件描述符一定是当前进程可用文件描述符中最小
8、的一个,例如,每一个进程都默认打开了 3 个文件标准输入(文件描述符为 0)、标准输出(文件描述符为 1)和标准出错(文件描述符为 2),如果该进程第一次打开文件且打开成功时,其文件描述符一定是 3,即 open 函数返回值为 3。假设关闭标准输出,再次打开一个文件,其文件描述符则一定是 1,该文件替代了刚刚关闭的标准输出文件的位置。本书后面章节还会讲到如何利用这一特性。如果 open 函数失败,则返回1,并且设置 errno 变量值。这里有必要解释副作用的概念。该概念通常针对函数而言,如果一个函数的运行结果不仅仅只通过参数和返回值表现出来,即一个函数在运行时除了改变参数和返回值之外,还改变了
9、一些全局资源,那么该函数就是有副作用的,如图 17-1 所示。图 17-1 函数的副作用示意图?注意:所有的系统调用都是有副作用的,对于有副作用的函数,使用时要特别小心,以免其对全局资源的改变影响到其他函数的调用。下面是一个 open 函数的实例,实例中使用 O_CREAT 选项,并对其详细讲解,其他打开文件选项在涉及时还会详细介绍。该程序调用 open 函数打开一个文件,open 函数被设置了 O_CREAT 选项。因此,如果文件不存在,则创建该文件。第 17 章 文件 I/O 511(1)在 vi 编辑器中编辑以下程序:程序清单 17-1 open.c 打开一个文件,如果该文件不存在则创建
10、它#include#include#include int main(void)int fd;/*文件描述符*/*以“读写”模式打开当前目录下的 test.txt 文件,如果不存在则创建该文件,*并且将其文件权限字设置为八进制的 0700 */fd=open(test.txt,O_RDWR|O_CREAT,0700);if(fd=-1)perror(fail to open);/*打开文件出错,打印错误号*/exit(1);else printf(open OKn);close(fd);/*关闭文件,有关内容见本书后面小节*/return 0;(2)在 shell 中编译以下程序:$gcc o
11、pen.c-o open (3)打开一个一进存在的文件 test.txt,使用 ls 命令查看 test.txt 的详细信息。$ls-l test.txt-rw-1 admin 0 Feb 7 07:45 test.txt (4)在 shell 中运行以下程序:$./open open OK (5)删除该文件,这时再次打开文件的时候应当创建该文件。$rm test.txt (6)再次在 shell 中运行以下程序:$./open open OK (7)观察 test.txt 文件的详细信息。Linux C 程序设计大全 512$ls-l test.txt-rwx-1 admin 0 Feb 0
12、 07:45 test.txt 以上程序打开一个文件,如果当前目录下没有 test.txt 文件,运行该程序后出现一个新文件 test.txt,其权限为文件所有者可读、可写和可执行,对应文件权限字 700(八进制)。关于 O_CREAT 选项,有两点需要解释。?如果需要创建文件,必须保证 open 函数的第 1 个参数正确。即路径名有效,且每一个组成部分都有相应的权限。否则会出现权限错误,导致 open 函数返回1。?如果不指定 open 函数的第 3 个参数,系统不会提供一个默认值作为新文件的权限字,而是使用一个随机值。该项错误不会导致 open 函数返回1,但是在后续操作使用该文件时,会出
13、现权限方面的问题。因此在使用此选项时,一定要使用 open函数的第 3 个参数。17.2.2 打开文件的出错情况 对于 Linux 系统来讲,open 函数不太容易出错,所以,在实际编程过程中,检查 errno变量就显得尤其重要。经常出现的一个问题是需要打开的文件权限不够,例如,程序需要以写方式打开文件,但是该用户只有对文件读的权限。另一个常见的问题是在创建文件时对当前目录没有写权限。?注意:检查 errno 可以有助于迅速排查程序中的错误。下面实例演示了两种打开文件出错的情况,一种是打开一个不存在的文件,另一个是打开文件的方式是文件权限所不允许的。该程序打开的两个文件 no_such_fil
14、e 和“denied”中,一个不存在,一个只有只读权限。(1)在 vi 编辑器中编辑以下程序:程序清单 17-2 file_error.c 演示常见的文件打开错误#include#include#include int main(void)int fd;fd=open(no_such_file,O_RDONLY);/*尝试打开一个不存在的文件*/if(fd=-1)perror(fail to open);else close(fd);fd=open(denied,O_WRONLY);/*以权限不允许的方式打开文件*/if(fd=-1)第 17 章 文件 I/O 513 perror(fail
15、to open);else close(fd);/*关闭打开的文件*/return 0;(2)在 shell 中编译以下程序:$gcc file_error.c-o file_error (3)在 shell 中运行以下程序:$./file_error fail to open:No such file or directory fail to open:Permission denied 因此,使用 perror 函数可以迅速地发现文件打开方面的错误。在实际编程过程中,文件打开的错误基本属于以上两种情况,其他情况是很少见的。17.2.3 关闭一个文件 在 Linux 环境下使用 close
16、系统调用关闭一个打开的文件,其函数原型如下:#include int close(int fd);该函数的参数是需要关闭的文件的文件描述符,成功关闭返回 1,失败则返回1。在一个进程结束运行时,系统会隐式地关闭该进程打开的所有文件描述符,其结果和在程序中显式地调用 close 函数一样。但是作为一种良好的编程习惯,open 函数和 close 函数是要配对的。也就是说,打开的文件最好要显式地关闭。如果一个程序的运行时间很短或者打开文件个数并不多,使用隐式文件关闭方法没有问题;如果编写一个常驻内存运行的程序,进程就会因为打开文件数过多而无法再打开任何文件,从而不能正常工作。关闭一个文件可以同步外
17、存中的文件内容并且释放所有的加在文件上的锁,如果使用关闭一个已经关闭的文件描述符会导致出错。同样,如果关闭一个空的文件描述符同样会引起严重后果。同 open 函数一样,close 函数同样很少出错,因此,在一般的编程过程中都不对 close函数的返回值进行检查。但是在有些极限情况下检查 close 函数的返回值是十分必须的,例如,close 函数关闭的文件是一个远程的网络文件时,这时就需要检查 close 函数是否成功执行。由于 close 函数在关闭的时候会将内存缓冲区中的内容写到外存上,因此,关闭一个网络文件相当于与对端进行一次数据通信,这时出错的概率是很大的,因为网络中随时可能导致数据包
18、的丢失,如图 17-2 所示。Linux C 程序设计大全 514 图 17-2 close 函数关闭网络文件出错 17.2.4 创建一个新文件 当需要创建一个新的文件时,除了使用 open 函数之外,还可以使用 creat 函数。在 Linux环境下使用 creat 函数创建一个新文件,并以只写的方式打开,其函数原型如下:#include int creat(const char*pathname,mode_t mode)该函数的第 1 个参数和 open 函数的第 1 个参数具有同样的意义;第 2 个参数则和 open函数的第 3 个参数等效。如果成功创建文件并且以只写方式打开,则返回 1
19、,失败返回1。调用该函数等于调用。open(pathname,O_CREATE|O_WRONLY|O_TRUNC,mode);上述调用表示如果文件不存在,则创建该文件并且以只写方式打开;如果该文件存在则将其截短为 0,再以只写方式打开。?说明:所谓截短为 0 就是将该文件原有内容全部丢弃,在写文件时会对原文件造成覆盖。下面实例演示了使用 creat 函数创建一个文件。该程序使用 creat 函数创建了一个新文件,并且其内容为空。(1)在 vi 编辑器中编辑以下程序:程序清单 17-3 creat.c 创建一个空文件#include#include#include 第 17 章 文件 I/O 5
20、15int main(void)int fd;fd=creat(test.txt,0700);/*创建一个新文件,使用权限字为 700*/if(fd=-1)perror(fail to creat);exit(1);else printf(creat OKn);/*输出提示信息*/return 0;(2)在 shell 中编译以下程序:$gcc creat.c-o creat (3)在 shell 中运行以下程序:$./creat creat OK (4)使用 ls 命令查看新创建的文件。$ls-l test.txt-rwx-1 admin 0 Feb 0 07:55 test.txt cre
21、at 函数只能以只写方式打开新创建的文件。如果要对新文件进行读操作,则需要关闭该文件再重新以读方式打开。这样做很不方便,在有了更好的替代方法后,creat 函数现在已经很少使用了。open(pathname,O_CREATE|O_RDWR|O_TRUNC,mode);17.2.5 文件定位 每一个打开的文件都有一个与其相关的文件偏移量,它一般是一个非负的整数,所有关于文件的操作,例如,读、写,都是从当前文件偏移量开始的,并且使文件偏移量增加,如图 17-3 所示。图 17-3 读写操作改变文件的读写位置 Linux C 程序设计大全 516 在系统默认情况下,打开文件的偏移量为 0。在 Lin
22、ux 环境下使用 lseek 函数更改打开文件的偏移量,实现在文件内部的定位,其函数原型如下:#include off_t lseek(int filedes,off_t offset,int whence);lseek 函数的第 1 个参数是一个已经打开的文件的文件描述符。lseek 函数的第 2 个参数和第 3 个参数的解释如下:?当 whence 是 SEEK_SET 时,表示将该文件的文件偏移量设置为距文件开始位置offset 个字节。?当 whence 是 SEEK_CUR 时,表示将该文件的文件偏移量设置为当前文件偏移位置增加 offset 个字节,offset 的值可以是一个负数
23、。?当 whence 是 SEEK_END 时,表示将该文件的文件偏移量设置为当前文件结尾位置增加 offset 个字节,offset 的值可以是一个负数。?注意:lseek 函数的第 3 个参数只能是上述 3 个宏所代表的整型值中的一个,上述 3 个宏的定义也在 unistd.h 中。lseek 如果成功执行,其返回值是所设置的新的偏移量,否则返回1。因为文件偏移量可能为负值,所以检查 lseek 函数是否出错时,不能检查其返回值是否小于 0,而应检查其返回值是否是1。根据其返回值的特性,可以得出打开文件的当前偏移量。下面实例演示了使用 lseek 函数得到当前文件的偏移值。该程序打开一个文
24、件,得出未进行任何读写操作的文件的偏移值。之后调用 read 函数读取 5 个字节的内容,再次调用lseek 函数得到文件的读写位置。(1)在 vi 编辑器中编辑以下程序:程序清单 17-4 lseek.c 取得文件当前偏移量#include#include#include#include#define MAX 1024 int main(void)int fd;off_t off;char bufMAX;fd=open(test.txt,O_RDWR);/*打开一个文件*/if(fd=-1)perror(fail to open);exit(1);第 17 章 文件 I/O 517 prin
25、tf(before readingn);/*输出提示信息*/off=lseek(fd,0,SEEK_CUR);/*调用 lseek 函数得到当前文件的读写位置*/if(off=-1)perror(fail to lseek);exit(1);printf(the offset is:%dn,off);/*输出提示信息*/if(read(buf,5,fd)=-1)/*读取 5 个字节的文件内容*/perror(fail ot read);exit(1);printf(after readingn);/*输出提示信息*/off=lseek(fd,0,SEEK_CUR);/*再次调用 lseek 函
26、数得到当前文件的读写位置*/if(off=-1)prerror(fail to lseek);exit(errno);printf(the offset is:%dn,off);/*输出结果*/close(fd);/*关闭文件*/return 0;(2)在 shell 中编译以下程序:$gcc lseek.c-o lseek (3)在 shell 中运行以下程序:$./lseek before reading the offset is:0 after reading the offset is:5?注意:并不是所有的文件都可以使用 lseek 函数,Linux 环境下有些文件是不能够做文件定
27、位操作的。例如,套接字文件和管道文件这些特殊的文件是不能够进行文件定位的。17.2.6 文件截短 有时用户需要将文件的尾端处一些数据抛弃,使文件能够保证在一定的尺寸之内。这Linux C 程序设计大全 518 个时候需要对文件进行截短操作。该操作将文件截短为用户指定的字节数,超出的部分会被系统自动放弃,如图 17-4 所示。图 17-4 文件截短示意图 Linux 环境下使用 truncate 函数截短一个文件,其函数原型如下:#include int truncate(const char*pathname,off_t length);truncate 函数的第 1 参数表示一个文件的路径。
28、该路径的要求与 open 函数相同。truncate函数的第 2 个参数表示将文件截短的字节数,超过该字节数的部分将被系统放弃。如果文件的实际大小小于指定的值,则系统会自动拓展该文件,这时文件实际末尾和拓展的新末尾之间形成一个文件空洞,如图 17-5 所示。图 17-5 文件拓展后的空洞示意 如果成功截短一个文件,truncate 函数返回 0,失败则返回1。下面实例演示了截短一个已经存在的文件,该文件的实际大小小于指定的截短字节数。该程序首先拓展调用truncate 函数拓展该文件,之后再向文件空洞中填充内容,并且会写到外部设备中。该程序的格式为:程序名 拓展后的文件字节数 实际的文件字节数
29、 第 17 章 文件 I/O 519其程序执行流程如图 17-6 所示。图 17-6 实现截短文件的程序流程图(1)在 vi 编辑器中编辑以下程序:BBLinux C 程序设计大全 520 程序清单 17-5 truncate.c 使用截短操作实现文件填充功能#include#include#include#include#define MAX 32 int main(int argc,char*argv)int fd;int len;int rest;int i;if(argc!=3)/*根据命令行参数设置扩展后的文件字节数和需要填充的字节数*/len=MAX;rest=0;else len
30、=atoi(argv1);rest=atoi(argv2);if(truncate(test.txt,MAX)=-1)/*截短操作,将文件拓展为指定字节数*/perror(fail to truncate);exit(1);/*添加写方式打开文件,每次写的内容会自动添加到文件的结尾*/fd=open(test.txt,O_RDWR|O_APPEND);if(fd=-1)perror(fail to open);exit(1);i=0;while(i rest)/*设置填充内容,余下的文件内容填充为字符0*/bufi=0;i+;if(write(fd,buf,rest)=-1)/*填充文件*/p
31、error(fail to write);exit(1);第 17 章 文件 I/O 521 close(fd);/*关闭文件*/return 0;(2)在 shell 中编译以下程序:$gcc truncate.c-o truncate (3)查看 test.txt 文件的内容,及其大小。$cat test.txt hello world$(该文件内的字符串后面没有n)$ls-l test.txt-rwxr-r-1 root root 12 Feb 12 19:12 test.txt (4)在 shell 中运行以下程序:$./truncate 32 20 (5)再次查看该文件。$cat t
32、est.txt hello world00000000000000000000$ls-l test.txt-rw-r-r-1 root root 32 Feb 12 19:14 test.txt Linux 环境下使用 ftruncate 函数截短一个已经打开的文件,其函数原型如下:#include int ftruncate(int filedes,off_t length);ftruncate 函数的第 1 个参数表示需要进行截短操作的文件,该参数是一个已经打开文件的文件描述符。ftruncate 函数的第 2 个参数表示需要截短的长度,该参数同样也可以大于文件的实际大小。如果成功截短一个
33、文件,ftruncate 函数返回 0,失败则返回1。17.2.7 清空一个文件 有时需要将一个已经存在的文件清空,这时可以使用文件截短操作将文件截短为 0 即可。下面实例演示了截短一个已经打开的文件。该程序首先打开一个文件,之后调用ftruncate 函数将其截短为 0。当关闭该文件后,其原来的文件内容就访问不到了。(1)在 vi 编辑器中编辑以下程序:程序清单 17-6 clear.c 使用截短函数清空一个已经存在的文件#include#include#include#include Linux C 程序设计大全 522 int main(void)int fd;fd=fopen(test
34、.txt,O_WRONLY);/*需要将文件截短,该文件的打开方式必须可 写*/if(fd=-1)perror(fail to open);exit(1);if(ftruncate(fd,0)=-1)/*将原文件截短为 0,文件内容不可访问*/perror(fail to truncate);exit(1);close(fd);/*关闭文件*/return 0;(2)在 shell 中编译以下程序:$gcc clear.c-o clear (3)查看原文件的内容和大小$cat test.txt hello world$ls-l test.txt-rw-r-r-1 root root 12 Fe
35、b 12 17:12 test.txt (4)在 shell 中执行以下程序:$./clear (5)再次查看该文件的内容和大小。$cat test.txt$(没有文件内容)$ls-l test.txt-rw-r-r-1 root root 0 Feb 12 17:18 test.txt?说明:在打开一个文件时也可以将其清空,这个时候需要使用 open 函数的 O_TRUNC选项。该选项将文件以只写方式打开,并且将原文件截短为 0。下面实例演示了将一个文件清空并且写入新的字符串。该程序以只写方式打开一个文件,并且使用 O_TRUNC 选项将其清空为 0。之后向该文件输入一行字符串作为提示信息。
36、(1)在 vi 编辑器中编辑以下程序:第 17 章 文件 I/O 523程序清单 17-7 clear.c 使用截短函数清空一个已经存在的文件#include#include#include#include int main(void)int fd;char*p=linux okn;/*测试使用的字符串*/fd=fopen(test.txt,O_WRONLY|O_TRUNC);/*该文件只写打开,并且将文件 截短为 0*/if(fd=-1)perror(fail to open);exit(1);if(write(fd,p,strlen(p)=-1)/*输入新内容,该内容会完全取代旧的内容*/
37、perror(fail to write);exit(1);close(fd);/*关闭文件*/return 0;(2)在 shell 中编译以下程序:$gcc cut.c-o cut (3)查看 test.txt 文件中的内容。$cat test.txt hello world (4)在 shell 中运行以下程序:$./cut (5)再次查看 test.txt 文件的内容。$cat test.txt linux ok 17.2.8 文件的读写操作 Linux 环境下使用 read 函数读取文件内容,其函数原型如下:Linux C 程序设计大全 524#include ssize_t rea
38、d(int filedes,void*buf,size_t nbytes);read 函数的第 1 个参数是一个文件描述符,表示从哪个文件中读取,该文件一定是一个具有读方式打开的文件。第 2 个参数是缓冲区,将文件内容读入到该缓冲区中。第 3 个参数表示需要读的字节数。read 函数的返回值是实际读出字节数,其值可能有以下 3 种情况:?从指定文件中读入 nb 个字节,返回值和参数 nb 相等,这是所遇到最多的情况。?文件剩余字节数小于 nb,返回值是实际读出的字节数,如果文件一节到达末尾则返回值为 0。?读操作出现错误(例如,文件没有以读方式打开),返回值为1。如果所要读取的文件太大,应采用
39、分批读入文件的方法。?注意:read 函数不会在读入的内容后面添加0结束符,这一点和 C 语言函数库中提供的读文件函数是不同的,如果需要,则必须由用户手动添加。以下给出了添加0结束符的代码:#define MAX 100 char bufMAX;/*缓冲区*/int bytes;/*读入的字节数*/read(fd,buf,MAX-1);/*最多读入 MAX 1 个字节,缓冲区最后一个位置留给0结 束符*/bufbytes=0;/*添加0结束符*/read 操作会影响打开文件的文件偏移量。Linux 环境下使用 write 函数写一个文件,其函数原型如下:#include sszie_t wri
40、te(int filedes,void*buf,size_t nbytes);同 read 函数类似,write 函数的 3 个参数分别代表要写的文件,需要写到文件中内容的缓冲区和要写的字节数。write 函数的返回值是实际写到文件中的字节数,出错则返回1。?说明:对于 write 函数来讲,常见的出错原因是外存没有足够的存储空间或者文件长度超过了进程文件长度的限制。下面实例演示了使用文件读写函数实现一个简单的 cp 命令。该程序首先判断命令行参数是否合法,正确的命令格式如下:my_cp 目标文件名 源文件名 之后打开两个文件,对于目标文件如果其不存在,则创建该文件。顺序读取源文件内容并将其写
41、入到目标文件中,其程序执行流程如图 17-7 所示。第 17 章 文件 I/O 525 图 17-7 my_cp 程序的执行流程(1)在 vi 编辑器中编辑该程序如下:程序清单 17-8 my_cp.c 简单的 cp 程序实现#include#include#include#include /*linux cp 命令的简单实现,命令格式:cp des src*成功返回 0,失败返回-1,失败原因保存在 errno 中*argv1:目标文件名 des(本例使用绝对路径)Linux C 程序设计大全 526*argv2:源文件名 src(本例使用绝对路径)*/int main(int argc,c
42、har argv)char bufMAX;int in,out;/*输入文件和输出文件*/int n;if(argc!=3)exit(1);if(in=open(argv2,O_RDONLY)=-1)/*源文件,“只读”打开*/perror(fail to open);exit(1);/*目标文件,该文件不存在则创建,该文件存在则覆盖且只写打开*/if(out=open(argv1,O_WRONLY|O_TRUNC|O_CREAT)=-1)perror(fail to open);exit(1);while(n=read(in,buf,MAX)0)/*读入文件*/if(write(out,bu
43、f,n)!=n)/*实际写出字节数不等于 n,写出错*/perror(fail to write);exit(1);if(n 0)/*读入出错*/perror(fail to read);exit(1);printf(copy donen);/*输出提示信息*/close(in);/*关闭两个文件*/close(out);return 0;(2)在 shell 中编译该程序如下:$gcc my_cp.c-o my_cp (3)使用 cat 命令查看源文件的内容。$cat src.txt hello world (4)查看当前的工作目录。第 17 章 文件 I/O 527$pwd/home/ad
44、min (5)在 shell 中运行以下程序:$./my_cp/home/admin dest.txt/home/admin src.txt (6)使用 cat 命令查看 dest.txt 文件的内容。$cat dest.txt hello world 17.2.9 文件同步 上一小节讨论了文件读写的内核机制,由于文件的写操作会由于缓冲的缘故致使输出延时,所以在一段时间内,会导致内存中的文件内容和外存上的文件内容不一致,如图 17-8所示。图 17-8 缓冲区机制示意图 为了避免该种情况,用户可以指定系统在缓冲区并未填满的时候将文件内容回写到磁盘上。这种操作叫做文件同步,Linux 提供以下函
45、数用于文件同步操作,其函数原型如下:#include int fsync(int filedes);int fdatasync(int filedes);void sync(void);sync 函数所做的操作最简单,该函数将所有打开的文件写回到磁盘上,将文件中修改Linux C 程序设计大全 528 过内容的盘块放入系统队列后就返回,并不等待盘块实际写入外存。因此,该函数能够加快文件同步速度,但是并不能保证真正的文件同步。fsync 函数可以确保文件的实际写出,该函数会阻塞直到修改的盘块写到外存后才返回,成功返回 0,如出错则返回1。注意:fsync 函数是需要保持同步文件的描述符。fsyn
46、c 函数可保证文件内容及时更新,并且不用频繁做打开关闭文件的操作。下面实例演示了使用 fsync 函数进行文件的同步操作。该程序首先打开一个文件,之后每隔 5 秒钟向文件中输出一行字符。输出字符之后程序调用 fsymc 函数将输出的内容回写到磁盘上。用户可以打开该文件,观察输出的信息是否即时写回到了磁盘文件上。(1)在 vi 编辑器中编辑以下程序:程序清单 17-9 fsync.c 使用 fsync 函数进行文件同步#include#include#include#inlcude int main(void)int fd;int i;fd=open(test.txt,O_RDWR);/*打开一
47、个文件,其打开方式为可写*/if(fd=-1)perrror(fail to open);exit(1);i=0;while(i 3)sleep(5);/*休眠 5 秒钟*/printf(hellon);if(write(fd,6,hellon)=-1)/*向文件输出一行字符串作为提示信息 */perror(fail to write);exit(1);if(fsync(fd)=-1)/*输出信息后立即进行文件同步,保证输出信息即时写回 到磁盘上*/perror(fail to fsync);exit(1);第 17 章 文件 I/O 529 i+;close(fd);/*关闭文件*/retu
48、rn 0;(2)在 shell 中编译以下程序:$gcc fsync.c-o fsync (3)在 shell 中运行以下程序:$./fsync hello (5 秒钟之后)hello (5 秒钟之后)hello (5 秒钟之后)(4)在另一个 shell 终端中打开 test.txt 文件观察。$cat test.txt (5 秒钟之后)hello$cat test.txt (5 秒钟之后)hello hello$cat test.txt (5 秒钟之后)hello hello hello fdatasync 函数和 sync 函数类似,但不同的是 fdatasync 函数只更新文件的数据部
49、分,而不更新文件的属性部分。fdatasync 函数的参数和返回值与 fsync 函数的意义相同。而 sync函数将所有打开文件的内容回写到磁盘上,所以该函数不需要任何参数,也不返回任何值。17.3 文件描述符操作 17.2 节讨论了文件的基本 I/O,后面的几个小节将讨论关于文件描述符的操作,这些操作不操作文件本身,而是操作文件与进程的链接点文件操作符,如果读者对文件描述符的本质有些遗忘,请回顾本章 17.1 节的内容。17.3.1 复制文件描述符 Linux 环境下可以使用 dup 函数和 dup2 函数复制一个文件描述符,其函数原型如下:#include int dup(int file
50、des);int dup2(int filedes,int filedes2);Linux C 程序设计大全 530 dup 函数的参数是需要复制的文件描述符,该函数总是找到进程文件表中第 1 个可用的文件描述符,将参数指定的文件描述符复制到该描述符后返回该描述符。例如,一个进程创建后默认打开了 3 个文件分别是标准输入、标准输出和标准出错,3 个文件的文件描述符分别是 0、1 和 2。所以这时执行如下操作:fd=dup(0);这时 fd 的值是第 1 个可用的文件描述符,也就是除了 0、1 和 2 之外的第 1 个描述符,本例中为 3。复制后的文件描述符共用一个文件表项,所以其两个文件的偏移