所有高级语言的运行时(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. pandas之数值计算与统计

    数值计算与统计 对于DataFrame来说,求和.最大.最小.平均等统计方法,默认是按列进行统计,即axis = 0,如果添加参数axis = 1则会按照行进行统计. 如果存在空值,在统计时默认会忽略 ...

  2. 一个简单的Android小实例分享,包含recycleView与recyclerView嵌套

    先上图: 1.首页 2.第二页 3.第三页 项目目录: 代码不多,本人太懒,就不贴了 项目地址:

  3. 官宣!AWS Athena正式可查询Apache Hudi数据集

    1. 引入 Apache Hudi是一个开源的增量数据处理框架,提供了行级insert.update.upsert.delete的细粒度处理能力(Upsert表示如果数据集中存在记录就更新:否则插入) ...

  4. Django学习路9_流程复习

    以下图示为 学习过程中,在千锋教育视频上截图的                     视频链接: https://www.bilibili.com/video/BV1rx411X717?p=11 2 ...

  5. PHP gettimeofday() 函数

    ------------恢复内容开始------------ 实例 返回当前时间: <?php// Print the array from gettimeofday()print_r(gett ...

  6. PHP fputs() 函数

    定义和用法 fputs() 函数将内容写入一个打开的文件中. 函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行. 如果函数成功执行,则返回写入的字节数.如果失败,则返回 FAL ...

  7. HDU Typewriter 6583 dp SAM 卡常

    LINK:Typewriter 好久没写SAM了 什么都给忘了. 写了大概2h.感觉被卡常还看了题解. 考虑dp 然后容易想到维护前面的一个j决策 尽可能小. 然后每次考虑向后加一个字符 不过不行就跳 ...

  8. ZR 提高十连 DAY 4

    哇 这题目怎么一次比一次毒瘤 当然这次还好 有会做的题目. T1 一眼看上去 毒瘤!再看一眼 我真不想看了 扔了. T2 哇感觉能写 哇这不是 随便都有40分了么 二分?优化一下65到手了.然后剩下的 ...

  9. ABP使用Nginx代理导致租户ID(Abp.TenantId)丢失

    描述:ABP使用Nginx代理导致租户ID(Abp.TenantId)丢失,自定义header无效无法传递,导致租户选择认证失败.原因是因为 Nginx 过滤是“.”这符号. 解决: 1,先从代码人手 ...

  10. CSP-J 2019游记

    准备篇 11.16早上,在南校集合后,大巴车开往日照. 在车上颓了一上午 中午到达日照,考场在山东外国语技术大学(SWUV) 到了大学里的餐厅潦草的吃完饭后去学术报告厅继续颓废 一到山外突然想起了暑假 ...