输入/输出(I/O)是在主存和外部设备(例如磁盘驱动器、终端和网络)之间复制数据的过程。

输入操作是从I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备。

Unix I/O


一个Linux文件就是一个个字节的序列:

所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行:

  • 打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的信息。应用程序只需记住这个描述符。
  • Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2).头文件<unistd.h>定义了常量STDIN_FILENO\STDOUT_FILENO和STDERR_FILENO,它们用来显式的描述符值。
  • 改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式的设置文件的当前位置k。
  • 读写文件。一个读操作就是从头文件复制个字节到内存,从当前文件位置开始,然后将增加到。给定一个大小为m字节的文件,当时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件文件结尾处并没有明确的“EOF符号”。类似的,写操作就是从内存复制个字节到一个文件,从当前文件位置开始,然后更新
  • 关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

文件


每个Linux文件都有一个类型(type)来表明它在系统中的角色:

  • 普通文件(regular file)包含任意数据。应用程序常常要区分文本文件(text file)和二进制文件(binary file),文本文件是只含有ASCII或Unicode字符的普通文件;二进制文件是所有其它的文件。对内核而言,文本文件和二进制文件没有区别。Linux文本文件包含了一个文本行(text line)序列,其中每一行都是一个字符序列,以一个新行符(“\n”)结束。新行符与ASCII的换行符(LF)是一样的,其数字值为0x0a。
  • 目录(directory)是包含一组链接(link)的文件,其中每个链接都将一个文件名(filename)映射到一个文件,这个文件可能是另一个目录。每个目录至少含有两个条目:“.”是到该目录自身的链接,以及“..”是到目录层次结构中父目录(parent directory)的链接。可以使用mkdir命令创建一个目录,用ls查看其内容,用rmdir删除该目录。
  • 套接字(socket)是用来与另一个进程进行跨网络通信的文件。

其它文件类型包含命名通道(named pipe)、符号链接(symbolic link),以及字符和块设备(character and block device)。

文件路径名的两种形式:

  • 绝对路径(absolute pathname)以一个斜杠开始,表示从根节点开始的路径。
  • 相对路径(relative pathname)以文件名开始,表示从当前工作目录开始的路径。

打开和关闭文件


进程是通过调用open函数来打开一个已存在的文件或者创建一个新文件的:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>int open(char *filename, int flags, mode_t mode);//返回:若成功则为新文件描述符,若出错为-1。

open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。

flags参数指明了进程打算如何访问这个文件:

  • O_RDONLY:只读。
  • O_WRONLY:只写。
  • O_RDWR:可读可写。

flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的指示:

  • O_CREAT:如果文件不存在,就创建它的一个截断的(truncated)(空)文件。
  • O_TRUNC:如果文件已经存在,就截断它。
  • O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。

mode参数指定了新文件的访问权限位。这些位的符号如下表格所示。

作为上下文的一部分,每个进程都有一个umask,它是通过调用umask函数来设置的。当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置为mode & ~ umask。例如,假设定下面的mode和umask默认值:

#define DEF_MODE    S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH|S_IWOTH
#define DEF_UMASK   S_IWGRP|S_IWOTH

接下来,下面的代码段创建一个新文件,文件的拥有者有读写权限,而所有其他的用户都有读权限:

umask(DEF_UMASK);
fd = Open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE);
掩码 描述

S_IRUSR

S_IWUSR

S_IXUSR

使用者(拥有者)能够读这个文件

使用者(拥有者)能够写这个文件

使用者(拥有者)能够执行这个文件

S_IRGRP

S_IWGRP

S_IXGRP

拥有者所在组的成员能够读这个文件

拥有者所在组的成员能够写这个文件

拥有者所在组的成员能够执行这个文件

S_IROTH

S_IWOTH

S_IXOTH

其他人(任何人)能够读这个文件

其他人(任何人)能够写这个文件

其他人(任何人)能够执行这个文件

进程通过调用close函数关闭一个打开的文件。

#include <unistd.h>int close(int fd);//返回:若成功则为0,若出错则为-1。

读和写文件


应用程序是通过分别调用read和write函数来执行输入和输出的。

#include <unistd.h>ssize_t read(int fd, void *buf, size_t n);//返回:若成功则为读的字节数,若EOF则为0,若出错则为-1。
ssize_t write(int fd, const void *buf, size_t n);//返回:若成功则为写的字节数,若出错则为-1。

在某些情况下,read和write传送的字节比应用程序要求的要少。这些不足值(short count)不表示有错误。出现这种情况的原因有:

  • 读时遇到EOF。假设我们准备读一个文件,该文件从当前文件位置开始只含有20个字节,而我们以50个字节的片进行读取。这样一来,下一个read返回的不足值为20,此后的read将通过返回不足值0来发出EOF信号。
  • 从终端读文本行。如果打开文件是与终端相关联的(如键盘和显示器),那么每个read函数将一次传送一个文本行,返回的不足值等于文本行的大小。
  • 读和写网络套接字(socket)。如果打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值。对Linux管道(pipe)调用read和write时,也可能出现不足值,这种进程间通信机制不在讨论。

实际上,除了EOF,当你在读磁盘文件时,将不会遇到不足值,而且在写磁盘文件时,也不会遇到不足值。然后,如果你想创建健壮的(可靠的)诸如Web服务器这样的网络应用,就必须通过反复调用read和write处理不足值,直到所有需要的字节都传送完毕。

读取文件元数据


应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(有时也成为文件的元数据(metadata))。

#include <unistd.h>
#include <sys/stat.h>int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);//返回:若成功则为0,若出错则为-1。

stat函数以一个文件名作为输入,并填写下图中的一个stat数据结构。fstat函数是相似的,只不过是以文件描述符而不是文件名作为输入。

/* Metadata returned by the stat and fstat functions */
struct stat{dev_t            st_dev;        /* Device */ino_t            st_ino;        /* inode */mode_t           st_mode;       /* Protection and file type */nlink_t          st_nlink;      /* Number of hard links */uid_t            st_uid;        /* User ID of owner */gid_t            st_gid;        /* Group ID of owner */dev_t            st_rdev;       /* Device type (if inode device) */off_t            st_size;       /* Total size for filesystem */unsigned long    st_blksize;    /* Total size, in bytes */unsigned long    st_blocks;     /* Block size foe filesystem I/O */time_t           st_atime;      /* Time of last access */time_t           st_mtime;      /* Time of last modification */time_t           st_ctime;      /* Time of last change */
};

Linux在sys/stat.h中定义了宏谓词来确定st_mode成员的文件类型:

  • S_ISREG(m)。这是一个普通文件吗?
  • S_ISDIR(m)。这是一个目录文件吗?
  • S_ISSOCK(m)。这是一个网络套接字吗?

下面展示了如何使用这些宏和stat函数来读取和解释一个文件的st_mode位。

 1    #include "csapp.h"23    int main(int argc, char **argv)4    {5        struct stat stat;6        char *type, *readok;78        Stat(argv[1], &stat);9        if(S_ISREG(stat.st_mode))    /* Determine file type */
10            type = "regular";
11        else if(S_ISDIR(stat.st_mode))
12            type = "directory";
13        else
14            type = "other";
15        if((stat.st_mode & S_IRUSR))    /* Check read access */
16            readok = "yes";
17        else
18            readok = "no";
19
20        printf("type: %s, read: %s\n", type, readok);
21        exit(0);
22    }

读取目录内容


应用程序可以用readdir系列函数来读取目录的内容。

#include <sys/types.h>
#include <dirent.h>DIR *opendir(const char *name);//返回:若成功,则为处理器的指针;若出错,则为NULL。

函数opendir以路径名为参数,返回指向目录流(directory stream)的指针。流是对条目有序列表的抽象,在这里是指目录项的列表。

#include <dirent.h>struct dirent *readdir(DIR *dirp);//返回:若成功,则为指向下一个目录项的指针;若没有更多的目录项或出错,则为NULL。

每次对readdir的调用返回的都是指向流dirp中下一个目录项的指针,或者,如果没有更多目录项则返回NULL。每个目录项都是一个结构,其形式如下:

struct dirent{ino_t d_ino;        /* inode number */char  d_name[256];  /* Filename */
};

虽然有些Linux版本包含了其他的结构成员,但是只有这两个对所有系统来说都是标准的。成员d_name是文件名,d_ino是文件位置。

如果出错,则readdir返回NULL,并设置errno。可惜的是,唯一能区分错误和流结束情况的方法是检查自调用readdir以来errno是否被修改过。

#include <dirent.h>int closedir(DIR *dirp);//返回:成功为0;错误为-1。

函数closedir关闭流并释放其所有的资源。下面展示了怎样用readdir来读取目录的内容。

 1    #include "csapp.h"23    int main(int argc, char **argv)4    {5        DIR *streamp;6        struct dirent *dep;78        streamp = Opendir(argv[1]);9
10         errno = 0;
11        while((dep = readdir(streamp)) != NULL){
12            printf("Found file: %s\n", dep->d_name);
13        }
14        if(errno != 0)
15            unix_error("readdir error");
16
17        Closedir(streamp);
18        exit(0);
19    }

共享文件


可以用许多不同的方式来共享Linux文件。除非你很清楚内核是如何表示打开的文件,否则文件共享的概念相当难懂。内核用三个相关的数据结构来表示打开的文件:

  • 描述符表(descriptor table)。每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
  • 文件表(file table)。打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成(针对我们的目的)包括当前的文件位置、引用计数(reference count)(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少响应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为零。
  • v-node表(v-node table)。同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。

下图展示了一个示例,其中描述符1和4通过不同的打开文件表表项来引用两个不同的文件。这是一种典型的情况,没有共享文件,并且每个描述符对应一个不同的文件。

如下图所示,多个描述符也可以通过不同的文件表表项来引用同一个文件。例如,如果以同一个filename调用open函数两次,就会发生这种情况。关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。

我们也能理解父子进程是如何共享文件的。假设在调用fork之前,父进程有图10-11所示的打开文件。然后,下图展示了调用fork之后的情况。子进程有一个父进程描述表的副本。父进程共享相同的打开文件表集合,因此共享相同的文件位置。一个很重要的结果就是,在内核删除相应文件表表项之前,父子进程必须都关闭了它们的描述符。

I/O重定向


Linux shell提供了I/O重定向操作符,允许用户将磁盘文件和标准输入输出联系起来。例如,键入

linux> ls > foo.txt

使得shell加载和执行ls程序,将标准输出重定向到磁盘文件foo.txt。

I/O重定向工作方式:

  1. 使用dup2函数。
#include <unistd.h>/* dup2函数复制描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd。
*/
int dup2(int oldfd, int newfd);//返回:若成功则为非负的描述符,若出错则为-1。

标准I/O


C语言定义了一组高级输入输出函数,称为标准I/O库。

这个库(libc)提供了打开和关闭文件的函数(fopen和fclose)、读和写字节的函数(fread和fwrite)、读和写字符串的函数(fgets和fputs),以及复杂的格式化的I/O函数(scanf和printf)。

标准I/O库将一个打开的文件模型化为一个流。对程序员而言,一个流就是一个指向FILE类型的结构的指针。每个ANSI C程序开始时都有三个打开的stdin、stdout和stderr,分别对应于标准输入、标准输出和标准错误:

#include <stdio.h>
extern FILE *stdin;    /* Standard input (descriptor 0) */
extern FILE *stdout;   /* Standard output (descriptor 1) */
extern FILE *stderr;   /* Standard error (descriptor 2) */

类型为FILE的流是对文件描述符和流缓冲区的抽象。流缓冲区的目的和RIO读缓冲区的一样:就是使开销较高的Linux I/O系统调用的数量尽可能得小。例如,假设我们有一个程序,它反复调用标准I/O得getc函数,每次调用返回文件的下一个字符。当第一次调用getc时,库通过调用一次read函数来填充流缓冲区,然后将缓冲区中的第一个字节返回给应用程序。只要缓冲区中还有未读的字节,接下来对getc的调用就能直接从流缓冲区得到服务。

资料《深入理解计算机系统》。

计算机系统:系统级I/O相关推荐

  1. 深入理解计算机系统——系统级I/O

    一.UNIX I/O     在UNIX系统中有一个说法,一切皆文件.所有的I/O设备,如网络.磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行.这种将设备映射为文件的方式,允 ...

  2. 5.1深入理解计算机系统——系统级I/O

    一.UNIX I/O     在UNIX系统中有一个说法,一切皆文件.所有的I/O设备,如网络.磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行.这种将设备映射为文件的方式,允 ...

  3. 安卓* 系统级 Java*/C++ 代码调试

    1 简介 应用开发人员当前可通过安卓* SDK 来设计和构建安卓应用项目,并使用 Eclipse* 进行调试. 但是它并没有提供相应的功能来调试系统级 Java*/C++ 代码,该代码位于安卓代码库中 ...

  4. 软硬件协同设计的系统级开发环境~BPS软件介绍

    软硬件协同设计的系统级开发环境~BPS软件介绍 0 赞 发表于 2010/7/19 14:38:11 阅读(32881) 评论(0) 1)BPS简介: BEEcube Platform Studio( ...

  5. 性能调优之Java系统级性能监控及优化

    性能调优之Java系统级性能监控及优化 对于性能调优而言,通常我们需要经过以下三个步骤:1,性能监控:2,性能剖析:3,性能调优 性能调优:通过分析影响Application性能问题根源,进行优化Ap ...

  6. 系统级性能分析工具perf的介绍与使用

    测试环境:Ubuntu16.04 + Kernel:4.4.0-31 apt-get install linux-source cd /usr/src/tools/perf make &&am ...

  7. 本地方法中printf如何传给java--java系统级命名管道

    本地方法中printf如何传给java--java系统级命名管道 摘自:https://blog.csdn.net/dog250/article/details/6007301 2010年11月13日 ...

  8. linux系统下常用或有用的系统级命令

    本文章记录我在linux系统下常用或有用的系统级命令,包括软硬件查看.修改命令,有CPU.内存.硬盘.网络.系统管理等命令.但本文不打算介绍生僻命令,也不介绍各个linux发行版下的特有命令,且以后会 ...

  9. ac9560不支持承载_IPFS 最新进展:0.5 版本改进较大,已出现系统级支持 | 火星号精选...

    免责声明:本文旨在传递更多市场信息,不构成任何投资建议.文章仅代表作者观点,不代表火星财经官方立场. 小编:记得关注哦 来源:IPFS原力区 原文标题:IPFS 最新进展:0.5 版本改进较大,已出现 ...

  10. linux 解析pdf下载工具,Linux高级系统级性能分析工具-perf.pdf

    Linux高级系统级性能分析工具-perf Linux 的系统级性能剖析工具‐perf (二) 承刚 TAOBAO  Kernel Team chenggang.qin@ 第三章  Perf top ...

最新文章

  1. 转载:thread的六种状态
  2. 7.5 程序示例--PCA for 数据可视化-机器学习笔记-斯坦福吴恩达教授
  3. mysql 事物封装_mysqls 一个node对mysql的封装库 链式调用、支持事务
  4. Json与List的相互转换
  5. python爬虫从入门到放弃(九)之 实例爬取上海高级人民法院网开庭公告数据
  6. Q89:全局光照(Global Illumination)——Path Tracing(只用于间接光照)
  7. 根据日志统计出每个用户在站点所呆时间最长的前2个的信息
  8. 精英主板bios_各品牌主板和品牌电脑启动项与bios启动键大全!!值得收藏
  9. 强制刷机NOKIA E6-00方法
  10. nanodlp micro USB mini USB接线图
  11. 人工智能就是计算机科学的英文,AI(人工智能)的英文全称?AI指什么,包含什么?
  12. 软件研发过程客观体检指标
  13. Google Chrome 启动 0x0000005 崩溃问题的解决方法
  14. jquery利用ajax请求数据渲染二级菜单
  15. [Unicode] Unicode的上标与下标
  16. python控制苹果手机触摸屏失灵怎么办_苹果6sp屏幕失灵怎么办 这些解决方法绝对最易学...
  17. Android性能优化全方面解析
  18. 基于Arduino uno单片机的仿生螃蟹制作
  19. golang设计模式——备忘录模式
  20. QPST驱动安装失败的原因以及解决方法

热门文章

  1. 如何写数据分析岗位简历?看完瞬间秒懂
  2. 百度智能云服务网格产品 CSM 发布 | 火热公测中
  3. 什么是 503 服务不可用错误?
  4. 【解决方法】Windows7 任务计划程序 “任务计划程序服务不可用。任务计划程序将尝试重新与其建立连接。”
  5. 恶魔奶爸语法4-6课
  6. 阿里云 DataV 产品简介
  7. win 平台上排名第二的下载工具——IDM
  8. 【PS】证件照修改尺寸
  9. 通过二进制逻辑运算解题
  10. 全面解读人工智能、大数据和云计算的关系