这一章的主题是了解几种 FILE I/O模型下的system calls。他们分别是 open(), read(), write(), close(), lseek()以及ioctl()。
4.1 简单介绍
所有上述和file I/O相关的system calls都会涉及到一个概念 file descriptor文件描述符,它通常是非负整数,它通常用于引用到各种打开的文件类型,比如pipes, FIFOs, sockets, terminals, devices以及常规文件。每个进程也有自己的文件描述符集合。
当然在文件打开之前,已经有三个标准的文件描述符被占用,并且他们可以被开始的程序所继承,即
在usr/include/unistd.h中则可以找到相对应的#define,当然我们也可以使用0,1,2直接代替他们的文件描述符。
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
当然按照书上提示的一点,stdin, stdout, stderr当然是指向process标准input,output以及error的变量,但是他们也可以被改变去指向任何其他的文件通过freopen()库函数。当stdout接受了freopen的文件描述符后,大概率它也就不再是1了。
下面是属于universal I/O模型的最重要的四个system calls,下面做一些简单的介绍:
-
int open(const char *pathname, int flags, mode_t mode);
open system call会打开以pathname (path + name)命名文件,然后返回所打开文件指向的文件描述符。flags主要描述类似是否想要产生新文件如果想要的文件本身不存在的话。mode主要描述该打开的文件应该后面定义为何种权限如果是新生成文件的话。具体内容见下一小章节。
-
ssize_t read(int fd, void *buf, size_t count);
read system call 帮助从fd所指向的文件当中读取最多count字节数的内容到buffer中。numread接收返回的实际读取字节数,如果文件为空则返回0。
-
ssize_t write(int fd, const void *buf, size_t count);
write system call 帮助将buffer中存储的内容里最多count字节的内容写入到fd所指向的文件当中。返回值为实际写入fd指向文件的总字节数。
-
int close(int fd);
在所有I/O活动结束之后调用该system call为了释放文件描述符以及它所关联的内核资源。
以下是书上所提供的一个简易版cp命令符的造轮子程序,调用方式为./copy fromFile toFile。
这里是我所准备好的copy.c 以及与copy.c相关文件以及他们的文件夹系统:
.
├── Makefile
└── src
├── fileio
│ ├── copy.c
│ └── Makefile
└── lib
├── ename.c.inc
├── error_functions.c
├── error_functions.h
├── get_num.c
├── get_num.h
├── Makefile
└── tlpi_hdr.h
这里面除了Makefile以外,别的都可以在List of source code files, by chapter, from "The Linux Programming Interface" 找到源代码。
依照书上的方式,我们先放出来copy.c的代码,在之后的章节中再一点一点分析:
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"
#ifndef BUF_SIZE /* Allow "cc -D" to override definition */
#define BUF_SIZE 1024
#endif
int
main(int argc, char *argv[])
{
int inputFd, outputFd, openFlags;
mode_t filePerms;
ssize_t numRead;
char buf[BUF_SIZE];
if (argc != 3 || strcmp(argv[1], "--help") == 0)
usageErr("%s old-file new-file\n", argv[0]);
/* Open input and output files */
inputFd = open(argv[1], O_RDONLY);
if (inputFd == -1)
errExit("opening file %s", argv[1]);
openFlags = O_CREAT | O_WRONLY | O_TRUNC;
filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH; /* rw-rw-rw- */
outputFd = open(argv[2], openFlags, filePerms);
if (outputFd == -1)
errExit("opening file %s", argv[2]);
/* Transfer data until we encounter end of input or an error */
while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
if (write(outputFd, buf, numRead) != numRead)
fatal("write() returned error or partial write occurred");
if (numRead == -1)
errExit("read");
if (close(inputFd) == -1)
errExit("close input");
if (close(outputFd) == -1)
errExit("close output");
exit(EXIT_SUCCESS);
}
4.2 Universality of I/O
UNIX I/O最大的特点就是university I/O 通用IO的概念,也就意味着四个system call, open(), read(), write()可以用于任何一种文件包括设备和命令行终端。
因为Universality的设备相关的细节仅被在kernel中处理,所以我们可以在编程的时候一般忽略与设备相关的因素。
4.3 打开文件:open()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
这里pathname包含了它具体的文件的地址和文件名,当然相对应的,当成功打开并返回的时候,我们可以得到一个File Descriptor来作为该文件的索引。书上有一个例子比较有意思,当close(STDIN_FILENO)成功之后,它会释放File Descriptor 0,这个时候再打开一个新的文件的时候,因为FD总是会分配最低没有被占用的数字给新FD,这个时候新打开文件的FD将会取得0这个数字。更具体的关于FD的内容将在5.5章展开。
当然如果没有正确打开,它会返回-1并写入errno。
RETURN VALUE
open(), openat(), and creat() return the new file descriptor, or -1 if
an error occurred (in which case, errno i