TLPI读书笔记第4章-文件IO3

 

4.4 读取文件内容:read()

ssize_t read(int fd,void *buffer,size_t count)

count 参数指定最多能读取的字节数。 buffer 参数提供用来存放输入数据的内存缓冲区地址。缓冲区至少应有 count 个字节。

如果 read()调用成功,将返回实际读取的字节数,如果遇到文件结束( EOF)则返回 0, 如果出现错误则返回-1。 ssize_t 数据类型属于有符号的整数类型,用来存放读取的字节数或-1(表示错误)。 一次 read()调用所读取的字节数可以小于请求的字节数。对于普通文件而言,这有可能是因为当前读取位置靠近文件尾部。 当 read()应用于其他文件类型时,比如管道、 FIFO、 socket 或者终端,在不同环境下也会出现 read()调用读取的字节数小于请求字节数的情况。例如,默认情况下从终端读取字符,一遇到换行符( \n), read()调用就会束。

C 语言对字符串处理的约定,在字符串尾部追加标识字符串结束的空字符 如果输入缓冲区的结尾处需要一个表示终止的空字符,必须显式追加。

buffer[numRead]='\0'
printf("the input data was :%s\n",buffer)

 

4.5 数据写入文件:write()

write()系统调用将数据写入一个已打开的文件中

ssize_t write(int fd,void *buffer,size_t count)

write()调用的参数含义与 read()调用相类似。 buffer 参数为要写入文件中数据的内存地址, count参数为欲从 buffer 写入文件的数据字节数, fd 参数为一文件描述符,指代数据要写入的文件。 如果 write()调用成功,将返回实际写入文件的字节数,该返回值可能小于 count 参数值。这被称为“部分写”。对磁盘文件来说,造成“部分写”的原因可能是由于磁盘已满,或是因为进程资源对文件大小的限制。 对磁盘文件执行 I/O 操作时, write()调用成功并不能保证数据已经写入磁盘。因为为了减少磁盘活动量和加快 write()系统调用,内核会缓存磁盘的 I/O 操作。

4.6 关闭文件

int close(int fd)

显式关闭不再需要的文件描述符往往是良好的编程习惯,会使代码在后续修改时更具可读性,也更可靠。进而言之,文件描述符属于有限资源,因此文件描述符关闭失败可能会导致一个进程将文件描述符资源消耗殆尽。在编写需要长期运行并处理大量文件的程序时,比如 shell 或者网络服务器软件,需要特别加以关注。 像其他所有系统调用一样,应对 close()的调用进行错误检查,如下所示:上述代码能够捕获的错误有:企图关闭一个未打开的文件描述符,或者两次关闭同一文件描述符,也能捕获特定文件系统在关闭操作中诊断出的错误条件

4.7 改变文件偏移量:lseek()

对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个 read()或 write()操作的文件起始位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为 0

文件打开时,会将文件偏移量设置为指向文件开始,以后每次 read()或 write()调用将自动对其进行调整,以指向已读或已写数据后的下一字节。因此,连续的 read()和 write()调用将按顺序递进,对文件进行操作。 针对文件描述符 fd 参数所指代的已打开文件, lseek()系统调用依照 offset 和 whence 参数值调整该文件的偏移量。

#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence)

offset 参数指定了一个以字节为单位的数值。whence 参数则表明应参照哪个基点来解释 offset 参数,应为下列其中之一: SEEK_SET 将文件偏移量设置为从文件头部起始点开始的 offset 个字节。 SEEK_CUR 相对于当前文件偏移量,将文件偏移量调整 offset 个字节1。 SEEK_END 将文件偏移量设置为起始于文件尾部的 offset 个字节。也就是说, offset 参数应该从文件最后一个字节之后的下一个字节算起。

lseek()调用成功会返回新的文件偏移量

lseek()调用只是调整内核中与文件描述符相关的文件偏移量记录, 并没有引起对任何物理设备的访问

lseek()并不适用于所有类型的文件。不允许将 lseek()应用于管道、 FIFO、 socket 或者终端。一旦如此, 调用将会失败, 并将 errno 置为 ESPIPE。 另一方面, 只要合情合理, 也可以将 lseek()应用于设备。例如,在磁盘或者磁带上查找一处具体位置

4.7.1 示例程序

 

/** seek_io.c

lseek()与 read()、 write()的协作使用。该程序的第一个命令行参数为将要打开的文件名称,余下的参数则指定了在文件上执行的输入/输出操作。每个表示操作的
参数都以一个字母开头,紧跟以相关值(中间无空格分隔)。
s{offset}:从文件开始检索到 offset 字节位置。
r{length}:在当前文件偏移量处,从文件中读取 length 字节数据,并以文本形式显示。
R{length}:在当前文件偏移量处,从文件中读取 length 字节数据,并以十六进制形式显示。
w{str}:在当前文件偏移量处,向文件写入由 str 指定的字符串

   示例:

        seek_io myfile wxyz s1 r2
*/
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    size_t len;
    off_t offset;
    int fd, ap, j;
    unsigned char *buf;
    ssize_t numRead, numWritten;

    if (argc < 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s file {r<length>|R<length>|w<string>|s<offset>}...\n",
                 argv[0]);
    /* 打开一个文件 */
    fd = open(argv[1], O_RDWR | O_CREAT,
                S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                S_IROTH | S_IWOTH);                     /* rw-rw-rw- */
    if (fd == -1)
        errExit("open");
    /* 遍历参数 */
    for (ap = 2; ap < argc; ap++) {
        switch (argv[ap][0]) {
        case 'r':   /* 输出当前偏移量所在字符 */
        case 'R':   /* 输出当前偏移量所在字符,以16进制方式 */
            len = getLong(&argv[ap][1], GN_ANY_BASE, argv[ap]);

            buf = malloc(len);
            if (buf == NULL)
                errExit("malloc");

            numRead = read(fd, buf, len);
            if (numRead == -1)
                errExit("read");

            if (numRead == 0) {
                printf("%s: end-of-file\n", argv[ap]);
            } else {
                printf("%s: ", argv[ap]);
                for (j = 0; j < numRead; j++) {
                    if (argv[ap][0] == 'r')
                        printf("%c", isprint(buf[j]) ?  buf[j] : '?');
                    else
                        printf("%02x ", buf[j]);
                }
                printf("\n");
            }

            free(buf);
            break;

        case 'w':   /* 把字符串写入当前偏移量 */
            numWritten = write(fd, &argv[ap][1], strlen(&argv[ap][1]));
            if (numWritten == -1)
                errExit("write");
            /*FIXME: should use %zd here, and remove (long) cast */
            printf("%s: wrote %ld bytes\n", argv[ap], (long) numWritten);
            break;

        case 's':   /* 改变偏移量 */
            offset = getLong(&argv[ap][1], GN_ANY_BASE, argv[ap]);
            if (lseek(fd, offset, SEEK_SET) == -1)
                errExit("lseek");
            printf("%s: seek succeeded\n", argv[ap]);
            break;

        default:
            cmdLineErr("Argument must start with [rRws]: %s\n", argv[ap]);
        }
    }

    if (close(fd) == -1)
        errExit("close");

    exit(EXIT_SUCCESS);
}

 

 

4.8 通用IO以外操作:ioctl()

在本章上述通用 I/O 模型之外, ioctl()系统调用又为执行文件和设备操作提供了一种多用途机制。

int ioctl(int fd,int request,.../*argp*/)

fd 参数为某个设备或文件已打开的文件描述符, request 参数指定了将在 fd 上执行的控制操作。具体设备的头文件定义了可传递给 request 参数的常量。 ioctl()调用的第三个参数采用了标准 C 语言的省略符号( ...)来表示(称之为 argp),可以是任意数据类型。 ioctl()根据 request 的参数值来确定 argp 所期望的类型。通常情况下, argp是指向整数或结构的指针,有些情况下,不需要使用 argp。

 

posted @ 2021-04-07 16:21  Mars.wang  阅读(75)  评论(0编辑  收藏  举报