Unix/Linux编程实践教程


第 1 章 Unix 系统编程概述 概在 Unix 系统包含用户程序和系统内幢 内核囱多个于军统陶成 内核管理所有的程序和贤m. · 进程之间的通信时 Unix 程序是很重要的 . iHd是系统编程 相 提命令 • b, • more 1.1 介绍 什么是系统编程 ' 什么是 Unix 系统捕程 ' 本书具体会带且哪些知识 ' 本意力图回答 上述问题 . 首先从分析操作系统的职责人手 , 来解静如何编写与操作革统茸密相关的程序 . 然后 通过分析标准的 Unix 命令,以且它们用到的 ;在统阙用 , 进 一 步指导匪者自己编程实现相应 的功能 . 这 章的是后告通过一幅回来描述 Unix 系统 . 本书的主要学习形式就是通过图 示和剖析立中程序所在b 盈的命令、技术,进而实现系统捕程 . 1. 2 什么是系统编程 1. 2. 1 简单的程序模型 你可能写 过各种各样的程序 . 有科学计算方面的、金融方面的、因憧方面的、文字处理方 面的等 . 大部分的程序都是基于四下模型 , 如图1.1所孟 . 自自1. 1 计算饥中的程序2 • Unix/ Li nux 编程实践披程 在这个模型中 , 程序就是可山在计算机上运行的一段代码 . 程序把输入数据做相应扯理 后辅出 . 例如用户在键盘上输入量据.然后在扉幕悟到输出 . 程序可能对磁盘进行操作,还 可能会用到打印机. 遵循上述模型 , 看以下代码 / * copy fr帽 stdin to st由此时 mainO lnt C; 内扎 e ( ( C .. getchar() } t. 四'" 阴山tchar (c) ; 过段代码对应图1. 2 所示的模型 . 图l. 2 程序的输入:输出 在图1. 2 中键盘和显示器与程序直接相连 . 在简单的个人计算机中,实际情况是很类似 的,键盘和显示卡直接连到计算机的主植上, CP U 和内再也是通过插槽直接连在主匾上 , 它 们通过主植上的印刷蜡路,连为一体 . 如果能够打开舰楠 , 所看到的大致如此 . 1. 2. 2 军统模型 如果所使用的f<统是 个多用户系统 . 如典型的 Unix 系统,那全是 一 副垣样的情形呢。 - - 凶1. 3 多个用户、程序和设备第 1 章 Unix 系统编程候选 . 3 . 刚才的简单模型已经不适用,图1. 3 会直接近一些. 在这个罪统中有多个用户同时运行多个程序,可能需要访问多个设备 . 虽然模型亘古了,但对程序而宫 , 它还是从键盘碍到数据 , 将结果显示在显示器上 , 也可 以对瞌盘读写.这些操作都世有任何问题,它使用的还是简单模型 . 接下来考虑一种直为复杂的情况,有许多键盘 / 显示器,它们可以随意地连接到不同的 程序,随章地操作它们,这种情况如图1. 4 所示 . 因1.. 终蝇可以随意地连接到程序 实际上,在计算机 内部,这种随意的连接是不允许的 ,必须果用 一种机制进行管理 . 1. 2. 3 操作事统的职资 计算机用酷作亘统来管理所有的暨霄,井将不同的世岳和不同的程序连接起来 . 从连 接的角度来讲 , 操作系统的作用就惶主桓上的印刷线路一样 . 有了操作革统以后,因1.4的慌乱状态就可以得到政置 , 新的模型如图1. 5 所示 . 用户空间 -系统空间 因1. 5 操作系统是一个特殊的程序 操作革统也是程序与普通程序一样也运行在内存中,同时官卫是 个特辑的程序 , 它 能把普通程序与其他程序草世备连接起来 ., • 4 • Uni x!Li m,l lt 编程实践教程 1. 2. 4 为程序提供服务 现在的问题〈系统中的事个用户和程序是如何连接起来的〉和大致的解决办法 (通过一 个管理程序〉已经很清楚了,接下来看具体的解决方提 . 首先要解释一 些术语 . 内存空间用来存llJ(程序和盘据,就惶古雅典人腾出空间来撞在腿 一样 ,所有的程序都必须在内存空间中才能运行 ,用米睿纳操作罩统的内存空间叫 做系统空 间 , 事蛐应用程序的内存空间叫做用户空间 . 操作军统也桂林为内核有了内核的障2.后,再来看计算机系统的直接情况,如图1. 6 所示 . 回1. 6 内恢管理计算机系统的直彼 住章,在囱L6中可以盎现,程序要访问设备〈如键盘、磁盘和打印机〉必须通过内核,所 以只有内核才能直接管理设备 . 程序如果要从键盘得到数据,必须向内镀监出请求 , 若在显示器上显示结果 . 也要通过 内核,程序中所有时世备的操作都是通过内脏进行的 . 困1. 6 中的线是内核提供的虚拟连接线,内核向程序提供服务 U使程序能够访问到设备 . 解释了这些内睿后,再来看什么是系统植程 . 描写普通程序时可以认为程序是直接连 到键盘、显示器、碰盘带设备的 , 但在进行矗挠编程时 , 必须对系统的结掏和工作方式有 E 裸 的了解,要知道内幢幢供哪些服务〈军统调用) . 如何恒用它们,系统有哪些贤源和址备 . 不阔 的贤晦租设备该如何操作. 1.3.理解系统编程 内棋提供服务 U便~统程序可以直接访问系统资源 , 那么有哪些系统贤哥和服务呢? 1. 3. 1 罪统资源 1 处理器 (Processo r ) 程序是由指令构成的.扯理器是执行指令的硬件设备.一个罪统中可能有多个扯理器 .'再 1 章 Uni x 系统编程很注 .7. 是如何时这么多的用户进行管理的呢 ? 在暨景过程中,当用户名和密码通过验证后,革统合启动一个叫 shell 的进程·然后把用 户吏结这个进程,囱这个进程处理用户的精求 . 每个用户都有属于自己的 shell 进程 . 图1. 7 是用户噩最到 Unix 系统中的示意图 . 因1.7 用户登录到系统 因1. 7 中左边的大盒子表示计算机系统,坐在键盘和显示器前的是用户,计算机里有内 存 ,内核运行在内存中. s hell 为用户提供服务, shell 租用户之间的连接由内核控制 . Shell 在屏幕上显示出提示符 . 表 示现在可以接收用户的幢入 . 时干事辛通用户而盲提示 符一般是" $" ,也可能还告显 示其他的提示信息 . 用户可以在提示符后输入要运行的程序的 名字,内核负责把用户的输入传给 shell . 例如,运行显示日期和时间的程序如下: s $ dat. Sat Jul 1 21 ,34 ,10 EDT 2000 S d a te 命令显示 出日期,接下来显示命令提示符 . 要运行其他命令,只要输入程序名即可, U nix 中有 一 个程序 f ortune . 下面是它运行的 例子: 导 ,=‘,- A1g01- 60 surely lII ust be ~egarded as the most i.m port缸" progra且且也可 language yet devel oped T. Cheathall $ - 当用户注响时,内棋盘结束所有分配结这个用户的进程 . 内核是如何创建 shell 进程的呢 。 s heli 进程是如何得到输入的程序名,内在卫是如何运 行程序的呢 ? 噩录革统和运行程序井非想惶的那么简单 . 这些细节告在第 8 章讨论 .第 1 章 Unix 系统编程徽述 • 13 • 因1. 10 IB.务器上的牌,提 阁1. 1 0 巾精加了第 5 个实体一一牌桌,牌桌位于服务器上在 4 个玩茸的眼里牌是撞 在牌桌上的,他们也是通过牌桌才能事开始游戏 . 在现实生活中玩牌的时候,人们轮班出牌,但在网络游戏中,是由谁来控制匮哪 一 个人 出牌?牌卫存放在哪里 ' 某个人手中有几张牌卫章峰着什么 9 如何罪保证不让两个人有同 一张牌'这在理实生活 中 不会有 任何问题,但在虚拟的 网络中确实要仔细考虑 . 图1. 11 显示了在网络盾牌中的情血流 . 」 圈1. 11 分布的程序向其他用户发送倍息 网络桥牌的倒于展示了 Unìx 革统编程中 3 个重要的方面 . (])通信 某个用户革进程如何与其他用户或进程吏换信息 ' (2) 协作 在同一个时刻 · 网络桥牌的两个用户不会都击拿同 张牌,程序如何来协调多个进程使 他们能瞥世有冲安地访问共享资源? (3) 网络访问 在这个例于'1'.互相独立的计算机通过网络连撞到 起,那么计算机中的程序是如何来 使用间蜡的呢? 1. 5.3 bc: Unix 的计算器 Unix 革统中的 bc 命令是执行一个基于字样的计算器程序 bc 有阁个重要的特点.稍后 全讲到 . 要启动迫个计算器,只要输入第 1 掌 Unix 系统销瞿慨述 • 15 • s hell 进程,那么 d,是什么 9 在大多数的 Unix 罩统中都提供了联凯帮助,可以从那里得到需要的信息,要找关于耻 的信息,只要输入 ,-'" User commar羽ds dc(1) NA且E dc - desk ca1culator SY回)PSIS dc [ fi1回且咫 ] DESCRIPTION dc i.!! an arb立,巳 rary preci.!!ion aritluoetic package . 0玄dinar ily it operates on dec 且!lIa 1 integers, b.1t one !lIaY specify an input ba..se , output base, and a nWl!be r of fractional diqits M 回国 inta切00. The overal1 struct'.u:e of dc 二 S ð stðcki呵 ( reverse Polish> calculator. lf an arqwaent is qiv田,泣""" ür taken frOlll that f 址e unt.il its a剧, then f ro倒 the standard input 这些信息来自子 SunOS 5. 8 的联机帮助 , 大多数 Unix 系统茸于 d ,的描述都是类似的 , 它说明了 d ,是一个计算器.它能够接收逆世兰牵达式,算出茬达式的 值 . 坦瞌兰费达式指的 是操作数在前 , 操作符在后 . 也称做后辍费达式,如要计算幸达式 2+3 的值,它所对应的逆植 兰者注式是 23 + • dc 的输入 /楠出如下 2 3 • p s 内部进行的操作是这样的先将 2 人械.再将 3 入战,然后将拉回的两个散出钱,计算它 们的和 . 井特结果人栓, p 是为了将投顶元素打印出来 . 这样可山知道 d ,是一个基于梭的计 算器,那么 b,是 什 么? dc 的运行矗件丑是如何苗满足的呢' 醒了 b,的联机帮助就会知道 , b, 是由的预处理器,它将用户输入的表达式转换成逆踵 兰 表达式 , 然后通过一个林为管道 (pipe) 的通信程序吏蜡 d" 如图1. 12 所示 . + 44L 4 ,>2 ……咱'4 国1. 12 程序吏缺信息 用户输入中缀理这式如 "' 2+2" . bc 将它转 化 为 相 应的后缀费达式形式 , 吏给 d , 执行 , d, 2 飞 16 • Uni x/ Linux 编程实践被程 计算表达式的值.将结果远回给 bc , bc 再将结果以告适的扉式显示 在显示器上 . 所以对普通 用户而言. b,就是计算器 . 与同培桥牌类似 . 计算辑也是囱不同的程序互相胁作构成一个完整革统的,每个程序有 各自的功能,互相植宜、相互协作 . Unix 罩挠捕程在很多场合下.就是要解决好建立这些 植 直程序之间的连接和协作方式的问题 . 1. 5. 4 从Þc/dc 到 Web 从架构的角度来看,万维罔 (World Wide W eb) 与 bc/ d c 是十分类似的 . 通过刚才的学 习可以知道 b ,负责用户界面 .d ,负责后台运算,在万维网中,制览器负责用户界面.在后面 负责提供网页的;l! W eb 服务器,如图1. 1 3 所示 . " , 因1. 13 游览器和 W, b 服务精温情 用户直接撞作浏览器,从制览嚣上看到同页的敢果,而阿页并不存脆在制览稽上 , 而是 存放在 We b IlIU,器上,同 页囱 H TML 语言写成 .就悻 d , 的语法 样 .HTML 不是很喜易理 解 . 且不直观 . 用户端的工具 浏览器就惶 b,一 样,从服务器上接收到信息后 . 会把它以 睿易理解的形式直观地显示 给用户 . 所以说万维网与 bc/ dc 是类似的,而 Web 首先出现在 Unix 平台上也是 件自然而然的 事情 . 1. 6 动手实践 前面的部卦通过两个问题进行学习,它们是"它能做什么机租"它是如何实现的俨撞下 来应该是第 三个问题了"能不能自己编写 一 个。"在本节试着自己编写一个程序来实现 more 的功能 . 首先 .more 能做什么 ' mQre 可 以分页显示文件的内睿,大部分的 Unix ~统都有士本文件 / e l c/ t ermcap. 它经 常矗立本蝙辑器和曲瑞程序用到 . ffJ mo rc 来查看它的内寄 z S .,re /眈cJt.r.çap• 20 • Unix/ Li nllx 编程实段敏程 户的输入.这显酣有问题 , 因1. 14 描述了这种收况 . 图1. 14 mor e 从标准输入读敛据 解决这个问题的方法是 . 从标准输入中(1;人要分页的世据 , 直接从键盘读用户的输入 , 如图1.1 5 所示 . 因1. 15 mo r e .M.键鑫这用户的输入 图1. 15 中 有一个文件 / deV/ HY . 这是键盘租显示器的匪备描述文件 . 向这个文件写相当 于显示在用户的屏幕上 , 读相当于从键盘藐取用户的输入 . 即使程序的输入 /输出被 " < "或 " > "重定向 , 程序还是可以通过这个文件与跑端变换量据 . 从图1. 15 巾可山如迫, more 有两个输入 , 程序的标准输入是i>的输出 . 将其分页显示 到JJI.幕上 , 当 mo r e 需要用户楠人时,它可以从 / dev / tty 得到数据 . 运用上述知识改进 more01. c ,得到 more0 2. c / ‘回re0 2.c - versionO.2of lDOre • read and print 24 lines then 回国e for 11 few specia1 coemands .,田 ture of vers立阳 0 . 2 立国ds frc. / dev/ tty f 町 c。晒"""" -/ 捋 include <:stdio. h:> 梓 define PAGELEH 24 ** def iJ回I.lNELEN 512 void do_田。re(FILE 铃" int see 画。因 ( FILE . ) i nt 阻 in ( int êlC , char 儒 av[ J ) FILE • fp; , 22 Unix/ L i n u累编理实战披覆 printf( 气 033[7瓢lIore ? 飞 033[画 " ); "hile( (c. getc(cmd)) .. EOF) / * reve四e on a vt100 */ μ N田 reads frOla tty */ if(c.... 'q. ) return 0 i f ( c .... , , ) return PAGELE.tI i f (c ""\0' ) return 1 ; return 0 编译井测试新的程序 s ∞ · 。因re02 .-ore02. c $ ls j bi n I 回<0" / * q -> N*/ ; . • , -> next pi'ge . / / * how 田ny to show 旷 / -阳 ter key "'> 1 line */ m o re02. c 可山从际准输入押到 数据,也 可 以从键盘得到用 户的输入 , 同时通过描写 m o re02. c , 1曾加了对文件 /dev/ tty 的了解 . m o r e0 2. c 还需要进一步完善 , 当用户描主楠键或输入 "q"后 , 还得挂回车键,程序才舍动 作,而且输入的字符合显示出来.实际的 more 是不需要额外的回辜的 , 而且输入的字符也 不会回显 . 3 对输入的进-步处理 用户辑作的鳝咱有很多参数,可山调整参数使得用户捕人的字持世 立 即逝到程序,而不 用等待回车,还可以使输入的字符不回显,如图 1. 16 所示. 图1. 16 终精参敏是可洞的 因1. 16 中新加入部分是用于调睡终端盘声量的 .程序运行的时候可 U 动态地问整终端的 盎散 . 要描写一 个完普的 more 还有很多工作要做 . 以下是留给捷者的问题 . 如何知道文件中 已显示的百分比?要知道百分比就必须知道主件的大小 . 这些信息据作军统是提供的.需要 用合适的革统调用来得到 . 如何且白昼示立字。如 何确定每 一 页的行散?这些都跟跑端类 型有关,如果将每→页定为 24 行、提喘类 型 定为 vt 100 ,那么程序就献王足够的灵悟性 . 如第 1 章 L' nix 系统销程儒述 • 23 • 何使程序能瞥扯理各忡盎型的终墙'那就需要学习如何搜制和调整酶端 .fl 的知识l . 1. 7 工作步骤与概要图 1. 7. I 接下来的工作步骤 Unix~ 统是一个多用户革统,官允许 I~ 享用户很多程序同时工作 , 程序经常对文件、目 录进行据作.勇士散据进行转换或传输 . 同一台机器上的不同程序之间甚 E不同机器上程序 之间通过网蜡部可以互相通信 . 这么多复杂的程序,它们的功能是什么?是如何工作的。在中间操作革统做了些什么? 随苦学习的深入,这些问题全越来越多地挂解答 . 在研究 m o r e 的过程 中展示了解决 问题的步事.首先卦析一个实 际存在的程序,弄清它 的功能 , 分析它的实现原理 , 然后自己描写 个 . 通过对程序的不断完善来更多地了解 Unix 罩统和它的工作原理 . 这也是本书果用的主要方佳 . 1. 7. 2 Unix 的概要图 Uni x 的结构如困1. 17 所示 . 图1. 11 Unix 系统的主要铺陶 图1. 17 描述了 Unix~ 统的主要结掏 ,内 存瞌升为革统空间和用户空间内幢租它的散 掘结构由下系统空间,用户扭序位于用户主问 . 用户通过暨瞄连撞到 1民统 · 主件存放在磁盘 上 · 各种各样的性骨瞌内接直接管理 , 用户程序可以通过肉模来访问世备,最后还有 罔蝠连 接.用户可以通过网络接入某统 . 接下来的每章都会芷桂罩统的基 个部分 , 介绍相关的革统间用逻辑和世据结构 . 学 完本书后,舍时因1. 1 7 中每一个部卦都有所了解 . 应商能尊编写一般的 U n ix* 统程序 . 如 网络桥牌 . 1. 7. 3 Unix 的发展历程 这本书的主要内喜是介绍 Unìx 的革统结构刷相关檀;在 · 以且如何编写 Unix 程序 , 体可 能会问 Unix 从哪里来?它是垣么盎屉的 , 在这里简要地介绍 UnÎx 的监展历程 . 1 969 年 , 贝航实验置的计算机科学瘟芷咽了 Unix 最初的 Unix 是自内棋刷一些工具组 成的它并不是一个商业产品·实际上在 20 世纪 70 年代 • y! 京实验室向大学和1 研究机构提供第 2 章 用户、文件操作与联机帮助: 编写 who 命令 植在与技巧 · 联机帮助的作用与使用方世 • Unix 的文件操作函数 open 、 read 、 write . lseek.clo出 士件的建立与读写 文件描述符 · 缓冲用户级的缓冲与内核级的缰冲 . 内核模式‘用户模式和革统调用的代价 • Unix 表示时间的方法与时间桔式间的转换 . 借助 utmp 文件来列出巳壁章的用户 · 系统调用中的错误检测与扯理 相县的系统调用 • open.read.write 、 creat 、 lsee k 、 close • perror 相提命 令 m.n w h 。 op • login 2.1 介绍 在使用 Unix 的时候,经常需要知道有哪些用户正在使用革统 , 系统是否很繁忙,某人是 否正在使用系统等 . 为了回害这些问题 ,可以使用 w h o 命令,所有的事用户革统都会有这个 命令 . 这个命令全显示系统中活动用户的情况 . 接下来的问题是, who 命令是如何 工作 的呢 9• 26 • Unix/ Linux 编程实践被程 本章分析 Vnix 中的 who 命令,通过分析来学习 Unix 的文件操作 . 除此之外,还将学习 如何I 从 Unix 的耻 Vl 帮助中排到l 有闸的信息 . 2.2 关于命令 w ho 下面给出了 Unix ~统的概览图.如图 2 . 1 所示 . 图 2. 1 用户.文件 ω 迸恒、设备和内核 图 2. 1 中战士的长万体代表计算机内存 , 它瞌升为用尸空间和军统空间 , 用户通过终端 连接到某统 , 一大一 小两个柱状体代表荫个硬盘,系统中还有 一 个打印 ~l . 罩上方的 3 个较 小的长方体代表 3 个应用程序它们运行在用户空间,通过内核与外界进行通信 , 应用程序和 内核之间的连续代表通信营且. 本章的第 个程序通过对以下 3 个问题的解答辈学习 wh。命令 : 1. who 命令能做也什么? 2. who 命令是如何 E作的? 3 如何描写 wh o? 命令也是程序 在开始之前 ω 何要卢明的且 .在 Umx *统中,儿乎所有的命令部是人为描写的程序. J!U who 和 ls . 而且它们巾的大事数都是用 C ì在吉写的 . 当在命令行巾捕入 ls , Shell (命令解辑 器)就知道曲:想运 fi 名字为 1 ,的程序 . 如果对 [ ,所提供的功能还不瞒章 ,完全可以描写和使 用自己的 h 命令 . 在 Unix 系统巾增加 l 新的命令是-件很喜晶的事 . 把程序的可执行文件放到以下任意 一个目景就可 U 丁 I bi n .! usr I bin .! usr 1 1 时 al / bin . 这些目朵里面存放军1M 多军统命令 ­ Unix 革统中 开始并世有这么多的命令 · 一些人描写程序用来解决某个特定的问题 . 而其他 人也觉得这个程序很有用 , 随珩越来越辜的使用 , 这个程序耻逗渐成了 Unix 的标准命令 . 蝇不定哪 一 天体编写的程序也会成为标准命令 .• 32 • sbort e ex 址, ! ut_exi t; t i.lle_t ut_t 皿" char ut_host[ 64J ; / * Oefinitions for ut_ type 时 .b‘.p. h(60 ‘> Uni x! Lin~x 编程实践'发徨 / " Pr民回...江 status _/ / " The exit stat us of a process mark回国阻皿 P限阻55 时 / * T i.ll e 田 "y 晒E 圃de _/ 1" Host na回国lIle as MAXl阻四AI!ELEN 时 睹过所有介绍性的内在,直撞来着 utmp 结构所保存的茸或记录 . 它包吉 8 个成员变 量 . ut use r 数组保存噩录名. ut line 数组保存监备名.也就是用户的终端类型. ut Il me 保存 噩量时间, ut host 保存用户用于莹录的远程计算机的名 字 . utmp 这个结构所包古的其他成员没有瞌 who 命令所用到 . 各个平台上的 utmp 结构可能不会完全相同,具体的内窑囱 utmp . h 来决定 . 从成员变 量来看 , 其中的绝大部分字段在大事量的 Unix 平台上是相同的 , 桂标记为盖在容 (compatibility) 的行E 可能出现不同 . 通常头文件中的注释提供了 一些有用的倍且 . who 的工作原理 通过阅读 who 和 utmp 的联机帮助,以且头文件 / u 町/ include / utmp. h ,可以知道 wh o 的 工作原理, wh。通过读文件来获得需要的信息.而每个堕量的用户在文件中都有时应的记 录 . who 的工作由程可以用图 2. 2 来表 示. 打开",mp 吁:131 图 2 . .2 who 命令的敛缉流 文件中的结构数组存曲垂录用户的信且 , 所 U 直接的姐告就是把记景 个 个地读出 井显示出来 , 是本是就这么简单呢? 虽然没有看过 w h o 的圈代码 , 但从联机串助中可以了解 who 要完成的功能且实现原理 . 所涉及的数据结构的信息也可山从头文件中获取 . 接下来是实践的时候了 . 2.5 问题 3. 如何编写 who 接下来要编写自己的 who 程序,为了能略顺利完成,需要经常从联机帮助咿获取信且 . 测试程序时,要把程序的输出与系统 who 由寺的输出做比较 . 通过卦析可以确认 . 在编写 who 程序时只有两件事情是要做的 z34 • Uni x/Li nux 编程实践敏程 dght now ( may:国 boc.四e 1ft were close to 阳、d ~ of - file, or beca咀e .e 缸 e I"eading frOll ð pope, or fr幅 a termi nal) , or because readO 咽s interrupt国 by a dqnal 阳 error . - 1 is returned, and errno i8 set appropriately. 1n this case i t is l eft unspecif ied whether the f ile position( if any) c ha咱回 这个罪统调用可以将文件中 一 定数目的字节读入一个缓冲 ß. 因为每眈都要由人一个 靠据结构 , 所以要用 sizeo f(s t ruct utmp) 果措定每次读入的字节数 . read 画散需要一个士件 描述符作为输入垂数 , 如何得到文件描述抨呢 9 在 read 的联机帮助中的最后部分有以下 描述 R旦.ATED I NFORHATIQN(called SEE ALSO in s。但 e versions) Functions: fcnt1(2) . creat( 盯 dup (2) ,立∞ ü ( 川 , getm 呵〈剖, 1 阻kf()) , lseek (2) , .ll tio( 盯 , 。.pen( 盯, P 且 pe(2). poll(2) , S民ket(2) , 3 ∞ketpair( 2) , te rJll io时的 , øtr国mio( 7), oper到dir 口) , lockf(3) Standards , star回ards(5) 其中包啻时 open ( Z ) 的引用,在命令行插入 $ aan 2 ope皿 查看 open 的联机帮助 , 从叩 .n 中卫可以找到时 d 。目的引用 . 通过阅读联机帮助.可以知道 四上 3 个系统嗣用部是进行文件操作所必需的 . 2 , 5.2 答案使用 open 、 read 和 c lose 使用上述 3 个 革统调用可以从 utmp 1.:件中取得用户垂量信息,这 3 个系统调用的联机 帮助会包吉很多内窑,尤其是应用于管道、设备和!其他数据源时牵涉且很多不常用的选项 以且复杂的用法 , 但在这里,只需要关心它们最基本的用法 . l 打开一个义件 open 这个系统调用在进程和文件之间建立一条连接,这个连接桂林为文件描述符 , 官就悻 一 条囱进程通向内核的管道 , 如图 2. 3 所示. 进徨 文件描述符 字符组组 因 2.3 文件描述符是对文件的连後 o pen 的基本用法如下 .• 46 • Uni x/ Linux 编程实践敏程 图 2.4 通过读和写来复制文件 文件在磁盘上 ,晦丈件在左边,右边的是目际文件 . 进程在用户空间 , 缓冲区是进程内存 的一部分 , 进程有两个文件描述持, 一个指向晦文件 , 一 个指向目际文件从面文件中读取盘 踞写入缓冲,再将缓冲中的世据写入目悻文件.下面就是实现上述逻辑的代码 z / .. cp1. C .. version 1 of cp - uses read aJ田d write with tunabl e buffer süe . .. usaqe: cpl src dest -/ # include ~ 8tdio.h:> 样 include <皿is td. h> 样打'IClude < fcntl. h> 样 deE ine BlJFF'Fl四 ZE 4四6 忡fine 四YMODE 0644 void oops(char " , char 叫, aain(int ac, c har .. av[ ] ) int in_fd, out_fd , II_CharS; char buf [民盯'ERSlZE) ; i f ( ac ! -3)( f printf( stder:r. "usa伊‘ s source destination\nn, .. !lv); exi t (1) ; 辛苦 ((in fd =open (av[ 汀'-"回几,Y))事.. - 1 ) ∞P' ( 吧且not 0阳阳", av[l J) ; if { (曲 t fd 鑫" '国t ( av(刀 ,田凹m出 )) _ . -1) 回归 ( nCannot creat " ,剧 [2 ] > /* check args */ /惕。pen files ~ I r.. copy files */ 第 2 司在 用户、文件操作与联机梧刷编写 who 命令 • 49 • 因 Z . 5 系统洲周时的技制流圈 固 2 . 5 中用户进程位于用户 空 间·内核位于系统空间·磁盘只能量内核直接访问 . 程序 , pl 要读取瞌盘上的数据 只能通过 '!t- 统调用 read ,而 rcad 的代码在 内核巾, 所以当 read 间用 生生时 ,执行权告从用户代码转移到内核代码 ,执行内核代码是需要时间的 . 系统调用的开明大不仅但是因为要传输盟掘 , 当运行内脏代码时 .CPU 工作在管理员 (supe r visor 卫称超辑用户〉模式·这时应于一些特腊的堆战和内存环境,必须在系统调用监 生时建立好 . 系统调用结束后( read 返回时 ) , C P U 要切换到用户蟆式,必须把堆校和内存环 境幢盟成用户程序运行时的状态,这种运行环境的切性要消辑很盛时间 . 当工作在管理员模式下程序可以直接访问磁 盘 、终端、打印机等桂备,ì!可以访问圭部 的内存空间而在用户模式 ,程序不能直接宙间设备 , 也只能访问特定部升的内存空间 · 在 运行时刻 , 系统会根据需要不断地在两种监式间切换 . 管理虽模式租用户模式的切接与 C PU 关系很大 , C P U 中有特定的标记来区分当前的工作模式,而 UnÎx 系统的设叶必须考虑 到 CPU 的这种特点·才能堪实现不同工作监式间的良好切换 . 举个lIi片届人的例子·当肯特〈生活中的超人〉 要从用户摸式〈普通人〉切换到管理员模 式〈超人〉时·他得先找个地方 , 比如电话亭,脱下西辈,摘掉眼镜 ,再改 变监型变成超人后才 能去拯敬别人 , 事情完了以后,还得找个地方变回普通人 . 查来型E 击是需要时间的·要是肯 特整天忙于亚来查击,就不含有大事的时间来拯瞌久提了 . 在计算机的世界中也是一样 , 要是 CPU 把太多的时间悄舵在执行内在代码和扭式切性 上,就不可能有 f且要时间来执行程序中业务逻辑的代码或提供系统服务 · 所以要恩可能地睡 少模式间的叨换 . 时系统来说这种时间上的开睛是昂贵的 , 那么 whp 版本花费在语、写散据 的时间开剧有多大呢, 2, 7, 3 低效率的 who2. c 每 IX 从 utmp 中读出一条记录 , 就如同要煎 3 个荷包壶 ,每位到超市去买一个鸡置 . 煎奸• 50 • Unix/ Linu J:编程实践敏程 了再去买一个 , 这是很低效率的方法 . 完圭可叫一次把 3 个鸡置都买回来 . 时于 wh。而言, 可以一 位读入多个记录放在缓冲 E 中 , 下面是实现上述盟娃的伪代码 geteq'归。 { 应〈呵gs_lef飞 in carton •• 0 ) refill carton at store it ( 呵r9S _ðt_store •• 0 ) return EndOfEqgs 明s_leftλcart.on -- return one 明, getegg 的每民调用舍从篮于里拿一个鸡蛋 , 而不是每在都要到超市去买 , 只有当篮子空 了才需要去起市 . 垂且 / usr / include/ stdio . h 中 getc 的代码 .getc 的实现使用了与 getegg 类似的算法 . 2.7.4 在 who2 . c 中运用缓冲技术 在 who2. c 中加入缓冲机制可以提高程序的运行敢率 , 接下来要把 getegg 中的想桂用代 码实现 , 如图 2.6 所示 . 用 utm~址 ib 来锺冲 ~m 函'配调用 utmplib. c 中的函'度来取 得下一条 Ulmp 记来 ulmplib. c 中 定义的函数每次从文件中读 取得 , *记豪放人'世貌中 当'度组中的'*记录豁孩'民走后,才调周 内核服务.,育读眩晕'自 • 图 2. , 在用户空间数据加入璧鑫锺冲 用 一个能睿纳 16 个 utmp 结构的数组作为缰冲区 , 在图 2. 6 中标识为 buffer , 就悻体 一 配合买很多个鸡蛋一样, buffer 可以存放很多盘据 . 编写 utmp_ next 函数来丛缓冲区中取得 下个 utmp 结构的数据 . 悻改原来的主函数 maln , 通过调用 utmp _ next 来取得世据 , 当缓冲 E 的数据都瞌取出 后. utmp_ next 会调用 read. 通过内棋再改革得 16 条记录克精缓冲区 . 用这种方量可以使第 2 章用户文件操作与联机帮助 1 编写 who 命令 i nt 困血。 / - 民ruct utlll叫 p • utbufp , μholds pointer to next r饵- / ‘"'町 next() ; / . retums pointer to next 锺 / 注( utllp_open( 阴暗 FILE) _. -1)l per l" or( trI哑P]ILE) ; 国it( l) ; wh ile ( ( utbufp .. ut&p_next() ) !. ((struct utllp *)阳且〉 ,hσw info( utbufp > utmp_close( ) ; return 0; • .how 皿fo () • 53 • 悻改后的主函数世有直接对 open 、 r ea d 和 close 进行调用,而是调用与之等价的具有缓 冲模式的面l!<接口 . 用于显示的画盘 show_info 世有受到任何Jj响 . 2.8 内核缓冲技术 应用缓冲技术对提高系统的强率是很明显的,它的主要思想是一位w;入大量的数据脏 入缓冲区 , 需要的时候从缓冲区取得数据 . 内核使用缓冲吗 管理员模式和用户模式之间的切换需要情耗时间,相比之下 , 磁盘的1/ 0 操作 1月糙的时 间更多.为了提高敢率内核也使用缓冲技术辈提高对磁盘的访问速度 . ~U 图 2 , 7 所示 . 内核缓冲区 ( 缸子系统空间 ) 图 Z , 7 内核缓冲磁盘上的数据56 ' 文件开始位置 文件当部位霞 Uni 孔 I Li n lJ J‘编覆实践敏极 rnd }J..当"位Il il 入细 ,.民度的'段 '居然厨移功 当.伎重俯针指向下­ 个木 球的,障'情 图 2.8 内核为每个何开的文件保样一个位置指针 当从丈件读盘据时 ,内核从指针所标明的地方开始.匪取指定的字节.然后草动位置指 针 . 指向下一个未幢 i章取的字节.写文件的操作也是类似的 . 指针是与文件描述符相关联的,而平是与文件~联 . 所 以 如果两个程序同时打开-个文 件 , 这时会有两个指针两个程序时主件的撞操作不会互相干扰 . 系统调用 lseek 可以改变己打开文件的当前值置 lsee k 的用法如下= ' ~k ~I示 使指针细向文件中的指定位置 头文件 j:I indude ;:i include < unistd. h> 画戴愿望 orCt oldpo ~ "" lseek(im fd , oH_1 disl , lnt bue) IO. Id 文件描述符 disl 移动的距离 ""~ SEEK_SET -> 文件的开始 SEEK_CUR -> 当前位置 SEEK_END -> 文件蜡尾 返回值 - 1 遇到铺设 。ldpos 指针变化荫的位置 Is ee k 改变文件描述符所提联的指针的位置 . 新的位置由 dis t 租 base 来指定 bllse 是基 准值置 .di别是从基准位置开始的偏事量 . 基准位置可且是文件的开始 (0) 、当前位置(1 ) 或 文件的结尾(2) , 如 z 1seek( 时. - (eizeof( lItruct utap日SEEI<_'"肘, 把指针往前事一个 utmp 结构,注意偏移量可以是血的 . ".由 (fd. 10 • IIh回 f(8t t1ω , "'呻) .且由 SET) : 上述代码把指针指到第 11 个记录的开始位置.下面的代码 b eek(fd , 0, SEEK_ENDl; 第 3 章 目录与文件属性:编写 Is 帽幸与撞巧 · 目录矗立件的功l 表 · 如何 i主取目章的内在 ' ;X:件类型以且如何知道主件的要型 . 文件属性以及如何知道主件的属性 . 位撞作且掩码的使用 . 用户与组 ID 且 passwd 散据库 相关系提调用与函由 opendir 、 readdir 、 closedir 、 seekdir • stat chmod 、 chown 、 u tL me rename 幅提命令 ls 3 , 1 介绍 己经介绍了如何撞/ 写文件内窑的方法 . 除丁内窑之外,文件还有植多属性 , 比如 :立件 所有者、段后惶改时间、文件大小、英型等 . 文件名在目录中列出 , 正如电话号码博中列有人 名一样 . 如何读取文件名和文件的属性呢? 1 ,命令可J;(列出目景中所有文件的名字 , 以 E 这些文件的其他信息 . 本章通过分析 1 , 命令辈学习 目 录和l 文件的英型与属性 . 3, 2 问题 1: Is 命令能做什么 3.2.lls 可以列出立件名和立件的属性 在命令行输入 1 ,68 • Unix/ Li nüll 编覆实践敏程 Ll8RARY Standard C Library (1 i趾 . l SYNOPSIS 将 扯到cl ude <町 sj type s. h> 将 i ocl回e < dirent. h> OIR .. opendir ( 回国 t c har .. di r na固的 , s truct dirent .. readdir{OIR .. d i r~pointe时 , int r回dd ir_ r ( DIR .. di rßinter , s truct d irent .. entry, struc t dirent 赞.. result) ; l ong telldir(DIR .. dü"_ p。回国叶, void seekdi r(DlR .. d i r_pointer. 1 。可 l∞at ion ) ; void 玄~ in<刽 ir ( DIR .. dir_pointer) ; int cl osedir(DlR .. dir_poin恒的 , [-=-J ( 11 ‘ > 通过联机帮助可以知道,从目录读数据与从文件读数据是类似的, opendîr 打开 一 个目 录. readdir 温回目量中的当前项 , clo sedir 提闭 一 个目景 . seekdîr 、 l elldi l', rewìnddir 与 lseek 的功能提似,如图 3 . 2 所示 . 时."川 r (c h a r ~ ) crntes a conn ecllon, rt !utn' 8 D1R • readdir ( D1 R . ) rud. neJ< ' record. re!urn5 a 阳 nt c r 10. . truct dirrnl d osedir , 目标 得到文件的属饺 '"文件 函"靡型 ..戴 咎 incl ude <町 s/ sta!. h> En t re8ull st8.l(char. fname, struct stat • bufp) fname 文件名 bufp 返回值 指向 buffer 的指针 遇到铺误 o 成功近团 5 t at 把文件 f n sme 的信且复制到指针 bufp 所指的结构中 . 下面的代码展示了如何用 51at 来得到文件的大小 / * filesize.c - prints size of pass回 file 旷 拌却羽01""也 < stdio. h> ** include < sys/ stat. h> int lIIðin() st:ruct stat inf由查, if ( stðt( " / etc/归自四 &infobu f) "'"' - 1 ) perror ( " / etc l归田 wd " ) ; else printf(" The size of /眈c/passwd i8 ‘d\n" , infobuf. st s且ze ); ; * place to st。因 info */ /* qet info */ 5 t a t 把文件的信息草制到结构 ìnfobuf 中,程序从成员变量 5 1 slze 中 读到文件大小 .77 自策与文件属性,幽幽写 1 ,第 3. 把些 bil 置为 O . 注童 . f'l 字中的某些 1 是如何被置为 o 的 . EE EE 。 II OO 。11 1010 囚。 EE囚 。 。。 0 0 0 0 l & o o , 0 I 0 。。 I 0 位与操作 (4)使用八进制盘 直撞处理二进制数量很枯蝇王睐的 . 如同处理 一 长串十进制胜时人们常将它们 三 位 一 组分开〈如 23 , 234 , 456 , 022) 一样 . 一种简化的方法是棉二进制数每 三 位卦为一组来操作.这 就是八进制盘 (0 ~ 7) . 如可以把二进制的 1 000000 11 0 11 0 1 00 分为 1 , 000 , 000 , 1 10 , 1 10 , 100. 从而得到八进制 的 1 0066 4 ,这样直在易理解 . 3 使用 掩码来解码得到文件奏型 文件盎型在模式宇段的第一个字节的前四位,可以通过庵码来将其他的部分置 O . 从而 得到类型的值 . 在 < SYS/ St8t. h > 中有以下定 且 倒 3.6 / . type Qf tile . / j * requ l 町 . j / . dl rl!ctory */ r. b l()(虫叩ec i .,, 1 . / / _ charocter 叩白描1 . j / . fifo./ / _ sy.bolic link */ j .. ∞ket */ 0110000 5 lFlJT 5 IFREG 0100000 0040000 006000(] 。 020000 0010000 0120000 5 1FD1R 5 [FBLIJ S IFO愤 S IFU。 5 [FLIIII S lFSαx 筝。ef i ne 捋 define 桦曲"固 树 defi帽 拌由引用 将 defif理 睿 def i.ne 将 defin~ 0140000 S_IFMT J/; 一个掩码.它的值是 0 1 70000 . 可以用来过跑出前四位幸示的文件类型 S _ IFREG 代者普通 芷件,值盐 0100000 . 5 I FDIR 代表目最文件,值是 00 4 0000 . 下丽的代码 ir ((info.st .国e ‘ 0170000) •• 0040000 ) pw、 tf( ~th il i8 iI directory") ; 通过掩码把其他茸茸的部分置 。 , 再与理示目录的代码比较.从而判断这是否是一个 目录 . ]l!简单的方桂是用 < sys / stat. h > 中的 窜来代替上届代码 j . (((.)‘ (0170000)) •• (OOlOOOO )) File 町pe IIIllCr09 . j ~"'f 缸le s ISFIFO(.) • 第 3 尊曾 启蒙与文件属性编写" • 93 • 目隶 苦酒宜件 圈 3. 7 磁盘上有目录‘文件及 t 们的属性 3. 3 每个用户都有用户毡,每个用户 41 都有时应 的用户 I 口,是否可能闹个平同的用户 名对应 个相同的 ID? 是否允许同-个用户拥有两个不同的 I 口,如果有 root 扭 限,可以试一试,创建两个用户,改血同 →个lD.但有不同的用户名和密码 . 这两 个用户是否可以悻政对方的文件? who 输出什么? 19 - ]输出什么?命令 ; d 输出 什么 , 相互盘送e- mail 呢?从中陡瞥看出些什么?事个用户使用同一个 10 还有 何么其他用量 ? 3.4 与 u遇的文件 一样,目录也有特畸属性位,其中包吉 set - u se r-ID 和 se t -gro up - 10 位 , 使 se t - user - ID 有敢对目录有什么Ii响'如果有·那么是什么'为什么, 如果世有Ii响,那么你能想辈出这些位有什么作用吗, 3. 5 每个文件的执行权限都可以撞盯开或 关闭 , 假设 一个纯文本文件具有执行权限 · 它是否可以被执行'如果→个包肯可执行代码的文件 , 如对 C 语盲编译后的可执 行文件 B . O UI ,没有执行权限的话,宫是否可以瞌执行 , 讨论执行权限和可执行代 码之间的区别 . 它们之间再关系吗 q 盎且命令 [i!e 的联机帮助 . 3.6 每个用户都有用户名利用户 10 . 这可以被认为是两种际识罩统 , 为什么要这样 ? 能不简直接用用户名来在示文件所有者?为什么,能不能只用其中 一 种悻识罩 统 ? 两茸茬示 革统有什么 优缺点 9 如果由体来设计亘统 · 体会如何设计 9 3. 7 命令 di r ent Ó; i 节点残 忍掘匹 . I j J 1 I ….一 属性存储在这里 内空存储在总盟 因 4.4 文件系锐的三 个区就 - 部卦带为数据臣,用来存放文件内窑 . 另一部卦林为, - 节占辈们 node I sb!e ) . 用来存 盟主件属性 . 第 三部分称为超级块 ( s uperblock) .用来存腊文件"统本身的信且 . 文件军统 囱这 3 部介组合而成 , 其中任 部分都是由相要有序磁盘块组成的 . (J)超级块 文件革统中的第一个块髓称为超辑块 . 这个块存M1x 件旱统本身的结构信且 . 例如, 翅辑块记最丁每个区壤的大小 . 超级块也存放未被使用的磁盘血的信息 . 不同版本 Unix 的超级块的内罪和结构稍有不同可以事者联机帮助和头文件 . 以确定体的革统的超级块所 包含的内窑 . (2 川-节点表 士件军统的下一个部分瞌再为 1 节点者 . 每个文件都有一些属性 , 如大小‘文件所有者 和最近悻改时间等 . 这些性质瞌记革在一个称为 1 节点的结构中 . 所有的 t 节点都有相 同的大小 , 并且 l 节点在是这些结陶的 一 个列表 . 文 件系统中的 每个文件在该表巾都有 一 个, - 节点 . 如果体有 root 扭限 , 就可以幢操作文件 样将分 E 打开、阅读井显示,-节点表 . 在显示 utmp 文件时就用过类似的技术 . 以下这→点很重要:我中的每个广节点都通过位置来#识 . 例如,悻识为 2 的 1 节点 ( inode 盯住于文件系统「节点费中的第 3 个位置 . (3) 数据区 文件系统的第 3 个部分是数据Il . 文件的内容悻存在这个区域 . 磁盘上所有挠的大小 都是 一样的 . 如果文件包吉了超过 一 个块的内在,如l 文件内容会存撞在事个磁盘块中 . 一 个较大的文件很喜品分布在上千个强立的砸盘块中 . 那么 , 系统是如何跟踪这些独立的磁 盘块呢? 4. 3.4 文件军统的实现 · 创 建-个 立件的过程 文件的内喜和属性卦区存放的想桂看起来很简单,但实际上它是如何工作的呢?创建 一个新士件的时候丑垂直生什么?考虑以下命令 $ ..ho > 四 erlist 当这个命令完成时 , 文件革统中增加了一个存蓝命令 who 输出内喜的新文件 . 这是垣 么回事'• 102 • Unix/ Li nux 编程实践敏程 文件有内容和属性,内核将文件内容存撞在世据区 , 文件属性存放在,-节点,文件,g再 撞在目录 . 因,. 5 显示了创建一个文件的例子,这个新文件需要 3 个存储块来存盟各部分的 世据 . i 节点.,-- 在放德与‘中布偏文件内容 47 存储放据与是序列 也且叫 文件所使用的散黯睛列钱 ltI拥到 目 录的人口 因,. 5 士件的内部结构 创建一个新文件的 4 个主要操作如下 ­ (1)存储属性 块蝙马 文件属性的存储内核先找到 个空的 1 节点 . 图 4. 5 中, 内核找到「节点。 47 . 内在 把文件的情且记盖其中 . (2) 存储数据 文件内窑的存储:囱于该新文件需要 3 个存储磁盘块,因此内接从自由块的列囊中找出 3 个自囱块 . 图 '. 5 中,它找到块 627 、 200 和 992 . 内核缓冲区的第 块数据草制到块 627 下 块数据复制到块 2 00 ,最 后一棋盘E 据直制到屈 992 . (3)记 录分配情况 文件内在挂顺序存撞在战 627 、 2 00 和 992 中 . 内幢在 E 节点的磁盘分布 E 记录了 上 述 的块序列 . 瞄盘分布区是一个磁盘块序号的列罪,这 3 个 蝙号放在量开始的 3 个位置 . w 添加文件名到目录 新文件的名字是 u se rlist . Unix 如何在当前的目录中记幸这个文件'菩蛊很简单 . 内 擅持人口 (47 , userli st) 添 加到目录文件 .文件名和, -节点号 之间的时应关系将芷件名和文 件的内在且属性连接了起来 . 这个问题将在下面进→步地讨论 . 4. 3. 5 立件军统的实现目录的工作过程 目 录是一种包吉 T ::l:件 名 字列者的特腊文件 . 不同版本的 Unix 目 录的内部结构不同 , 但是它们的抽血模型且是致的 一个包含「节点号和文件名的罪 .104 • Unix/ L inux 编程实践敏程 在很目录中另一个重要的例子是左上角的" " 和" "这两项都有「节点号 2. 所以. .. 和1 ". • "都指向同一个目最 . 当前目录:);么会和父目录相同呢, use r! îst . 当读取 一个立件时卫垂直生什么呢 9 读取命令如何工作? X1 如下的命令 z S çat userliat 果用从目录立件一步 步找到敛据的方法来加以了解 ­ (1)在目录中寻找文件名 文件名存储在目录文件巾 . 内核在目录文件中寻找包吉字符串 use r! ist 的记录 ­ userlist 所在的记最包宫描号为 47 的 1 节点号,如图 4.6 所示 . S cat 旧时'" 从文件备到文件肉容 在目展中寻拽文件名 使用蝙号定位 z 节点 1-节省包含!'l据统的 列表 47 EEE--E B 牛 41: A C ? 1. 2∞ 621 992 固 4.6 从文件名萝l 磁盘坟 (2) 定位 1 节点 47 井读取其内在 . 内西在文件嘉统中的 1 节点区域找到 1 节点 .7 . 定位 个,节点可能需要一些简单 的计算 , 所有的「节点大小相同 , 每个磁盘且都包古相同数量的「节点 . 为了提高访问敢 靠,内核有可能将 1 节点置于缓冲区中 . ,节点包吉费生据块编号的列在 . (3) 访问存储主件内喜的数据且 通过以上过程,内额已经可以知迫文件内在存撞在哪些世据换上.1:1 &它们的顺序 . 囱 子'"平断地调用阿.d 函数 . 使得内核不断将字节!A磁盘复制到内核缓冲区 . 进而到这用户 空间 . 所有从文件读取数据的命令,例如 cat , cp 、 more 、 who 等,都是将文件名传结 open 来访 ① ,到IJ' Lath.o.m &. Ja ffe 罐著的 fm My Own Gr.ndpa.. 19H 对相关盒'目的付论 .第 4 章文件系统 111 写 pwd • 105 • 间文件内事 . 时 open 的每 IX 调用都是先在目录中寻找丈件~然后根据目景中的 1 节点号 Vt lU: 件的属性,量提找到文件的内睿 . 理在可山甜、草 下在叩 en 一个世有读革写权陋的文件时将直生什么情况 . 内核首先嗣 据文件名找到 l 节点号 , 然后根据 1 节点号找到 1 节点 . 在 1 节点巾,内核找到文件的权 限值相拥有者的用户 10 . 如果权限位设置你的用户 10 对文件没有访问极限,则 open 垣回 - 1 井且将圭局变量 er r no 的值设为 EPERM . 通过对目晕、「节点和数据挠的描述,相信能提高对其他的文件操作的理解 . 可以通过 阅读一些版本的 Unix 革统部代码来如以检验 . 4.3. 7 i - 节点 和大立件 Unix 士件罩统如何跟踪大文件呢 ? 其实前一个章节中的解稀井不完整 .简 短地说 . 问 题是= 事实 1 一个大的文件需要多个磁盘换 事实 2 在 i 节点 中存放有磁盘块分配列褒 问题 一个固定大小的「节点如何存销较低的分配列裂' '尊决方案 将分配列表的大部分存储在徽 III 扶.在 「 节点中榨放指向那些钱的指针 . 考虑图 4. 7 中描述的解决方事.这个士件需要 14 个数据典存储它的内在 . 因此,卦配 链者包含 14 个挠的辅号 . 但是很遗憾,文件的「节点只包吉一个古有 13 个项的分配链茬 . 14 个编号如何撞到 13 个项中呢?其实很简单.将分配链茬中的前 10 个编号且到 1 节点 中 , 将最后 4 个编号直直到一个世据块中 . 这有点悻把某些货物且在架子上而把剩下的融在仓 库里 . E具体地说 , 就是该「节点的链牵包吉分配 13 个块编号的空间 , 链表里的前 10 个项惶 分配列军费从←节 嘿列漠中的前 JO 个模开始 . 分配列费在间接 统中罐镶埃 11 在储唱'瓢个块的 '自号 . 块 12 存储着包含更多 闽撞峡的那个戴德埃 的销号 . 这个模罐称 做 =饭闽徨换 . 细 4. 7 在徽据区延伸的块分配列表 统 J3存储曾包 含更多工段向 撞块的那个缺 的偏号 . 远个 梭徨称惚三级 阳l 很埃 .108 • 图 4.9 J圣上例的图示 . 网户角度 "'mωdJ , Unîx/ Linux 编程实战敏程 系统角度 匿自 E 圈量 I111[111 1 因 4.9 文件名和指向文件的指针 (1) .文件在目 景中"的真正吉且 一般部说文件存放在某个目录中,但是现在已经知道目录中存茸的只是文件在「节点 量的人口,而文件的内睿则存储在数据 ß . 士件在某个目录中是什么意思 。 例如,从用户的 角匮辈看,文件 y 在目景 demodir 中,而且革统角度来看,看到的则是目章中有-个包古文件 名 y 和「节点哥为 491 的人口 . 类似地"文件 x 在目录 a 中"章睡着在目录 a 中有 个指向 2 节点 402 的链撞 ,这 个链 接所附加的文件名为 h 注章,这一点很重要,在左端镀低处标为 dl 的目最包吉一个 指向: 司节点 402 的链接,那个链撞撞称为 xlink. 林为 democli r /a/ x 的链接相称为 clemodir/ c/ dl / xlink 的链接指向同一个文件 . 简短地说,目录包吉的矗立件的引用,每个引用桂林为链撞 . 文件的内睿存储在世据 挠,文件的属性被记录在一个髓称为「节点的结构中, ,节点的辑号和文件名存储在目量 中"目录包吉于目录"的原理与此相同 . (2) "目最包吉于目录"的真正吉且 从用户的角度来看,目录 a 是目最 demodir 的 一个于目罪,那么在革统内部究竟是如何 运作的呢 9 实际上 demodir 包肯一个指向那个于目录尸节点的链撞 . 从系统角匪来看,最 上面一个牵包吉一 个指向「节点 277 的链接.融为 h 如何知道 277 是左边那个目录的 1 节点号呢 9 每个目录都有一个「节点 ,内在在每个目录都设置一个指向 日录本身的 ,-节点 的人口,这个入口桂林为" "在左边的小方框中,点牵示 1 节点 277 ,因此左边的目录囊示 「节点 277 . 如回 4. 10 所示 . ,-节点 520 的目录是如何被包含在 de moclir 中的.它在目景 democlir 中 的名字融标识为 C. 类似地. ,节点 247 是另一个目录 ,名 字为础 ,它 是,- 节点 520 的一个• 112 • Unix/ Li nllx 编程实贱'安夜 在 附n阳 e 执行之前 n:l1ume ( "y M, "e f dl f y. O ld 叮执行之后 噩Z m~ 圈豆豆| 团副 图 4. 11 将文件移动到新的目录 首先 , 在 dem odÎr 中存在营一 个指向「节点 491 的链接,称为 y . 然后, 一个林为 y. old 的指向「节点的链撞出现在 c/d2 中 . 而原来的链接捐失了 . 内核是如何事动这些链接的呢 。 在 Linux 内眉 , rename 的基本逻辑是 z . :!l:制链接至新的名字 / 位置 · 删除原辈的链接 UnÎx 提供系统调用 IÎnk 和 unlink 完成这两个操作 . 因此, r ename( 气 ", " z") 是这样运 作的 if ( 1ink( "x" ,阿 zft)! "' - 1 ) un1ink{ " x") 实际上,过去世有 rename 这个罩统嗣用 , 因此命令 mv 使用系统调用 IÎnk 和 unlink . 在 内核中增加革统调用 rename 解决了两个问题 . 首先, r ename 使得重命名或重定位一个目最 变得Jl! tn '1<圭 . 过去, 一烛的用户不能对目最进行 link 或 unlink 操作 · 因此他们世有办法置 命名 一个目录 . 蜀一个使用 rename 的优点是支持非 Unix 文件系统 . 在 Unix 中,重命每 个文件或目 录是通过改变链接完成的,但是其他的系统可能不佳这种方式工作 . 将通用方桂 rename 睡 加至内核隐藏了实现的细节,使得相同的代码能静在各种文件革统上运行. (6) cd ,d 用来改变进程的当前目录 . c d 时进程产生'¥响,但是井不I!!响目最个用户可能 会说"我进入了 /t mp 目最·盘磁一大堆的垃盟主件"而另一个用户可能告说"我进入了阁 幢,盎现了很多旧书" cd 使用革统词用 c hdir:第"量文件系统编写Jl wd 1 17 • 其他的系统卫是如何酷的呢?有些操作果统将盘符就曾标分配结每个磁盘或分 E ‘井 将字母或名字作为一个文件圭路径的一部分 . 另 种做法屉,有些系统统 给所有的磁盘 分配换的编号山 g 'illt 个虚拟的单 磁盘 . Unix 使用第 三种方措 . 每个什 E有自己的文件系统树 . 当计算机上有妻子一 个的文件 军统时 . Unix 提供 种 Ji 法将这些间整合成一 幅 E 大的树 . 困 4. 13 表示了这个方法 . 用户角度一槐树 系统角度陶个盘 回 4. 13 树的嫁倏 图 4. 13 中用户看到的是一棵完好的目录树,但是实际上有两槐树 , 一个在瞌盘 I 上 , 个在瞄盘 2 上 . 每楝树都有 一个相目景 . 一个文件革统酷命名为根文件系统,这棉树的顶蝙 是整棵树的真正的相另 一个主件系统则被酣拥到根丈件*'统的某个子目录上 . 在内部,内 棋在根文件革统将 个目录作为插针 ,指 向另 个文件军统的粮 , 这样两个文件系统就联军 起来丁 . 4. 6. 1 装载点 在 Unix 中.桂缎文件系统 (to mOUlll H fi!e system) 是指特它盹入到j 已有的罩统l;.\iUt得 某些支持.子树的根目录融融入到根文 件系统的一 个目录中,于树所在的目录被称做第二个 革统的靠靠点 (mount pOint) . 命令 mount 列出当前所革载的文件革统以且它们的噩噩点 s …t /devμ咄 1 on I type ext2 (rw) /dev / t础6 on / 00晒 type ext2 ( 四) none on Iproc type proc (rw) 回回 on /dev/ pts type devpts (凹,囚。de.0(20) $ 输出的罪 行表明 / d ev/ hda 上的分区l(嚣 个lD E 设岳〉幢革栽在树的根目最上 . 这 个分区是根文件罩统 . 输出的第二行表明白v/hda6 上的文件黑统幢革戴在根文件阜统的 / home 目录上 . 因此· 当 用户使用 chdir 从 "1" 进入.. / h ome" 时 ,实际上是且 个文件罩统进 入了另 一 个文件系统 , 当民 pwd 描树向上回酬时 , 它就会停在 / home . 因为它到站了匮文件 系统的顶端120 • Un; J( ! I , ;n \lJ(编程实践放程 名字的文件槛 hn 握了 , 梓号链撞则将指向不同的文件 , 指向目茸的特号链按可以指向父 目 录.因此在目录树中产生循耳套 . 符号链摆可山将你的文件军统彻底搞乱但是内脏知道这 些仅是押号憧接 . 而不盐真正的链摆 . 所 U 能够植直丢失的引用和罗E 循环 . 革统调用町 mlink 用于创边 一 个持号幢幢 . 系统阿用 readlink 用于获取原始文件的名 字 . 1s 1at 用于获取原始文件的 ftî 且 . 阳民联 ~ l 柑助 J.iI ~.:J. r 解 unlink. link 和 1 怦 号 链提是如 何作用的 . 小结 1 主要内容 U nix 特存储在瞄盘中的盘踞扭扭成文件系统 . 文件系统是文件和目录的靠古 . 目 录是名字和指If的列在 . 目录中的每一个人口指向 一 个文件或目是 . 目录包古指向 立目最初子目录的人口 . Unix 文件系统包吉 3 个主要部分皑级块 l 节点表和由据区域 . 文件内窑存俯在 数据块 . 文件属性再储在, -节 白 . 程中 l 节点的值 1宣称为文件的 1 节点号 . , - 节 在号是文 件 的惟一标识 . · 相同的 l 节点号可能以不同的名字在若干个目录中出现 . 每个人 口 被称为指向 主 件的使链接 . 怦号髓接是通过文件名引用文件 . 而不是 l 一节点号 . · 若干个文 件 系挠的目录树可被植告成一槐树 . 内在将 个文 件军统的目录链挂到另 个文件系统的根的操作悻为革载 . • U n ix 包吉若干种系统调用 , 允许程序旦进行创述和删除目录 Sl 制指针、删除插针、 改变连接和分离其他文件军统等的操作 . 2 图示 目录入口矗立件名和, - 节点号组成的对 . , -节 点号指向菌盘上 的 一 个结构 , 该结构包 吉立件倍且和数踞挠的分配 , 如图 4. 1 5 所示. 日呆 i-节 点 寝 图 4. 15 i- 节点、数据块、自袋、指针i!l l这 文件的 『节 点包含 个指向数 翻块的指轩 的冽寝 . 第 "舷连搜位制学 习 S t1 y 设备文件的,节 点包含 个指向内核中设备驱 功嚣的指针 . 因 5 】 指向数据块或驱动蟹的:节点 • 129 每个, - 节点编号指向 「 节点程中的 一个结构 . , - 节点可以是瞌盘文件的,也可以是设 备主件的 . , - 节点的盎型酷记录在结构 5 1at 的成员变量 "m。由的提型区域中 . 瞌盘文件的 1 节点包吉指向数据块的指针 . 世备芷件的, - 节点包吉指向内核于程序 毒的指针 . 主设备号用 于告知从设备读取数据的那部卦代码的位置 . 寺虑一下 read 是如何工作的 . 内核首先找到文件描述符的 「 节点 , 该「节点用于告诉 内菌文件的类型 . 如果文件是酷盘文件·那么内核通过访问块分配牵来读取盘据 . 如果文 件是设备主件,那么内核通过调用该设备驱动程序的 read 部分来读取敬据 . 其他的操作 , 例 如 。 p'n 、 wri te, lsec k 和c1 0se 等都是盘似的 . 5.3 设备与文件的不同之处 磁盘文件和民备主件都有文件名和属性,从表面上看很类似 . 巫统调用 。 p , n 用 于创建 与文件和设岳的连接 . 但是与幽盘文件的连接不同于与路端的连接 . 困 5 .2 且示 了带有两 个文件描述押的进程, 一个是到瞌盘文件的连接 , 另→个是到终端用户的连接 . 罐瘟文件 图 5.2 拥有两个文件描述的进程130 • Unix/ Linu~ 编在实践敏硅 现在已经丁解了一些关于连接的内部情况 . 与磁盘士件的直接通常包啻内核缓冲区 . 从进程到磁盘的字节先世缓冲,然后才丛内核的缓冲区瞌盎迭出去 . 磁盘连接具有理冲这 样 一个间性 . 到j 路瑞的连接则不间,进程需要思快把到终端的数据传选出去 . 与终端或调制解调器的连接也具有属 性 . 连接拥有世特率、奇偶位、暂停位的个盘 .一 般情况下所输入的宇符都会显示在屏幕上,但是有些时候,例如当楠人密码时 · 字怦井不回 显在屏事上 . 田旦字样不是键盘任务的 部分,也革是程序应该做的 e 回且是连接的-个属 性.到磁盘文件的连接世有这些属性 . 连接属性和控制 Unix 让文件和设备既有相似之处,卫有军同之处 . 与磁盘文件的连接不同于与调制解 调器的连接 . 关于连撞的属性可以问: 1 连接可有哪些属性? 2 如何检测当前的属性? 3 . 如何改变当前的属性 ? 下面介绍两例磁盘连接的属性和终端连接的属性 . 5.4 磁盘连接的属性 系统调用 opcn 用于在进程和磁盘文件之间创建一个连接 . 该庄撞吉有若干个属性 ,下 面先仔细学习其中的罔个属性,照后再了解一 下其他的属性 . 5. 4. I 属性 1 缓冲 图 5 . 3 显示了当两个管道通过 个进程单元连接时文件描述骨的情况 . 那个进程单元 是用来进行缓冲和完成其他进程任务的 . 在方框内的是控制变量 , 用以决定文件描述符应 该果取哪个进程步骤 . 世童文件描述怦役 因 5. 3 敛据流中的进程单元 可以通过悻改控制变量政变文件描述特的动作.例如 .通过简单的 3 步操作关闭键盘缓 冲. ~U 图 5 .4 所示 .第 s 意连後撞倒:学习 stly 改变啄动器设震 I 获取设置 2 修改讼It 3 存储 i昼置 图 5 . 4 修改文件描述符的运作 131 首先 , 生成一个系统调用将控制变量从土件描述符草制到进程 . 然后 , 悻改革个重制过 来的控制噩噩 . 最后,再告政过的 值选国内脏 . 新的世置撞置置在进程代码中 , 内核根据新 的设置处理数据 . 下面是遵循上述 3 步的代码 3幸 include ~ fcntl . h:> int5; // sett 但"l' s " fcntl (fd, F_GETFL); //get fbgs s I " O_S'I'NC; /Iset S'!'NC bi t res \l lt 左 fcntl (fd, F_SETFL ,的 //昭t flags if ( r esul t .." 1) 1/ if error perror ( 町田 tt i..ng SYNC") ; 11四"''' 文件描述符的属性世 编码在 个整数的位巾 . 军统调用 fcntl 通过ìl;写旗整盘位来控制 文件描述符 . 目标 头文件 画'交 愿 望 ,戴 退 回 值 rcnll 控制文件描述符 一猝 in c\ ude 替 in c1 ude < unistd. h> 样 include int result fcn t!(int fd , int cmd) ; inl re~ull ~ fcnt1(i'l川 fd , int cmd , long arg) ;nl rcsult "'"仕 nll(i nl fd , i 川 cmd , strucl flock * lockp); fd 幡控制的文件描述符 脑血。 intc, n" 0; while( (c • getchar()) ! "' '0' ) printf( 吃 h,,,鲁 3d is ‘ c code ‘d\n", n++. c , c); 这个程序以一个接 个的方式扯理字符 , 读取字符,打印盘值、字持本身以及它的内部 代码 . 编译井运行这个程序.结果如下所示 z $ . j li 眈d缸'136 • Uni~Linu~ 副局程实践敏程 hello ""''' o is h code 104 """,, 1 is e code 101 """,, 2 is 1 c由::I e 108 ""'" ) ill 1 C由:íe 108 ""'" 4 is 0 C由::!e 111 """,, 5 is code 10 。 $ 接下来垂直生什么事情 ? 如果宇押代码直接从键盘班向 get char . jllJ 在每个字怦后可看 到一个响 应. 输入单词 h ello 中的 5 个字符井接回牢键 . 然而仅在边个时候,程序才开始址 理这些字持 . 输入看起来世绩冲了 . 就悻班向酷盘的数据,从提端流出的数据在沿遣中的 某个地方被存储起来 T . li s t c h ars 显示了另外一些内窑 . E nter 惶或 R etu rn 键通常直选 A SC Il 码 1 3 , 即回车符 . listchars 的输出显示 ASC I I 码 13 被换行带(代码 10) 所替代 . 第 三种处理!Ii响程序的输出 o li s t chars 在每个字符串的束尾悟加一个换行符 (\n) . 换 行符代码告iIf鼠标掉到下一行 , 但设有告诉它事到最左边 . 代码 13 ( 回 车符}告诉鼠悻回到 且左描 ; eo12. .. <由x!ef > 眈art 事 "' Q; stop .. "S; s u.sp • ^Z; rprnt ..咱 , 吨rase .. "11 lnext '"衡 v , flush 军 ^O; .in .. 1; t忌_. 0 _.呻 - parodd cs8 - hupcl - catopb =ead - clocal - crtacts - iqnbrl< brkint 豆 gnpar - panlrk 皿p::k iatrip - iI让cr - iqncr icrr让 ixon - ixoff - iuclc 一 叫 :一 。>poat - olcuc - oc口让。nlcr - onocr - onlret - ofill - ofdel nl0 crO t abO bsO vtO ffO isig ic缸lOn iexte.n echo echoe echolt - echonl - noflsh - xcase 牛 tostop - echoprt 。 n ,是细由 TelelYPC 公司生产的老式打印鱼'编• '自s 意连接撞制 1 学习 511y 5. 5. 5 编写 终蜡驱动程序 关 于 画 慰 改变终端朝;/)程序的桂置就悔瞌噩噩盘主件连接的世置一样· (l)从咂动程序获仰属性 , (2)幢改所要蜡改的属性 s (3) 将悻改过的属性造田驱动程序. 例如,以下代码为一个 直接开启字符回显 $丰 include < tenlios. h> struct teraiO.!l attriha to雷电~tattr ( fd, &gettir哩9) ; setti吨 s. c_lfb,g I • ECHO; / * 9tn却ct to hold attributes */ /* get attribs fr帽 ddver */ / _ tur咽 m 阻iO b让 in fl吨 set _/ 民 setattr ( fd ,明:SAHON , &sett i.t吻 "户 .6回 M衍 ;bo 国ck to driver _/ 通常的过程在 回 5. 11 巾 进行描述 . ~ i l><:: l 回e < te...ioø. 11> .truct te...iOll .ett 呵' 呻~,u旧时吨的, / . te.t, set. or d帽.r bi t . . j tenbottr(fd. how. bettu咽剖, 111 5.11 调用 I cge l sll r 和 I cse l sll r 役'而终编理动帮 • 139 • 库画 fl t c getattr 和 t cseta ttr 提供对终唱驱动程序的访问 . 两个函数在 t e r m lO s 结构中 变换面置 . 以下是详细描述 . 目标 头 立体 函 戴' 缸 .1自 返 回 僵 t 咿 t a ltr L寞取 uy 驱动程'"的属性 精 indude 符 include < uni~ t d. h> int result - lcgets‘tr(inl fd. Slrucl lermios • info) , Id info - t 与赞辅相联的文件铺 i是符 指向,每晴结构的指针 遇到铺误 o 成功返回148 • Unixj Li nuκ 编程实践'支程 write , close 和 lseek 可瞌用于任何文件或设备 . 文件权限位以同样的方式应用于控 制设备立件和磁盘文件的访问 . • 1自l 酷盘文件的连接在仕理租传输数据方面不同于到监岳士件的连接 . 内艘中管理与 设备连接的代码桂林为设备驱动理序 . 通过使用 fcntl 和 ioctl. 进程可以法取和改变 设备驱动程序的设置 . 到鲤端的连接是如此的重要,以至画撒 tcgetaltr 和 tcsetattr 专门用革提供时理端驱 动器的控制 . Unix 命令 stty 使得用户能够访问 Icgetattr 和 Icsetaltr 画盘 . 2 图示 进程使用 wrlte 特数据写入文件描述符,用 read 从文件描述符读出量据 . 文件描述符可 被连接到磁盘文件、路揣和排部世备.芷件描述符指向世备驱动程序时,设备驱动程序具有 属性设置,如图 5. 13 所示 . lI y llI劝 .. 图 S . 13 文件很迷符、连续和驱动器 3 下-章的内阜 从酷盘璋取世据相对在品,但是从用户终端 i章取有点麻烦,因为人是不可预知的 . 需要 用户输入量据的程序可以利用终蝙驱动器的一些特别的连接控制功能.在下 一 章中将详细 了解一些有关用户程序编写方丽的主题 . 4 习趣 5. 1 在一个 Linux 机器上,很喜岛读取鼠标的输出 . 做这个工作需要处于文本模式 . 在 shell 中,确保再为 gpm 的程序不在运行输入 gpm - k . 然后,输入 C8t / dev/ mouse . 然后事动鼠标井按键 . 命令 w 从设备主件中由取数据.iA该立件 E章取 的字节是鼠标产生的按键次数和草动消且 . 5. 2 设备文件中的执行位是什么意思。学习命令 b; 日,考虑革个位的作用 . 5.3 前面已经讨论了设备文件的输入 /输出如何运作 . 那么惶 ln , mv 和 'm 等的目最 操作如何运作呢?利用图 5. 1 ,解释这 三个命令是如何罪响目录 J 节点和驱动程 序的 .• 154 • Unix/ Linux 编程实践敏程 图 6. I 3 种标准文件描述符 事实 太多敌进程 启功将前 3 个文件 描述符 1T1f • 它们 不帘要调网创汇叫) 来建立连瘦 且些程序的输入和输出能够被重定向到任何类型的连接上 S .ort > O\1tputfile $ .世t " > /由v/ lp S who l tr' b , b • >C, z->a * purpose:useful for sho 四啕 tty 1M)曲, - / 1* include < stdio. h> 拚 include int 困in() int c; whi1e ( ( c 写 getchar() ) ! B EOF ) 1 jf (c.. ' z ' ) C '" 'a ' ; e1se if (islower(c)) 0" putchar (c} ; 6. 2. I 规范模式 · 缀冲和编辑 使用默认民置运行这个程序 (<二是温幡擅 〉 $ ec r otate. c - 0 rotat" $ . / rot.te a.IXr:< - CCI ","", efgCtcl - C s 图 6 . 2 显示 了终端、内核、 rot ate 程序和盘据班 . 民创民"程 序 回 6.2 输入的内窑和穰序所得到的内容 上述的韭验揭示了际准幢人址理的如下特征: 155 • 156 • Ulliν Li llu x 编程实践敏程 (1)串 Jf 未 得到输入的飞这是因为血牺键删除了它 (2)击 键的同时字符且示在屏幕上 , 但是直到按了回车键,程序才 tl 收到捕入 = (3 ) Ctrl-C 键结束输入并终止程序 . 程序 rotate 不做这些操作 . 缓冲回且、编钝和控制键扯理都由咂动程序完成 . 围 6. 3 显示 f 驱动相 1 于中的操作居民 . 输入处理 输入编栩 将 " \ r " ,专热成" 回且 因 6.3 终瑞驱动器中的处理层 ro!at~ 程陪 理冲和描朝包含凰范处理 (canonical processing) ~ 当这些特征世启动 , 路端连接桂林为 处于111.范模式 . 6. 2. 2 非规范处理 理在尝试这个试验(输入仍旧是 abx <- cd . 然后.输入 efg Ctrl-C) , .t可 - " 陋。n ; . / rotate abbç .. y^命。dd, .ff咀h s stty ic胆固 命令 stty -lcanO rt 韭闭了驱胡程序 中 的规范模式仕理 . 上倒井没有展示非现范幢式的 各个侧面而只是演示了输入处理方式植改变了 . 特别地 . 1f 规范模式世有理?中 . 输入宇母 e a" , 驱动程序跳过理冲层,将宇符直接遭到程 序 rota 凹 , 鼎后程序显示字符 "b" . 用户输入禾 髓缓冲可能是一 件麻烦"' . 当用户试图删除 一个字符,驱动程序不能做任何事情字符早就量给程序了 . 且后一个试验 , 尝试以下命令 , 然后再 1x 输入 "'abx <- cd"和 "dg"Ctrl-C : $ 批 ty - i c缸>an - ecbo ; . / r.侃a<. bcy^? de fgh ' "句 icanon echo (拉盒 l 你看不到这个 . 为什么 。〉 在这个例子中关闭了规范模式租回且幢式 . 咀动程序不再显示所输入的字符 . 输出第 6 章 为用户揭程 1 终铺位制和销号 • 157 • 但来自程序 . 当退 出这个程序时,咂动程序仍旧扯于无回显、非规范模式巾,井且 一直址 于那种状在直到j 程序政变了世置 . shell 打印 - 个显示符,等待下 行命令 . 有些 sh ell 重 置驱动程序 , 而有些不这么做 . 如果 sheU 井不重置驱动程序 , 将继续处于无回显、非规范 的模式中 . 6. 2. 3 终端模式小结 如果还未在路端尝试过些例子,现在就做 . 这些例子擅示了终端驱动程序的不同模式 . 当为 Unix 设计用户程序时,需要决定哪种些端模式适合这个应用 . 1 规范模式 规范模式,也桂睹为 coo ked 模式,是用户常见的模式 . 驱动程序输入的字持保存在缰冲 臣,井且仅在接收到回草键 ①时才持这些缓冲的字符主选到程序 . 缓冲数据使理动程序可以 实现最基本的描辑功能,如删除字符、单词或整行 . 当用户卦别捶下删除键、单词删除键或 是提止键时 , 这些功能就会瞌调用 . 瞌拮据到这些功能的特定键在驱动程序里设置,可通过 命令 stty 或革统调用 tcsetattr 来佳政 . 2 非规毡模式 当缓冲和编辑功能 瞌 关闭时,连接世昨为处于非规范模式 . 终端处理器仍旧进行特定 的字样处理 , 例如 , 处理 Ctrl-C 且换行捋和回车持之间的转换 . 但是,用于删除、单词删除 和终止的编羁键世有特耻的意义,因此相应的输入量视作常规的盘据输入 . 如果用非规E模式编写程序 , 井且希望用户能譬编辑他们的输入,需要在你的程序中实 现描羁功能 . 3. raw 模式 每个处理步骤都瞌 个组立的位控制 · 例如, IS I G 值控制 Ctrl-C 键是否用于再止-个 程序 . 程序可随意是闭所有这些扯理步事 . 当所有处理都被关闭后,咀动程序将 输入直接传递给程序 . 在这种情围下,驱 动程序瞌林为处于 '"W 模式 . 在终端驱动 程序E为简单的老版本罩统中 , 有个特定 的模式融林为 '"W 模式 . 命令 stty 支持 ," w 模式,将它作为命令行的 一个选项 . 联 机帮助上有盖 stty 的部分解释了,"w 模式 的古且 . 路端理动程序是内核中 一 些复杂的程 序 . 理过前面的学习和试验可以更为清楚 地了解它们的各个组成部分和功能 . 图 6 .4 显示了其主要部分 . @ 或者是当曲'定义的 EOF 翻'通常为C, rl - O . 图 6.4 终精驱动在序的主要组成部分168 • Unix/ Lin ux 缅寝实践敏程 6.4 信号 Ctrl - C 中断当前运行的程序 . 这个巾断囱 个再为信号的内苗机制产生 . 信号是一 个 简单而重要的舰意 . 下面特探讨信号的基本概念.学习怎样使用它们解诀 play_ again 3 的问 题 . 在下 一章中将直谭入地学习信号 . 6.4. 1 Ctrl - C 做什么 输入 Clrl - C ,程序便酷些止了 . 个单 的击键是如何杀死 个进程的呢 。 终端驱葫 程序在这里起丁相应的作用 . 图 6.6 显示了相应的事件键 . 囱 6 .6 Ctrl - C 如何工作 l. JIl 户输入 。 rl - C z. 1III功程It收到字符 3 匹配 VINTR 掬 ISIG 的字符..开启 4 础动但附测附倍号系统 5 俯号系镜发运 SIGINT 到避,也 6 遮缸收到 SIGINT 7 搓程消亡 中断信号的击键组告不一定非是 Ctrl - C. 可以使用 stty( 或者 Icsetattr ) 将当前的 V I NTH 控制字符瞥换成另一种键 . 6. 4. 2 僧号是什么 按下 Cl rI -C 产生一个信号 . 那么信号是什么呢?信号是由单个词组成的消息 . 绿灯是 个信号 . 停止标牌是一个信号,裁判手势也是一 个信号 . 这些物体和事件不是情且,童、停 和出界才是捐且 . 当世 Ctrl-C 时,内核向当前 正在运行的进程监选中断信号 .每个信号都 有 一个数字编码 . 中断信号通常是编码 2 .m 信号从哪里来'信号来自内核 , 生成信号的请求来自 3 个地方 ,如固 6. 7 所示 . (1)用户 用户能串通过输入 Ctrl-C 、 Ctrl-\. 或是提端咂动程序分配给信号控制字符的其他任何 键来请求内核产生倍号 . (2) 内在 当进程执行出错时,内核结进程芷造一个信号,例如 , 非挂段存取.带点数溢出 . 茸是 一 ① 才自"变它.许多 s heU 脚本将出向.. .第 6 拿为用户编程 z 终编控制和信号 169 ' 图 6.7 信号的 3 种来源 个非齿的机器指令 . 内核也利用信号通知进程特定事件的主生 . (3) 进程 个进程可以通过革统调用 kill 给另一个进程盎选信号 . 一个进程可以和另 个进程 通过信号通信 . 囱进程的某个操作产生的信号瞌样为同步信号 (synchronous si gnals). 倒姐,酷牢除 . 囱幢用户击键这样的进程外的事件引起的信号桂林为异步信号 (asynchronous signals) " 哪里可以找到信号的列在 9 信号编号以且它们的名字通常出现在 lusr/include/signa l. h 文件中 . 这里是这个文件的 部分 符 define SIGHUP 卢国呻UP .9回erat国 when tenl幻副 di~αnnects 旷 替自 fine SIGINT 2 I ~ interrupt,generated fr国 te I1ll inal special char 旷 样 define 51咽JIT ) μ(.)quit , g四盯'ated frOla tenlinal special cha:r */ 符 de fine SIGILL 4 I梯{咎) i11可al instruction (阳代 reset ..hen ca啕 ht) _/ 符 define SIGTRAP 5 户〈缔) trace trap (not reset ..hen caught) 时 样 define SIGAB时 , I叫. ) abort proc:ess ,.j 符 define SlG1到T 7 ;* ( • )四T instruction */ 斗事 define SIGFPE B / * ( .. ) fl幅山可归却 t exception • / 符 define SIGKILL 9 / * kill (cannot be ca咱忱。r 甲、归国) - / ** define SlG阻JS 10 fI define SIGSEGV 11 # define 51国YS 12 ** define SIGPIPE 13 梓 define 51'臼LRI! 14 #血fine SIGTERM 15 / - ( 幡 ) bus error (speci fi国 tion exception) 时 / * (.) s吨mentation viol眈 '0" 时 / ‘( - ) 国d .",恤阻tt。可st ea. call _/ ;* vri te 0.11 a pipe with nO one to read it 旷 / .. alðnn c1oc:k t擅自ut _/ /* 80ft唱re te I1l i田 tion signal */ 例如,中断信号槛称为 S I G I NT. 迫出信号面带为 SIG,QU I T , 非睡段存取信号是 SIGSEGV ,任何一个版本的 Unix 的手册都包含更多的相关信息 · 在 Linux 中,可以查看 signal(7) 的相关联机帮助 . 信号做什么?这要视情况而定.很多信号最死进程 . 某时刻进程还在运行 , 下 一 秒它 就晴亡了 , 从内存中瞌删除 · 相应的所有的文件描述符苗关闭,并且从进程幸中世删除 · 使 用 SIGINT 情页一个进程 . 但是进程也有办法保护自己本瞌杀死 .第 6 章 :为用户编覆 z 终蝙撞倒和信号 6.4.4 信号处理的例子 l 捕捉住号 / * sigd回01. c - shows ho咽"豆,回国ndler ..orks • - run this and pr田 s Ctrl - C " f ew t iJnes 旷 .川、clude < stdio. h> 樨三nclude 回 inO void f( int); 甚 nt :i ; signal( SIGINT, f ) fO f" (i . 0; i<5;iφ.){ printf(同 hello'\n叮, s1eep( 川, void f(int signum) print f( ~αJCH! \n") / . decl缸"'"国ndle f" . / / 锦 insWl tlle hand l er 旷 / * do 50mething else../ / .. this f wx:tion i8 咀江'" . j • 171 • 主函数囱南部分组成 , 调用 signal 后进入一个循环 . sigdemol. c 调用 signal ;来设置 SIG I NT 的扯理画散 f . 如果进程接收到 SIG I NT 信号,内在全调用函数 f 来处理这个情号 . 程序跳转到那个函数 , 执行它的代码,然后 jjj 回到跳转前的位置,就惶子过程调用一样 . 图 6.8 显示了两个独立的控制班一个是正常的路径 , 进入 maln , 执行循环.然后从 mam 堪回;另 一个是由信号引起的路径,进入 f ,然后退回 . 1-一一 正常侄制槛 S 旧"叮的到这将控制愧转向情号 处理器 IA僧号处理 11返回后继援 以行原来的阳倒流 . 因 6.8 俯号引 起子过程的调用172 • 以下是程序运行情况! S . / .igde田Z he U。 hello press Ctrl - C no" αJOI! hello preS1J Ctrl - C 110., αJOI' hello hello s Ullilt / LiIl UI 编理 实峨敏程 试编 i革井远行这个程序 . 程序 中没 有 XH 的显式调用.接查到的信号寻 1 1i丁对那个函 数的调用 . 2 息 略 信 号 / * sigd回。.2. c - sho.,s how t。 咱m四I!I signal P'四 C t: rl\to kill this one . / 咎 include < stdio. h> 咎 include Mm() signal( SIGINT, SIG• IGN ) ; printf( "youcan'tstopllle! \n") ; .,hile( 1 ) sl eep( 口, printf( "hah工。 rçfrçsh 更新真实廓幕 因 7.4 Curses 保待真实屏幕的副本 真实辟幕是眼前的 一 个字符数组 . curs四保留了屏幕的两个内部版本 . 一个内部屏幕 是真实屏幕的豆制 . 另 一个是工作屏幕 , 其上记最了时扉幕的政动.每个函数,比如 move , ad ds tr 等都只在工作屏幕上进行修改 . 工作扉幕就惶磁盘缠存 , cur ses 中的大部分的函盘都 只时它进行悻改 . refresh 函数比枝工作扉幕和真实屏幕的是异 . 然后 refresh 通过理端驱动迭出那些能 使其实屏幕与工作屏幕一敬的字符和控制码 . 例如,如果真实屏幕的左上角是 Smith 、 James. 然后用 addstr 把 Smith 、 Jane 放在相同的位置 , 调用 rdresh 也许只是用 n 和空格替提 了 James 中的 m 和 ' . 这仲只传输政聋的内曹而本是Ii悔本身的技术被用在视踊班中 . 7.4 时钟编程: sleep 为了写一个视频游戏 , 需要把罪惶在特定的时间置于特定的位置.用 curses 把罪悔置 于特定的位置 . 然后在程序 中 添剧时间响应 . 第 1 步使用系统画披 sleep . 动画例于 1 , hcllo3. c / _ helloJ. C '" purpose using refresh and s1eep for animated effects 第 7 章 事件驱动编程 1 编写一个幌频游戏 · > 89 • 7. S. J 添加时延 s l 四 p 为了在程序中摇曲时延,使用 sleep 函盘 slcep(n) .'l leep( n) 将当前进程挂起"秒或者在此期间幢→个不能想略的信号的到达所唤醒 . 7.5.2 sleep() 是如何工作的:使用 Unix 中的 Alarms sleep 函曲的工作凯理与你扭睡定佳时间的党-样 (1)设置闹钟到你想睡的和数; (2) 睡觉 , 直到闹钟的铃声响起 . 图 7. 7 是这个凯制的示意图 . 果统巾的每个进程都有一个私有的闹钟 (alarm clock) . 这个闹钟很像一个计时器,可以设置在一定#数后闹钟.时间 到,时钟就发造 一 个信号 SIGALR M 到进程 . 除非进程为 SIGALRM 匪置了扯理函数 (handler) ,否则信号将最死这 个进程 . sleep 画盘囱 3 个步骤组成 1 为 S I GALRM 设置-个处理函数 $ 2 词用 .1盯 m( num seconds) 3 调用 pausc . 悠个逃程宵QE的 It町iII s l~~p 函戴是如何工 作的 E .. "国 ( SIGAl且, handler ) , alllno(nl , 阳明 ..0 , 图 7 .1 -个选程没置一个闹钟后挂起 革统嗣用 pause 挂起进程直到信号到达 . 任何信号都可以唤醒进程 · 而非仅仅等待 S I G ALRM . 以上想法且结为以下代码 /钟 sleepl. c 植 purpo :s e shc阳 how sleep works * USIlgE' sleepl • outl.ine sets hanc量 ler. sets 1I1ari1, pauses, then returns . / 样 inclo由 < stdio. h> 拌inc: l ude < 缸gna.l. h> // 捋 define 51怪lHH =扫刊 )190 • Uni ll / Li nUll 编疆实践'重覆 void wakeup(int); 严 intf ( "about to sleep for 4 sec Ol'回.$\n- ) ; 8ignal(SlGALRM , wakeu肘 / . catch 此 . / 1I. 1arlll( 川 / . set cl田k 篝/ paule口 / * freeze here _/ print叭 M }!o rn iJ习 .00圆n' 飞 n M ); / _backto 嗣出 旷 yoid wak四p ( int d 91l u国〉 将 ifr回.f Sl!HHH printf( "Alar‘ received frOlll kerne l\nηJ 梓 e也汗 这里调用 signal 世置 S I GALRM 扯理画曲 . 然后调Jfl ala rm 世置一个 4# 的计时器,虽 后调用 pause 等待 . 调用 pause 的目的是挂起进程直到有一个 1"号瞌扯理.当汁时器计时 4#钟以后 . 内脏 迭出 S I GAL R M 结进程导致控制从 pause 院转到倍号处理函盘 . 在信号处理程序 中 的代码 幢执行 , 然后控制远回 . 当信号被扯理完后. pause 远回 . 进程继续 . 图 7. 8 .!直结丁 pause 的 执行过程 . 阳晒<()系统调用导搬进 包阻塞j(到恼号被处理 囱 7.8 迸人处组函敬的执行流 下面是 alarm 和 pause 的细节 z 目标 头文 件 画 敛 1lI! 0! ..量 返回 值 alarm 战置发送倍号的计时'意 。'm由 de < unisld. h> unsigned old - alarm(unsigncd secondρ , eronds 等待的时间《钞〉 1 如 S展出错 。Id 计时嚣剩余时间'院 7 司监事件驱动编程,描写 个很 11111 戏 • 191 a!arm 世置本进程的 H 时器到 seconds 秒眉目世盎信号 . 当设定的时间过去之后,内核监 埠 SIGALRM 到这个进程 . 如果汁时器已蛊醒世置 .al a rm 温回剩最非数〈注童.调用 ølørm (0) 童 峰精失掉闹钟 ) . 自鲁露 头文 件 画 11. 型 "自 温团筐 ,..~ 等待俯号 科 includ l! r<'IUlt - pau s~() g览有多数 总是 - 1 pause 挂起调用进程直到-个筒号到这 . 如果调用进程幢这个信号终止. pause 世有堪 回 . 如果调用进程用-个处理函 fl 捕夜 , 在控制从处理画盘扯返回后 pause 逼向 . 这种情况 下 e r rno 世世置为 E1NTR . 7. 5. 3 调度将要发生的动作 计时器的另一个用蓝是调度一个在将来的某个时刻监生的动作同时做些其他事情 . 调 皮 一个将要监生的动作 fR 简单,通过调用 alarm 来世置 H 时器 , 捕后罐罐做别的事情 . 当计 时器计时到 0 , 信号芷送 . 处理函数酷调用 . 7.6 时钟编程 2: 间隔计时器 Unix 很早就有 sleep 和 alarm . 它们所蝇供的时钟精庄为静,对于很~应用来说这个精 直是不能让λ楠童的 .后 来一个E强大和使用广臣的计时器革统植描加 进来 . 这个新的革 统使用一个幢昨做为间隔叶时器 (i nterval tim e r) 的慨章,有更高的精度 . 而且每个进程部有 3 个蛐立的计时器而不是原来的 一 个 . 这还不矗圭部 . 每个计时器都有两个设置 : 初始向 隔和重复间隔设置.新的革统还支持 ølsIrn 和 sleep ,它们对大多 fl 应用来说已经且悟了 . 图 7 . 9 是它的一个相应的示意图. 每个避画有主人计时. 每个计时."两个设置 E 缆!II -个情号的时间和两次 倍号闸的时问问隔 图 1.9 每个选徨有 3 个计时" 可以用这个新的系统束酷加时噩和为事件定时 .192 • Uni x!Linu )t销徨寨战敏程 7. 6. J 添加精度更高的时延 usleep 为了添剧精度亚高的时延 . 使用 usleep: us1 eep { 时 usJeep( 时将当削进程挂起"世桔或者直到有一 个不能瞌扭略的信号到边 . 7.6.2 三种计时嚣: 真实、避程和实用 进程可以以 3 种方式革计时 .考虑一个程序在运行了 3 0 ,后 结束 . 在 一 个卦时果统 中.且个程序不是一直在运行的 .lt 他的程序与它共事处理器 . 阻 7.10 显示了 一 种可能性 . o 5 10 15 20 25 30 . .~ 用户代码 肉倾代码. ---- 酬 时「句 真实 305 !I拟 1 0 s ( 刷 户在) 实用"时网户 +倏 .ú~) 因 7.1 0 时 间则在.里 固 7.10 显示从 0 到 5 ,进程在用户檀式运行接着从5!iJI5 ,睡眼 , 然后在核心志运行 到 20 s 睡眠 , 如此这般 . 显然从开始到结束 . 理序使用了 10 ,的用户时间 .S s 的系统时间 . 井显示了 3 忡时间Jt实时间.用户时间和用户时间+革镜时间 . 内在提供 11 时睡来计量这 3 种类型的时间 . 3 类计时器的名字 和功能如下。 (1 ) ITIMER_REAL 且个计时器计量真实时间.如同手表记最时向~ . 也就是且不管理序在用户在还是睡心 事用了事少处理器时间它都记录 . 当这个计时器用恩 .监遭到GALRM 消息 (2) ITIMER_ VIRTUA L 这个计时器就悔!ll式幢徨肆中用的计时方法只有进程在用户在运行时才计时 . 虚拟 计时器 ( \l i rtual t imed 的 30 ,比实际计时器 (rea[ ti me r)的 30 ,要民 . 当虚拟计时器用恩.监 矗 S I GVTALRM 情且 . (3) lTIMER PROF 这个计时器在进程运行于用户毒草囱该进程调用而陷入核也、牵时计时 . 当这个计时器 周 恩 . ;!t退 S I GPROF 悄且 . 7. 6. 3 两种闽 隔 初始和 重复 医生蜻体一些药丸并告诉曲"过一个小时吃第一植.然后每隔 4 个小时吃一植体情第 7 章事件驱动编程编写-个愧频游戏 • 193 • 要设置计时器到 1 个小时.然后在每性时尽后再世置为 4 个小时 . 每个问隔计时器的面置都 有这样两个垂数韧始时间和重重间隔 . 在间隔计时器用的结构体中初始时间是 ;t va\uc. 重主间隔是几 imerva\ . 如果不想要置直这 一 特征,将 ;1 ;me r va\ 进置为 O . 要把两个时钟 都韭掠.由 ;t value 为 O. 7. 6. 4 用间隔计时器编程 程序中使用 a\arm 不难.只要传给 alarm tþ 数就可以了 . 程序中使用间隔计时器要 复杂一点.要选择计时器的类型,然后需要选择初始间隔和重重间隔.iE要设置在 strucl 巾 merval 巾的值 . 比如,为丁使用间隔 H 时器来提酶体按 7. 6 . 3 节规定的计JlJ 吃药 ,世 置 it_ value 为 1 小时.设置 it _ interval :为 4 小时 , 然后将这个结构体通过调用 sclltlmer 传 蜡计时器.为了撞取计时器设置 . 使用 gelll1mer . 图 7. 11 是罩统响应的示意图 . 图 7 . 11 读写计时器设置 l 间隔计时器例于 ticker_demo. c 硅序 licker_demo. c 演示丁如何使用一个间隔计时器 / * tickec_ 转 incl叫e < sys/ t i.JIe , h> 拌 include < siqnal. h> int 国 in (】 void co皿 tdown( in时, signal (SlGALRM , countdown) if ( set ticker(SOO) • • - 1 ) perror("set ticker 吟, " .. while( 1 ) 飞第 7 章,件驱动铺程 t 编写个视频捕'戏 • 195 • 回到U main.ticker_ demo. c 进入一个无尽的循环 . 其间调用 pause . 每过大的 5 00ρ ,控 制跳转到 countdown 函数 . countdow n 将一个静态变量的值递减 , 打印 一 矗消息,通常情况 下远回调用者 . 当变量 num 盐到 o 时, countdown 调用 ex l1. 当然 .maln 不是一 定要调用 pa u se 的.主程序可以做些其他更有趣的事情 · 这样在每个 预定的时刻坯是合院转型IJ coun t down 的. 间隔计时器的设置是通过 struct itimervlll 来完成的 . 这个结构类型包括初始间隔租重 E 向阳 , 两者存储在 struct timeva! 中 z 且~t 让 Ùlerval "阳ct timeval i 乞 value; / * time to next t Ùler expiration*/ struct t i.m eval 让 interval 归,, 1 回 d i t value with this 旷 8truct tiaeval i time t tv 盹 / * seconds./ 阳眶。"四s t tv usec / _ M到d lII icros但町lds* / 不同的 Unix 版本 struc t timeval 的细节可能有些差异 . 查一下体的罩统的相应手册和 头文件 . 图 7. 】 2 显示了结构中各成员的韭菜,因 7. 13 显示了如何载入盘据以便第 一 1x计时器到 达时间为 6 0 . 5 s ,然后每 240.25 s 重重眈. JTJMER V1RTUAL JTlMER PROF :jltLE23 ..ru创 itÎIDCrval 每个计时器有两个设量:剩下的树间相 重复时间 这两个设E囱 s t ruct tlmtv剧 中的两个成员变量表示 StruCl timeval struct tÎmeval 有两个成R 变量钞敛相假钞散 因 7. 12 向阳叶时梅内部第 7 意 事件驱动编程=编写一个视频游戏 • 197 • 进程的私有 li 时器为 5 ,的同时卫世置另一个进程的私有叶时器为 1 2 s? 一个古老的时钟是如何让时针卦针和秒针以不同的速度转动的,它们的警直是 一 样 的 . 每个进程设置自己的计量时间 ,操作*统 在每过 个时间片后为所有的计盘器的数 值 做瑾瞄 .一个实际的例子可以置晴这些概念 . 考虑两个进程进程 A 和进程队进程 A 匪置它的真实计时器 (rea l timer ) 为 5 , .进程 B 设置它的真实计时器为 1 2 S . 为了使数字看起来简单,假设系统时钟每和跳 100 下 . 当进 程 A 设置它的时钟时,内核设置它的计敛器为 5 00. 当 进程 B 设置它的时钟时,内核设置它 的计量器为 120 0 . 如图 7. 14 所示 . 每个选程的间隔计时器 …个真实的时钟 每个进覆通过调用时町n 来设量宦的私萌 it时揭 肉核在每次 收到时钟审断的时候更新所啊的进徨计时器 图 7 . 14 两个 it时器 ‘ 个时钟 每当内核收到系统时曾脉冲 , 它遍历所有的问隔计时器,使每个计数器藏一个时钟单 位 . 当进理 A 的叶数器达到1) 0 的时候 · 章睐着已经有 50 0 时钟节拍过去了,内核左道 S I GALRM 插进程 A . 如果进程 A 已经世置了计时器的 it _ interval 值,内幢将这个值草制到 it value 汁数器,否则内核就关掉这个什时器 . 再过 舍 , 内核将进程 B 的计数器也减到 O . 相 应地向进理 B~ 出信号 . 如果 B 设置了 计时器的重载值 ( r eloa d value) . 内核就世置 it value 为相应的 值 ,然后蛙续处理下 一 个计 时器 . 通过边个简单的机制,每个进程就可以世置自己的计时器 . 这个计时器在进程睡眼的 时幢也在倒计时 . 其他的两个计时器如何工作 9 它们不是固定的倒计时 , 而仅但在进程扯于某个特定拉 在时倒计时 . Linux 醒代码清楚地结出了它们是如何实理的 . 7. 6. 6 计时器小结 一个 Unix 程序用计时器来挂起执行和调度将噩噩取的动作 .一个计时器是内核的 一 种饥制 .通过这种机制, 向核在一定的时间之后向进程盎造 SIGALRM . alarm *统调用在 特定的实际非量之后监送 SlGALRM 结进程 . setlu mer 革统调用以更高的精度控制计时第 7 章事件黯动 111 程, 111 写-个貌颁m ,虫 • 199 • 号" , 就好幢说"不可靠的老鼠"一样 . 这倒有些奇怪了 . 2 设计-个更好的系统 捕鼠器问题只是早期信号系统的一个弱点 . 为了能说明问题的重酷性,考虑以下这些 实际生活中的问题 . 3 处理 F 个信号 真实世界充满信号,也就是意外的打扰 . 假世你在办公室里工作 . 电话可能会响·口I 能 有人敲门,或者火警响起 . 对于这些事件,可以担略,也可以处理 . 扯理 一 个电话意暗暗放 下当阳的工作,拿起咆话,与打电话的人主埠,挂起电话,然后回去做撞在 一 边的工作 . 处理 酷门和注明相似 . 如果采访者在你接电话的时候酷门会怎样呢? 1草得政下电话,按保持键.开门,租来访 者更谈,然后回去继续接电话 . 在接完电话之后,回到办公桌旁继续工作 . 这种情况下,第 二个信号打断了时革-个信号的处理 . 接下来 . 正当体与第 一 个来访者吏谈时,第 二个来访者来了卫ìt;:l;么卉呢 9 一 般情况 下 , 第 个人合挡住(j . 这样第二个人就得等体与第一个人空谈完毕 . 当你与第一个人主由 完毕 , 第二个人就可以敲门了 . 这种情况下,林第二个采访者在接持第一个采访者措革之前 被阻噩 (blocked ). 还有,如果来访在来的的时候你正专注于电话那头讲话:l;么如 9 当你从门口回来重新 拿起电话,是继续刚才的话题还是告诉时方你已经忘记刚刚说到哪儿了' 最后,如果在你扯理 :k 誓的时候电话响了或者有人随门卫该如何呢?如果一 个值火警 一样重要的信号达到,体或许希望阻事其他情号 . 就惶你在处理火警时不合营电话骨是否 响了 , 或者是否有人醒门 样 . 有些时候就算你世有在处理事件也不想酷其他事情打扰 . 4 进程的多个信号 进程要雨时的问题和你要面对的世有什么太大不同 . 想靠一个进程在它的小屉〈内存〉 里工作 . 如图 7. 15 所示,用户可能通过挂下 Ct r1- C 来产生一个 S I GINT 信号 . 或者是 Ctrl\ 产生 S I GQUIT 信号,或者计时器到时产生→个 S IGQLRM 信号 . 就像电话和敲门的 访者,所有这些信号可能同时到这 . 在 Unix 革统里 -个进程如何响应多个信号。 因 7 . 15 个钱收到l 多个消息的进程 (1)处理函数每/l:使用之后 都要酷禁用吗 ? (捕鼠器模型〉 (2) 如果 S IGY 消息在进程扯理 SIGX 消且时,到达垂直生什么'第"往 事件驱动编程:编写一个Il频游观 void inthandler(int s) printf( ~ R回""回 signal ‘ d 咱"却鸡飞 n" , S >; sleep(2) ; printf( ~ [届.1咱 inthar回 ler \o"} void quithandler( int 的 printf( "酌配 eived siql回 ‘ d 晒 itir吼 n " , s ) ; sleep(]) printf( 例Leav iI可'1"比 handler \n"); • 201 试着山不同的方式常规输入和两个信号生成键 Ctrl ~C 租 Ctrl -\. 特别地,以不同的时 延试试以下组合跟踪图 7. 16 中且示的画量的控制配 . (1) -C-C-c一c (2)气 c \C (3) hellσC R eturn (4) h t!llo Relurn-C (5)\\hcllo'C mainl∞ p 气:handlcr 气handler whUe(l} "{ a 哩 r~ ad. (Oibuf '.len1 ; wtiteU, t只叫. :\1-; 、. 图 7. 16 损踪这些函数的撞倒流 这些试验的结果显示了你的系统是如何处理信号组合的 . i 不可靠的馆号〈捕11.器〉 f喃 / 、交 如果两个 SIGINTS 信号杀死丁进程,那么意睹者你的系统是不可靠的信号处理画盘 必须每在都重量 . 如果多个 S I GINTS 倍号世有最死进程,意睡着扯理画盘在苗嗣用后还起 作用 . 现代信号处理帆制允许你在两者之间做出选择 . 2. S J GY 打断 SIGX 的处理品数〈接电话的时候有人敲门〉 当接连挂下 Ctrl -C 和 Ctrl \ 会看到程序先跳到g inthandler ,接着跳到 quithandler ,然后第 7 意事件驱动编徨编写-个视频"成 • 209 • 7.10 kill: 从另-个进程发迭的情号 倍号来自间隔计时器.终瞄骚动、内接班者进程.一个进理可以通过 k;ll *统耐用向另 一个进程直追信号 z klll 目标 向 -个选程发送一个 倚' 头文 件 ** ind l.lde < sys!types. h> #: indude < signal. h> 函'自厚望 int kill (pid_l pid. int 5ig) …a pid 目标避樱 id ". 要 It:!t 迭的俯画' 返回值 -1 失戴 o s.劝 kill 向 一 个进碰芷造一个的号 . 幸遭倍号的进程的用户 ID 必须剧目悻进程的用户 ID 相同,或者监量信号的进程的拥有者是一个超坦用户 . 一个进程可以向自己盎造信号 . 一个进程可以向其他进程监道任何信且,包括一般来自键盘、间隔 lt 时睛或者内酶的信 号 . 比如一个进程可以向另 一个进思芷量 SIGSEGV 倩号 . 就好惶目标进程执行了非桂内存 撞取 . U n ix 命令 kill 使用 kill 革统调用 〈 如困 7. 17 所示). 因 7. 17 一个进瞿使用 k ìl1 0J候发送俯息 l 进程间通馆的舍义 撞圭倍号的进程几乎可以桂置任何信号的处理者 . 考虑 一 下在收到 S I GINT 时就打印 OUC H! 的程序 . 如果其他进程向 OUC H! III 序监造 S I G I NT 卫皆如何呢? QUC H! 程序 舍捕在倍号 . 跳转到处理者.打印 OUCH! ( 如回 7. 18 所示) . E进一 步 . 如果第 一个程序设置一个问隔计时器,计时髓的情号处理函数向 OUC H! 程序监迭 SIGINT 倍号 . 这样相应的扯理画量就瞌调用 . 从而 个进程的计时器搜嗣了另 一个进程的画数调用 . 实际上一组进程可以幢幢幢球运动员传递橄槛璋那样传递伯等 .• 210 • Uni x/Lin 山编程实战教程 图 7. 18 信号的复杂用法 2. IPC 1在号说计, SIG USR1 .SIGUSR2 Unix 有两个信号可以世用户程序使用 . 它们是 SIGUSR1 和 SlGUSR2 . 这两个信号世 有预定义任务 . 可以使用它们以避免使用已经有预定义语义的信号 . 将在后面几章学写进程间通信 . 捕程时可以有很多方桂组合使用 ki !l和 slgactlon . 7.11 使用计时器和信号:视频游戏 现在回到视频醉戏 . 游戏有两个主要元章动画和用户输入 . 动面要平滑 , 用户输入会 改变运功状革 . 下一个程序 bounceld. c 让用户可 以将字符串在牌幕上弹来弹击 . 7.11.1 bounceld. c 。 在-条线上控制动画 首先来看看 bounceld 墙上去是什么样子 . 界面如图 7. 19 所示 . bounceld. c 将 个单 词平情地在扉幕上事动 . 当用户挂下空格键。单词就向反方向事动 . 气"键和" f" 键分别增加 和睦少单词的事动速度 . 按 "Q"键退出程序 . :;: h"l1ol 退出 回 7. 19 bounceld 的运行界面用户控制的动画 这个程序是如何实现的呢?哉们已经知迫如何实现动画 . 在一个地方面一个字符串 , 等几毫秒,然后擦去旧的罪悔井在原来位置的左边或右边 一 个单位距离重新画同一个字符 南 .这里希望擦去和重画动作以相同的问隔连续的进行 .所以使用间隔计时器来调用 相应 的扯理函数 . 两个噩噩分别记最睡前的方向和速度 . 设置方向亚量的值为 + 1 和 一 l 分别罪示向左第 7 章事件驱动编 fj ,编写一个槐频洒'戏 • 211 和向右串动 . 延时噩噩记录间隔计时器的间隔长直 . 校佳的延时意味着较慢的直庄,反之 则章峰营较快的草皮 . 现在向程序添加方向和l 速度控制 . 根据用户的键盘输入幢改方向和速度变量.程序的 逻辑如图 7.20 所示. bounceld 体现了两个重要的技术 :状 态变量和事件处理.记录位置、 方向和延时的变量定立了动画的状态 . 用户输入和计时器信号是改变这些状志的事件.每 眈计时睡到达信号就调用改E 位置的处理函数 . 每次得到用户键盘输入信号就调用改变方 向和速度变量的代码 . 以下是它的代码 . 随 7 . 20 用户输入改变变量值而变量值控制动作 / * bowx:eld.c ·严upose ani且ation with user controlled speed 缸ld direction 铃 note the handler does the anialation 键 乞】回国l.JI progr!m. re是 ds ke:lboard input 镰 c OOl pi l e cc bo 山、celd. c set_ticker. c - 1 curses - 0 bounceld . / 替 incl ude < stdio. h> 捋 include < curses. h> 将 i nc: lude < signal. h> /铃 sOIIe globëll settings main 回 d the handler use _/ 样 d.巳"'阻SSAGE "hello" 特 define 阻'>'OK int ro"; /* curr四 t row 旷 int col; / * current col\llllIl *j int dh; j . where we a.re g01咱旷 int main( ) int del ay; 川军时elay , 1* bigger '"> s lower . / /幡 new de l ay 如/第 "霍事件驱动编程编写一个貌,第游戏 • 213 • dir .. 1 else 迁 ( dir ." l 届四1 + strl审议皿ESS'哩) >- ∞国 〉 d 立r" - 1 1 递归还是阻革 r 一个真实的例子 在学习 i.哥处理函数的世据损暨时提到过重人函量 . bo unce ld 提供了 个考靠这个问 题的真实例子 . 开始时信号扯理函数 move_ msg 每勒钟幢调用 5 ~ . 投 " f" 键来睡小计时器 延时以增加动画速度 . 如果按很多民 "f" 键雨眈计时器消息之间的问陌可能比 眈处理函 数的执行时间还要短 . 如果计时器悄且在扯理画盘忙于擦去和童画字恃牢时到这卫告如何 ? 且个问题的分析田作习题 . 在这个程序中使用 signa l ,到底是遇归还是阻事依赖于你的 革统 . 2 下一步做什么 ? 如何扩展 bou n ce ld 为 一个弹球游戏 9 首先,要用 "0"来替换 hell o ". 因为 ~O " !J!像一个 璋 . 然后 , 要让蹲在左右兽功之外还可上下事动 . 为了均加上下瞎动的能力要睡加状EE 量 . 现在已经有 col 和 COW 来记录璋的位置 dir 来记录水平事动方向 . 如果要使障能上下 草动 , 还要舔加什么变量呢 ? 7.11.2 bounce2d.c : 两维动画 程序 bounce2d 产生阳维的动圃 , 可以让用户控制水平速庄和垂直速度,如图 7.2 1 所示. 边出 。, ' 、\· '. 反弹 〈简未实现j E疆噩噩噩噩圈圈圈噩噩噩噩噩跑革 被迫 加速 因 7.21 两维动画 b o un ce2d 的 3 个由 ;1 部分与 bOU ß celd 相同. (1)计时器驱动 间隔计时蜡桂世置为产生固定的 SIGALRMS 信号施 . 响应一个信号,璋向前事动 一步 . (2) 等待键盘输入 程序阻噩等待键盘输入 . 根据用户按下的键的不同来取不同的动作 . (3) 状态噩噩 变量记录了球的速度和方向 . 用户输入悻政的变量值决定丁小璋的蓝蓝 . 计时器扯理第 7 意事件理动编程编写一个视频游戏 ωd 町 ~data =c二:Y 伽'"阻。 圄噩 _ O/I_a1anoO 噩噩噩 图 7. Z4 键盘和计时传部发送信号 • 219 来设置文件描述符 o 中的。 ASYNC 位来打开楠人信号 . 最后 , 循环调用 pause 等持来自计 时器重键盘的信号 . 当有 个从键盘来的字符到达 , 内核向进程盎遭到G I 0 信号 .. $IGIO 的耻理函数使用标准的 curses 面世 getch 来键入这个字符 . 当计时器间隔超时 , 内核盎盖以 前已经处理的 S I GALRM 信号 . 以下是踵代码 / * bounce_lI.sync. c .. purpose animati创1 wi th u.s er control, US :U哩。I_ASYNC on fd .. note set_ tick:er() 9剧由 SIGAL血. handler does animation • k.eyboard ser到ds SIGIO . main only cal1s pause<> 如 .0恩.pi1 e cc bo阳、c e_ lI. sync. c set_ticker. c - 1 curses - 0 b。因lCe_as yne: - / 样 include ~ stdio .h;> 将立 nclude ~curse s.h:> 符 inc l ude < signal. h> # include < fcntL h> /帽"理 s t.a t e of the 9四. -/ 得 define 阻ESSAGE "t回llo ~ 梓 def ine BLANK int row 10; int col .0; 且nt d ir • 1; int delay 事 200 ; i ntdone .. 0; 国 inO void On→1I. 1arm(in时, / - cu!"rent r ow 时 / . current col umn 旷 / * where 四 are going 旷 / * hov 10呵 '0 帽忧- / 1* tandle.- for alann 时第 8 章近程和程序编写命令解释樨 , h • 229 • i量也 文件 问 因 8. ] 系统中的进程和程序 世据朝程序存储在磁盘文件中,程序在进程中运行 . 以下的几章里将学习进程幢;吉 从命令 p' 和 sh 开始,然后写一个自己的 Unix shell. 8.2 通过命令 ps 学习进程 进程存在于用户空间 . 用户空间是存脏运行的程序和它们的盘据的一部分内存空间 . 如图 8.2 所示,可以通过使用 ps(process status 进程状态的简写 〉命令来查看用户空间的 内 睿 . 且个命令告别出当前的进程 . 用户空间 I I 院副 嗣 -.J. I 萨容纳进程 ]>>-< P画 4 lJ.j 国| 文件 1f. t,宽容纳 E 与文件和目是 .... 11]' IS.8 Is-I 四日 .2 ps 命令列出当前进瞿 $ P' PID 'ITY TIME C凯D 1175 pts/ l 00;00:17 bash 19自1 pts/ l 00:00:00 ps 这里有两个进程在运行 bash (shell) 和 p s 命令 . 每个进程都有一个可以惟 际识它的 数字·瞌称为进程 10 . 一 般简称为 PID . 每个进程都与 一 个跑辅相连 。 这里是 / dev / pts/ l . 每个进程都有一个巳运行的时间 . 注意 ps 时巳运行时间统计并不是非常的精确,从 ps .R用 了 o 非就可以看出 . ps 有很多可选项 . 和 Is 命令- 样, p ,支持 - ,可选项 z• 232 • Unix/ Linu lI编程实践数程 在命令行输入 . 这些系统进程做些 ft 么呢 9 列表中开始的儿个分别处于内存的不同部卦·包括内黯缓冲和虚存页面 . ,'1 罪中的其 他些管理亘统日志 ( klogd. syslogd ) 、嗣庄批任务 (cron , a t d ) 肪植可能的政击 (portscntry ) 和让 般的用户垂录 ( sshd , getty ). 可以通过 ps - ax 的输出租 Unix 孚册了解很多革统的 情况 . 运行 p ,就悻透过显微镜看 滴池塘水 . 能看到很多各式各样的进程运行在*'统中 - 8. 2. 2 进程管理和立件管理 从运行 ps 的结果看出进程有很多属性 . 每个进程属于某个用户 ID ‘有 一 定的大小、 个起始时间、已运行的时间、优先级和"'四 n es s 辑别 . 有些进程与某个终端相应,而其他一 些 贝 '1 没有 . 这些属性存撞在什么地方呢 ? 曾对文件提过同样的问题 , 内核管理内存巾的进程 和磕盘上的文件 . 这些管理活动有什么相但之处吗? 文件包吉散据 , 进程包吉可执行代码 . 文件有一些属性进程也有 一 些属性 . 内核埠立 和销毁文件,进程类似 . 就{掌管理磁盘的事个文件,内核管理内存中的事个进程 , 为它们分 配空间并记最内存分配情况 . 内存管理和键盘管理有什么相似之处? 8. 2. 3 内存和程序 进程这个帽念有些抽象·但是古代表了 些非常实际的主体内存中的一 些字节 . 图 8.3 演示了计算机内存的 3 种模式 . 内存可以看作是一个 曹纳内骸 翻进程的空问 . fIl:i: ~民筑把内仔看作曲页面构 成的'皮细精选槐份制到不同 的页面 目 钩理上二 这些页丽可 能彼得"在回体的芯片 '1' 且且』 面勤勤 曹雪' 图 8. 3 计算机内存的 3 种模式 Unix 系统中的内存升为系统空间和用户空间 . 进程存在于用户空间 . 内存实际上就是 个宇节序列,或者 个很大的数组 . 如果肌器有 64 MB 的内存 , 那意味着直个数组有大约 6 7 00 万个内存位置 . J革中的 一些用来存放组成内核的机器指令和数据 . 还有一些存融组成进程的凯器指令和盘据 . 一个进程不 一 定必须要占 一 段连续的内 存 . 就惶文件在瞄盘上被分成 '1、块,进程在内存也世分成小块 . 同样和文件有记录分配丁 的瞌盘块的列表相似,进程也有保存分配到的内存页面 (memory pages) 的数据结构 . 因此 , 将进程牵示为用户空间内的一个小万块只是某种程度的抽盘 . 特内存囊示为连续的字节数组也是一种抽血 . 理在的内布一般情况下是囱小电路植上• 234 • Un i 嚣/ Li nux 编程实践徽程 shell 同时也是带有变量和现程控制的描程语 盲 . 在上面的例子中 , 可以看到使用了两 个壁量 . 首先,直量 TZ 桂设置成茬示莞国西海岸时区的字符串 . 然后这个值瞌作为#盘传 蜡 dale 命令来打印当前的日期相时间 . 例子的后面部分,可以看到有汗。 then 语句 . 'l!'量 NAME 植置为字梓串 "Ip" . $ NAME 的值在 grep 命令中被使用 . g'叩的结果自;r语句进行判断 . 如果在文件 / etc / P'臼 wd . 中擅窜到宇符 串 "Ip" , shell 就执行命令 ec h o hello lmail $ NAME . 否则跳 E 下一矗 命令 . 在本章中,先来看看 shell 是如何运行→个程序的 . 在后面的章节中将学习 shell 的脚本 语言和输入输出的重定向 . 8.4 shell 是如何运行程序的 s hell 打印提示符,输入命令 . s h e ll 就运行这个命令然后 shell 再眈打印提示符一一如此 liS! . 那么这些现靠的背后到底发生些付么? 一个 shell 的主循环执行下面的 4 步 〈 如图 8.4 所示) , (!)用户键入 a . out i (2) s hell 建立一个新的进程来运 行这个程序, (3) s hell 将程序从磁盘辑人; (4 )程序在它的进程中运行直到结束 . 8. 4. 1 sheU 的主循环 s hell 由下面的循环组成: while( !町、d_of_input) get c叫 execute COIMI!IInd 图 8.4 用户要求 shell 运行一个程序 wð i tforc叩 to finish 考血下面这个与 s hell 典型的互动 shdJ • 236 Unix/ Linux 编程实是是教穰 Un;x 切问屋 1 i 个 W j< execvp < prOKnamc . argl 刚》 1 将Ilìli':的 rur 复制到调用 它的近徨 2 将 衔店的字符'. ' 般组作为 arg v 口 传给这个 m于 3 运行"个 ,程序 因 B. 6 c x ccvp 将程席复制到内存后运行官 (1)哩!芋调用 execvp (2) 内核从@.盘将程序辑入 (3)内居将 arglist U 制到进理 (4 )内随调用 main (argc, argv) 下面是运行 ls - 1 的完整程序= / - execl. c - shows how 倒可比 is for ð pl"ogl"ðlD to run a pl"ogram -/ 回 in() 0"0< 剑 ðrglist[3] ; ðl"glist[O] " ">s "; arglist[l] .. " - }" arglist[2] .. 0 printf( " *警* About to exec 1s -1\n") execvp( "1.. " , argli..t ) printf( "费*. 18 is done. bye\n") exec叩有两个垂盘 z 要运行的瞿序,g和 Rß 个程序的命令行垂数 数组 . 当程序运行时命 令行参盘 t.t argv [] 传蜡程序 . 注意 · 将盘组的第一个元肃置为程序的 名 称 . 还要注意 , 最后 一个元章必须是 null . 编译并远行这个程序 z $ CC el 转 define DELA'l' 5 国 inO int ne叩 i d; void c tj il d_code 口 , parent3odeO prin t町 ~before 町p id is ‘ d飞 n ~ , get pidO) : if ( ( newpid .. forkO> -1 ) 归 rror( "fo响" ) else if ( newpid : a 0 ) child_code(DElAY) ; else 第 8 章 进程和程序:编写命令解,警棍 , h • 253 • 如何做到。 psh2. c 工作正常 . 新的 shell 撞圭程序名称、垂数列毒、运行程序、报告结果 ,然后再重 新接圭和草行其他程序 . psh2. c 融少常用的 shell 的 一些曹饰性功能,但可以作为}个坚实 的基础开始了 . 下←个版本中将做如下改进. (1 ) 让用户可以通过按下 Ct rl - D 或者输入 "exit"坦出程序: (2) 让用户能够在行中输入所有垂数 . 在 F一 章主理的版本中加上这些功能 . 在那个版本中,将加上一些变量和控制植程使 它更惶一个捕程语盲 . 这之前必须E正 个严重的错误 . 倍号和 psh2. c 从测试中可以看到,退出程序 psh2 的惟一 方法是按 Ctrl-C 键 . 如果在阴阳等持于进 程结束时键入 C t rl-C 键告如何呢'比如 $ • / pah2 > l O9out i l1 curr , not in prev -> 109in 皿e prev '" curr repeat 叫 ood, "00 I sort > pre哑 ..hile true 由 s1eep 60 '00 I 白白 > curr echo " 1明回 out , " C叩 - 23 prev curr echo " 1 ogqed 凶 COllllll - 13 pr刷=, mv curr prev do", 此脚本使用丁 Unix *,统所提供的 7 个工具、 一个 while 循耳和1 1/ 0 iIi定向,描写这个程 序解决了问题 . 仔细看 一下这些程序 的细节 ,以且 它们之间的连接 . 脚本中的第一行建 立 了 一个在此脚本运行时已登录用户的则表 , 并按用户名进行排序 . wh o 命令输出用户列茬,而 国n 命令将列表作为输入读避,然后输出一个排好序的列费 . 命令 who I 50rt > prev 告诉 s hell 同时执行 who 和 I 50rt 将 who 的输出直接道到 50rt 的输入 。 如图 1 0 . 1 所示 . wh o 命令并不 一 定要在 50 rt 命令开始读取和排序之前完成肘 utmp 文件的分析 . 过两个进程以很小的时间间隔为单位来调度.它们相系统中的其他进程 起分享 CPU 时间 . 然后,四川 > prev 告际 5he ll 将 50 rt 的输出送至 prev :X: 件中 . 若此士 件不存在,则创建此文件:若已经存在,如l 替换其内窑 . woo 则 > fi1e 回 1 0. 1 将 who 的输出连缩到 50 rt 的输入• 302 . Unilt/ Linult 编程实贱'安程 告给第三个班 . 如果扭略迫些幢幢班的去向问题, sort 工具的基本原型就如图 1 0.3 所示 . 三个数据现分别如下: · 辑幢幢入一一需要处理的世据配 标准输出 -一一结果数据班 · 标准错误输出一一错误消且流 回 1 0 , 3 sort 工具将输入匮进并输出结果和错民消息 10.3. 1 概念 1 , 3 个标准文件描述符 所有的 Unix 工具都使用图 10.3 中所示的 三种璋的模型 . 此模型通过一个简单的规则 来实现 . 这 三种植的每一种郁是一 个特别的文件描述符,其细节如国 1 0 . 4 所示 . 标准文件偏远符 0: stdin 1: stdotn 2: stdcrr 图 1 0 .4 3 个特殊的文件描述符 悟$,所有的 Unix 工具都使用文件描述特 0 .1 和 2 . 挥罹输入文件的描述符是 O. 标准输出的文件描述符是 1. 而标准错误输出的文件描述符 则是 2 . Unix 假设立件描述符 0 , 1 , 2 已经瞌打开 , 可以分别进行读、写和写的操作了 . 10.3.2 默认的连接 tty 通常通过 shell 命令行运行 U n ix 系统工具时, stdin , stdout 和 stderr 连接在终端上 . 因 此,工具从键盘憧取盘据井且把输出和错误消 J且写到扉事 . 举例来说,如果输入 50 f t 并接下 回事键,路瑞将会被连接到IJ 50ft 工具上 . 随便输入几行士字,当按 Ctrl- 0 键来结束文字辅第 1"震 νo.定向和铸造 • 305 • Uni~ 因 1 0.5 ..低可用文件描述符 U原则 幢 ?生当打开文件时 , 为此文件萤排的描述怦品是此盛世组中晶低可用位置的章引 . 通过文件描述符建立一个新的连接就像在 最多路电话上接收一 个连接 样 . 每当有 用户撞一个电话号码,内部电话罩统为这个瞌哥请求分配 条内部的线路号 . 在许多这样 的革统上.下 一 个打进来的电话就瞌卦配给最小可用的线路号 . 10. 3. 7 两个慨念的结合 巳经介绍了两个基本的植企 . 首先. Uni x:进程使用文件描述符 0 , 1 , 2 作为际准输入、输 出租错琪的通迫 . 其眩 , 当 进程请求一个新的文件描述符的时候,辜统内核将最低可用的 文 件描述符赋结它 . 将这两个概念结合在一起,大事就可以理解1/0 里应向是如何工作的 了 , 也就可以自己写出程序来完成1/0 的重定向 . 10.4 如何将 stdin 定向到文件 下而将详细地考察,程序如何将标准输入重定向以至可 以从文件中读取数据 . 更加精 确 点诅,进程井不是从文件读数据 , 而是从文件描述抨击数据 . 如果将文件描述捋 o 定位 到 个文件 , 那么此文件就成为标准输入的1Il . 下面将考事 三种将标准输入定位到文件的方法 . 其中有些方法井不适合于文件·但使 用管迫的时候,这些方法都是必要的 . 10. 4.1 方法 1: close th en 叩四 第一种方世是 close - t hen - open 策略 . 这种技术类似于挂断电话再敢-条线 路,然后 再将电话拎起从而得到另 一条线路 . 具体步黯如下 . 开始的时候军统中果用的是典型的设置 . 即 三 种标准施是世连接到终端设备上的 . 输入的敏据施经过文件描述符 o 而楠出的搞经过士怦描述符 l 和 2 . 如且图 1 0 . 6 所示 . 接下米·第 步是 close( 肘 ,即将际幢幢入的连接挂断.这里调用c! ose(O) 将标准输入 与终端设备的直接切断 . 图 10. 7 中显示了当前文件描述特数组中的第 一个元章现在址在主 闲状态 .• 306 • Unî x/ Lînux 编程实践敏程 图 10.6 典型的初始化配置 唰用 cl osc: (0) 之后 因 10.1 5tdin 披关闭 最后,使用 open (fi lename , O_ RDONL Y) 打开一个想连接到 stdin 上的文件 . 当前的最 低可用文件描述符是 0 ,因此所打开的文件将瞌连接到标准输入上去 . 如图 10. 8 所示,任何 从悻推输入读取盘据的函数都将从此文件中读入 . 。"" 调附创嘘了一个到 文件的直撞并煌宣指向 是低可能寝项由指针 . 图 1 0.8 ,叫 m 现在已经连接到文件上 7 下丽的程序郎使用c1 ose - then - open 方法。 *1 stdi l'l red王宫1., ·阴~l"pOSe , s OOw how to red 豆 rect stardar唱'豆n阴~t by replacl.ng tile .. descriptor 0 with a connection to a file .. acti。罚 reads three 1 革 nea frooa standard 皿 put ,由由3 .. clos回 fd 0, opens i!I. d且 sk f ile , then reads i l'l • 308 • Unix/ Linux 编程实践敏程 此理序井世有什么特别的地方,它仅仅挂断 电 话 E 撞了 一 个新的号码而已 . 当直接建 立起来后 , 就可以且标准输入的 一个新的暇接收数据了 . J 0 . 4. 2 方法 2. 叩 eO . . close. . dup. . close 考虑→ 下这种情况:电话响了,你拿起丁撞上的分凯,但你意识到自己应商下幢击接电 话 . 于是你让睡下的人把电话持起,这样就有两个连接,然后把楼上的分机挂断此时楼下 的电话是惟一 的连接了 . 这种情况大事是不是很热~ ? 其实这种方法的思路就是从楼上的 电话直制 一个连接到楼下然后就可以在不断线的情况下将楼上的连接切断 . 如阁 10 . 9 所示 U n ix 系统间用 d"p 建立指向已经存在的文件描述样的第三个连接 . 这 种方法需要 4 个步珊 . (1) o pen( fîle) 第 一步是打开 st dìn 将要重定向的文件 . 这个调用远回 个文件描述符 . 这个描述怦井 耳是 O . 因为 o 在当前已经苗打开了 . (2) c!ose(O) 下一步是将文件描述符 o 韭闭 . 文件描述符 o 现在已经空 闲了 . (3) d"p(fd) 革统调用 dup( fd ) 特立件描述符 f d 做了 个重制 . 此l!;!!制使用最低可用文件描述捋 号 . 因此 , 获得的文件描述骨是 O. 这样,就将磁盘文件与文件描述怦 o 连接在一起了 . (4) c!ose(fd) 最后 . 使用c! ose (f d) 来韭闭士件的原始连接,只留下文件描述符 o 的 连接 . 将这种方陆 与把电话从一个分饥转事到另一个分帆的技本做一个比较 . fd • o"en('f 闹。UmONLY) ; close(O) dup(fd: ; close(fd) ; 图 1 0.9 使用 dup. 定向第 JO 章 1 /0 重定肉和管道 子远程锺示了父返程指向打开 文件的指针 . 子透疆军定向标 准输出: d05t( >) , create( ~f " ) ..~υ l 被子选程打开文件 图 1 0 . 10 .'I hell 为子送覆重定向其输出 看一 下如何使用这个原则来重定向标准楠出 . 1 明始情况 • 311 • 如图 10.11 所示 ,进程运行在用户空间中 . 文件描述符 l 连接在打开的文件 I 上 . 为了 使这幅图清楚岛理解,其他打开的文件井未画出来 . 图 1 0 . 11 在调用 f。 此之间的进程以及官的标准输出 2 父进程调用 fork 之后 如图 10 . 12 所示 , 新的进程出现了 . 此进程与原始进程运行相同的代码 , 但它知道自己 是于进程 . 此进程包含了与立进程相同的代码、数据和打开文件的文件描述符 . 因此文件 描述符 1 依然指向的矗立件 f . 然后于进程调用了 clos e ( 1) 0 指向打开的艾件 盯开文件 子选段 图 1 0 . 12 子进程的标准'自由从父进程黯儿继承而得 3 在于进程调Jfl c! ose (l) 之后 如图 1 0 . 1 3 所示,父进程并世有调用c1 ose ( 1) ,因此宜进程中的文件描述符 1 仍然指向 L 子进程调用 close (l ) 之后,文件描述符 l 变成了最低未用文件描述符 . 于进程现在试着• 312 • Un i x/ Linux 编程实践敏程 打开丈件 g . mR \可用的夜项 父迸惺保持造撞 图 1 0. 13 子进程可以关闭其标准输出 4ιe 在子 i选t程调周 c口"回"叫( "飞g 如图 1 0 . 14 所示 ' 立件描述符 1 蓝连接贸到l 立件 g . 于进程的标准输由瞌直定向到 gι. 于 进程然后 调用 e臼x阳e田c 来运行 who . mm 围 10. 14 子进程打开一个额的文件得到 fd = 1 5 在于进程使用 exe c 执行惭税房之后 如图 10 . 1 5 所示 ,于进程执行了 who 程序 . 于是于进程中的代码和数据都植 wh o 程序 的代码和数据所替代 7 ,黯而文件描述特量保圄下来 . 打开的文件并非是程序的代码也不 是靠据,它们属于进程的属性,因此 exec 调用井不改变它们 . 子进瞿 新的包序 指向打开文件的指 针是选程的一部分 i 但此数组并不是艘 序中的政娼 . 图 1 0 . 15 子进程运行程序并将标准输 出重定向 who 命令 将 当前用户列表追至文件描述持 1 . 其实这组字节 已经苗写到文件 g 巾去了 而 who 命令却毫不知晓 . 下面的程序 who t o file. C 展示了上面所诅的过种方挂• 314 • Unix/ Li nux 编理实践'虫覆 编写可以支持以上两种操作的代码就留给大辈作为练习去完成 . 10.6 管道编程 现在已经学 习了如何描写程序将际准输出量定向到文件 . 下面将要讨论如何使用管盟 辈连接一个进程的输出租另 一 个进程的输入 . 阻 1 0 . 16 展示了管道的工作原理 . 管ilI是内 核中的一个单向的数据通道 . 管型有一个读取端朝 一 个写入端 . ~理 who I sort 这样的撞 作,需要两种植巧如何创建管遭.以且如何将标准输入和输出通过管道崖撞起来 . 网 1 0 . 16 稠个造思囱管遭连接在一起 10.6.1 创 建管道 图lO. 17 所 jf- 即为 一 植1'f坦 . 可以使用如下的军统调用来创娃1'f逝 . 目 标 头文件 画 隙'望 …a 温圄僵 P啊川 写入蟠 P'P' 创建管遭 $* indude < unÎsld. 11> TC5Ult - 阴阳 ( Îm 盯 r ay[ 2 ]> , M"Y 包含两个'"'类型'段揭露的'生组 - 1 发生'费泯 o 成功 pipe [01 11取蟠 图 1 0.17 i'fill 调用户归来创童管道井将其阴端直接到两个文件描述特 . 盯 ray [O] 为读敏据睹的文件第 1 0 章 1/ 0 重定向和管道 • 315 • 描述符,而 array [ l] 则为写数据端的文件捕连带 . 惶 一 个打开的文件的内部情况一样 , 管道 的内部实现隐藏在内属中,进程只能看见两个主件描述符 . 图 1 0 . 1 8 显 示 了进程创述一个管理前后的收配 . 前 张困 〈调 用 p !pe 之前 )显示丁标 准 立件描述符姐 .后一 张困(词用 plpe 之后〉显示了 内核中新创建的管道,以及进程到管道的 两个连接 . 注意 ,类似于 o pe n 词用 .plpe 间用也使用最低可用文件描述符 . 惆用 P' P' 之前 耐用 P' P' 之后 进担打开-些*蟹的文件 内接创建管道并没置文件描述符 固 1 0. 18 进程创建管理 下面的程序 piped e m o . c 展示了如何创盟管道并使用管道来向自己监造盘据 / * pip回国。 C 帽。回。nstra tes , how t o cr刨出 and use a pi pe • * Effect, creates a pipe, wr ites into writ呵 怜巴nd , υ理nn皿, =。山ld and reads fro. Tead i.r唱 " end. A 1 此 tl e ..e i时. but deMonstrates the idea -/ 样 卫.nc lude < stdio. h> 得挝、c:1 ude < unistd , h> lIð.in( ) int l en, i , IIpipe [ 2J; ;* two f ile descdpto t"S 旷 char buf [田目 Z 旬, / * for read缸可"'" / . qet a pipe 旷 if ( pipe ( 叩ipe)--- l) ( perror ( 吧。'I11dno t 国ke pipe 叮, 回it ( 1); 时 print f( "Got ð pipe! I t is f ile de9CZ"i ptors , \ ‘ d ‘ d )\ n" . 'P 且 pe[O] , ap且R【 1 J) ; / .. read fr:回 stdin , write 却to pipe, r嗣d frOD. pi pe, p rint 旷 whil e ( fgets(buf ,田白血, stdin) ) ( l en • strlen( buf ) • 316 U n ix / Linux 编程实麟敏程 if (wdte( apipe[l] , buf, len) ! - l en){ perror( "胃口 tir咆 to pipe 吟, b,回<, '0' ω - 0 i < 工曲; i ++ ) but[ i] =鸣 l回. ,ωd ( ap 孟 pe [ 町 , buf ,即fFSI Z ) i f ( 1 回 - .. - 1 H perror(nr田dil可怕泪 p ipe" ) ; break; if (盯 ite{ 1 , but, 1回 】 ! '"len ){ 归口。氏"町让U唱 to stáoutn) b,田k , /* send 旷 /镰由m 旷 / * pipe 时 / _ wipe _/ /* read */ / fI fr四 时 / * pi pe 时 / . pipe 4/ / . ser回./ / 4 to"/ / . pipe . / 图 10. 19 显示了从键盘到进程 · 从进程到管道,再从管盟到进程以且从进程回到费端的 数据传输配 . 图 1 0 .19 pipedemo.c 中的数据流 现在已经学巧了如何创建管坦,如何向管道 中 写墨宝据以及如何从曹迫中读取盘据 . 实 际上 , 很少会有程序用管道向自己芷选数据 . 将 plpe 和 for k 结合起来 . 就可以连接两个不同 的进程丁 .第 10 章 1 / 0.定向和管道 • 317 • 10.6.2 使用 fork 来共 享 管道 当进程创盟一个管道之后 . 该进程就有了连向管道两端的连接 . 当这个进程调用 fork 的时候,它的于进程也得到丁这两个连向营造的直接,如图 10. 20 所示 . 茸进程和于进程都 可且将数据写到管迫的写数据瑞口,井从撞撞据喘口将数据 i主出,如固 10 . 21 所示 . 两个进 程都可山语写管道.但是当一个进程埠 , 另一个进程写的时住.管盟的使用效率是最高的 . 其事管道 边观调用臂涩,内恢创建-个 '" iIl J~ 修细注接管;a精点的文 件细述符组针11<组 在资 .i墨程调 m fork. 内核刨缰 个..进程排队父避现复制造 罐管迫输点的文件描选符指针 数组 两个涯'啦'眼访问管 i庄的两编 !I!e. 圄 1 0 . 20 共掌管道 组 10.21 近程之间的数据流 下面的程序 pipedemo2. c 说明了如何将 plpe 和 fork 结合起来.创建一 时通过管道来通 信的进程 . / * pip回国。.2.c • 1)田。 nstrat田 阳帽 p i pe is dupli c:a t回 in fork门 。. k叫 4 ,; 01 - P=四 t continues to wri te 缸ld read pi~. 斗" but child also wri tes to the P卫'" -/ 4阵 incl uo:揭 < stdio. h> 样 define OUID HF.SS "I wantac∞k:ie\n" • 326 • Unix/ Linux 编程实践敏程 成的工作就林立为服务 , 而自己则是服务的事户 . 上面的例子跟 Unix 有什么荣革呢? Unix 中的管道可以把数据从一个进程传盖到另外 一个进程 . 进程和管道元但类似于一条生产线来完路产品 , 还类似于一个大的服务产业 . 本意将关注于进程间的数据通信 , 这也是事户 / 服务器编程的基础知识 . 11. 2 一个简单的比喻:饮料机接口 程序处理信息就像人们消耗饮料一样 . 以图 1 1. 1 中 的自动碳酸饮料机为例,在你投 入硬币,按下按钮之后,饮料就自动由出 . 在此过程中 , 饮料机内的分配器在做什么工作 呢?在饮料机内,应该有一 桶碳酸水和另 一 捅浓缩的世料什水,当接下按钮时,将撤活 一 个配制原材料井不断地传送户产生的碳酸性料的过程 . 另一种 方法则是只需要将 瓶颈 先配制奸的碳酣饮料辑到 一 个抽水泵上 , 缸下按钮这个动作就简单地将饮料抽出并送给 外面的杯于 . 累 很掘"求到脏停 来自存储部分 阁 1 1. 1 动态产生或来 自 静态饮料 就惶碳阪饮制分配器一样, Unix 提供 个接口来处理可能来自不同数据源的数据 , 如 图 1 1. 2 所示 . 图 1 1. Z 一个後口和不同的数黯源 ( 1. 2) 磁盘 /设备士件 用叩 '0 命令连撞 . 用 read 和 w n le 传递数据 . • (3) 曹道 4 种樊型的".匠,厚 1. IU量文件 2 设备 3 管道 4. Sockeu 使用同- 个1/0 镰口 用 p l pe 命令创建 , 用 fo.r k 其事,用 read 和 wrlle 传递盘据 .第门掌迄候到近蝙载运端的选程服务嚣与Soc k et(套後字 〉 327 • • (4)Sockets 用 soc k et , listen 和 connect 连接,用 read 和 wnte 传递数据. 11. 3 bc: Unix 中使用的计算器 几乎每个版本的 Unix 都包啻 b,计算器 , 尽管这些计算器的版本有些盖异. b,计算器 中包含变量、循环和函数的功能,井如在第 1 章中所青到的那样主恃时1':整数的处理 . \\ 17 03 肝 m Mm 47 28 21 38 73 19 33 36 70 82 93 66 29 39 07 62 59 37 31 21 59 73 35 92 茹扭2 7 ·8 65 75 33 76 503 091 207 359 976 565 393 747 mm 盹 262 196 mm 曰 "且由225 3 095 2 228 ez4 7 6 b < 240 7292 $ 2 2 62 每行末尾的反制线罪示赞宇行的继续 . 1. bc 并不是-个计算器 一个计算辑程序分析它的捕人,执行操作,然后将输出打印出来 . 大部分版本的 b ,程序 都只卦析输入 , 井不执行操作飞 其实. b,在内部启明了 d ,计算器程序 , 井通过管理与其进 行通信 . d ,是一个基于拢的计算器,百需要用户在指定具体的操作符之前,先输入所要操作 的世据 . 例如,用户输入 22 + 来代茬 2 阳 2 的操作 . 因 1 1. 3 显示了 b ,如何来处理 2+2 的过程 . 用户输入 2+2 . 然后撞回车 . b ,从悻推输 入匪取该罪达式,分析出数据和操作持 , 接下来把 - "列的命令 "2" , "2". "+"和 p 传给 d " d ,则将盘指入战 . 运行曲操作 . Jil后把钱顶的数值送到标准输出 . 图 1 1. 3 bc 和 d,作为协同进程 b,从 连接到 d ,标准输出的管道上读取结果,再把结果转监给用户 . 这样的话, b,甚至 都不需要持有变量 . 如果用户输入 x=2+2.bc 告诉 d ,执行曹操作井且把结果存到寄存器 x 中 . 命令 bc -c 可 山显 示分析器传蜻计算器的数据 . 就连 GNU 版本的 b ,也是把用户的 捕人转换成基于梭的后缀表达式 . 2. 从 b , 方法中得到的思想 (])事户 / 服务器楼到 bc/ dc 程序时是喜户 / 服务器模型程序世汁的一个;;l:例 . d ,提供服务 z 计算 . d ,所识别 的语言是众所周知的逆波兰表示法 . b ,和 d , Z 阿通过标准输入 stdin 和标准输出 stdout 进 行通信 . b,提供用 户界面,井使用 d ,提供的血务 . 这里 b, 幢幢为 d , 的客户 . ① GNU 版本的 b,是执行计算操作的• 328 • Unix/Li n Ulc 编程实践敬程 这两个部分是根本上姐立的程序 . 可且使用不同版本的 d ,.这井不I!i响 b,正常工作 . 类似地,可以描写一个固Jf>界面的 b, . 而仍用 d ,作为计算引擎.甚至可以用这样 一 个程序 来替换 d ,.该程序先分析 d,所识别的语言,然后把它所要做的工作传结可能位于另 台直 高速计算机上的程序. (2) 双向通信 窑户 / 服务器模型不同于生产线的盘据处理模型,它要求 一个进程既跟另 个进程的标 准输入也要和它的括准输出进行通信 . 传统的 Unix 的管道只是单方向地传是数据 ∞ ,图Jl 3 给出了 b ,和 I d ,之间的两个管道 , 其中上面的管道把一些计算命令传给出的悻幢输入,下 面的骨迫把 d,的怀准输出传给 b , . (3) 永久性服务 k 只是让单一 的 d,进程处于运行状态,这就不同于 shell 程序,这种程序中的每个用户命 令部创建一个新的进程 .oc 程序持睡不断地相 d,的同一个事倒进行通信,把用户的输入转换 成命令传给 d,. 他们之间的关系井不同于标准函数中所使用的调用返回机制 . b c/ d c 时桂林之为协同进程 (co r o utin es) 以 用来区别于于程序 (subroutìne s) . 两个程序 都持撞运行,当其中的一个程序完成自己的工作后将把控制权传给另一个程序 . b , 的任务 是分析输入且打印 , 而 d ,则负责计算 . 11.3.1 编写 bc : pipe 、 rork 、 dup 、 exec 图 1 1. 4 显示了内核特用户连接到 b,并将 b, 连接到 d,的数据连接 . 这里以该图作为 编写下面代码的指南 . ([)创建两个管道 . (2)创建一 个进程来运行 d , . ( 3) 在新创建的进程中,重定向标准捕入和悻准楠出到曹逝,然后运行 e xec dc. 〈川在立进程中 ,读取井分析用户的输入,特命令传给 dc , dc 读取响应,井把响应传给 用户 . I!I l 1. 4 Ix:、 d ,剥 内核 ① " 盔 '雪"'也能双 向传输 ..."见小筒中编雹橡习 1 1. 1 1) .第 11 章 连接到近蝙或远地的进程服务锦与Soc k创〈毒援字〉 FlLE.. fp; fp" po pe:r汉 "18" , " 宫" ) fgets(buf , len.f肘, pclose( fp); / . S !IJIIe type of struct 旷 / .. arqs are pr吨'UM圃, connection type */ μexactly the 且me functions 旷 / * close when done 时 • 333 • 困 1 1. 5 且示丁 popen 和 fopen 之间的相似性 . 两者使用相同的语法格式,井具有相同 的返回值英型 . pOp凹的第 个垂数是要打开的命令的名都'Ë可以是任章的 shell 命令 . 第二个垂数可以是"产直 .w" 但决不会是 a" . popen ("'s', "r") fopcn ("file" , .叫 图 1 1. 5 fopen 和田"" 下面的程序持 whol50rt 作为数据源 , 通过 popen 来获得当前用户排序列表 : 1* popendemo. c 精deIIonstra tcs how t.D open 画 P'呵 ram for s ta:回!lrd 1 1。 .呻Z国 t points, .. 1. popen() returns a FILE 俘, just like fopenO .. 2. the FlLE ..此 returns can be read/盯比t~ • with all the standal:d f unc:tions .. 3. you need 回国e pclose'口 when done . / ** include < stdio. h> 将 include < stdl 汕 h> lnt 1M缸'0 FILE .. fp; char buf[100] ; int i 0; 句 s popen( "who l Bort" ,叮叶 / * open the 0帽且'" . / while ( 何ets( buf, 100, fp 川 z NULL ) / * reðd from c帽皿'"旷 printf( "‘3d奄 s" , i++ , bufl; ; *pdnt 由回. / pc: lose( fp ) ; 1* IMPO R'I'ANT! 时 return 0; r 第 1 1 章 连接到近蟠或远峭的透湿 g 服务稽与Soc ket( 套援字〉 句 '" fdo四川P( O] . "r") return fp dup ( p [ 汀, 0 close{ p[l]) • 335 • exec( "/ bin/sh" ,"曲 ", " -c " , C回 mn且 , 因 1 1. 6 从 shell 命令中该取 下面的程序 popen.c 是上述班程的一个实现 / -阳 pen. c - a 飞ler !JÍ on of the Unix po胆n() 1 ibrary function _ FILE 揭阳pen ( c har .. cOll1laaIld, ct町. ...ode ) - ,~叫 18 a r呵"lar shell command "时e i9 "r" or "w" -…a str四巾 hod t。由 c叫, 0< 阻 .. execls"sh" " -c " c 。但..00 .. todo 时回 t about signal har划ling for child process? . / 将 include <:stdio.h:> 将皿clude 4牢 def i.ne RF.AÐ 0 1* define ilR lπ FlLE 费 pop创刊 const char .. c。但mand , const char .. m圆的 il>t pfp[2] , pid; FlLE .. fdopen<). .. tp int parent_er划 child_ e:r划, if ( ..lIIOde "'" ' r ' ){ p<<四飞回币d '" READ , h孟 ld_end '" ilRI 'I'E : 1 else if ( 幡凰锐je..'w' )l parent_end 酣m chlld eJ田d"READ; ) else 国turn NULL ; i f ( pipe(pfp) ",. - 1 ) return I阻, /* the pÎpe 由、d the process */ / * fdopen 皿.k:es a fd a stream *j / * of pipe */ 1'"' fiqure out direction */ j "getap孟..旷第 11 意 连拨到近峭或运峭的进程 g 服务据与Soc ke t ( 套磁字》 • 337 • · 方法 2 ,从画撞在取数据 可以通过调用函撞来得到数据 .一个库函数用标准的画数撞口辈封装数据的格式和 位置 . U nix 提供了读取 utmp 文件的函数撞口 . getu t ent 的帮助信息描述了自取 utmp 数 据库函数的细节 . 这样的话,就算底层的存储结构变化了,使用这个接口的程序仍能正常 工作. 使用基于应用程序接口 (AP I)的倩血腥务也井不 一 定是最好的方法.有两种方法可 以使用库函数 . 一个程序可以使用静牵连撞来包吉实际的函数代码 . 但是这些函量有可 能包吉的井不是正确的主件名或文件格式 . 另 - 方面, 一 个程序可以调用共享库中的面 世但是这些共享库也井不是辈辈在所有的革统上,或者其版本井不是程序所要使用的 版本 . · 方法 3 ,从进程获取数据 第 三种方法是从进程中璋取数据 . bc/ dc 和 popen 例子显示丁如何创建一个进程到另 外一个进程的连接 . 一个要得到用户列表的程序可以使用 popen 来建立与 who 程序的连 接 . 囱 wh o 命令来负责使用正确的文件名和文件格式以且正确的库函数,而不是你的程序 . 调用独立的程序获得数据还有其他的好处 . 服务器程序可以使用任何程序设计语言编 写 shell 脚本、 C ,J ava 或是 Perl 都可以 . 以独 立程序的方式实现革统服务的最大好处是在 户描程序和服务器端程序可山运行在不同的机器上 . 所有要做的只是和不同机器上的 一 个 进程相连接 . 11. 5 socket :与远端进程相连 管迫使得进程向其他进程盎量数据就悻向文件呈送数据一样事昂,但是管道具有两 个重大的融陷 . 骨道在 一 个进程中槛创建,通过 f or k 来实现共享 . 因此 , 管理只能连接 相 关的进程 . 也只能连接同 一台 主凯上的进程 . Unix 提供了另外 一 种进程间的通信机制 四 c k et . soc k et 允许在不相关的进程间创建类似管道的连接,甚至可以通过 soc ket 连接其他主 机上的进程 〈如图 1 1. 7 所示 h 本节将学习 socket 的基础知识 , 理解如何用 socket 连接不同 主机上的客户端和服务器端 . 其思想就跟打电话查询当地时间一样简单 . 进程 进但 阳,,, socket 图 1 1. 1 连撞到远峭的进程338 • Uni x. / I.inu x.编程实践数程 11. 5. 1 类比..电话中传来声音现在时间是 .. 叶辛城市都jj!,f 提供时间服务的电话号码 . 只要拨打族号码 , 机器将负责告诉你该蜡 市的时间 . 它是如何工作的呢 9 如果扭!I!立自己的时间服务 , 在如何做呢 9 可以罪用图 11 8 中所描述的比较简单的方陆 . 你坐在办公室里,将 个种桂 {t 椅 t 来份由提供时间服务的 服务器 . 你所要遵循的步骤和 Unix 中 基于 !;ocket }]立时间服务器的步骤主主一致 . 下面 将详细讨论过些步骤 e 客 尸 刷务;斟 维立 I)j务 拢到咆:昌 等符请求 --, 讲习段时间 ·…--- .. 摆收情求 获得:;掘 '一一一一---一一一一发送:;据 | 挂断 挂断. 因 1 1. 3 时间服务 1. .在三服务及与服务相关的操作 - 且"好井萤革丁曲:自己的时钟 , 如何边立和 l 操作时间脱#器呢 。 (1) 建立服 务 建立 服务包含 U 下 3 个步骤 . . 获取一 根电话线 首先 . 需要 一根接自公用电话网上的电话线来连接办公桌旁墙上的插班.在电话线和 插应使你可以连擅到电话网上 · 接下来外面的呼叫可以酷传送到 4年的品公桌 . 这里的描i 座 通常桂林为通信端点 (endpoint of communication) 0 下一 眈如果需要在草里去裴电话线,可 ~:,( r闪电话局巾情辈革 一 个通信揣占. 为电话线申请号码 在户端需要通过呼叫 一个电话号码连接到体的通信端点 . 电话同蜡 U 电话号码来区分 每个墙上的 J'í 座 e 从这个类比本身考店 , 由姐的5 是在-个大的商务罩在 中 ,除了时间服务以 外压需提供其他的服务 . 因此 , 每个插座要以电话号码附加 1 一个分机号码来标识 . 例如你的号码可能是 617→ 999 - 1234 ,分凯号码是 8 080 电 话号码标识了悻的办公室 所在的大峻的号码,扩展号码 8080 标识了撞楼叶'每个具体的电k'i . 一个号码来标识大幢­ 个分 m 号码米标识你的服务 , 这是→个电耍的机制 . · 处理接人也诏 你可能使用的是克来也显示的电话 . 体的服务不需要上述费型的电话服务 . 但必须通• 350 • Unix/ Li nux 编程实践被程 写入的数据特从该文件描述符读取 . · 步罪 3 和 4 传递数据和 1 挂断 在成功连 接之后,进程可山从班主件描述符自写数据 , 就惶与普通的文 件或管道相连接 样 . 在时间服务的事户 / 服务器例子中. t imeclnt 只是从服务器读取一行数据 . 由取时间之后 , 在户端关闭文 件描述符站后退出 . 若喜户端且出而不关闭描述符,内接 将完成关闭文件描述件的任务 , 1 1. 5 . 7 测试 limecln t. c 大革已经有好几页世有霄J)J 插图了 . 大擅已经忘记这些代 码的任务了吧 . 阁 1 1. 9 将 会提醒我们 . 服务器进程运行在 自 肌 器上 , 而喜户揣程序在另一台机器 上 通过网络与 服务器连撞 . 服#器通过 wnte 调用辈茸茸数据,在户端通过",,, d 调用辈接收泪且 . '"户 因 1 1. 9 位于不同机锦上的进程 对上面这段代码真实的测试需要在平同机 器上运行这两个程序 . 我不太确定下面写在 书上的测试情况是否足够明确,不过大事可以作为盎考 z $ hon.- computerl. mys i te.net $ ~ t 皿.\I~ .C -o t.皿B町 $ ./tJ....erY‘ [1] 1 们" $ $ acp t.u..clDt . c bruc:e@ec.put..-2 , bruce@comput.er2 ' s p!l SS嗣" t u.eclnt.. c I l llB $ sah b=c.@ca.put.er2 bruce@c帽 puter2' s password I No _il I 1. 8阻( , 00叩uter2 , bruces cc t. illeclnt. ç - 0 t.UeclDt 将位查当前机器 样第 一 台机器 样建立殴务器 将并屋行它 林 2发送客户代码 1日j 另 台机部 IETA, 00 ,00 ,00 1 1ω ‘ 00凰.puter2 , hr皿 e S.!t圃tcln t. α_puted 13000 Wow! Got a call ! The ti盹 here 18 .. T回归g 14 02 ,44:31 2001 c。但puter2: hnx:e$ 第 J J 素造簇到近瑞或远峭的选程服务指与Soc ket ( 套徨字 ) • 351 服主于器编译好后 , 运行在凯器 l 上 a 然后把客户端程序 E 制到机器 2 上再壁最到机器 2 . 在机器 2 上,编译好事户瑞后 ,然后让事户端请求与运行在凯器 1 上监听在 1 3 000 揣口 的服务器相连接 . 这里墙到的消且就是从机器 1 上的服务器通过罔蝠盎送给机器 2 上的事 户的 . 而在户再把悄且主选至标准输出 . 从上面的拥试结果是否能茸的看到机器 2 上的输出 ' 我是从机器 l 连接到机器 2 的,所 以显示桐且的路端实际上是直接到机器! 上 的 . 后面的→些练习合让你仔细考血其运作 原理 . 通过 timese r v/ timeclnt 程序,可以得到另 - 台机器上的时间 . 检查另 一 台机器上的时 间可以保证不同机器的时钟同步 . 网络 t 的某俞机器可以作为战威时间服务器,而其他的 机器可以利用上面的程序周期性地来调整自己的时钟 . 11. S. 8 另-种服务器远程的 1 , 下 个项目是编写一个可以打印远端凯器上文件列翠的程序 · 件可能在两台机器上拥 有账号 . 若想着另一 台机器上的文件 J'J 击时如何去世呢 ? 可以暨最到另 一 台机器上,然后 运行 1 , . 一个快璋的且更加方便的盘径是运行一个远程的 1 ,程序 · 这里称它为r! s ( rem o te 1 ,λ 可以指定主机名和目录, $ rls c帽p!.I ter2. site. net jbol酶;_jcode 当然. rls 需要在另一台饥器上的一个服务进程接收请求,仕理请求和返回结果 . 该系统 看上去盎似于图 1 1. 1 0. 服务器运行在 台机器上客户运行在另 一 台机器上井与服务器连 接,把目量的名字盎选结服务器 . 服#器将位于该目录下的文件列者信息温回给客户端 . 喜户端将结果道主际准蜻出把它们显示出来 ‘ 两进程系统提供了对于不同机器上的目最访 问的支持 . 客户 /I!务 III 网络 图门 1 。一 l'远峭的 J ,系统 l 设计远程 1 ,系统 这里需要 3 个要素 来实现r! s 系统 (J)胁拙 (2)窑户端程序 (3) 服务器端程序第 11 拿 连接到后编或远稽的选程 t 服务樨与Socket ( 套後字 〉 if ( write {s∞k_ ìd , av[巧 , strlen(av[2J)) 三左 川 。ops ( "write") ; i t ( write(sock_id, "\"" .υ - I I 回'ps ( "盯 ite" ) : while( (n_ read 鑫 read{sock_ id , buffer ,应JFSIZ川 > 0) if ( 町 i te口, buffer, n_read) .... - 1 ) ∞ps { "wr ite " ) ; close( sock_id ) • 353 • 世章该在户端与时间服务喜户端的不同之处 . rls 客户端首先把目录名写到 socket 中 . 上面的协副规定了害户端每次监造 一行 . 因此程序中在行尾增加一个换行持 . 匪下来 , 事户 端进入一个循耳,将从 50cket 所接收的世据复制到标准输出 . 直到接收到文件结尾标志 ­ rls. c 使用低级别的 w Tl te 和 r ead 调用来租服务器吏换盘据.循环中周到了标准大小的缓存 U 提高耻率 . 下面将辅写服务器端的程序 . 4 服务器程序r1 sd 服务器必须得到 个 socket ,然后调用 bind 、 listen 命令,最后调用 accept 来接收一 血呼 叫 . 在接收呼叫之后,服舟器从 socket 憧取目录名,然后列出该目录下的内睿 . 服务器是如 何结出文件列毒的呢?这里可以把第 3 章写的 1 ,理序 E制过来 , 但也可以用一个更简单的 方法仅仅使用 popen t章取常规版本的[,程串的输出 . 如回 1 1. 11 所示 . 客户 客户扁清凉 图 1 1. Il 使用 popen"[.. "来显示远峭目录 下面的代码 中 使用丁 popen / .. rlsd. c - a reaote 1s 5crv",r - with paranoia 时 将inc:lu血 < stdio. h> 样 include < unistd.h> 林立 nclude <町lI/ typeS . h> 捋忌地l回e < sysj socket. h> 梓inc: lude 第 lZ 拿连援和协议编写 W ,b 服务器 客户 客户 连拢到圆务媲 获取服务 1.1晰 i茎扫毒 因 1 2 , 1 下面将分别讨论每个操作 . 二二二 :2; 1 客户 /服务黯交互巾的主要步骤 12.3 操作 1 和操作 2 :建立连接 • 361 • 基于班的军统需要建立连接 . 这里特回回一下建立连接的步罪 , 陆后将这些步骤抽血 且一些库函数 . 12.3. 1 操作 I 建立服务器端 socket 首先,如图 12. 2 所示 ,描述了服务器设 立一个服务的过程 . 设立一个服务一般需要如下 3 个步骤: (!)创建一个 sock et socket socket(PF_INET , SOCK_STREAM ,的 (2) 给 soc ket 摒定 一个地址 bi nd(sock, &addr, sizeof(addr)) (3) 监昕 接入请求 listen(sock, queue_si~e) 步骤 1 图 1 2 . 2 创埠服务器瑞 socket362 • UlllldLin~x 编程实践放程 为了避免在编写服务器时lIi:!!输入上述代码将过 3 个$黯组合成一个函数 make server_soc kel . 该函散的代码位于本章后丽将给出的 socklil、 c 文件巾.在描写服务器的时 候只要嗣用在函数就可 U 创埠 个服务器喘 soc ket . 具体如下 sock" nake_server_socket(int portn四〉 return - 1 if error, 。, r a server socket listening at port "portn四" 12.3. 2 操作 2 建立到服蛊器的连接 Jt院将在户连接到服务器 . 如 l 图 1 2 . 3 所示 , 革于拢的网络客户连接到服务器包含山 下 两个步骤: (1)创建一 个 SOCkCl socket '" socket (PF_I~ ,曰::K_S'J'RF.AM,的 (2)使用该 sockel 连 使到服务撑 connect(sock , &serv_ðddr, sizeof(serv addr)) 将这两个步黯倒叙成一个函数 COnnttl \0悦 rv~ r . 当编写客户崎程序时只要调则该函数就可以建立 到lIi务嚣的选後 . 具体如下 1 fd connect_to• server( hostna圈, portnum) return - 1 江'"饵, 。 r a fd open for readi呵 ~d 盯立ting connected to th陪 s ocl< et 此 port "portn四" on host Hhostname !Ji1l 2 岱',jj!并 主帽务揭 因 12.3 连撞到服务器 12. 3. 3 socklib. c / . soc业lih.c ' • ThÜ! file contllins functions UStlÚ lots wæn writ i呻 i nternet • cli阻 t/server programs. The t 回国 in functions here are, • int make_server_socket( portnum ) returns a server socket • 。r - 1 if error * in t 国ke_ server _ socket _ q( portn咀, backl呵〉. mt connect_to server(char 'ho"tname, int portnum) 第 12 章 连篷和协议 l 编写 W.b 服务锦 i f( fd "',. 四川 回且 t {l) ; talk_with_server(fd) ; close(fd) ; ; . or d且.. / / _ chat ..ith 四凹er */ / . ho呵 up when done 旷 • 365 • 函数 l a l k_ with _ se rve r 处理与服务稽的生话 . 具体的内窑取决于特定应用 . 例如 , e- m a il 客户和邮件服务器吏模的是邮件,而天气预报事户租服务器吏模的则是天气 . 2 一傲的服务嚣棉 一个典型 的服务器如下 : 皿 1nO int soclc , fd; sock ., make_se凹 er_ socket(por时 , if(sock. "" -1) H迅 t (l ) .h立le (1) fd • i!tCCept ìJ1 胁议是iI么 9 吏互的事务是什么?迫里的事据管理胁世包括 F 丽的两个主要事务 ① 这个慢念并不难理'阿帽'附注备越来越先进 , 连徨锺来锺方便很可能在不久例将来就可以通过你的收音机询问 殴务"是 2/4 1 娼妓你勤部"歌跳的作町.第 13 章 基于数据报 ",~.. ... 因 1 3 . 5 通馆可以披连接和中断 • 流 socket 负责分割排序、重组的所有工作 . 数据报 soc ket 则不会 . 下表给出了它们之 间的区别 .第 13 拿 基于曼史据报(Ða lllgram) 的编程 E 描写许可证'民务帮@ 1 接收数据报 - 合主机可能有多个雄收5O<: ket . 每个5O<: ket 撞衔'段给 个特定的精口号 . ."止用主机上的喝口来区分 . 因 13. 7 使用 ~ndlO 初 r .., vfrnm • 389 • 程序 dgrecv . c 是一个简单的基于数据报的眼身器 . dgrecv. c 使用命令行传过来的端口 号建立 socke t ,然后进入循环,接收和打印从事户端盘来的数据报 /………·譬.."...... ..铮 铮~......~....~.*...... .........*曾饵. .相.. .. .. .. ..倒昏. .器"… ·啕E缸'v .c - 由 tagr咀 recelVer uSðge 啕军民 'P。此n四 揭 action , listens at the specfied port and repoz:ts lIlesBages . j #旦'lC lude < atdio. h> ** include < stdlib. h> ** :inc:lude < sys/ types. h> 再 include < sys/ socltet. h> #虹x::lude #- define ∞.ps( 圃,对 ( perror( lI ) 回此〈剖, I int lIIM:: e 啕rð.ll se凹" ,民ket( int); int get_ internet_aódress(char 链, int, int 臂 struct sockaddr_in 怪) • void say_who_called(struct sockaddr_in .. ) '"'咀皿(int ac , char .. 叫 l> int port; int s民k char buf [即SIZ] ; 旦回 t .$gl四 struct …'" "曲, s础len_t "addrlen; /锦 use this 归此时 /* for this s出k" 旷 / * to receive 由ta here 旷 / * store its length here .. j /* put sender' s address he四旷 归缸xl i t.!I le吨th here */ if ( ac ~ a 1 11 (por:t ato孟 (av [ l ] ) )< - O){ fprintf(stden, "\l sage ,啕T e<: v por t.'l.咀ber\n") ; 自比 (1)第 1 3 章 提于激据报 (Datagram ) 的编程编写诗可证版务帮③ SERVER , GOT: GBYE 25915. 2( 10.200. 7~. 200 ,1055) SERVER , SAID τ1iN X See ya! (10. 200. 75. 200 ,1055) CU四T[2591S ] , rele臼ed t:cket Ol'i • 407 • 实际运行的程序可能有非撑不同的结果 .YA 廿如此,从中可以看到服务器如何接收请 求、给出 !ffi 据J;l]<军事户端如何技取联据并开始工作的可山青到进程 25914 没有排到票据. 因为在在进程出现时 . 所有的事Hll 都已经融占用了 , 而 Z 前进程 25915 却得到了一张票据 。 如果运行在程序多次.可能会看到不同的结果 . 13.6.4 进步的工作 版本 l 的许可证服务器可 WR 鼻子地工作丁 服务器处理请求井且维持持有琪据的进程 罗 '1 表 . 客户可以从服务器 m .ßJ 票据.现在为止一 切都正常.看起来这非常理想 . 不过皿 Z 世界并不.~, j主样芷好软件且其使用者并不完全是体所期望的那样 . 可能出现什么错误呢 ? 在如何址理这些错误呢。 13.7 处理现实的问题 这里的评可证服务器能根好地工作 , 前提是所有的进理是正常工作的 . 有时软件页I 能 运行出错 . 如果 SuperSleep 程序匾另外一个用户杀死丁 , 茧者程序发生段存取错误而酷内 核杀死了 . 该如何呢?肘子它所占用的票据该如何处理呢 9 如果服务器崩攒了革么呢。在 服务器重启后旦特 :!l: 生什么呢? 现实世界中的理序必须能瞎扯理异常剧由 . 这里考虑两种情形容户端崩世和服务器 崩攒 . 13. 7. 1 处理害户端崩 溃 如果窑户端崩脯,在尸将不会归还票据 , 如图 13.8 所示 . 将不会返回票据 因 1 3 .8 客户不归还票据 童出列夜中死进 程响应的条口 在出租车公司里 . 屉且可能群职桂解聘、回事或死亡 , 但仍持有公司轿车的钥匙 . 这些 对公司将造成什么影响呢 9 盎出列表指明票据仍槛占用.其他进程就不能得到古票据 . 但 是 . 如果有足够多的进程崩睛,整出罗 '1 量就可能虽鼎瞒丁 , 但此时却世有运行的事户程序 . 钥匙管理员通过给持有钥匙的人打电话 . 就可 U 收回钥匙 . 他可以定期地浏览辈出列 表.辑后给每个司机打屯话你仍在使用车吗俨 . 如果无人响应管理且把他的,g字从童出 列表中刘去 . 管理员险壶的频率越高 ~IH 列在的精确性就越高 .• 408 • Uni x/ Li nux 编程实战披程 许可证服务器可 U 使用相同的技术定期检查票据数组确认其中的每个进程是否还吊 着'如果某个进程已量不存在了,服务器可 U 把该进程从盘组中去除,释放其占用的票据 . 幢茸程序运行的越颇蟹,数组的精确性踵南 . 1 收回丢失的桥据调皮 服务器中如何增加收回票据的代码 ' 如何调用这些代码?服务指必须实现两个独 立 的 操作等待窑户的情束,同时周期性地收回丢失的票据 . 而调度行血是简单的只要使用 a[arm 和 signal 技术来周期地调用一个函数 . 在前面章节的事动文字例子中,使用了这种技 术 . 告订的程序班程如图 13.9 所牙、 . 因 13. 9 使用 ø. ! a rm 来调度禀偌消除理序 在世计需同时扯理两件事情的程序时 , 必定要考虑函数之间的冲吏 . 如果服务器正在 扯理事户请求的同时,被 SIGALAM 信号胜监调用回收丢失的原锚的函数,舍产生问题吗 ? 这两个处理画数共享置量或者数据结构吗 ? 显然是的,监植票据需要曾改靠出如l 茬,而收回 票据也需要静改辈出列茬 . 这种冲要可能会破吓晕据的一致性吗 ? w:问题国作课后练习 . 考虑到置圭性,在扯理情革的时候关闭 alarm . 2. 收回丢失的事据编程 服务器希望能回收已经不存在进程的票据 . 那么如何判断进程是否还活着呢 ? 可以使 用 popen 来运行阳 , 然后从胆的输出中查找 PID. 以确定持有票据的 PID 是否存在 . 另一 种快踵简洁的方桂是使用 kill ~统调用的特殊功能 . 可以通过给进程盎选编号为 o 的信号以确定它是否存在 . 如果进程不存在,内核将不会 主选信号 , 而是远回错误井设置 e r rno 为 ESRC H . 在 ti c ket reclaim 中使用了该特征,班函 数在 Ise r v_ func s2 . c 文件中 z 样 由fine RECLAIM_HIl'ERVAL 60 1. r回la i1凹ery 60 seco咄 旷 /……….....................传……....... .份....."......给.....铮.费........ • t icket recla皿 • 90 through a11 t i clc:ets and reclaim 0随, bd。可口哩 tod田d processes .. Results , none . / void t icket_reclailllO • 410 • Uni x/ Linux 编程实践敏程 通过上丽的悻础 , 叶可证服 JHi 就 "f VJ 周期性地检查原据了 。 确实市 ~i在样周期性地 险盒吗?为付么不只在~酷刑 二丧描 f 并且有在户的哨求世拒绝的时候检查呢?这样会更 好吗 ' 13. 7. 2 处理服务器崩溃 服务器 lûi 攒通常有两个严重的后果 . 白先.辈出罗 '1 表丢失.失去迸程持有票据的记最 . 其在 . 新客户不可 U 再后行.因为分左许可证的程序已经不存在 . Jil简单的解决方法是重新 启动服务器.如国 13. 1 0 所示. 1Ii l年严'可 以得到l 一 张票据 图 13 . 10 服务费在崩攒后重启 a野启动萨厦 务器篮 III 列及为空 重启服务器使得前的客户可以运行 , 但会带来两个新的问题 . 首先 重 M 服 务器的J;.!据敛组是 莹的 .服务器吉有新的来桩取走的黑据列表 . 崩酣的 n~ 卦嚣的票据数细可能已 经 满了而重启服务器仍给其他在户盎道件可 1主样草草地关闭服 务器再重自服务器就悻印钞机一样可以产生更多的事据 . Jl:次,恃有旧的服务器的栗世的事 户在归正t1据时,将会被认为是伪造事据 . ] 单据验证 上述问题的 个解陡方法是票据验证 . 票据验证表示每个在户周期性地向服务器盎选 票据的副本 . 客户监逝世时包,对服务器且 这是我的事据 . .&\合法的吗。",如图 13. 11 所示 . 图 13. 1 1 客 户 验证禀倡 票据含有盘组描号和 PID . 服务器检查盛出如j 毒 . 如果jJ<项为主 , 服好器可以认为撞 事据是自己先前的主例赋于的 . 服务器将会把草原据加到列表中 . 逐步地,在户提供票据 来验证辈出夕 '1 牵酷重新填人 . 服务器置建甚出页。表解决 丁在 丢失问题 , 但可能导致其他问题 . 如果 一 个新的事户在 表重建野之前,请求票掘,服务器可能分盎→个已经结予其他在户的票据结该在户 . 当持有 旧的事 据的事户时其罪掘进行验证时,服虫子器会拒绝它 .第 1 3 意 基于敛据报(D,剖 agram ) 的编徨编写许可证服务樨 @ • 411 • 另 种方桂是服务器拒绝袋中世有的所有的票据 . 恃有被拒琪掘的客户尝试再申请新 的 . 这种方桂是否E 好 ' z 协议中增加股证 票据验证是协议中的一个新的事务 U酣 VALD t ickid SERVER , GOOD or FAIL i nvalid ticket 这里必须改变客户和服务器以支持验证 . 3 在户端增加验证 客户瑞增加验证 , 需要编写 个函数井在主函数中词用它 ,其配程如图 1 3 . 12 所示 . clm … 一_ TICJ:… GBY! 'ickid ___ _ - - …一一 叩 ,~ 副 1 3 . 12 客户定期验证票据 在户可以根据系统的需要以一定的间隔足期验证票据,可以匪置一个计时器辈定期验 证 . 如果在户是一个电子制表程序,嘘证可以在一定量的计算结束后进行 .一个许可证服 #器舍响应验证请求 . SuperSleep 客户程序可以把 1 0 静的睡眠时间分割成两个 5 耻,在这期间进行验证 . 这 作为课后蝶习- 4 服务器揭槽加#.-据验证 服务器端增却 l 验证 需要做两处改动.i!!c功后的程序位于新的文 件 l serv funcs2. c 中 . 首先 , 增加一个函数来验证票据 /…………·晏…"………'"医必钝………... ........刽..................键 .. do vali由国 .. Val idateclient's ticket " IN mS9_P message received from client .. Results, ptr to response ..σTE , return is in static buffer overwritt田 hy each ca11 . / static: char 指由,_ vali date (char .. 1118g) intp主d , slot; /"恤归 n回 国。f ticket *j j * ..s9 1 ∞ks like VALD pid. slot - parse i t 配时 validat e 时• 414 • Unixj Li nux 编程实践敬程 S即回.R , SAID 哑倒'X see ya! (1 0. 200. 75 .200 , 1087) CLIENT [ 3阅 09 ] , rel ea.se t icket 01': [2] Done . /1cl nt2 [ 4]- Done /lclnt 2 $ P' PIO TrY Tll!E 。吃D 23509 pts/ 3 00 ,00 ,00 bash 30日 08 pts/ 3 00 ,00 ,00 l serv2 30810 pU/ 3 00 ,00 ,00 ps $ 看起来还不错 . 不妨试一 下上述程序,现事其 中 的主互 过程 . 13.8 分布式许可证服务器 许可证服务器和 1 世许可程序通过 socket 进行通信. soc ket 可以连接不同主机上的进程 . 理论上 , 与 W , b 服务器和窑户运行在不同主帆上类似庄里的事户可以运行在一 台饥器上 , 而服#器运行在另一白机器上 . 当它们运行在不同的机器上时,会有问题吗 ? 是的 . · 问题 l 重重的进程 10 进程 10 在→白机器上是惟 一 的 , 但在平同主机上的进程可能拥有相同的进程 10 . 因 13. 1 3 说明的情况不古有任何错误且很常见 . 作可证'自务植 圈 13. 13 跨网络刊 D 不惟一 存曲t'i据的表中肯有 P IO 和离 据捕号 . 在图 1 3 . 1 3 的情况巾 , 许可证服务将认为给同 个进程分配了 3 张票 . 每个进程只要一 张票就可以运行 , 所以这是错误的 . 请求 E辜的票 据 , 可山酷认为是喜尸 端的→个漏洞 . 这里可以扩展票据表项的棉 式和内容 , 使其包吉标识运行程序的主凯从而解决lIUl P IO 问题 . · 问题 2 . 回收票据 服务器通过调用 kil l( pid . 的 命令 向客户 回 收票据 . kill ( pid. 的监量信号 o 给持 有 票据 的进程 . 通过幢订过的事据茬 , 服务器现在可以知道事户 运行在哪台主机上 . 但是 , 服务器不能给其他机器上的进程盘盘信号.如图 1 3 . 14 所示 . 如果许可证服务器 想蜡主机 3 上的进程韭送信号 . 1IIi身器必 须产生主机 3 上的请求 .纳 13 意 鉴于数据报 ( Dalllgrllm) 的编程到自写诗可证服务稽 @ • 4 15 • 窗口 M 逃程不能给其他主机发送信号 为什么不在每白机器上都运行一个服务嚣的实例 9 如因 13. 15 所示 , 每个本地的服务 器可以盹控吾失的I:l据 . 许回 I 证服务器 件可证帽务帮 件可证幅画务器 回 13. 15 运行本地直制的 IserT 本地服务器解决了向主但 l :!t量信号的问题,但是卫带来新的问题 :哪个服务器 盎布票 据?主服务器如何和本地服务器通信 。 在户把tI据盎结谁验证 9 · 问题 3 主机崩酣 如果其中的一 台机器停止运行 ,将会E 生 什么 ?主 服务器如果还在运行的话,如何收回 事据 9 喜户端程序如何验证票据 φ 如果运行主服务嚣的主机停止运行 .谁来分盎票据 。 如何建立 个分布式许可证罩统来同时支持多台机器?这 里有 3 种方桂.试考虑它们 的设计细节以及民融占 , 并考虑当事户、服务器、计算机革者罔描崩酣时每个方案的后躲 . • 1J 挂] 客户瑞服务器和中央服务器通信 每台机器都有 一 个本地服务嚣,就像本主描写的那样 .每个客户且本地的服务器通信 . 本地服务器把请求转主措中央服务姆 . 中央服务器远回票lI!或者拒绝 . 本地服#器记 最井 把应答转盎蜻客户 . 本地服务器也可以强制执行-些时本地在户的限制 , 例如该舰器上 可 U屋行的程序实倒数,或者程序可以运行的时刻 . · 方哇 2 每个事户都和 中央服务 器通信 在户直接给特定主凯上的服务器皮革请求 .本 地耻务器运行在每个主舰上,但这 些 服 封器不初在户迦倍,它们在重新声明栗酷的时佳作为 中央』国务器的代理 . · 方击 3 客户服务器和在户服务器通信 每台机器都有本地服务器 , 每个在户眼本地服务器通信 . 没有中央服务器 . 所有的本 地服务器问 互相通俗 . 每 IX 个在户请求时,本地服务器而J 问其他所有的服务器目前已经 用掉了多少张票 . 如果所用票据盘小于所允叶的屈、盘,本地服务器分配一张票据给喜户 .第 14 章钱程机制 z 并发函敏的使用 • 425 上面的每一行输出之间都有一个 l 静钟的时廷,程序共运行了 1 0 砂 . 因 14. 1 展示了此 程序的执行配程 . 因 14. 1 ..执行路径 一↑进侄 陶个 函1!1 个线瞿 首先,执行路径进入 rnaln 函数,然后进入函数 prml_msg . 接着,从 pnnt _ msg 中堪回到 mam ,之后再一 眈进入 prmt _ m sg 画量进行第二 民的函数调用 . 所有指令执行完革后,从 mam 中返回 . 不间断地跟踪指令执行的路径在庭里世称做执行路线 (thread of execulion) . 传统的程 序只有 一 条单独的执行路线 . 就算是包古有 gOlo 语句以及递归于程序的程序也只有一条执 行路线 .1军管这条路线有时有些弯弯绕绕 . 14.2.2 一个 事线程程序 如果想同时执行两个对于 pnnt _ msg 函量的调用,就惶使用 fork 建立两个新的进程一 样 . ~II 该理么舟呢?这种思想清楚地体现在圄 14. 2 中 . 因 14. 2 多执行路径的趋序 首先.一条执行结路进入 mam 函数 . 韧曲的线路新建丁 条新的执行线路来运行函盘 prml _ msg . 韧始线路继续执行下一条指令从而新建丁另一条线路来时 pnnt _ msg 函数进行 第二眈的调用 . 最后 , 韧始线路等待两条新的线路捆人自己 , 再从 mam 函盘中返回 . 人们无时无刻不在进行着这种事线程的任务管理 . 如果立母需要做许多琐事,他们通 常全带上强于一起去 . 父母让一个孩于到杂货铺买牛奶 . 另一个孩于去图书馆还书 . 最后 等两个孩于都回来之后.大家再起回事 . 一个线程就类似于上例中帮父母做事情的 一个孩子 . 如果扭同时完成许事事情,最好第 14 拿钱程机制并发函数的使用 1nt counter :: 0; main( ) ptlrr国 d_ t 口, /* one thr国d 旷 void .. print_ count(void 叶 / * its function */ int i; pthread_create(&tl , N1JLL, print_c。回 t ,肌江川, for ( i - 0 i <肌.1M; i+t ){ counter + t "四 p(l) pthreacU。皿《口, NULL); void .. print_count{void 幡回〉 皿 t i; for ( i"O i < 肌润; i++ H printf("c。国生 · ‘ d飞 n" , counte叶, sleep(1) return NULL; • 429 • 程序In cpnnt. c 使用了两个线程 . 韧始辑程执行了一个循环来使计量器值每秒钟增 1 • 初始线程在进入循环之前,创建了一个新的线程 . 新的缉程运行了一个函数来将 co unter 的 值打印出来 . ma m 画敬和 prmt_co unt 画数量行在同一个进程中,所以都有时于 cou nter 的 访问权 . 固 14. 3 展示了两个函数和圭局变量的内在逻辑 . 四 ]4.3 两个线程共享全局变量 当 mam 画世改型l: T counte r 值之后. prmt _ coun ter 画触立 即可山访问到新的值 . 因此 并不需要通过营造或者毒接字等方法传造新的值 . 编译这个程序,然后运行宫,结果如下 z S cc incprint. c - 1院hread - 0 incprint $ . / inçprint 430 • counl: ,. 1 count .. 2 count • J count ., 4 count '" 5 Uni x/ Linux 编程实践被程 程序显然可以正常工作 .一个画散佳 ilr 了变量,另 -个函敢自取并显示丁壁量的值 . 这个例于展示丁如何使运行在不同线程中的函数共草圭局变量.不过下个例于还要有趣 . 14.3.2 例 2: twordcount. c 很多学生都有这样的经验,对着电脑盘自己学期论文的字数以确定字量是平是足够 . 假设一个学生有 一篇 1 0 页纸的论文 , 它有两种方法来计算这篇文章的字数 . 一种是 一 个字 一 个字地数了 1 0 页纸 , 另一种方法是找 1 0 个 同学来,给每个同学一页纸,让他们分别计算, 然后将结果罩加起辈 . 显然并行地计算过 1 0 页纸的宇量的方法会快很多 . Unîx 平台上的 w ,程序的作用是计算 - 个或1>个文件中的行、单词以及字符个数 . 不 过 w,是 个典型的单线程程序 . ~样来设叶一 个事线程程序来计数井打印两个文件中的 所有宇数呢 9 · 版本 l 两个线程、一个计数器 第一个版本程序创建分开的线程来时每一个文件进行计算 . 所有的钱程在检查到单词 的时候对同一个计数器精值 . 因 14.4 体现了这个思路 . 一个进程 个计触嚣 两个钱程 阁 14.4 两个线程共写在一个通用计敛糯 此版本的代码包含在文件 tword count l. c 中 / . twordc阻ntl.c - threaded 四 rd counter fo::: _t ll'o files. Version 1 _/ 样 ""' 1 回e < stdio. h> 号; inc:lude < pthr回d.h> 辖立 ~1回e < ctype. h> int total_ IIOrds Ølain(int ac, char • av口 } pthread_t tl ,口, /* two threads 时第】"脏 线程机制:并发函数的使用 p'皿tf ( " U8呵e ‘ s filel file2\ n飞剧样 [ 0 ] , 四 it ( l); 回国l_words .. [ 0] ; pt hread_c reate ( &t l ,阳且, coun飞崎rds , 归。 id 11 ) 剧 【 1 ]) ptllread_cr回国 ( &t2 ,阳IL , COunt_lIOI"白, (void ,, ) av[ 2刀 , pthread_j oin( tl ,阳LL) ; pt trr田 d_j oin ( 且,肌JLL ) ; pdntf("警 Sd , total words飞 n" , total_"ords) ; void " count words ( 币。基 d " f ) 0 ,,",慢 f il ename .. (char 铃 ) t; rru: 11 fp; int c , prevc ..\0' ; if ( ( fp .. fopen ( filer田e , n r 胃口 ! .. N1:血.)( "hi> e( ( c " getc( fp ) 川 = 回 .F }{ if ( isaln咀 (0) 且 i salnum ( prevc ) )l ptkud--Utu-LEMKαmt缸 1忧的, total_words +令, pt trr回 d_~utex_ un l ock ( &counter_l ock ) ; prevc " c; fclose( fp) ; I el se perror( f il e啤a的, ret= 即毡, 此程序的逻辑类似于图 14. 6 所示 . • 433 • - 个进程 个 计敏l!.! 细心的读者可以监现,在原来的程序中仅仅如 I 7=-行代码 . 首先定且了一个 pthread _ mutex _ l 类 型的全局噩噩 counter lock. 然 后赋结它一个初值 . 另外改动的地方就是把对于 count words 的操作亮 在对两个函数 pthread _ mutex _ lock 以及 pthread _ mutex unlock 的调用之间 . 图 14. 6 两个线程使用互斥锁来 共享 ltlt 器 现在西个辑程可以安全地共享计散器 7 . 当 个线理调用 pthread _ mutex _ loc k 的时 候 ,如果另 一 个线程已经将这个互斥量锁住了部这个线程只好阻 事等恃着这个锁植另 个线程解开后,才可以对汁数• 436 • Unix/ Linux 编程实践敏程 struct a咱I_set * IlrqS - a; /* oast arg back to correct 叮归 时 FILE * fp; int c , pl'evc ' \0 '; 卫 f(( fp - fop审议""1'一 >f n.!lJl e , "r"川 , . 阻lLL ){ Ifhile( ( c = getc( fp)) !,. E(到')( if ( ! i且lnUØl (c) && isalnum(prevc) ) 缸"gs - > count ++ prevc - C; fclose( fp); I el se pecror(args - > fnam时, 因 turn 阻, 这里通过定义一个 以文件名和该文件中字数为成员的结构体解决了同时传递两个垂靠 的问题 . maln 函融定且了两个这种类型的局部变量,井将这两个壶量的地址传给线程 (如图 14. 7 所示 L 传递本地结构体指针的方法既量免了时E 斥量的依赖,卫悄除了圭局变量 . 一个进程 注意这里,宁波有销 两个结蟹 细 14. 7 每个线覆都拥有一个指向自己结构体的指针 每眈调用函数 co unt _ w o rd s 之后都会接收到一个指向不同结构体的指针,因此线程从不 同的文件中醒目主信息,并时不同的计量武器进行增 l 操作 . 因为结构体是 m am 中的局部变量, 所以分配给各计数器的内存空间在 mam 画盘边回前一直保存着 . 14.3.3 线程内部的分工合作 · 小结 进程的数据空间包吉了所有属于它的壶量 . 此进程中运行的所有钱程都拥有对这些变 量访问的仅限 . 如果这些变量值不聋的话,线程可以主误地读取井使用它们的值 . 不过如果进程中的任何线程修改了一个 E 量缸,所有使用此变量的钱程必须呆用某种 策略来量免访问冲吏 . 在某一时刻,只有惟 的线程可以对变量进行访问 . 字数统计程序的 三 个不同版本显示了三种不同的方法来进行线程间的变量共草.在 twordcount1. c 中使用的第 种方法,先许辑程主任何告作来蜡改同一个噩噩 . 这个程序本 身存在着很大问题 .第 1. 章线程机制并l<画敛的使用 • 439 • 一个文件要比第 二个文件的时间茸的事 . 那么在第 二个钱程完成之后,如何来通知原线 程呢? 一 个线程是如何与另一个线程互通消息的呢?在一个计数线程完成任务之后,它是如 何通知原线程它的结果已经产生了呢?对于进程来说 , 当于进程终止后,系统调用 wa Jt返 回 . 是不是对于线理的仕理也有类似的机制呢?某个钱程是否也可以等待其他线程完成? 事果是否定的 . 线程主法按照这种方佳工作 . 因为肘子钱程而吉井没有立线程、子线程的 砸在 , 因此井不存在某一个明显的线程可以去通知. 14. 5. 1 通知选举中心 某选区完成计事后,将其结果盎送给管理中心着 下下面的这个方击,此方法是用来从 选区得到选票 , 然后将其监送给管理中心 ( 也许营起来有些古怪 , 不过钱程确实就是用这种 方法来与另外的事件互通消且 λ (1)在选举中心有 一个世迪选举报告的邮箱 . 这个邮箱在某 一 时刻只能撞收 份票数 报告 . 此邮箱前有 一 商旗帜.它可阻桂升起来.但很慎就会撞恢直至原位 . (2)选举中心等待这面旗帜升起来 . (3) 某选区负责人将选区统计结果脏入邮箱中 . (4)某选区负责人将邮箱的旗帜升起〈盎造倩号) , (5)选区 中心看到脱朝升起来了,便执行下列步骤: . 从邮箱中取出击 E 的统计报告$ · 处理此统计报告+ · 回到原来的地方继续等待 , 即循环转至步事 (2) , 上面的黄略起韧看起来有些古怪,但确ll:很有童且 . 直选方将数据存入睿器中,然 后升起 面旗帜来通知接收方盘据已经准备好了 . 图 14.8 槽楚地展现了选举中心与两个选区之间的荣革 . 每一个选区将自己的报告放入 邮箱中,然后通知选举中心来取 . 最后选举中心处理了报告 . 在这个例子 中 升旗帜的挂本 术语叫做主选信号 , 即接收方在等待信号的到来 . 当然 , 这里只是个比咱,对旗帜的操作与 U n ix 里的信号量机制 点主革也世有,两者仅仅是基本思路相同而已 . 选举咿心 条件标记 报告倩箱 筒箱锁 选区 1 选区 2 囱 14 .8 使用加锁的邮箱来传递数据第 14 意线程凯倒并发函敏的使用 • 441 通知 lf储戴帽的变量 豆 f辛糊 ,也.hreaι"卧... ' 民 ruct Ilt !LlòQt "mb l。εk 初始线夜 两个接程 因 14.9 用加锁的变量机制来传递数据 中去 . 然后原线程草出信号 , 以防别的计曲线程正在哥待 . 且后原线程循环回去,继续调用 pthread_cond_ wait 函数,自动将互斥量解锁 , 井将自己挂起直到下 民的信号到来 . 上 面一段所讨论的步骤恰好对应于投票革统的原型 .也同样 ~ ;t 应于下面将君到的 twordcount4. c 中的代码 ; * tllordc。山nt4.c - threaded 四 rd co回国 r for two f iles " - Version 4: CO f}(到 tion variabl嘘 .11。司, co山 lt er ~ fuoctions to report resul ts 国rly • #: ir田clude < stdio. h> 林 include < pthr回 d.h> 样 豆 oclude < ctype. h> 拭目ct arg_set ! cmr "fnarne intc。回 t , ,,=乞町,.t 叫 ilbo l<; pthr国d__u恤x_t lock ,.凹'HREAD MUTEX I N ITIALIZ阻, P 二 hread_COlv:t t flag ~凹lfREAl)_ COND _INITIALlZER; 旧 in( int ac. çhar 部 av[]) pthre<'ld_t t >, t2 struct arg_set argsl , arlJS2; void 镰 c。 ωt_ .. ords(void 帽〉 int reports_in " 0 / * two val ues in one 缸g 旷 / " f ile to eXðllline 时 / * nUJQber of ..ords */ />t盹 threads . 1 1'" two argsets 时第 14 拿线程 4乳制 f 并发函数的使用 动画 i曼置 随盈钱程 动画线徨 阁 H. 11 动画钱程及键盘线覆 14.7. 2 ~线程版本的 bounce1 d. c 比较一下原先版本 bounceld. c 与新的两线程版本 tbounceld.c 的1& ll'J , / .. h tbounceld. c , controll回回孟国 tion 回 E唱 two th国.'" .. note one thr时 handles ani且 tion .. other thread handles k. eyb曲-x'd input .. cOIIpile cc tbounceld. c - lcurses - lpthread - 。由。 unceld . j 拌 include <:stdio.h:> 将打d叫e 符幻、clude < pthread. h> 将挝、clude < stdl 恼h> 拌 include < unistd. h> /* shaI"ed v缸 iables both 乞Irr国由国 e. These ne回 a .utex 旷 符 define 阻ESS'咀 n hello~ int row int col; int dir; int deloy; 且a矶。 int 回elay; 基盹 0 , pthread_t Q(Lthread void .. ..ovir可圃", 0 皿拉配 r O ; c OlOde() ; noecho() j * cuu四t rOIl时 / * cun-,四t c01咀n . j / * where we ð l"e gOll可时 / * ôe l 町 b.".使n~吨• . j j . 睛.. del町 . j 归国0< ill胆 t . j / _ i!l thread ,,/ ;* init curs自由"'t叮时 • 453 • 第 1 . 掌线程机制:并发函敛li'J使用 455 • 盘 中 创建了-个新的线程果执行 m o vlng_msg 函数 . movmg_ msg 函数执行了 个简单的循 耳,\,叩 , m Q Ve ,检查跳跃, repeat o 同时,在同 一 个进程的另一部分 .maln 函散也执行 个简 单的循环 :ge t c h ,扯理 repcat . 告改后的程序仍然用圭局变量辑示璋的壮志 . 在基于中断的版本中,必须使用圭局变 量 , 因为主陆特垂撒传给信号处理者 . 然而线程饥制却允许线程接收盎盘,因此可以惶 上 面 字数统计程序的第三 第四版本一样,通过创建结构体,井将其 地 址传给钱程的方式果改进 程序 . 14.7.3 基于事钱程机制的事重动画 tanimate . c :!l样;j可 ~1 同时使多矗消息活动起来呢 。 牵线程的字盘统计程序井宜地运行了多个字 散统计函数的事例.每一 个调用部传递给此函数 一 个文件名和 - 个拙 立 的计盘器 . 用同样 的血理可以运行井行的动画 . 下面的事线程动画程序 t8 m mate. c 是 tboun cel. c 的 一个扩展 o tammate. C 最多可山接 受 1 0 个命令行的垂数,使每一 个盎数在不同的行上静动 , 井且有自己独 立 的速度和方向 . 例 如.下面的命令 t an.t.ate 'Buy this' 'Orive t h主scar"S洁白d Money here ' C。因 ume '1 阳y! 将产生一个包吉事种幌动的动画消息 , 如图 14. 1 2 所示. 剧Iy this! ".山e thi~ ca!'! Spend mon~y herel COIISWlIC! Buy! 0' t o quit, '0'. , '4- to bounce 『卢严 圈 14. 12 多仲躁动的动画消息 用户可以通过挂键 " 0 " 、 "1 "等使处在该行上的消息跳动 . 也可 以 使一组字持南活动起来,甚至是那些 U n ix 的工具,可以尝试下面的命令 : t 8JIi aate I 11 I 国皿恤 'users' t ani Q t. 'date' tammate 在不同的线程中运行控制动画的函数 . 这个画盟 的每 个事例都 接 收到原始 线程所传的 一组不同的盎数 . 参数指定丁消息名林、行号、事动方向以及且!lt. / . tan u跑回 c , animate severa1 str iI啕 S UBU唱 tlrr幽幽, curses, ·四 leep【》• 460 • 一个钱穰读取并 处理用户输入 Uni x/ Linux 编程实践敏在 每个侈动的图片由一个独立的钱段但制 这些 线限将对屏稿画面的夏'听请求政入俏箱中 一个战侄接收 到对扉幕的更 新晴求 。 然后 调用屏幕处理 函数 图 14. 13 貌 立处理屏幕控制的线程 可 以把革个屏幕控制线程营成在一个大公司中的公共荣革部门 . 任何想要向媒体主选 消且的部门都必须向公共关系部门左道请求 . 公共荒草部门里的员工只茸 'C'、如何将消息盎 造出去 . 其实且个扉幕控制线程在功能上就悻这个公关部 一 样 , 任何希望向屏幕监消息的线程 都必须向这个扉幕管理线程盎邀请求 . 果用这种饥制之后,每个线程所监迭的情草就应该包吉这些信息行 号、 列哥和消息字 持申 . 控制动画的线程盎出消息 , 扉事管理辑程接到消息后 , 将其显示在屏幕上 . 这种生产者〈监消息结程 〉 捐费者《接收悄且钱程 〉 的设计类似于前面学习的事线程 版的字散统计程序: 需要 - 个存储置量来肢置消且,通过互斥量来避免对此存储置量的访问 冲突,以且一个条件查量 , 在动圆辑程韭追捕息之后,画Hl 通过这个条件墅量将屏幕控制线 寝唤醒 . 对于扉幕控制功能的盘中化和抽盘化世汁使得程序更加里话 . 就算屏幕显示系统直接 了,也只需改变屏幕控制线程中使用的函数 . 扉幕控制统程甚至还可以直起同远蹦显示服 务器的 一个舍话,通过管理或茸撞宇将消息!,[t 过去,然而这一切对于控制动画的线程来说都 是造明的 . 改造这个程序就留给大事作为练习完成 . 小结 l 主要内在 · 执行线路即为程序的控制配程 . pthreads 的线程库允许程序在同 一 时刻运行多个 函盟 . 同时执行的各函数都拥有自己的局部变量,但共草所有的圭局变量和动在分配的世 据空间 . 当 线程共事变量时,必须惺证它们不会主生共享 冲 突 . 线程使用互斥锁来保证在某 一 时刻只有一个线程在时共事变量前问 . · 线程间通过条件变量来互相通知和同步数据 . 一个钱程挂起井等带着条件变量挂照 某种特定方式变化而另 个钱程则监信号使得条件变量监生变化 . · 线程需要使用互斥量来避免对于共享贵国操作函数的访问忡费 . 非重人的画量必须第 15 章 i!!程间通倍。陀 〉 • 465 • 15.2 talk 命令:从多个数据源读取数据 Un i x 的 talk 命令是一 种通信工具 . talk 允许人们在路蝙间传递信息 . t alk 甚至可以跨 越 I nternet 来连接不同机器的路端,如图 1 5 . 1 所示 . 图 1 5 .1 talk j皇锋网络上的两个终蝙 使用 tal k 命令的时候 · 屏幕瞌分为上下雨个部分 , 用户以字样终端模式来操作 . 输入字 符的时候,字符合同时显示在两个窗口中 . 体所输入的字符合出现在上丽的窗口中 , 而对方 输入的字出现在下面的窗口中 . t alk 使用 sockel 进行通信 ,如图 1 5 . 2 所示 . 因 1 5 . 2 I8. lk 命令的工作方式 talk 命令读取字符井将它们写到目的地 . 但与所学过的其他程序不间. talk 同时等待从 南个文件描述符的输入 . 15.2.1 同时从两个立件描述符读取数据 talk 从键盘租 socket 接收数据 . 从键盘输入憧取的字符瞌直制到扉幕中上半个窗口, 井通过 soc k et 'll 造出去 . 同样J}..,配 k" 读人的丰符酣睡加到肝幕 F 面的窗口巾 . tall‘ 的用户可以以任意速度和任意顺序输入字符 . talk 程序就 ~ø 须在任何时刻都准备 好从任一数据晖接收宇符,而不懂其他的程序可以佳靠明确的协 1J/. . 服务器等待着 read 或 recvfrom 的请求,井用 wnte 茸 scndto jt 回 个应替 . 用户不可能 一 直在切换自己的角色 (输入完之后等待别人的应答 . 然后再输入 ) ,那么 talk 程序如何解民这个问题呢。 talk 当然也不能这样做,如下述 ft 码 while(1){ ,.甜(fd_Itbd,&c , 川, 咱佬:lch(top lI' in ,叶, 盯 ite ( fd_ll'OCk , &c, 口 , read ( fd_ sock ,缸,1) waddch(botwin.c) ; 1* read fr帽 keybo!u:回 旷 1* add to screen */ / ~ SeJ回国。the J:" peraon _; / * read fr帽。 ther person */ / .. add to sc: re剧 时• 470 • Unìx/ Lìnux 篇程实践敏程 键入文阳俑迷符 惯写文件描述得 因 1 5 . 3 三个文件描述符 15.3.1 一个问题的三种解决方案 问题从服务器得到数据 , 将其传给害户端,如何来决定选择哪一种通信方桂呢?想 ­ 想前面使用菌 ""k崎过?时间 / 日期服务器 . 茶一进程知道当前的时间 , 而另-进程想班 取时间信息 ( 如图 1 5 .4 所用 ) . 如何让一个进程从另一个进程得到盘踞呢 ? 客户耀 服务 111 图】 5.4 某进程拥有另 一进程所要获得的偏怠 三种解决方盎文件、营造、共享内存 · 图 1 5 . 5 屉示了 三 种平同的方法 一种是已经学习 过的,但另外的两种方法却是新的 . 大事所熟悉的方章是使用文件,而另外的两种新方案是命 名管道 (na med pipe ) ,&共享内存段 (s hare m emory se阴 ent) 策略 . 这些方桂分别通过磁盘、内瞎 以及用户空间进行数据的传输 . 那么每种方桂具体如何应用 ? 各有何优融点呢, 因 1 5.5 三种传输数据的方法 15.3. 2 通过文件的进程间通信 中 量:?间可以通过士件来进行通信 · 某进程将数据写人士件,别的进程再将数据从文件 口〉使用文件进行通信的时间 / 日 期服务器 过里不必写一个完整的 C 程序,下面的一个 shell 脚本就可 U 完成任务了 . 样! /b三n/9h 梓"皿 ."凹 er - file veuion done while true 由 date > I rem p/ current_dat e sleep 1 第 15 章进程间通信 ( IPC ) • 471 • 此服务吉普每周 1 非钟将 当 前的时间和日期写入文件中 . 输出重定向符" > " 每眈将该文 件内容删除然后重写 . (2)使用文件进行通信的时间 / 日期客户器 害户瑞撞取文件的内睿 将 / bin/ sh 将 t ime :::1 且 ent - f ile version ç # include < sysf shm. h> # include < time. h> ** include < sys/ typeS. h> ** include < 5Y5/ S咀 . h> # l.ndude < sigr咀l.h> 梓 define Tl民".,吨 KEY 99 梓 define 'I'lME _ S:四ttEy 9900 / * lilcea f ilename 时 待 defin.啤 SEG_SlZE (何主 lt c_t)100) / .. size of s呵回回t 旷 样de.fine ∞阴阳,对 / peuor ( 叫 田比 (对 , } union semun 1 in 定 val s truct s国 id_ds ..以涩,回国 rt .array; 1; int S呵_ id , S四set_id; j * qlobal fOf cl eanup O 揭/ ,.立d cleanup( int); • 481 • 488 • Unix/ Lin 川编程实践放程 草内存和文件的版本看上去很简单,但E; 111 10 要锁相信号盘机制来保护盘据.要知监川人 锢相信号量也是相 当 且在的 一 件 'J ' . 然而文件和1 共享内存矶制允许多客户埔同时从服务器读取数据.允吁在户端和1 服务器 在不同的时刻l 运行 . 并且当进程剧愤时,允诽盘踞的保恃租恢 u . 管血和巴oc k ct 也包含了髓的肌制.管温和 socket 其实也是保存盘据的内存段.它将世 据从源端坦伽l 到目的瑞 .不 同的是骨坦和 I socket 中的锁和信号量是由内脑,而不是由;!l'1 来管理的 . 15.5 打印池 在时间 / 日期程序中.服务器盎墨宝据给客户端 . 拥而另外的-些程序却是山盹然不同的 方式工作 z 多客户端 :!t 散据给服务器 , 例如打印服务器上的打印池,酣么这种~型的程序如 何来世计呢? 15.5.1 事个写者、 个读者 如图 15. 9 所示 , 事用户共享 个打印机.如何I 使用吉 r' 咱 / 服务黠惧型来吕w - 个共车 打印机的程序呢?多个用户可能会在同时宜选打印请求但是打印机在某一时刻只能打印 -个文件 . 打印理序就必串I 接收多个并监的输入 · 并将 t(l 个的输出由送到 打 印世备上 . 如 何来写这个服务器程序呢。它们之间丑如何通信呢? 口口口口 个打印机 打印机任"队列 -些问步的打印饥请求 t!l !5.9 多个数据'原 个打印饥 这个I\!iî'囱哪些功能单元组成 9 这些单元之间卫传递哪些世据和调且随? 图 15. 10 将 - 个文件传给打印机 在 Unix 革统 中 灯印文件的最简单方法就是使用如下命令: cat fil刷啤阻 > / dev !l pl 或者 cp f i1凯ame / dev/lpt !!<15 意继霞间温情。陀》 • 489 • 迫里 I dcv/ lpl 是打印机世备主件的名称 . 当然系统中打印世品文件名码:井不一定租上 面的一样,但在 Unix 革统中将量据传给打印机或其他设备的惟 一 方曲就是通过 open 打开 文件 , 然后使用 wnte 罩统调用特世据写至打印文件中 . 可以使用写数据锁吗? 大事已经学习过写量据锁和倍号量机制了.为什么不可以自己写一个 00' 班叩的打印 瞿序 , 让它通过写盘指锁来防止对世矗立件的同步访问川 ' j居呢 9 基于锁机制的文件直制程序确实世有问姐 . 考虑 一 下时打印机 hu 锁后 · 结果全垣样 . 若某程序对打印机加锁 . 其他的文件直制程序部必须挂起等待第 一 个程序完成任务井辑匾 锁 . 那么下一 步哪个程序执行呢'内核将所有挂起进程中的一个唤醒 . 但是这个进程却不 一定就是排在第二 的 . 显然迫样的决定有失仕平 . 允许用户通过直制量据到打印设备文件 辈实现盯印还有另一个问题若有些人试图作假 . 他们可以平使用这样-个加锁的程序来何 印 . 第三个问题则是某些文件需要特醋的处理 . 例如回憧文件有可能需要酷转换为打印肌 可以看得懂的困悔命令 . 很多用户井不知迫如何将数据转换成通用的格式.那么他们就得 不到正确的结巢 . 然而这所有的问姐都可以囱靠中化〈事户 / 圃务器檀式〉来解决 . 15.5.2 害户 / 服务器模型 程序的事户 / JIl.务骨模型解快了前面提到过的打印的问困 . 只有 种称为战性打印棚 里(l ine printer daemon) 的服务器程序有极限去写数据到打印设备主件中 . 而其他的用户迸 程 ~IJ 平行〈如四 15. 11 所示) . 当用户需要打印文件的时幢 · 他们运行一个林为 1p ,的事户瑞 程序 . Ipr 对文件做了 个重制 , 然后将豆制的文件撞在打印任务队列配Þ . 用户可以删除草 编辑这个文件 . 并且打印精灵程序可以将因片制措式做转植起以使悍它们能事正确地瞌打印 出来 . 图 1 S. 11 客户 /服务"愤蟹的打印系统 在户揣和服务器如何通信呢?它们主互哪些世据 ' 事户端将整个文件传给服*睡正是 事户端仅仅将文件名传给服务器呢 9 如果凰务辑和事户不是在同 一 台机器上 , 情况卫将如 何 ' 这是否黠响 1日l 对通信方式的选得呢?不同版本的 Un ix 中有不同的打印巫统有些胆 m !l ocket . 有些盹用命名管坦,而另外一些仅使用 fork 和文件 . 是否使用靠中化的事户 / JIl.虫'器模式就可以不使闸锁机制来避免冲突了 '苟 且把罩统 世计成一个通 过构件进行通信相合作的模型来打印 一 台机器上的文件也可 U 用来打印 l nterne t 上的文件 . 可以将你自己的思路和不同版本的 Unix 打印革统的世计血路做一个
还剩506页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

ka520

贡献于2015-11-09

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf