所有高级语言的运行时(runtime)都提供了执行I/O功能的机制。
例如,C语言中提供了包含像printf()和scanf()等这样的标准I/O库函数, C++语言中提供了如 <<和>>这样的重载操作符。
从高级语言程序中通过I/O函数或I/O操作符提出I/O请求,到设备响应并完成I/O请求,涉及到多层次I/O软件和I/O硬件的协作。
I/O子系统和计算机系统一样也采用层次结构:封装+抽象+提供简单接口给上层使用。
I/O软件被组织成从高到低的四个层次,层次越低,则越接近设备而越远离用户程序。这四个层次依次为:
(1)    用户层I/O软件(I/O函数调用系统调用)
(2)    与设备无关的操作系统I/O软件   此层开始属于OS,OS在I/O系统中极其重要
(3)    设备驱动程序
(4)    I/O中断处理程序
大部分I/O软件都属于操作系统内核态程序,最初的I/O请求在用户程序中提出。从用户I/O软件切换到内核I/O软件的唯一办法是异常机制——系统调用(自陷)
最下面的两个层次才会和硬件直接打交道。
OS在I/O子系统中的重要性由I/O系统以下三个特性决定:
(1)共享性:I/O系统被多个程序共享,须由OS对I/O资源统一调度管理,以保证用户程序只能访问自己有权访问的那部分I/O设备,并使系统的吞吐率达到最佳。
(2)复杂性:I/O设备控制细节复杂,需OS提供专门的驱动程序进行控制,这样可对用户程序屏蔽设备控制的细节。 
(3)异步性:不同设备之间速度相差较大,因而,I/O设备与主机之间的信息交换使用异步的中断I/O方式,中断导致从用户态向内核态转移,因此必须由OS提供中断服务程序来处理。
 
因此,各类用户的I/O请求需要通过某种方式传给OS:
  • 最终用户:键盘、鼠标通过操作界面传递给OS
  • 用户程序:通过函数(高级语言)转换为系统调用传递给OS
 
系统调用(陷阱)是特殊异常事件,是OS为用户程序提供服务的手段。
OS提供一组系统调用,为用户进程的I/O请求进行具体的I/O操作:
用户软件可用以下两种方式提出I/O请求,最终都是调用系统调用。
(1)使用高级语言提供的标准I/O库函数。例如,在C语言程序中可以直接使用像fopen、fread、fwrite和fclose等文件操作函数,或printf、putc、scanf和getc等控制台I/O函数。 程序移植性很好。但是,使用标准I/O库函数有以下几个方面的不足:
  • 标准I/O库函数不能保证文件的安全性(无加/解锁机制)
  • 所有I/O都是同步的,只能串行执行,程序必须等待I/O操作完成后才能继续执行
  • 有些I/O功能不适合甚至无法使用标准I/O库函数实现,如,不提供读取文件元数据(文件大小和文件创建时间等)的函数
  • 用它进行网络编程会造成易于出现缓冲区溢出等风险
(2)使用OS提供的API函数或系统调用。例如,在Windows中直接使用像CreateFile、ReadFile、WriteFile、CloseHandle等文件操作API函数,或ReadConsole、WriteConsole等控制台I/O的API函数;对于Unix或Linux用户程序,则直接使用像open、read、write(printf最终也是调用了write)、close等系统调用封装函数(系统级I/O函数)。
注意点:C标准库中提供的函数并没有涵盖所有底层操作系统提供的功能;不同的C标准库函数可能调用相同的系统调用;此外,C标准I/O库函数、UNIX/Linux 和Windows的API函数所提供的I/O操作功能并不是一一对应的。
例如,它们的参数中对文件的标识方式不同:函数read() 和write() 的参数中指定的文件用一个整数类型的文件描述符来标识;而C 标准库函数fread()和fwrite() 的参数中指定的文件用一个指向特定结构的指针类型来标识。
 
API与系统调用有什么区别?
应用编程接口(API)与系统调用两者在概念上不完全相同,它们都是系统提供给用户程序使用的编程接口,但前者指的是功能更广泛、抽象程度更高的函数,后者仅指通过软中断(自陷)指令向内核态发出特定服务请求的函数。
系统调用封装函数是 API 函数中的一种。
API 函数最终通过调用系统调用实现 I/O。一个API 可能调用多个系统调用,不同 API 可能会调用同一个系统调用。但是,并不是所有API 都需要调用系统调用。
从编程者来看,API和系统调用之间没有什么差别。
从内核设计者来看,API 和系统调用差别很大:API 在用户态执行, 系统调用封装函数也在用户态执行,但具体服务例程在内核态执行。
 
用户程序总是通过某种I/O函数或I/O操作符请求I/O操作。
例如,用户进程读一个磁盘文件记录时,可调用C标准I/O库函数fread(),或Windows API函数ReadFile,或Unix/Linux的系统调用封装函数read()来提出I/O请求。不管是C库函数、API函数还是系统调用封装函数,最终都通过操作系统内核提供的系统调用来实现I/O。
即,用户程序中涉及I/O操作的函数最终会被转换为一组与具体机器架构相关的指令序列,这里我们将其称为I/O请求指令序列。
每个指令系统中一定有一类陷阱指令(有些机器也称为软中断指令或系统调用指令),主要功能是为操作系统提供灵活的系统调用机制。
在I/O请求指令序列中,具体I/O请求被转换为这条陷阱指令,在陷阱指令前面则是相应的系统调用参数的设置指令。
当CPU 执行到系统这条陷阱指令时, 会从用户态陷入到内核态;转到内核态执行后, CPU 根据陷阱指令执行时EAX 寄存器中的系统调用号,选择执行一个相应的系统调用服务例程;
在系统调用服务例程的执行过程中可能需要调用具体设备的驱动程序;在设备驱动程座执行过程中启动外设工作,外设准备好后发出中断请求,CPU响应中断后,就调出中断服务程序执行,在中断服务程序中控制主机与设备进行具体的数据交换。
标准I/O库函数比系统调用封装函数抽象层次高,后者属于系统级I/O函数。与系统提供的API函数一样,前者是基于后者实现的。
两者关系如图所示:
printf()函数的调用过程如下:
以下是write封装函数的原型:
ssize_t write(int fd, const void * buf, size_t n);
//fd是文件描述符,每个文件用一个int型的文件描述符来标示文件
//buf是要写的字符串的首地址。buf是void指针,可以通过强制类型转换变成任何类型的指针
//n是要写的字符个数,size_t是unsigned int
//返回值是真正写的字符个数,ssize_t是int,因为返回值可能为-1
write:
pushl %ebx //将EBX入栈(EBX为被调用者保存寄存器)
movl $, %eax //将系统调用号4送EAX
movl (%esp), %ebx //将第一个参数-文件描述符fd送EBX
movl (%esp), %ecx //将第二个参数-所写字符串首址buf送ECX
movl (%esp), %edx //将第三个参数-所写字符个数n送EDX
int $0x80 //进入系统调用处理程序system_call执行
cmpl $-, %eax //检查返回值,假定最大出错码为131(所有正数都小于FFFFFF83H)
jbe .L1 //若无错误,则跳转至.L1(按无符号数比)
negl %eax //将返回值取负送EAX
movl %eax, error //将EAX的值送error
movl $-, %eax //将write函数返回值置-1
.L1:
popl %ebx
ret
Linux 中有一个系统调用的统一人口,即系统调用处理程序system_call()。CPU 执行陷阱指令后,便转到system_call()的第一条指令执行。
进入system_call后根据调用号是4跳转到sys_read服务例程,然后在Linux内核中单向调用20次以上。
内核执行write的结果在EAX中返回,正确时为所写字符数(最高位为0),出错时为错误码的负数(最高位为1)
某函数调用了printf(),执行到调用printf()语句时,便会转到C语言I/O标准库函数printf()去执行;
printf()通过一系列函数调用,最终会调用函数write(); 
调用write()时,便会通过一系列步骤在内核空间中找到write对应的系统调用服务例程sys_write来执行。
在system_call中根据系统调用号知道要转到sys_write执行。

操作系统-I/O(6)I/O与系统调用的更多相关文章

  1. Day3---------Linux操作系统

    ---恢复内容开始--- 网络基础和DOS命令 一.网络分类 1.地理位置 1).局域网(LAN) 2).城域网(MAN) 3).广域网(WAN) 2.传输介质 1).有线网 2).光纤网 3).无线 ...

  2. 操作系统层面聊聊BIO,NIO和AIO (epoll)

    BIO 有了Block的定义,就可以讨论BIO和NIO了.BIO是Blocking IO的意思.在类似于网络中进行read, write, connect一类的系统调用时会被卡住. 举个例子,当用re ...

  3. 简介几种系统调用函数:write、read、open、close、ioctl

    在 Linux 中,一切(或几乎一切)都是文件,因此,文件操作在 Linux 中是十分重要的,为此,Linux 系统直接提供了一些函数用于对文件和设备进行访问和控制,这些函数被称为系统调用(sysca ...

  4. [Kernel]理解System call系统调用

    转自:http://os.51cto.com/art/200512/13510.htm 现在,您或许正在查看设备驱动程序,并感到奇怪:“函数 foo_read() 是如何被调用的?”或者可能疑惑: “ ...

  5. [转帖]PostgreSQL ident和peer基于操作系统用户的认证

    PostgreSQL ident和peer基于操作系统用户的认证 https://yq.aliyun.com/articles/55898 其实 local和127. 还是有区别的 这里面应该就是对应 ...

  6. 《操作系统导论》第14章 | 内存操作API

    内存类型 在运行一个C程序的时候,会分配两种类型的内存.第一种称为栈内存,它的申请和释放操作是编译器来隐式管理的,所以有时也称为自动内存.假设需要在func()函数中为一个整形变量x申请空间,我们只需 ...

  7. Python异步通信模块asyncore

    https://docs.python.org/2/library/asyncore.html This module provides the basic infrastructure for wr ...

  8. goroutine

    Go语言从诞生到普及已经三年了,先行者大都是Web开发的背景,也有了一些普及型的书籍,可系统开发背景的人在学习这些书籍的时候,总有语焉不详的感觉,网上也有若干流传甚广的文章,可其中或多或少总有些与事实 ...

  9. .net开发笔记(十三) Winform常用开发模式第一篇

    上一篇博客最后我提到“异步编程模型”(APM),之后本来打算整理一下这方面的材料然后总结一下写篇文章与诸位分享,后来在整理的过程中不断的延伸不断地扩展,发现完全偏离了“异步编程”这个概念,前前后后所有 ...

  10. [转载] goroutine背后的系统知识

    原文: http://www.sizeofvoid.net/goroutine-under-the-hood/ 文章写的非常好, 对内部原理解释的非常清楚, 是我喜欢的风格, 感谢作者的精彩文章. = ...

随机推荐

  1. PHP password_hash() 函数

    password_hash() 函数用于创建密码的散列(hash) PHP 版本要求: PHP 5 >= 5.5.0, PHP 7高佣联盟 www.cgewang.com 语法 string p ...

  2. 4.17 省选模拟赛 远行 LCT 启发式合并 倍增

    容易写出nQ的暴力 由于数据是期望的时间 所以直接dfs可以跑的很快 可以拿到70分. 当然 可以进一步优化暴力 使用换根dp 然后可以将暴力优化到n^2. const int MAXN=300010 ...

  3. centos7与centos6命令差异

    技术群: 816227112 查看ip centos6 : ifconfigcentos7 : ip addr 修改hostname centos6 : 修改/etc/sysconfig/networ ...

  4. 最详尽的datagrip使用

    什么是datagrip? datagrip是jetbrains旗下的一款数据库管理工具,相信做过java开发的都知道,idea就是这家公司发明的. 为什么要使用datagrip? datagrip的有 ...

  5. day4. 运算符

    运算符包括算数运算符.比较运算符.赋值运算符.成员运算符.身份运算符.逻辑运算符.位运算符 python运算符 注意点 算数运算符 % 取余 , //地板除 , ** 幂运算 比较运算符 == 比较两 ...

  6. 吴太银:华为消费者云服务Cassandra使用场景与最佳实践

    大家好,我是华为消费者云的吴太银. 我今天分享的主要是华为消费者云服务使用Cassandra的应用场景和最佳实践.我这个可能跟其他嘉宾分享的不太一样,因为前几个嘉宾讲的实际上对Cassandra原生的 ...

  7. VMware虚拟机磁盘收缩的几种方法

    原文地址:http://www.cnblogs.com/5201351/p/4290401.html 根据下面转载的内容,我在VMware 12.0.0 build-2985596的ubuntu上做试 ...

  8. myBatis源码解析-数据源篇(3)

    前言:我们使用mybatis时,关于数据源的配置多使用如c3p0,druid等第三方的数据源.其实mybatis内置了数据源的实现,提供了连接数据库,池的功能.在分析了缓存和日志包的源码后,接下来分析 ...

  9. Linux学习笔记之如何在图形界面旁边把终端添加显示出来

    首先旁边无终端,我们可以点击ctrl+alt+t,可以把终端显示出来 右键点击终端,然后点击Lock to Launcher,然后完成 PS:不想显示也可以点击其右键,选择Unlock from La ...

  10. 剑指offer之字符串是否为数值

    1. 题目 这是<剑指offer>上的一道题,刚开始觉得这是一道挺简单的题目,后来发现自己太年轻了,考虑的因素太少了,思考了而是分钟还是无从下手,看了作者的思路深深被他折服了,题目如下: ...