分类: LINUX
2008-05-29 16:25:54
首先,让我们了解如何读写文件。凭直觉就可以知道,read() 和 write() 系统调用是执行这些操作的最常用方法。这两种系统调用将有三个自变量:要访问的文件描述符、指向要读写的信息的指针以及应该读写的字符数。返回成功读写的
字符数。清单 1 说明了一个简单的程序,它从标准输入(文件描述符 0)中读取一行,并将它写入标准输出(文件描述符 1):
清单 1:
void main(void) {
char buf[100];
int num;
num = read(0, buf, sizeof(buf));
write(1, "I got: ", 7); /* Length of "I got: " is 7! */
write(1, buf, num);
}
关 于这个处理有两个值得注意的问题。首先,我们要求 read() 返回 100 个字符,但如果我们运行这个程序,只有在用户按下了 "enter" 键以后才能获得输入。许多文件操作都根据最佳效果工作:它们尝试返回程序要求的所有信息,但只有部分能够成功。缺省情况下,终端配置成一旦存在 "\n" 或新行符(通过按 "enter" 键产生)时,就从 read() 调用返回。这实际上非常方便,因为大多数用户都希望程序无论如何都是面向行的。但常规数据文件并非如此,如果依靠它就可能产生不可预料的结果。
另一个要注意的问题是我们不必在显示输出后写一个 \n。read() 调用给了我们来自用户的 \n,只将那个 \n 通过 write() 写回标准输出。如果您希望在没有新行符的情况下看到发生的事件,尝试将最后一行改为
write(1, buf, num - 1);
有 关这个简单示例的最后一点:buf 绝对不包含实际的 C 字符串。C 字符串由标记字符串结束的单一 \0 字符终止。因为 read()
不将 \0 添加到缓冲区的结尾,在 read() 上使用 strlen()(或任何其它 C 字符串函数)将可能铸成大错!这种行为可以让 read() 和 write() 对包括 \0 字符的数据处理,而这对于一般字符串函数来说是不可能的。
read() 和 write() 系统调用可以对绝大多数文件起作用。但它们不对目录起作用,目录应该通过特殊函数(例如 readdir())来访问。另外,read() 和 write() 对于某些类型的套接字也不起作用。
某 些文件,例如常规文件和块设备文件,使用文件指针的概念。它指定在文件中,下一个 read() 调用从哪里读取,下一个 write() 调用从哪里写入。read() 或 write() 后,文件指针随着已处理的字符数(在内部,通过内核)增加。这样,使用单一循环就可以方便地读取文件中的所有数据。清单 2 就是示例:
清单 2:
char buffer[1024];
while ((num = read(0, buffer, 1024))) {
printf("got some data\n");
}
这 个循环将读取标准输入上的所有数据,自动在每次读取后增加内核的内部文件指针。当文件指针处于文件结尾时,read() 将返回 0 并退出循环。某些文件(例如字符设备 -- 终端就是很好的一例)本身没有文件指针,所以对于这一点,该程序将继续运行,直到用户提供文件结束标记(通过按 "Ctrl-D")为止。
到 现在为止,我们已经知道如何读写文件了,下一步要学习如何打开一个新文件。打开不同类型的文件有不同方法;我们将在这里讨论的方法是通过路径名打开在文件
系统中表示的文件;包括常规文件、目录、设备文件和指定的管道。某些套接字文件有路径名,那些必须通过替代方法打开。
撇开放弃权利的,open()
系统调用可以让程序访问大多数系统文件。open() 是个不寻常的系统调用,因为它获取两个或者三个自变量:
int open(const char *
pathname,
int flags);
或者,
int open(const char *
pathname,
int flags,
int perm);
第一种形式更普遍一些;它打开一个已存在的文件。第二种格式应该在需要创建文件时使用。第三个自变量指定应该给予新文件的访问权限。
open() 的第一个参数是以正常 C 字符串表示的全路径名(即以 \0 终止)。第二个参数指定文件应该如何打开,并包括逻辑“与”操作的一个或多个以下标志:
O_RDONLY:文件可以只读
O_RDWR:文件可以读写
O_APPEND:文件可以读或附加
O_CREAT:如果文件还不存在则应该创建
O_EXCL:如果文件已存在,失败而不是创建它(只应该使用 O_CREAT)
O_TRUNC:如果文件已存在,从中除去所有数据(与创建新文件类似)
open() 的第三个参数只在使用
O_CREAT 时需要;它指定了以数字表示的文件许可权(格式与 chown 命令的数值许可权自变量的格式相同。为 open() 指定的许可权受用户的 umask 影响,后者允许用户指定一系列新文件应该获得的缺省许可权。大多数创建文件的程序都使用第三个自变量 0666 调用 open(),可以让用户通过 umask 来控制程序的缺省许可权。(大多数 shell 的 umask 命令都可以更改它。)
例如,清单 3 显示了如何为进行读写打开文件、如果它不存在则创建,以及废弃其中的数据:
清单 3:
int fd;
fd = open("myfile", O_RDWR | O_CREAT | O_TRUNC, 0666)
if (fd < 0) {
/* Some error occurred */
/* ... */
}
open() 返回引用文件的文件描述符。回忆一下,文件描述符总是 >= 0。如果 open() 返回了一个负值,就表示发生了错误,全局变量错误号包含了描述问题的 Unix 错误代码。open() 总尽量返回最小数,如果没有使用文件描述符 0,open() 将总返回 0。
进程带文件结束时,它应该通过
close() 系统调用关闭它,该系统调用的格式为:
int close(int fd);
close 的文件描述符是传递给
close() 的唯一自变量,在成功情况下返回 0。尽管 close() 失败的情况比较少见,但如果文件描述符引用的是远程服务器上的文件,系统无法正确清空它的高速缓存,close() 就可能真的失败。进程终止时,内核自动关闭所有还在打开的文件。
最后的一个常见文件操作是移动文件指针。这(自然)只对带文件指针的文件有意义,如果尝试在不恰当的文件上尝试该操作就会返回错误。lseek() 系统调用用于以下目的:
off_t lseek(int fd, off_t pos, int whence);
off_t 类型是表达 longint (long 就是 lseek 中 "l" 的来历)的一种别致方法。lseek() 返回相对于文件开始处文件指针的最终位置,如果有错误,则返回 -1。这个系统调用希望被移动的文件指针所属的文件描述符作为第一个自变量,将它移动到文件中的位置作为第二个自变量。最后一个自变量描述文件指针的移动
方式。
SEEK_SET 将它移动到从文件开始算起的
pos 字节。
SEEK_END 将它移动到从文件结尾算起的
pos 字节。
SEEK_CUR 从它当前位置开始向文件结尾移动 pos 字节。
open()、close()、write()、read() 和 lseek() 的组合为 Linux 提供了基本的文件访问 API。虽然还有许多其它操纵文件的函数,但这里描述的是最常用的。
大 多数程序员都使用熟悉的 ANSI C 库文件函数,例如 fopen() 和 fread(),而不是在此描述的低级系统调用。可以预见到,fopen() 和 fread() 是在用户级别库中这些系统调用的基础上实现的。仍然会经常看到低级系统调用的使用,特别是在更复杂的程序中。通过熟悉这些例程和接口,您就可以成为一个真
正的 Unix 黑客了。