Unix/Linux编程:通用的IO模型

概述

  • 所有执行IO操作的系统调用都以文件描述符,一个非负整数(通常是小整数)来指代打开的文件
  • 文件描述符用以表示所有类型的已打开文件,包括管道、FIFO、socket、终端、设备和普通文件
  • 针对每个进程,文件描述符都自成一套

按照管理,大多数程序都期望能够使用三种标准的文件描述符,如下表所示:

  • 在程序开始运行前,shell代表程序打开这三个文件描述符。
  • 更确切地说,程序继承了shell文件描述符的副本-----在shell的日常操作中,这3个描述符始终是打开的(在交互式shell中,这3个文件描述符通常指向shell运行所在的终端)。
  • 如果命令行指定对输入/输出进行重定向操作,那么shell会对文件描述符做适当修改,然后再启动程序
    Unix/Linux编程:通用的IO模型
    在程序中指代这些文件描述符时,可以使用数字(0、1、2)表示,或者采用<unistd.h>所定义的POSIX标准名称-----更为可取。

总结

文件描述符

  • 对于内核而言,所有打开的文件都通过文件描述符引用
    • 当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符
    • 当读/写一个文件时,使用open或者create返回的文件描述符标识该文件,将其作为参数传递给read或者write
  • 文件描述符是一个非负整数
    • 文件描述符0(STDIN_FILENO)与进程的标准输入关联
    • 文件描述符1(STDOUT_FILENO)与进程的标准输出关联
    • 文件描述符2(STDERR_FILENO)与进程的标准错误关联

API

open

NAME
       open - 用来 打开一个 文件或设备

SYNOPSIS 总览
       #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)

描述 (DESCRIPTION)
       open()通常用于将[路径名]转换为一个[文件描述符](一个非负的小整数, 在 read, write等[I/O操作中]将会被使用). 
             当open()调用成功, 它会返回一个新的文件描述符(永远取未用描述符的最小值). 这个调用创建一个新的打开文件, 
             即分配一个新的独一无 二的文件描述符, 不会与运行中的任何其他程序共享(但可以通过fork(2)系统调用实现共享). 
             这个新的文件描述符在其后对打开文件操作的函数中使用.(参考 fcntl(2)).  文件的读写指针被置于文件头




       参数 flags 是通过 O_RDONLY, O_WRONLY 或 O_RDWR (指明 文件 是以 只读 , 只写 或 读写 方式 打开的) 与 下面的 零个 或 多个 可选模式 按位 -or 操作 得到的:

       O_CREAT
              若文件  不存在  将 创建 一个 新 文件.  新 文件 的 属主 (用户ID) 被 设置 为 此 程序 的 有效 用户 的 ID.  同样 文件 所属 分组 也 被 设置 为 此 程序 的 有效 分组 的 ID 或者
              上层 目录 的 分组 ID (这 依赖 文件系统 类型 ,装载选项 和 上层目录 的 模式, 参考,在 mount(8) 中 描述 的 ext2 文件系统 的 装载选项 bsdgroups 和 sysvgroups )

       O_EXCL 通过 O_CREAT, 生成 文件 , 若 文件 已经 存在 , 则 open 出错 , 调用 失败 . 若是 存在 符号联接 , 将会 把 它的 联接指针 的 指向 文件 忽略.  O_EXCL is broken on NFS file sys‐
              tems,  programs which rely on it for performing locking tasks will contain a race condition.  The solution for performing atomic file locking using a lockfile is to cre‐
              ate a unique file on the same fs (e.g., incorporating hostname and pid), use link(2) to make a link to the lockfile. If link() returns 0, the lock is successful.  Other‐
              wise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.

       O_NOCTTY
              假如 pathname 引用 一个 终端设备 — 参考 tty(4) — 即使 进程 没有 控制终端 ,这个 终端 也 不会 变成 进程 的 控制 终端.


freopen

虽然stdin、stdout、stderr变量在程序初始化时用于指代进程的标准输入、标准输出和标准错误,但是调用freopen()库函数可以使这些变量指代其他任何文件对象。这为其标准操作的一部分,freopen()可以将流(stream)重新打开之际一并更换隐匿其中的文件描述符。也就是说,针对stdout调用freopen()函数后,无法保证stdout变量值仍未。

应用:cp命令

#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>

#ifndef BUF_SIZE
#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){
        fprintf(stderr, "%s old-file new-file\n", argv[0]);
        exit(0);
    }

    inputFd = open(argv[1], O_RDONLY);
    if (inputFd == -1){
        fprintf(stderr, "opening file %s \n", argv[1]);
        exit(0);
    }

    openFlags = O_CREAT | O_WRONLY | O_TRUNC;
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
    outputFd = open(argv[2], openFlags, filePerms);
    if (outputFd == -1){
        fprintf(stderr, "opening file %s \n", argv[2]);
        exit(0);
    }

    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0){
        if (write(outputFd, buf, numRead) != numRead){
            fprintf(stderr, "fatal:  couldn't write whole buffer!\n");
            goto _endl;
        }
    }

    if(numRead == -1){
        fprintf(stderr, "read !\n");
    }

_endl:
    if(close(inputFd) == -1){
        fprintf(stderr, "close file %s \n", argv[1]);
    }
    if(close(outputFd) == -1){
        fprintf(stderr, "close file %s \n", argv[1]);
    }
}

使用:

./copy oldfile newfile

UNIX IO模型最显著的特点之一是其输入/输出的通用性概念。这意味着使用4个同样的系统调用open()、read()、write()和close()可以对所有类型的文件执行IO操作,包括终端之类的设备。也就是说,仅使用系统调用编写的程序,对任何类型的文件都是有效的。也就是说:
Unix/Linux编程:通用的IO模型
要实现通用IO:

  • 必须确保每一个文件系统和设备驱动程序中都实现了相同的IO系统调用集。
  • 由于文件系统或者设备所特有的操作细节都在内核中处理,在编程中通常可以忽略设备专有的因素。
  • 一旦应用程序需要访问文件系统或者设备的专有功能时,可以选择选择瑞士军刀般的ioctl()系统调用,该调用为每一个通用IO模型之外的专有特性提供了访问接口。
上一篇:Linux之进程替换


下一篇:int main(int argc, const char * argv[])