第 1 章
Unix 系统编程概述
概在 Unix 系统包含用户程序和系统内幢
内核囱多个于军统陶成
内核管理所有的程序和贤m. · 进程之间的通信时 Unix 程序是很重要的
.
iHd是系统编程
相 提命令
,
• b
• mo re
1. 1
介绍
什么是系统编程 ' 什么是 Unix 系统捕程 ' 本书具体会带且哪些知识 ' 本意力图回答 上述问题 . 首先从分析操作系统的职责人手 , 来解静如何编写与操作革统茸密相关的程序 . 然后
通过分析标准的 Unix 命令,以且它们用到的 ;在统阙用 , 进 一 步指导匪者自己编程实现相应 的功能 . 这
章的是后告通过 一 幅回来描述 Unix 系统 . 本书的主要学习形式就是通过图
示和剖析立中程序所在b 盈的命令、技术,进而实现系统捕程 .
1. 2 1. 2. 1
什么是系统编程
简单的程序模型
你可能写 过各种各样的程序 . 有科学计算方面的、金融方面的、因憧方面的、文字处理方 面的等 . 大部分的程序都是基于四下模型 , 如图1.1所孟 .
自自1. 1
计算饥中的程序
2 •
Unix/ Li nux 编程实践披程
在这个模型中 , 程序就是可山在计算机上运行的一段代码 . 程序把输入数据做相应扯理
后辅出 . 例如用户在键盘上输入量据.然后在扉幕悟到输出 . 程序可能对磁盘进行操作,还 可能会用到打印机.
遵循上述模型 , 看以下代码
/ * copy
fr帽 stdin
to st由此时
main O lnt C; 内扎 e(
(
C ..
阴山tchar (c)
getchar() }
t. 四'"
;
过段代码对应图1. 2 所示的模型 .
图l. 2
程序的输入:输出
在图1. 2 中键盘和显示器与程序直接相连 . 在简单的个人计算机中,实际情况是很类似 的,键盘和显示卡直接连到计算机的主植上, CPU 和内再也是通过插槽直接连在主匾上 , 它 们通过主植上的印刷蜡路,连为一体 . 如果能够打开舰楠 , 所看到的大致如此 . 1. 2. 2
军统模型
如果所使用的f<统是
个多用户系统 . 如典型的 Unix 系统,那全是 一 副垣样的情形呢。
凶1. 3
多个用户、程序和设备
第 1 章
Unix 系统编程候选
. 3 .
刚才的简单模型已经不适用,图1. 3 会直接近 一些.
在这个罪统中有多个用户同时运行多个程序,可能需要访问多个设备 . 虽然模型亘古了,但对程序而宫 , 它还是从键盘碍到数据 , 将结果显示在显示器上 , 也可 以对瞌盘读写.这些操作都世有任何问题,它使用的还是简单模型 .
接下来考虑一种直为复杂的情况,有许多键盘/显示器,它们可以随意地连接到不同的 程序,随章地操作它们,这种情况如图1. 4 所示 .
因1..
终蝇可以随意地连接到程序
实际上,在计算机 内部,这种随 意的连接是不允许的 ,必 须果用 一 种机制进行管理 .
1. 2. 3
操作事统的职资
计算机用酷作亘统来管理所有的暨霄,井将不同的世岳和不同的程序连接起来 . 从连 接的角度来讲 , 操作系统的作用就惶主桓上的印刷线路 一 样 .
有了操作革统以后,因1.4的慌乱状态就可以得到政置 , 新的模型如图1. 5 所示 .
用户空间
-
系统空 间
因1. 5
操作系统是一个特殊的程序
操作革统也是程序与普通程序一样也运行在内存中,同时官卫是 能把普通程序与其他程序草世备连接起来 .
个特辑的程序 , 它
Uni x!Li m,l lt 编程实践教程
• 4 •
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.
Sa t Jul 1 21 ,34 ,10 EDT 2000
S d a te 命令显示 出日期,接下来显 示命令 提 示符 .
要运行其他命令,只要输入程序名即可, U nix 中有 一 个程序 fortune . 下面是它运行的 例子: 导 ,=‘,-
A1g01- 60 surely lII ust be ~egarded as the most progra且且也可 language yet devel oped
i.mport缸"
T. Cheathall $ -
当用户注响 时,内棋盘结束所有分配结这个用户的进程 .
内核是如何创建 shell 进程的呢 。 s heli 进程是如何得到输入的程序名,内在卫是如何运 行程序的呢 ? 噩录革统和运行程序井非想惶的那么简单 . 这些细节告在第 8 章讨论 .
第 1 章
因1.
• 13 •
Unix 系统编程徽述
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
it operates on
preci.!!ion aritluoetic package . integers , b.1 t one
dec且!lIa1
!lIa
0玄dinar ily
Y specify an
input ba..s e , output base , and a n Wl!be r of fractional diqits M 回国 inta切00.
The overal1 struct'.u:e of
dc 二 S ð stðcki呵
( reverse Po lish> calculator. lf an arqwaent is
ür taken fr Olll that
f 址e
un t.i l its
a剧,
then
qiv田,泣"""
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 所示 .
,>2
+ 44L
……咱' 4
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
Unix/ Li nllx 编程实段敏程
• 20 •
户的输入.这显酣有问题 , 因1. 14 描述了这种收况 .
图1.
14
more 从标准输入读敛据
解决这个问题的方法是 . 从标准输入中(1;人要分页的世据 , 直接从键盘读用户的输入 , 如图1.1 5 所示 .
因1.
15
mo re .M.键鑫这用户的输入
图1. 15 中 有一个文件 / deV/ HY . 这是键盘租显示器的匪备描述文件 . 向这个文件写相当
于显示在用户的屏幕上 , 读相当于从键盘藐取用户的输入 . 即使程序的输入 /输出被 " < "或 " > "重定向 , 程序还是可以通过这个文件与跑端变换量据 . 从图1. 15 巾可山如迫, more 有两个输入 , 程序的标准输入是i>的输出 . 将其分页显示
到JJI.幕上 , 当 mo r e 需要用户楠人时,它可以从 / dev / tty 得到数据 . 运用 上 述知识改进 more01. c ,得到 more0 2. c / ‘回re0 2.c
- vers i onO.2of
• read and print 24 lines then .,田 ture
of
vers立阳 0 . 2
<:stdio. h:>
梓 define PAGELEH
24
** def iJ回I.lNELEN 512 void int
do_田。re(FILE 铃"
see 画。因 ( FILE
int 阻 in(
int
FILE • fp;
êlC
. )
, char
回国e
立国ds
-/ 捋 include
lDO re
儒 av[ J
)
for
11
few speci a1 coemands
fr c. / dev/ tty
f町 c。晒""""
Unix/ Li n u累编理实战披覆
, 22
printf( 气 033[7瓢lIore ? 飞 033[画 " );
/*
" hile( (c. getc(cmd))
μ N田
.. EOF)
if(c.... 'q . )
reve四e
on a vt1 00 */
reads
fr Ola tty * /
/ * q - > N* /
r eturn 0 i f ( c .... , , )
;. • ,
/*
return PA GELE.tI
->
how 田ny
/ -阳 ter
i f (c " " \ 0 ' )
next
pi' ge
./
to show 旷
key
"'>
1 line
*/
return 1 ; return 0
编译井测试新的程序
s
∞ · 。因re02
.-ore0 2. c
$ ls j bi n I 回<0"
m o r e0 2. c 可山从际准输入押到 数据,也 可 以从键盘得到用 户的输入 , 同时通过描写 m o r e0 2. 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 不同机器上程序 之间通过网蜡部可以互相通信 .
这么多复杂的程序,它们的功能是什么?是如何工作的。在中间操作革统做了些什么? 随苦学习的深入,这些问题全越来越多地挂解答 .
在研究 mo re 的过程 中展示了解决 问题的步事.首先卦析一个实 际存在的程序,弄清它 的功能 , 分析它的实现原理 , 然后自己描写
个 . 通过对程序的不断完善来更多地了解 Unix
罩统和它的工作原理 . 这也是本书果用的主要方佳 . Unix 的概要图
1. 7. 2
Uni x 的结构如困1. 17 所示 .
图1.
11
Unix 系统的主要铺陶
图1. 17 描述了 Unix~ 统的主要结掏 ,内 存瞌升为革统空间和用户空间内幢租它的散
掘结构由下系统空间,用户扭序位于 用户主问 . 用户通过暨瞄连撞到 1民统 · 主件存放在磁盘
上 · 各种各样的性骨瞌内接直接管理 , 用户程序可以通过肉模来访问世备,最后还有 罔蝠连 接.用户可以通过网络接入某统 .
接下来的每章都会芷桂罩统的基
个部分 , 介绍相关的革统间用逻辑和世据结构 . 学
完本书后,舍时因1. 1 7 中每一个部卦都有所了解 . 应商 能尊编 写一般的 U nix* 统程序 . 如 网络桥牌 .
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 wh。
op • login
2.1
介绍
在使用 Unix 的时候,经常需要知道有哪些用户正在使用革统 , 系统是否很繁忙,某人是 否正在使用系统等 . 为了回害这些问题 ,可以 使用 w h o 命令,所有的事用户革统都会有这个
命令 . 这个命令全显示系统中活动用户的情况 . 接下来的问题是, who 命令是如何 工作 的呢 9
Unix/ Linux 编程实践被程
• 26 •
本章分析 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 •
Uni x! Lin~x 编程实践'发徨
sbort e ! ut_exi t;
/ " Pr民回...江 status _/ / " The exit stat us of a process mark回国阻皿 / * T i.lle 田 "y 晒E 圃de _/ 1" Host na回国lIle as MAXl阻四AI!ELEN 时
ex 址,
t i.lle_t ut_t 皿 " char ut_host[ 64J ;
/ * Oefinitions for
P限阻55 时
ut_ type 时
.b‘ .p. h( 60 ‘> 睹过所有介绍性的内在,直撞来着 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 程序时只有两件事情是要做的 z
34 •
Uni x/Li nux 编程实践敏程
dght or
now ( may:国 boc.四e 1ft
fr幅 a
were close
term i nal ) , or because
to 阳、d ~
of - file , or
readO 咽s interrupt国 by
beca咀e .e 缸 e
a
dqnal
eading fr Oll
I"
阳 error .
ð
pope ,
- 1 is returned ,
and errno i8 set appropriately. 1n this case i t is l ef t 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
Functions: fcnt 1( 2) . 。.pen( 盯, P且 pe(2).
creat( 盯
s。但 e
versions )
dup (2) ,立∞ ü ( 川 , getm 呵〈剖, 1阻kf()) ,
lseek (2) ,
.ll tio( 盯 ,
poll(2) , S民ket(2) , 3∞ketpair( 2) , terJllio时的 , ø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.
样打'IClude
< fcnt l. h>
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) ;
/ * check args */ i f ( ac ! -3)( f printf( stder:r.
"usa伊‘ s
source destination\n n , .. !l v);
exi t (1) ;
/惕。pen
辛苦 ((in fd =open (av[ 汀'-"回几,Y))事..
files
~I
- 1 )
∞P' ( 吧且not 0阳阳", a v[ l J) ;
if {
(曲 t fd 鑫" '国t ( av(刀 ,田凹m出 )) 回归 (
nCanno t
_.
-1)
creat " ,剧 [2 ] >
r.. copy files */
第 2 司在
用户、文件操作与联机梧刷编写 who 命令
因 Z. 5
• 49 •
系统洲周时的技制流圈
固 2 . 5 中用户进程位于用户 空 间·内核位于系统空间·磁盘只能量内核直接访问 . 程序 ,pl 要读取瞌盘上的数据 只能通过 '!t- 统调用 read ,而 rcad 的代码在 内核巾, 所以当 read 间用 生生时 ,执行权告从用户代码转移到内核代码 ,执行内核代码是需要时间的 . 系统调用的开明大不仅但是因为要传输盟掘 , 当运行内脏代码时 .CPU 工作在管理员
(supervisor 卫称超辑用户〉模式·这时应于一些特腊的堆战和内存环境,必须在系统调用监 生时建立好 . 系统调用结束后( read 返回时) , CPU 要切换到用户蟆式,必须把堆校和内存环 境幢盟成用户程序运行时的状态,这种运行环境的切性要消辑很盛时间 . 当工作在管理员模式下程序可以直接访问磁 盘 、终端、打印机等桂备,ì!可以访问圭部
的内存空间而在用户模式 ,程序不 能直接 宙间设备 , 也只能访问特定部升的内存空间 · 在 运行时刻 , 系统会根据需要不断地在两种监式间切换 . 管理虽模式租用户模式的切接与 CPU 关系很大 , CPU 中有特定的标记来区分当前的工作模式,而 UnÎx 系统的设叶必须考虑 到 CPU 的这种特点·才能堪实现不同工作监式间的良好切换 . 举个lIi片届人的例子·当肯特〈生活中的超人〉 要从用户摸式〈普 通人〉切换到管理员模
式〈超人〉时·他得先找个地方 , 比如电话亭,脱下西辈,摘掉眼镜 ,再改 变监型变成超人后才 能去拯敬别人 , 事情完了以后,还得找个地方变回普通人 . 查来型E 击是需要时间的·要是肯 特整天忙于亚来查击,就不含有大事的时间来拯瞌久提了 .
在计算机的世界中也是一样 , 要是 CPU 把太多的时间悄舵在执行内在代码和扭式切性
上,就不可能有 f且要时间来执行程序中业务逻辑的代码或提供系统服务 · 所以要恩可能地睡 少模式间的叨换 . 时系统来说这种时间上的开睛是昂贵的 , 那么 whp 版本花费在语、写散据 的时间开剧有多大呢,
2, 7 , 3
低效率的 who2.
c
每 IX从 utmp 中读出一条记录 , 就如同要煎 3 个荷包壶 ,每位到超市去买一个鸡置 . 煎奸
Unix/ Linu J:编程实践敏程
• 50 •
了再去买一个 , 这是很低效率的方法 . 完圭可叫一次把 3 个鸡置都买回来 . 时于 wh。而言, 可以一 位读入多个记录放在缓冲 E 中 , 下面是实现上述盟娃的伪代码 geteq'归。 { 应〈呵gs_lef飞 in
carton •• 0 )
refill carton at store it
( 呵r9S_ðt_store
•• 0 )
return EndOf Eqgs 明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 命令
• 53 •
i nt 困血。
民ruct utlll叫p • utbufp , μholds pointer to next r饵- /
‘"'町 next() ; / . retums pointer to next 锺 / 注( utllp_open( 阴暗 FILE) perl"or( trI哑P]ILE)
_. -1)l
;
国it( l) ;
wh ile ( ( utbufp .. u t& p_next() ) !. ,hσw
((struct utllp *)阳且〉
info( utbufp >
utmp_close( ) ; return 0;
/•
. how 皿 fo ()
悻改后的主函数世有直接对 open 、 read 和 close 进行调用,而是调用与之等价的具有缓 冲模式的面l!<接口 . 用于显示的画盘 show_info 世有受到任何Jj响 .
2.8
内核缓冲技术
应用缓冲技术对提高系统的强率是很明显的,它的主要思想是一位w;入大量的数据脏 入缓冲区 , 需要的时候从缓冲区 取得数据 .
内核使用缓冲吗
管理员模式和用户模式之间的切换需要情耗时间,相比之下 , 磁盘的1/ 0 操作 1月糙的时
间更多.为了提高敢率内核也使用缓冲技术辈提高对磁盘的访问速度 . ~U 图 2 , 7 所示 .
内核缓冲区 ( 缸子系统空间)
图 Z, 7
内核缓冲磁盘上的数据
Uni 孔I Li nlJ J‘编覆实践敏极
56 '
rnd }J..当"位Il il 入细 ,.民度的'段 '居然厨移功 当.伎重俯针指向下
个木 球的,障'情 文件开始位置 文件当部位霞
图 2.8
内核为每个何开的文件保样一 个位置指针
当从丈件读盘据时 ,内核从指针所标明的地方开始.匪取指定的字节.然后草动位置指 针 . 指向下 一个未幢 i章取的字节.写文件的操作也是类似的 .
指针是与文件描述符相关联的,而平是与文件~联 . 所 以 如果两个程序同时打开-个文 件 , 这时会有两个指针两个程序时主件的撞操作不会互相干扰 . 系统调用 lseek 可以改变己打开文件的当前值置 lsee k 的用法如下= '~k 使指针细向文件中的指定位置
~I示 头文件
j:I
;:i
indude include < unistd. h>
orCt
IO.
Id disl
文件描述符
""~
SEEK_SET -> 文件的开始 SEEK _CUR -> 当前位置 SEEK _END -> 文件蜡尾
- 1
遇到铺设
。ldpos
指针变化荫的位置
返回值
oldpo~
"" lseek( im fd , oH_1 disl , lnt bue)
画戴愿望
移动的距离
Is ee k 改 变文件 描述符所提联的指针的位置 . 新的位置由 dis t 租 base 来指定 bllse 是基 准值置 .di别是从基准位置开始的偏事量 . 基准位置可且是文件的开始 (0) 、当前位置(1 ) 或
文件的结尾(2) , 如 z 1seek( 时.
- (eizeof ( lI truct utap日SEEI<_'"肘,
把指针往前事一个 utmp 结构,注意偏移量可以是血的 . ".由 (fd.
10 •
IIh回 f(8tt1ω , "'呻) .且由 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 ,
Unix/ Li nüll 编覆实践敏程
68 • Ll8RARY
Standard C Library (1 i趾
.l
SYNOPSIS 将 扯到cl ude < 町 sj types.
将 iocl回e
OIR ..
h>
< dirent. h>
opendir(回国 t
c har .. di r
na固的 ,
s truct dirent .. readdir{OIR .. d i r~pointe时 , i nt r回dd ir_ r( DIR .. di r ß i nter , s truct d irent .. ent r y , struc t
dirent 赞..
r esult) ;
l ong te lldir (DI R .. dü"_p。回国叶, vo i d seekdi r( Dl R .. d i r _pointer. 1。可 l∞at ion ) ; void 玄~ in<刽 ir ( DIR
.. di r_pointer) ;
int c l osedir( Dl R ..
dir_poin恒的 ,
[ -=-J ( 11 ‘
>
通过联机帮助可以知道,从目录读数据与从文件读数据是类似的, opendîr 打开 一 个目 录. readdir 温回目量中的当前项 , clo sedir 提闭 一 个目景 . seekdîr 、 l elldil', rewìnddir 与 lseek 的功能提似,如图 3 . 2 所示 .
""'0
时."川 r (c ha r ~
) crntes a conn ecllon , rt !utn' 8 D1 R •
readdir ( D1 R . ) rud. ne J<' record. re!urn 5 a 阳 nt c r 10. . truct dirrn l d osedir
因 3. 2
)}..目录中读到项
目录矗立件的 ~J 毒.J!!:确切地说.是记录的序列.每条记录时应 一 个文件或于目录 . 通
过 readdir 来读取目章中的记录 , readdir 远 回 一 个指向目景的当前记录的指针,记录的类型 是 s truct dirent. 这个结冉定义在 l u s r / includ e/ diren t. h 中,联凯 ,警助 中 也可 U 查到 . 在 SunQS 中 盖子它的帮助信息、 如下 dir四t ( 4 )
File Fonrats
,OO! d皿 ent
- f ile s yst em.
indepel"羽d~乞 d irectory
entry
第"在
• 73 •
因素与文件属性:编写 1 ,
剖创 (name , p~ r )
...
iIl踵
n~m~ 所衔定的文件俏
.!i童入 一 个纷向中
因 3.3
用 SI"! 读取文件的属性
酷盘上的文件有很多属性,如文件大小、文件所有者的lD等 . 如果需要得到文件属性, 进程可以定义 -个 结掏 s truct s t at . 然后调用 s t at , 告诉内脏把文件属性存撞到这个结构中 .
'1>, 目标
得到文件的属饺
'"文件
咎 incl ude < 町 s/ sta!.
函"靡型
En t re8ull
..戴
fname
文件名
bufp
指向 buffer 的指针
h>
st 8.l (char. fname , struct s ta t • bufp)
遇到铺误
返回值
o
成功近团
5 t at 把文件 f n sme 的信且复制到指针 bufp 所指的结构中 . 下面的代码展示了如何用 51at 来得到文件的大小
/ * filesize.c - prints size of 拌却羽01""也 < stdio.
** include < int
pass回 file 旷
h>
sys/ stat. h>
lIIð in()
st:ruc t stat if ( stðt(
; * place to
inf由查,
" / etc/归自四
&infobu f) "'"'
perror ( " / etc l归田 wd " )
-
1 )
st。因 info
*/
/ * qet info */
;
else printf(" The size of infobuf. st
s且ze
/眈c/passwd
i8 ‘d\ n" ,
);
5t a t 把文件的信息草制到结构 ìnfobuf 中,程序从成员变量 5 1 slze 中 读到文件大小 .
第 3.
把些 bil 置为 O . 注童 . f'l 字中的某些 1 是如何被置为 o 的 .
EE囚 。
。
&
l
0
0
0
IIEE
OO
EE
。
1 1010 囚。
。
1
77
自策与文件属性,幽幽写 1 ,
0
。
。 I
倒 3.6
o,0 I
。
0
0
o
位与操作
(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 > 中有以下定 且
树 defi帽
5 [FBLIJ
0110000 0100000 0040000 006000(]
r.
bl()(虫叩ec i.,, 1
拌由引用
S
IFO愤
。 020000
/_
charocter 叩白描1
将 defif理
S
IFU。
睿 def i.ne
将 defin~
S
0010000 0120000 0140000
/ . fifo. /
5 [FLIIII
筝。ef ine
5
捋 define
5 IFREG
桦曲"固
5 1FD1R
S_IFMT
lFlJT
lFSαx
/ . type Qf tile . / j*
requl町 . j
/ . dl rl!ctory * / ./ .j
/ _ s y.bo lic link * / j . .∞ket
*/
J/;一个 掩码.它的值是 0 1 70000 . 可以用来过跑出前四位幸示的文件类型
S_
IFREG 代者普通 芷件,值盐 0100000 . 5 I FDIR 代表目最文件,值是 00 4 0000 . 下丽的代码 ir ((info .st
.国e ‘ 0170000)
pw、tf( ~th il
i8
iI
•• 0040000 )
direct ory" ) ;
通过掩码把其他茸茸的部分置 。 , 再与理示目录的代码比较.从而判断这是否是 一个 目录 .
]l!简单的方桂是用 < sys / stat. h > 中的 窜来代替上届代码
j.
•
File 町pe IIIllC r09
.j ~"'f 缸le
s
ISFIFO(.)
(((.)‘
(0170000)) •• (OOlOOOO))
第 3 尊曾
目隶
启蒙与文件属性编 写"
• 93 •
苦酒宜件
圈 3. 7 磁盘上有目录‘ 文件及 t 们的属性
3. 3
每个用户都有用户毡,每个用户 41 都有时应 的用户 I口,是否可能闹个平同的用户
名对应 个相同的 ID? 是否允许同-个用户拥有两个不同的 I口,如果有 root 扭 限,可以试一试,创建两个用户,改血同 →个lD.但有不同的用户名和密码 . 这两 个用户是否可以悻政对方的文件? who 输出什么? 19 - ]输出什么?命令 ;d 输出 什么 , 相互盘送e- mail 呢?从中陡瞥看出些什么?事个用户使用同一个 10 还有 何么其他用量 ?
3.4
与 u遇的文件 一样,目录也有特畸属性位,其中包吉 set - user-ID 和 set -gro up 10 位 , 使 set - user - ID 有敢对目录有什么Ii响'如果有·那么是什么'为什么, 如果世有Ii响,那么你能想辈出这些位有什么作用吗,
3. 5
每个文件的执行权限都可以撞 盯开或 关闭 , 假设 一个纯文本文件具有执行权限 ·
它是否可以被执行'如果→个包肯可执行代码的文件 , 如对 C 语盲编译后的可执 行文件 B. OUI ,没有执行权限的话,宫是否可以瞌执行, 讨论执行权限和可执行代 码之间的区别 . 它们之间再关系吗 q 盎且命令 [i!e 的联机帮助 . 3.6
3.
每个用户都有用户名利用户 10 . 这可以被认为是两种际识罩统 , 为什么要这样 ? 能不简直接用用户名来在示文件所有者?为什么,能不能只用其中 一 种悻识罩 统 ? 两茸茬示 革统有什么 优缺点 9 如果由体来设计亘统 · 体会如何设计 9
7 命令 di rent Ó<J 联机帮助中提到丁系统调用 getdent s(Z) ,它的功能是什么 ?与
U川 x/ Lîllu x 编程实践徽覆
• 100 •
4. 2. 6
U nix 立件革统小结
这个小节中从用户的角度现察了 Unix 的文件黑统 . 匣盘上呈现了 一个 能够在深度和 宽度上广泛延伸的目录树结构 . Unix 提供了很多命令来和这种结构的时且协同工作 . Unix 系统中的所有文件都存在于这种结构中 .
它们是如何工作的?目景是什么 , 如何知道主件所扯的日录'从一个目录转斟到另 一 个目最章暗暗什么? pwd 如何碍如你当前所处的位置。这些问题将引导下面的学司 .
4. 3
Unix 文件系统的内部结构
硬盘实际上是由 -些 磁性盘片组成的计算机系统的 一个由备 . 剖面章节中所提且的文 件系统是对该设备的 一 种事层性的抽ft/. .
4.3. 1
第-层抽象 ,从 磁盘到分区
一个磁盘能事存储大量的盘据 . 就惶一个国家能髓划分成 H 茸县 , 一个磁盘可 撞划分
成分臣,以便在一个大的主体内创建独立 的区域 . 每个分区都可以看作是一个独立的磁盘 -
4. 3.2
第二层抽象从磁盘到块序列
一个硬盘囱一些磁性盘片组成 . 每个盘片的表面都瞌划分为很多同心圆.这些同心圆
称作磁道 . 每个碰道卫进一步被划分成扇18:.就惶郊外的街道世划分成居住单元 . 每个崩区 可以存储一定字节数的数据,例如每个嗣区有 5 12 宇节 . 扇且是磁盘上的基本存储单元.现 在的瞄盘包古大量的扇18: . 闺 4.3 显示了序列号是如何分配给瞌盘块的 . 始眩耀块分
配毓号使
得 1114着也 来像-个敏 组
Ol234S678910 1 1 川
图 4.
3
为数据块分配编 号
为磁盘块编号是一种很重要的方法 . 结每个瞄盘块分配连续的描号使得革统能够计算 磁盘上的每个块 . 可以一个磁盘接 一 个磁盘地从上到下结所有的块编号 , 还可以 一个磁迫 接一个磁迫地从外向里结所有的块编号 . 就悻给每条街道上的每所房子描号一样 , 磁盘上 存储数据的软件给瞌盘上每条曲追上的每个块分配丁 一个序号 . 一 个特磁盘扇 E 编号的系统使得我们可以把磁盘扭为 一 直列块的组合 -
4.3.3
第三层抽黛从块序 引 到 三 个区域的划分
文件罩统可以用来存储文件内喜、文件属性〈文件所有者 、 日期等〉和目录,这些不同类
第"段文件系统编写 pwd
• 101
型的数据是如何存储在世编号的磁盘块上的呢 9
Unix 使用了一个简单的方法 . 如囱 4.4 所示,它将这些磁盘块分成了 3 部分 . 也级 j)>;
i 节点残
忍掘匹
. I j J 1 I ….一 属性存储在这里
因 4.4
内空存储在总盟
文件系锐的 三 个区就
- 部卦带为数据臣,用来存放文件内窑 . 另一部卦林为, - 节占辈们 node I sb!e ) . 用来存
盟主件属性 . 第 三 部分称为超级块 ( s uperblock) .用来存腊文件"统本身的信且 . 文件军统 囱这 3 部介组合而成 , 其中任
部分都是由相要有序磁盘块组成的 .
(J)超级块
文件革统中的第一个块髓称为超辑块 . 这个块存M1 x 件旱统本身的结构信且 . 例如, 翅辑块记最丁每个区壤的大小 . 超级块也 存放未被使用的磁盘血的信息 . 不同版本 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 serlist . Unix 如何在当前的目录中记幸这个文件'菩 蛊很简单 . 内 擅持人口 (47 , userlist) 添 加到目录文件 .文 件名和, -节点号 之间的时应关系将芷件名和文
件的内在且属性连接了起来 . 这个问题将在下面进→步地讨论 .
4. 3. 5
立件军统的实现目录的工作过程
目 录是 一 种包吉 T ::l:件 名 字列者的特腊文件 . 不同版本的 Unix 目 录的内部结构不同 ,
但是它们的抽血模型且是致的
一个包含「节点号和文件名的罪 .
Unix/ L inux 编 程实践敏程
104 •
在很目录中另一个重要的例子是左上角的" " 和"
"这两项都有「节点号 2. 所以. ..
和1 ". • "都指向同一个目最 . 当前目录:);么会和父目录相同呢,
4.3.6
立件系统的实现
cat 命令的工作原理
现在已经营到了创建一个新的文 件时内部所宜生的事情 , 例如 who
>
user!îst . 当读取
一个立件时卫垂直生什么呢 9 读取命令如何工作? X1如下的命令 z S çat userliat
果用从目录立件一步
步找到敛据的方法来加以了解
(1)在目录中寻找文件名
文件名存储在目录文件巾 . 内核在目录文件中寻找包吉字符串 user!ist 的记录 userlist 所在的记最包宫描号为 47 的 1 节点号,如图 4.6 所示 . S cat 旧时'" 从文件备到文件肉容 在目展中寻拽文件名
使用蝙号定位 z 节点
1-节省包含!'l据统的 列表
47
EEE--E
B
牛 41:
2∞
固 4.6
(2) 定位 1
A
C
992
621
? 1.
从文件名萝l 磁盘坟
.
节点 47 井读取其内在
内西在文件嘉统中的 1 节点区域找到 1 节点 .7 . 定位
个,节点可能需要一些简单
的计算 , 所有的「节点大小相同 , 每个磁盘且都包古相同数量的「节点 . 为了提高访问敢 靠,内核有可能将 1 节点置于缓冲区中 . ,节点包吉费生据块编号的列在 . (3) 访问存储主件内喜的数据且
通过以上过程,内额已经可以知迫文件内在存撞在哪些世据换上.1:1 &它们的顺序 . 囱 子'"平断地调用阿.d 函数 . 使得内核不断将字节!A磁盘复制到内核缓冲区 . 进而到这用户 空间 .
所有从文件读取数据的命令,例如 cat , cp 、 more 、 who 等,都是将文件名传结 open 来访
①
,到IJ' La t h.o. m
&. Ja ffe 罐著的 fm My Own Gr.ndpa.. 19H 对相关盒'目的付论 .
第 4 章文件系统
• 105 •
111 写 pwd
间文件内事 . 时 open 的每 IX 调用都是先在目录中寻找丈件~然后根据目景中的 1
节点号
Vt lU: 件的属性,量提找到文件的内睿 . 理在可山甜、草 据文件名找到 l
下在叩 en 一个世有读革写权陋的文件时将直生什么情况 . 内核首先嗣
节点号 , 然后根据 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 个项惶
统 J3存储曾包
含更多工段向 撞块的那个缺 的偏号 . 远个
梭徨称惚 三 级 分配列军费从←节
分配列费在间接
块 12存储着包含更多
嘿列漠中的前 JO
统中罐镶埃 11
闽撞峡的那个戴德埃
个模开始 .
在储唱'瓢个块的
的销号 . 这个模罐称
'自号 .
做 =饭闽徨换 .
细 4. 7
在徽据区延伸的块分配列表
阳l 很埃 .
Unîx/ Linux 编程实战敏程
108 • 图 4.9 J圣上例的图示 .
系统角度
网户角度 "'mωdJ,
E
匿自
圈量 I111[111 因 4.9
1
文件名和指向文件的指针
(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 的一个
Unix/ Li nllx 编程实贱'安夜
• 112 •
在 附n阳 e 执行之前
n:l1 ume ( "y M, "e f dl f y.Old 叮执行之后
噩Z m~ 圈豆豆| 图 4. 11
团副
将文件移动到新的目录
首先 , 在 demodÎr 中存在营一 个指向「节点 491 的链接,称为 y . 然后, 一个林为 y. old 的指向「节点的链撞出现在 c/d2 中 . 而原来的链接捐失了 . 内核是如何事动这些链接的呢 。 在 Linux 内眉 , rename 的基本逻辑是 z
. :!l:制链接至新的名字 / 位置 · 删除原辈的链接
UnÎx 提供系统调用 IÎnk 和 unlink 完成这两个操作 . 因此, rename( 气 ", "z") 是这样运 作的 if ( 1ink ( "x" ,阿 zft)! "' - 1 ) un1ink{ " x" )
实际上,过去世有 rename 这个罩统嗣用 , 因此命令 mv 使用系统调用 IÎnk 和 unlink . 在 内核中增加革统调用 rename 解决了两个问题 . 首先, rename 使得重命名或重定位一个目最 变得Jl! tn '1<圭 . 过去, 一烛的用户不能对目最进行 link 或 unlink 操作 · 因此他们世有办法置 命名 一 个目录 .
蜀一个使用 rename 的优点是支持非 Unix 文件系统 . 在 Unix 中,重命每 个文件或目 录是通过改变链接完成的,但是其他的系统可能不佳这种方式工作 . 将通用方桂 rename 睡 加至内核隐藏了实现的细节,使得相同的代码能静在各种文件革统上运行. (6) cd
,d 用来改变进程的当前目录 . cd 时进程产生'¥响,但是井不I!!响目最个用户可能
会说"我进入了 /t mp 目最·盘磁一大堆的垃盟主件"而另一个用户可能告说"我进入了阁 幢,盎现了很多旧书"
cd 使用革统词用 chdir:
第"量文件系统编写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
on I type ext2 (rw) on / 00晒 type ex t2 ( 四) none on I proc type proc (rw) /devμ咄 1
/dev / t础6
回回 on
/dev/ pts type devpts
(凹,囚。de.0(20)
$
输出的罪 行表明 /dev/ hda 上的分区l(嚣 个lDE 设岳〉幢革栽在树的根目最上 . 这 个分区是根文件罩统 . 输出的第二行表明白v/hda6 上的文件黑统幢革戴在根文件阜统的/ home 目录上 . 因此· 当 用户使用 chdir 从 "1" 进入.. / home" 时 ,实际上是且
个文件罩统进
入了另 一 个文件系统 , 当民 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- 节点、 数据块、自袋、指针
第 "舷连搜位制学 习 S t1 y
• 129
i!l l这 文 件的 『节 点包含
设备 文件的,节 点包含
个指向数
个指向 内核中 设备驱
翻 块的指轩
功嚣的 指针 .
的冽寝 .
因 5 】
指向数据块或驱动蟹的:节点
每个, - 节点编号指向 「 节点程中的 一个结构 . , - 节点可以是瞌盘文件的,也可以是设
备主件的 . , - 节点的盎型酷记录在结构 51at 的成员变量 "m。由的提型区域中 . 瞌盘文件的 1 节点包吉指向数据块的指针 . 世备芷件的, - 节点包吉指向内核于程序 毒的指针 . 主设备号用 于告知从设备读取数据的那部卦代码的位置 .
寺虑一下 read 是如何工作的 . 内核首先找到文件描述符的 「 节点 , 该「节点用于告诉 内菌文件的类型 . 如果文件是酷盘文件·那么内核通过访问块分配牵来读取盘据 . 如果文 件是设备主件,那么内核通过调用该设备驱动程序的 read 部分来读取敬据 . 其他的操作 , 例 如 。 p'n 、 wri te , lsec k 和c1 0se 等都是盘似的 .
设备 与 文件的不同之处
5.3
磁盘文件和民备主件都有文件名和属性,从表面上看很类似 . 巫统调用 。 p,n 用 于创建
与文件和设岳的连接 . 但是与幽盘文件的连接不同于与路端的连接 . 困 5 .2 且示 了带有两 个文件描述押的进程, 一个是到瞌盘文件的连接 , 另→个是到终端用户的连接 .
罐瘟 文件
图 5.2
拥有两 个文件描述的进程
Unix/ Linu~ 编在实践敏硅
130 •
现在已经丁解了一些关于连接的内部情况 . 与磁盘士件的直接通常包啻内核缓冲区 . 从进程到磁盘的字节先世缓冲,然后才丛内核的缓冲区瞌盎迭出去 . 磁盘连接具有理冲这 样 一 个间性 . 到j路瑞的连接则不间,进程需要思快把到终端的数据传选出去 .
与终端或调制解调器的连接也具有属 性 . 连接拥有世特率、奇偶位、暂停位的个盘 .一
般情况下所输入的宇符都会显示在屏幕上,但是有些时候,例如当楠人密码时 · 字怦井不回 显在屏事上 . 田旦字样不是键盘任务的 部分,也革是程序应该做的 e 回且是连接的-个属 性.到磁盘文件的连接世有这些属性 .
连接 属 性和控制
Unix 让文件和设备既有相似之处,卫有军同之处 . 与磁盘文件的连接不同于与调制解 调器的连接 . 关于连撞的属性可以问: 1
连接可有哪些属性?
2
如何检测当前的属性?
3 . 如何改变当前的属性 ?
下面介绍两例磁盘连接的属性和终端连接的属性 .
磁盘连接的属性
5.4
系统调用 opcn 用于在进程和磁盘文件之间创建一个连接 . 该庄撞吉有若干个属性 ,下 面先仔细学习其中的罔个属性,照后再了解 一 下其他的属性 .
5. 4. I
属性 1
缓冲
图 5 . 3 显示了当两个管道通过
个进程单元连接时文件描述骨的情况 . 那个进程单元
是用来进行缓冲和完成其他进程任务的 . 在方框内的是控制变量 , 用以决定文件描述符应 该果取哪个进程步骤 .
世童文件描述怦役
因 5.
3
敛据流中的进程单元
可以通过悻改控制变量政变文件描述特的动作.例如 .通过 简单的 3 步操作关闭键盘缓 冲. ~U 图 5 .4 所示 .
第 s 意连後撞倒:学习 stly
131
改变啄动器设震
I
获取设置
2 修改讼It
3 存储 i昼置
图 5. 4
修改文件描述符的运作
首先 , 生成一个系统调用将控制变量从土件描述符草制到进程 . 然后 , 悻改革个重制过
来的控制噩噩 . 最后,再告政过的 值 选国内脏 . 新的世置撞置置在进程代码中 , 内核根据新 的设置处理数据 . 下面是遵循上述 3 步的代码 3幸 include ~ fcntl . h:>
int5;
// sett 但"l'
s " fcntl (fd , F_GETFL);
//get fbgs
s
I"
/I set S'!'NC
O_S 'I' NC;
res\llt 左 fcntl
( fd ,
F_SETFL ,的
//昭t
( 町田 tt i..ng
flags
1/ if error
if ( r esul t .." 1)
perror
bi t
11四"'''
SYNC") ;
文件描述符的属性 世 编码在
个整数的位巾 . 军统调用 fcntl 通过ìl;写旗整盘位来控制
文件描述符 .
rcnll 目标
控制文件描述符
头 文件
一 猝 inc\ ude
画'交 愿 望
,戴
替 inc1 ude
< unistd. h>
样 include
<sys !t ypes. h>
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 , st rucl flock fd
幡控制的文件描述符
<md
得进行的操作
'"lock
操作的,数
退 回值
* lockp);
锁信息 遇到晴惧
othcr
依操作而定
fcntl ;在 fd 所指定的文件上 执行操作 cmd ~ arg 代表操作, md 所使用的
个妻盘.在上
例中,盎敛 F_GETFL 得到当前的也提(也就是 flags ) .变 量 s 存Iít这个 flag 韭 . lû.逻辑或 操作打开位。_SYNC. 自位告诉内核 .;;:t w r i te 的调用仅能在盘描写入实际的硬件时才能远 国.而不是在散据豆制到 内 核缓冲时就执行黠认的返回操作 .
U ni x/ Li nux 编段实战敏覆
132 •
最后·把悻改过的设置返回内属 . 将飞SETFL 操作作为第二个盎散 , 将悻改过的世置 作为第 三个 垂数 . 革 3 个 步骤 (从内核中 i主取民置到噩噩 .t墨西这 些设置,将由置退回内核 〉 是 Unix 中读取和悻改直接属性的典型方法 .
设置。 S YNC 会韭闭内撞的缓冲机制,如果没有很克分的理函,最好不要提闭缓冲 5.4.2
属性 z
自动添加模式
文件描述符的另 一 个属性是自动器加模式 (a uto 叩pend mode) . 自动器加模式对于若 干个进程在同 一 时间写入文件是很有用的 . h 什么自动器川是有用的 ?
考虑日志文件 wtmp . wtmp 存储所有的噩量和退出记录 .当
login 在 wtmp 的末尾追加
个用户壁最时 , 程序
是噩最记 最 . 当 一 个用户退出时,革统在 wtmp 的末尾追加
条且出的记录 , 如同系统维护的日记
样 . 这就悔人们写 日 记一 样 . 每篇都瞌晤加在末尾 .
不能使用 lseel‘在末尾进行添拥记录吗 ' 考虑一下垂量的逻辑 , 如图 5 . 5 所示 .
用 如下 系统调网将 敖倡 添加到文件
lsee k ( f d , O, SEEK ENO) ; write (fd , 晶 re c , lenl
图 5 .5
周 \ scck 和 W rL le 进行楼加
l se e k 特 当 前 位 置睡到文件的末尾 , 然后添拥壁章的记录 . 这里会产生什么错误呢 9 如果两个人同时垂录特古发生什么 9 含有时间过程,如图 5 . 6 所 示 . 时间
用户 B 登 囊
用户 A 登录
lseek (fd.O.SSE~1
. . 4:
2
lseek ( f d , O , SEE凡副0) .,本 te
3•
(!è.. I. reç .len J ;
writel fd. l. rec , lenJ;
v 囱 5 .6
\see k 初 wn l e 所 引起的混乱
wtmp 立件显 示 在中间 ,时 间箭头在左边 , 井显示了 4 个时间片断 . 用户 A 盘录的代码 显 示 在左边,用户 B 壁量的代码显示在右边 . 到现在为止 一 切都正常吗 ?
个重要的事实
Unix/ Li nux 编覆实践敏程
134 •
例如,可以通过 open 创建 一 个包古口 CREAT 标志位的士件 . 以下两个词用是等 价的 z fd ..
creat (
f ileJ阻耻 .pm 且 ssion b it时 ,
fd 事。.pe叫 filename , 0_四EAT
I
0_回℃皿 I
O_WRONLY , perll. ission_bit的
为什么 open 可以直现相同的功能 . 而 creat 依旧存在 9 在 茸的版本中 . open 仅仅用辈打 开文件 , c reat 用来创建新的文件 . 随后, open 被多眈悻班以主捋直辜的标志位 , 包括创建立 件选 项 .
open 支持的其他标志位: 。'_CREAT
归果不存在创建该文件 . 可奎清 o 口CL .
o TI<JNC
如果支持存在将文件长度置为 O .
。-田CL
0_且CL 标志也防止两个进程创建同样的文件
如果文件存在旦 o 田E 披置位.则返回 -
1.
口 CREAT 和 o EXCL 的组合用来调除以下竞争情况如果两个进程同时创建 相同的文 件将会主生什么情况?例如,如果两个进程都要写 wtmp . 但是这个文件不存在时 , 都要创盟该
文件,此时会主生什么情况'程序能够先调用 stat 1"i看文件是否存在,如果不存在,就调用 creat . 当 St8t 和 creat 间的过程瞌打断时 , 问题就出现丁 .
0 EXCL/ O
CREAT 的组告将这两
个词用构成了一个原子操作 . 虽然想法很好 ,但是这种方法在某些重要场合井不可行 . 一个可 靠的替代方章是使用 lin k . 本章的韩习握供了 一个 例子 .
5.4.4
磁盘连接小结
内撞在磁盘和进程间传输盛世据 . 内核中进行这些传输的代码有很事选项 . 程序可使用 。 p , n 和 fcntl ;军统调用控制这些盘据传输的内部运作 ,如图 5. 7 所示.
文件简述符
。
国 5.7
与文件的连徨具有属性设置
第"挺连後控制:学习 s tl y
• 135 •
终端连接的属性
5.5
革统间用 open 在进程和终端之间创建一个连接 . 现在来仔细了解
下与终端连撞的一
些属性 .
终端的1/0 并不如此简单
5.5. J
些端和进程之间的连接着起来简单 . 通过使用 get char 和 pUlchar 就能够在设备和进程 间传输字节.数据酶的草种抽且使得键盘和屏幕看起来就悔在进程中一样,如图 5.8 所示.
~
图 5.8
一个 简单直拨连楼的流程
-个简单的实验表明芷个模型井不完整 . 考虑以下这个程序 / - listchars. c
* purpose; list individually ",11 the chars se回 011 lnput .. output: char and lI.sci i, code , one pair pe:r l ine .坝lput;
•
stdin , until the letter
0
~t臼回国 ful to sho饵 that buffel"i呵/回让扫盲g 四 ists
.j 样也~ l叫:e
<s tdio. h>
脑血。
intc , n" 0; while ( (c • getchar()) ! "' ' 0' ) printf( 吃 h,,,鲁 3d is
这个程序以 一个接
‘ c code ‘d\ n" , n++. c , c);
个的方式扯理字符 , 读取字符,打印盘值、字持本身以及它的内部
代码 . 编译井运行这个程序.结果如下所示 z $ . j li 眈d缸'
Uni~Linu~ 副局程实践敏程
136 • hello
""'''
""",, """,,
""'" ""'" """,,
o is h code 104 1 is e code 101 2 is 1 c由::Ie 108 ) ill 1 C由:íe 108 4 is 0
C由::!e
111
5 is
code 10 。
$
接下来垂直生什么事情 ? 如果宇押代码直接从键盘班向 get char . jllJ 在每个字怦后可看
到一个响 应. 输入单词 hello 中的 5 个字符井接回牢键 . 然而仅在边个时候,程序才开始址 理这些字持 . 输入看起来世绩冲了 . 就悻班向酷盘的数据,从提端流出的数据在沿遣中的 某个地方被存储起来 T .
li s t chars 显示了另外一些内窑 . Enter 惶或 Return 键通常直选 A SC Il 码 1 3 , 即回车符 . listchars 的输出显示 ASC I I 码 13 被换行带(代码 10) 所替代 .
第 三 种处理!Ii响程序的输出 o li s t chars 在每个字符串的束尾悟加 一 个换行符 (\n) . 换 行符代码告iIf鼠标掉到下一行 , 但设有告诉它事到最左边 . 代码 13 ( 回 车符}告诉鼠悻回到 且左描
运行 listchars 表明在主件描述符的 中间 必定有一个处理层 . 回 5.9 显示了在层的部分 作用 .
putcbar
槐序发埠'飞 , ' . 但 是显示器级收到 ' \ r ' 和 ' \ 1\' 用户输入飞 ,
.
但是程序徨收到
因 5.
9
肉核处理终瑞数据
这个例子说明丁 3 种址理
1
进程在用 户输入 R eturn 后才撞收数据,
2. 进程将用户输入的 Return ( ASC II 码 1 盯着作换行符 (ASC lI码 1 0 ); 3
@
进程监造换行符 ,终端接收回车 换 行符 .
你可以向你的爷爷清敬如何在打字机上推左侧孚制使打字头回到纸的左边 .
第 s 拿走 ?庭位制学习!l tty
与鳝端的直接包含
S. S. 2
• 137 •
辛完整的属性和处理步黯
终端驱动程序
终端和进程之间的连接'"图 5.10 所示 .
图 5 . 10
终鸡'组动樱是内核的 - 部分
扯理进程和外部设备间数据班的 内 核子程序的集骨髓称为路端'lI!动程序或 tty 驱动程
序 叭 驱动程序包吉 1~ 事控制世备操作的设置 . 进程可以读、佳改和重置这些理动控制 标在 .
5.5.3
stty 命 令
stty 命令让用户读取和悻政终喘驱动程序的民置 . (I)使用 stty 显示驱动程序设置 s lI y 的输出如下所示: S stty .pe回 9600
baud; line .. 0;
$ IItty - .11 .9' peed
9600 baud; rO I<.9' 15;
colu监四 80;
line
a
0
intr .. "C; quit .吨,眩目 e" "?;kill '"叮,由活.. " 0; 国1 • <山回ef > ; eo12. .. <由x!ef > 眈art 事 "'Q; stop .. "S; s u.s p • ^Z; rprnt ..咱 , 吨rase .. "11 lnext '"衡 v , flush 军 ^O; .in .. 1; t忌_. 0 _.呻 - parodd
c s8 - hupcl - catopb =ead - clocal - crtacts
- iqnbrl< brkint 豆 gnpar - panlrk - iuclc 一 叫 :一
皿p::k iatrip - iI让cr - iqncr icrr让 ixon - ixoff
。>poat - olcuc - oc口让。nlcr - onocr - onlret - ofill - ofdel nl0 c rO t abO bsO vtO ffO
isig ic缸lOn iext e.n echo echoe echolt - echonl - noflsh - xcase 牛 tostop - echoprt
。
n ,是细由 TelelYPC 公司生产的老式打印鱼'编
• • 139 •
'自 s 意连接撞制 1 学习 511y
5. 5. 5
编写 终 蜡 驱动程序
关于画慰
改变终端朝;/)程序的桂置就悔瞌噩噩盘主件连 接 的世置 一 样· (l)从咂动程序获仰属性 , (2)幢改所要蜡改的属性 s
(3) 将悻改过的属性造田驱动程序.
例如,以下 代 码为 一个 直 接开 启字符回显 $丰 include
< tenlios. h>
/*
struct terai O.!l attriha to雷电~tattr
( fd ,
&gettir哩9)
setti吨 s. c_lfb,g 民 setattr
(
I•
9tn却ct to hold attributes
/ * get attribs
;
/_
ECHO;
fr帽 ddver
*/
*/
tur咽 m 阻iO b让 in fl吨 set
fd ,明:SAHON , &sett i.t吻"户 .6回 M 衍 ;bo 国ck
_/
to dr iver _/
通常的过程在 回 5. 11 巾 进行描述 .
~ il><:: l回e
< te...ioø. 11>
.truct te...i Oll
.ett 呵'
呻~,u旧时吨的,
/ . te.t , set. or d帽.r
bi t . . j
t enbo ttr ( fd. how.
bettu咽剖,
111 5.11
调用 I cge lsll r 和 I cse lsll r 役'而终编理动帮
库画 fl t c getattr 和 t cseta ttr 提供对终唱驱动程序的访问 . 两个函数在 t e r m lO s 结构中
变换面置 . 以下是详细描述 . t咿 ta ltr
目标
L寞取 uy 驱动程'"的属性
头 立体
精 indude 符 include < uni~ td. h>
‘
函 戴' 缸
int result -
.1自
Id
与赞辅相联的文件铺i是符
info
指向,每晴结构的指针
- t
遇到铺误
o
成功返回
返回僵
lcgets tr(inl fd. Slrucl le rm ios • info)
,
148 •
Unixj Li nuκ 编程实践'支程
write , close 和 lseek 可瞌用于任何文件或设备 . 文件权限位以同样的方式应用于控 制设备立件和磁盘文件的访问 .
•
1自l 酷盘文件的连接在仕理租传输数据方面不同于到监岳士件的连接 . 内艘中管理与
设备连接的代码桂林为设备驱动理序 . 通过使用 fcntl 和 ioctl. 进程可以法取和改变 设备驱动程序的设置 .
到鲤端的连接是如此的重要,以至画撒 tcgetaltr 和 tcsetattr 专门用革提供时理端驱 动器的控制 .
Unix 命令 stty 使得用户能够访问 Icgetattr 和 Icsetaltr 画盘 . 图示
2
进程使用 wrlte 特数据写入文件描述符,用 read 从文件描述符读出量据 . 文件描述符可
被连接到磁盘文件、路揣和排部世备.芷件描述符指向世备驱动程序时,设备驱动程序具有 属性设置,如图 5. 13 所示 .
lIy 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 编程实践敏程
事实
太多敌进程
启功将前 3 个文件 描述符 1T1f • 它们 不帘要调网创汇叫)
来建立 连瘦
图 6.
I
3 种标准文件描述符
且些程序的输入和输出能够被重定向到任何类型的连接上
> O\1 tputfile .世t " > /由v/ lp
S .ort $
S who l tr'
' [ A-Z丁 '
特定设 备 程序为特定应 m 控制设备
其它程序〈如控制扫描仪、记录压缩盘、操作磁带驱动程序和拍摄数码相片的程序〉也能 同特定设备进行主互 . 在本章中将通过了解最常见的与特定世备相关的程序 (通过暨端与 人吏互的程序〉来探讨在写这些程序时用到的幢念和技术 . 将这些面向终端的程序称为用
户程序 . 3
用 户程序
-种常见的设备相关程房
用户程序的例于有 VL , emacs 、 pme 、 more 、 lynx , hangma n , r obo ts 和许多加利福尼亚大学 伯克利分校编写的游就程序 。. 这些程序设置鳝端驱动程序的击键和输出处理方式 . 驱动 程序有很多设置 , 但是用户程序常用到的有
(J)立 即 响应击键事件 (2) 有限的输入靠 (3) 输入的超 时 (4)屏蔽 Ctrl -C
下面将通过描写
个实现所有这些特点的程序来学习这些主题 .
6.2
终端驱动程序的模式
首先讨论在前面章节中提到的终端驱动程序 . 下面通过
个简短的转换程序来深入理
解世各驱动程序的细节 ②
@
这些'"护的'眼代的可以在网上搜到哼找 b""ga mu .
②
可以向,,,命令 做网梯的事情,但是凹的 GNU 版本有输入缓仲这伴州 它来 做扳擎的例 子就不是很好了 .
第"雀 ,四 pa -> b ,
/ * rotate. c
*
purpose:useful for
b • >C ,
为用户编程鳝瑞撞倒和 信号
z->a
sho 四啕 tty 1M)曲,
-/
1* include
< stdio. h>
拚 include
int 困in()
int c; whi1e ( (
c 写 getchar()
) !
B
EOF ) 1
jf ( c . . ' z ' ) C
'"
'a ' ;
e 1se 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于中的操作居民 .
ro!at~ 程陪
输入处理
输入编栩 将 " \ r " ,专热成" 回且
因 6.3
终瑞驱动器中的处理层
理冲和描朝包含凰范处理 (canonical processing) ~ 当这些特征世启动 , 路端连接桂林为 处于111.范模式 .
6. 2. 2
非规范处理
理在尝试这个试验(输入仍旧是 abx <- cd . 然后.输入 efg Ctrl- C)
,
.t可 - " 陋。n ;
. / r otate
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 打印 - 个显示符,等待下 行命令 . 有些 shell 重 置驱动程序 , 而有些不这么做 . 如果 sheU 井不重置驱动程序 , 将继续处于无回显、非规范 的模式中 .
6. 2. 3
终端模式小结
如果还未在路端尝试过些例子,现在就做 . 这些例子擅示了终端驱动程序的不同模式 . 当为 Unix 设计用户程序时,需要决定哪种些端模式适合这个应用 . 1
规范模式
规范模式,也桂睹为 cooked 模式,是用户常见的模式 . 驱动程序输入的字持保存在缰冲 臣,井且仅在接收到回草键 ①时才持这些缓冲的字符主选到程序 . 缓冲数据使理动程序可以
实现最基本的描辑功能,如删除字符、单词或整行 . 当用户卦别捶下删除键、单词删除键或 是提止键时 , 这些功能就会瞌调用 . 瞌拮据到这些功能的特定键在驱动程序里设置,可通过 命令 stty 或革统调用 tcsetattr 来佳政 . 2
非规毡模式
当缓冲和编辑功能 瞌 关闭时,连接世昨为处于非规范模式 . 终端处理器仍旧进行特定 的字样处理 , 例如 , 处理 Ctrl-C 且换行捋和回车持之间的转换 . 但是,用于删除、单词删除 和终止的编羁键世有特耻的意义,因此相应的输入量视作常规的盘据输入 .
如果用非规E模式编写程序 , 井且希望用户能譬编辑他们的输入,需要在你的程序中实 现描羁功能 .
3.
raw 模式
每个处理步骤都瞌 个组立的位控制 · 例如, IS IG 值控制 Ctrl-C 键是否用于再止-个 程序 . 程序可随意是闭所有这些扯理步事 .
当所有处理都被关闭后,咀动程序将 输入直接传递给程序 . 在这种情围下,驱
动程序瞌林为处于 '"W 模式 . 在终端驱动 程序 E 为简单的老版本罩统中 , 有个特定 的模式融林为 '"W 模式 . 命令 stty 支持 ," w 模式,将它作为命令行的 一个选项 . 联 机帮助上有盖 stty 的部分解释了,"w 模式 的古且 .
路端理动程序是内核中 一 些复杂的程 序 . 理过前面的学习和试验可以更为清楚 地了解它们的各个组成部分和功能 . 图 6 .4 显示了其主要部分 .
@
或者是当曲'定义的 EOF 翻'通常为C, rl - O .
图 6.4
终精驱动在序的主要组成部分
Unix/ Lin ux 缅寝实践敏程
168 •
6.4
信号
Ctrl - C 中断当前运行的程序 . 这个巾断囱
个再为信号的内苗机制产生 . 信号是 一 个
简单而重要的舰意 . 下面特探讨信号的基本概念.学习怎样使用它们解诀 play_ again 3 的问 题 . 在下 一 章中将直谭入地学习信号 .
Ctrl - C 做什么
6.4. 1
输入 Clrl - C ,程序便酷 些止了 .
个单
的击键是如何杀死
个进程的呢 。 终端驱葫
程序在这里起丁相应的作 用 . 图 6.6 显示了相应的事件键 . l. JIl 户 输入 。 rl - C
z. 1III功程It收到字符
囱 6 .6
3
匹配 VINTR 掬 ISIG 的字符..开启
4
础动但附测 附 倍号系统
5
俯 号系镜发运 SIGINT 到避,也
6
遮缸收到 SIGINT
7
搓程消 亡
Ctrl - C 如何工作
中断信号的击键组告不一定非是 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) 内在
当进程执行出错时,内核结进程芷造一个信号,例如 , 非挂段存取.带点数溢出 . 茸是 一
①
才自"变它.许多 sheU 脚本将出向.. .
第 6 拿为用户编程 z 终编控制和信号
图 6.7
169 '
信号的 3 种来源
个非齿的机器指令 . 内核也利用信号通知进程特定事件的主生 . (3) 进程
个进程可以通过革统调用 kill 给另一个进程盎选信号 . 一个进 程可以和另
个进程
通过信号通信 .
囱进程的某个操作产生的信号瞌样为同步信号 (synchronous si gnals). 倒姐,酷牢除 . 囱幢用户击键这样的进程外的事件引起的信号桂林为异步信号 (asynchronous signals) " 哪里可以找到信号的列在9 信号编号以且它们的名字通常出现在 lusr/include/signa l. h 文件中 . 这里是这个文件的 符 define SIGHUP
替自 fine SIGINT
卢国呻UP .9回erat国 when tenl幻副 di~αnnects 旷 2
样 define 51咽JIT )
符 define SIGILL
部分
4
I~ interrupt , generated fr国 teI1llinal special char 旷 μ(.)quit , g四盯'ated fr Ola tenlinal special cha:r */ I梯{咎) i11可al instruction (阳代 reset ..hen ca啕ht) _/
符 define SIGTRAP 5
户〈缔) trace trap (not reset ..hen caught) 时
样 define SIGAB时 ,
I叫.
符 define SlG1到T
7
; * ( • )四T
斗事 define SIGFPE
B
/ * ( .. ) fl幅山可归却 t exception • /
)
abort pr oc: ess ,. j instruction
*/
符 define SIGKILL 9
/ * kill (cannot
** define SlG阻JS
/ - ( 幡 ) bus error (speci fi国 tion exception) 时
10
be ca咱忱。r 甲、归国) - /
fI define SIGSEGV 11 # define 51国YS 12
/ ‘ ( - ) 国d .",恤阻tt。可stea. call _/
** define SIGPIPE
13
;*
梓 define 51'臼LRI!
14
/ .. alðnn c1oc: k t擅自ut _/
#血fine
15
/*
SIGTERM
/ * (.) s吨mentation viol眈 '0" 时 vr i te 0.11 a pipe with nO one to read it 旷 80ft唱re teI1l i田 tion signal
*/
例如,中断信号槛称为 SIG INT. 迫出信号面带为 SIG,QU I T , 非睡段存取信号是 SIGSEGV ,任何一个版本的 Unix 的手册都包含更多的相关信息 · 在 Linux 中,可以查看 signal(7) 的相关联机帮助 .
信号做什么?这要视情况而定.很多信号最死进程 . 某时刻进程还在运行 , 下 一 秒它 就晴亡了 , 从内存中瞌删除 · 相应的所有的文件描述符苗关闭,并且从进程幸中世删除 · 使 用 SIGINT 情页一个进程 . 但是进程也有办法保护自己本瞌杀死 .
第 6 章
:为用户编覆 z 终蝙撞倒和信号
• 171 •
信号处理的例子
6.4.4 l
捕捉住号
/*
sigd回01.
c - shows
• - run th is and
ho咽"豆,回国ndler
pr田 s
..orks
Ctrl - C " f ew t iJn es
旷 .川、clude
< stdio. h>
樨三nclude
<signal.h>
回 inO
/ . decl缸"'"国ndlef"
void f( int); 甚 nt
. /
:i ;
signa l( SIGINT , f ) fO f" (i . 0;
/ 锦 insWl
tlle
hand l er 旷
/ * do 50mething else.. /
i<5;iφ.){
printf(同 hello'\n叮,
s1eep( 川,
/ .. this f wx: tion
void f(int signum) print f( ~αJCH!
i8 咀江'"
.j
\n")
主函数囱南部分组成 , 调用 signal 后进入一个循环 . sigdemol. c 调用 signal ;来设置 SIG I NT 的扯理画散 f . 如果进程接收到 SIG I NT 信号,内在全调用函数 f 来处理这个情号 . 程序跳转到那个函数 , 执行它的代码,然后 jjj 回到跳转前的位置,就惶子过程调用 一样 . 图 6.8 显示了两个独立的控制班一个是正常的路径 , 进入 maln , 执行循环.然后从
mam 堪回;另 一个 是由信号引起的路径,进入 f ,然后退回 . S 旧"叮的到这将控制愧转向情号
1-一一 正常侄制槛
因 6.8
处理器
IA僧号处理 11返回 后 继援
以行原来的阳倒流 .
俯号 引 起 子过程的调用
Ullilt/ LiIl UI 编 理 实峨敏程
172 • 以下是程序运行情况!
S .
/ .igde田Z
he U。
press Ct rl - C no"
hello αJOI!
pre S1J Ct rl - C 110.,
hello αJOI'
hello hello
s 试编 i革井远行这个程序 . 程序 中没 有 XH 的显式调用.接查到的信号寻 1 1i丁对那个函 数的调用 . 2
息 略信号
/*
sigd回。.2.
c - sho.,s how
t。 咱m四I!I
P'四 Ct:rl\to
signal
ki ll this one
./ 咎 include
< stdio. h>
咎 include
<si gr国
h>
Mm()
signal( SIGINT , SIG• IGN ) ; printf( "youcan'tstopllle! \n" ) ;
.,hil e ( 1 ) sl eep( 口,
print f( "hah
sigde m o2. c 调用 s ign al 来世置为担略中断信号 .可 以随意的按 Ctrl- C 而不会对进程
产生11'响 . signal(SIG I NT . S IG_ IG N) 的姓果如图 6.9 所示. 进侄告诉肉战需要忽略到G1 NT
s 唱"'~2
豆I二豆豆自 图 6.9
引 gnal 臼 lG 1NT .
5 1G IG川的 效果
第 7 拿事件驱动编程编写 - 个槐频游戏
refresh( )
/ . "陶 te the screen 旷
getch()
/侧 wait
endwin() ;
/ . rCllct the tty etc "' /
• 185 •
for user input*/
摘译井运行它 . 蝉的预测正确吗?
7.3.2
curs四内部虚拟和实际屏 幕
ref resh 函敢做了些什么?试验 一 下注释掉该行,重新编译,运行程序 . 结果是什么都 没有出现在扉幕上 .
curses 设计成为能够在不阻事通信线路的情况下 E 新文本扉幕 . curses 通过虚拟扉事 〈如图 7. 4 所示)来 最小化数据 班量 . M也 U
H e>工。
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 s1 eep for animated effects
第7章
添加时延
7. S. J
事件驱动编程 1 编写一个幌频游戏
·
> 89
•
sl四 p
为了在程序中摇曲时延,使用 sleep 函盘
slcep(n) .'l
leep( n)将当前进程挂起"秒或者在此期间幢→个不能想略的信号的到达所唤醒 .
sleep() 是如何工作的:使用 Unix 中的 Alarms
7.5.2
sleep 函曲的工作凯理与你扭睡定佳时间的党 - 样
(1)设 置闹钟到你想睡的和数; (2) 睡觉 , 直到闹钟的铃声响起 .
图 7. 7 是这个凯制的示意图 . 果统巾的每个进程都有一个私有的闹钟 (alarm clock) . 这个闹钟很像一个计时器,可以设置在一定#数后闹钟.时间 到,时钟就发造 一 个信号 SIGALRM 到进程 . 除非进程为 SIGALRM 匪置了扯理函数 (handler) ,否则信号将最死这 个进程 . sleep 画盘囱 3 个步骤组成
1 为 SI GALRM 设置 - 个处理函数 $ 2
词用 .1盯 m( num seconds)
3 调用 pausc . s l~~p 函戴是如何工 作的 E
.."国 ( SIGAl且, handler) ,
alllno(nl , 阳明 ..0 ,
悠个逃程宵 QE的 It町iII
图 7 .1
-个选程没置 一 个闹 钟 后挂起
革统嗣用 pause 挂起进程直到信号到达 . 任何信号都可以唤醒进程 · 而非仅仅等待 S I G ALRM . 以上想法且结为以下代码 /钟 sleepl.
c
植 purpo:se
shc阳 how
*
sleepl
US IlgE'
• outl.ine
sets
sleep works
hanc量 ler.
./
h>
样 inclo由
< stdio.
拌inc: l ude
< 缸gna.l. h>
// 捋 define 51怪lHH =扫刊 )
sets 1I1ari1, pauses , then returns
Uni ll/ Li nUll 编疆实践'重覆
190 • void wakeup(int); 严 intf ( "about
to sleep for 4
8i gnal(SlGALRM ,
secOl'回.$\n- )
wakeu肘
;
/ . catch 此 . /
1I.1arlll( 川
/.
paule口
/*
print叭 M}!orn iJ习 .00圆n' 飞 n M );
/ _backto 嗣出 旷
yoid
wak四p (
将 ifr回.f
int
set cl田k 篝/ freeze here _/
d91lu国〉
Sl!HHH
‘
printf( "Alar received fr Olll
kerne l\nηJ
梓 e也汗
这里调用 signal 世置 S I GALRM 扯理画曲 . 然后调Jfl ala rm 世置一个 4# 的计时器,虽 后调用 pause 等待 .
调用 pause 的目的是挂起进程直到有 一 个 1"号瞌扯理.当汁时器计时 4# 钟以后 . 内脏
迭出 SI GAL R M 结进程导致控制从 pause 院转到倍号处理函盘 . 在信号处理程序 中 的代码 幢执行 , 然后控制远回 . 当信号被扯理完后. pause 远回 . 进程继续 . 图 7. 8 .!直结丁 pause 的 执行过程 .
阳晒<()系统调用导搬进 包阻塞j(到恼号被处理
囱 7.8
迸人处组函敬的执行流
下面是 alarm 和 pause 的细节 z
alarm 目标
战置发送倍号的计时'意
头文 件
。'm由 de
画 敛 1lI! 0!
unsigned old -
..量
, eronds
返回 值
1 。Id
< unisld. h> alarm(unsigncd
等待的时间《钞〉 如 S展出错
计时嚣剩余时间
secondρ
'院 7 司监事件驱动编程,描写
个很 11111 戏
• 191
a!arm 世置本进程的 H 时器到 seconds 秒眉目世盎信号 . 当设定的时间过去之后,内核监 埠 SIGALRM 到这个进程 . 如果汁时器已蛊醒世置 .al a rm 温回剩最非数〈注童.调用 ølørm (0) 童 峰精失掉闹钟 ) . ,..~
自鲁露
等待俯号
头文 件
科 includ l!
画 11. 型
r<'I Ult
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 •
7. 6. J
Uni x!Linu )t销徨寨战 敏程
添加 精 度更高的时延
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.
间隔计时器例于
l
11
读写计时器设置
ticker_demo. c
硅序 licker_demo. c 演示丁如何使用 一个 间隔计时器
/*
tickec_
•
d町flstrates
必
signala ,
use of interval ti.e :r to generate
wh ich ace in turn caught and
.j $零 "、c lude
< stdio. h>
转 incl叫e
< sys/ t i.JIe , h>
拌 include
< siqnal. h>
int 国 in ( 】
void
co皿 tdown( in时,
signal (SlGALRM , countdown) if (
..
set ticker(SOO ) • •
perror("set
" while( 1 )
ticker 吟,
- 1 )
r叩 1 .,
us回 to count 由m
飞
第 7 章,件驱动铺程 t 编写个视频捕'戏
• 195 •
回到U main.ticker_ demo. c 进入一个无尽的循环 . 其间调用 pause . 每过大的 500ρ ,控 制跳转到 countdown 函数 . countdown 将一个静态变量的值递减 , 打印 一 矗消息,通常情况 下远回调用者 . 当变量 num 盐到 o 时, countdown 调用 ex l1.
当然 .maln 不是 一 定要调用 pa use 的.主程序可以做些其他更有趣的事情 · 这样在每个 预定的时刻坯是合院转型IJ coun t down 的.
间隔计时器的设置是通过 struct itimervlll 来完成的 . 这个结构类型包括初始间隔租重 E 向阳 , 两者存储在 struct timeva! 中 z 且 ~t 让 Ùlerval
"阳ct
timeval i乞 value;
/ * time to next t Ùle r expiration*/
struct
t i.meval 让 interval
归,, 1回 d
i t value with
this 旷
8truct tiaeval i time t
tv
阳眶。"四s
盹
/ * seconds./
t tv usec
/ _ M到d lII icros但町lds* /
不同的 Unix 版本 struc t timeval 的细节可能有些差异 . 查一下体的罩统的相应手册和 头文件 .
图 7. 】 2 显示了结构中各成员的韭菜,因 7. 13 显示了如何载入盘据以便第 一 1x计时器到 达时间为 6 0 . 5 s ,然后每 240.25 s 重重眈.
JTl MER PROF
JTJMER V1RTUAL
:jltLE23 StruCl timeval
..ru创 itÎIDCrval
每个计时器有两个设量:剩下的树间相
struct tÎmeval
重复时间 这两个设 E囱 struct tlmtv剧 中的两个成员变量表示
变量钞敛相假钞散
因 7.
12
向阳叶时梅内部
有两个成 R
第 7 意
事件驱动编程=编写 一 个视频游戏
• 197 •
进程的私有 li 时器为 5 ,的同时卫世置另一个进程的私有叶时器为 1 2 s ?
一个古老的时钟是如何让时针卦针和秒针以不同的速度转动的,它们的警直是 一 样 的 . 每个进程设置自己的计量时间 ,操作*统 在每过 个时间片后为所有的计盘器的数 值 做瑾瞄 .一 个实际的例子可以置晴这些概念 .
考虑两个进程进程 A 和进程队进程 A 匪置它的真实计时器 (rea l timer ) 为 5 , .进程 B 设置它 的真实计时器为 1 2 S . 为了使数字看起来简单,假设系统时钟每和跳 100 下 . 当进 程 A 设置它的时钟时,内核设置它的计敛器为 500. 当 进程 B 设置它的时钟时,内核设置它 的计量器为 120 0 . 如图 7. 14 所示 .
每个选程的间隔计时器
…个真实的时钟
每个进覆通过调用时町n 来设量宦的私萌 it时揭 收到时钟审断的时候更新所啊的进徨计时器
图 7 . 14
两个 it时器 ‘
肉核在每次
个时钟
每当内核收到系统时曾脉冲 , 它遍历所有的问隔计时器,使每个计数器藏一个时钟单 位 . 当进理 A 的叶数器达到1) 0 的时候 · 章睐着已经有 500 时钟节拍过去了,内核左道 SIGALRM 插进程 A . 如果进程 A 已经世置了计时器的 it_interval 值,内幢将这个值草制到 it value 汁数器,否则内核就关掉这个什时器 .
再过
舍 , 内核将进程 B 的计数器也减到 O . 相 应地向进理 B~ 出信号 . 如果 B 设置了
计时器的重载值 ( reload 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 来产生一个 SIGINT 信号 . 或者是 Ctrl\产生 S I GQUIT 信号,或者计时器到时产 生→个 S IGQLRM 信号 . 就像电话和敲门的 访者,所有这些信号可能同时到这 . 在 Unix 革统里 -个进程如何响应多个信号。
因 7.
15
个钱收到l 多个消息的进程
(1)处理函数每/l:使用之后 都要酷禁用吗 ? (捕鼠器模型〉 (2) 如果 SIGY 消息在进程扯理 SIGX 消且时,到达垂直 生什么'
第"往
• 201
事件驱动编程:编写一个Il频游观
void inthandler(int s) printf( ~
R回""回 signal ‘ d
咱"却鸡飞 n" , S
>;
sleep(2) ;
printf( ~
[届.1咱 inthar回 ler
void quithandler( int
\o"}
的
printf ( "酌配 eived
siql回
‘d
晒 itir吼 n " ,
s );
sleep(]) printf( 例Leav iI可'1"比 handler
\n");
试着山不同的方式常规输入和两个信号生成键 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
whUe( l } mainl∞p
f喃
"{
a 哩 r~ad. (Oibuf'.len1
wtiteU ,
t只叫.
;
:\1-;
气:handlcr
/
、.
、交
气handler
图 7.
16
损踪这些函数的撞倒流
这些试验的结果显示了你的系统是如何处理信号组合的 .
i
不可靠的馆号〈捕11.器〉
如果两个 SIGINTS 信号杀死丁进程,那么意睹者你的系统是不可靠的信号处理画盘
必须每在都重量 . 如果多个 SI GINTS 倍号世有最死进程,意睡着扯理画盘在苗嗣用后还起 作用 . 现代信号处理帆制允许你在两者之间做出选择 .
2.
S J GY 打断 SIGX 的处理品数〈接电话的时候有人敲门〉
当接连 挂下 Ctrl -C 和 Ctrl \ 会看到程序先跳到g inthandler ,接着跳到 quithandler ,然后
第 7 意事件驱动编徨编写 - 个视频"成
7.10
• 209 •
kill: 从另- 个进程 发迭的 情号
倍号来自间隔计时器.终瞄骚动、内接班者进程.一个进理可以通过 k;ll *统耐用向另 一个进程直追信号 z
klll 目标
向 -个 选程发送 一个 倚'
头文 件
** ind
l.l
de < sys!t ypes. h>
#: indude < signa l. 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.
l
17
一 个进瞿使用 kìl1 0J候发送俯息
进程间通馆的舍义
撞圭倍号的进程几乎可以桂置任何信号的处理者 . 考虑 一 下在收到 SIGINT 时就打印 OUC H! 的程序 . 如果其他进程向 OUC H! III 序监造 S I G I NT 卫皆如何呢? QUC H! 程序 舍捕在倍号 . 跳转到处理者.打印 OUCH! ( 如回 7. 18 所示) .
E 进 一 步 . 如果第 一 个程序设置一个问隔计时器,计时髓的情号处理函数向 OUC H! 程序监迭 SIGINT 倍号 . 这样相应的扯理画量就瞌调用 . 从而
个进程的计时器搜嗣了另
一个进程的画数调 用 . 实际上一组进程可以幢幢幢球运动员传递橄槛璋那样传递伯等 .
Uni x/ Lin山编程实战教程
• 210 •
图 7.
2. IPC
1在号说计,
18
信号的复杂用法
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" l1 ol
退出
回 7.
19
bounceld 的运行界面用户控制的动画
这个程序是如何实现的呢?哉们已经知迫如何实现动画 . 在一个地方面 一 个字符串 , 等几毫秒,然后擦去旧的罪悔井在原来位置的左边或右边 一 个单位距离重新画同一个字符
南 .这里希望擦去和重画动作以相同的问隔连续的进行 .所以使用间隔计时器来调用 相应 的扯理函数 .
两个噩噩分别记最睡前的方向和速度 . 设置方向亚量的值为 + 1 和 一 l 分别罪示向左
第 7 章事件驱动编 fj ,编写一个槐频洒'戏
• 211
和向右串动 . 延时噩噩记录间隔计时器的间隔长直 . 校佳的延时意味着较慢的直庄,反之 则章峰营较快的草皮 .
现在向程序添加方向和l 速度控制 . 根据用户的键盘输入幢改方向和速度变量.程序的 逻辑如图 7.20 所示. bounceld 体现了两个重要的技术 :状 态变量和事件处理.记录位置、 方向和延时的变量定立了动画的状态 . 用户输入和计时器信号是改变这些状志的事件.每
眈计时睡到达信号就调用改 E 位置的处理函数 . 每次得到用户键盘输入信号就调用改变方 向和速度变量的代码 . 以下是它的代码 .
随 7 . 20
用户输入改变变量值而变量值控制动作
/ * bo wx: eld.c ·严upose
ani且ation
铃 note
the handler does the anialation
键
乞】回国l.JI
镰 cOOl pi l e
cc
with user cont r olled
progr !m.
bo 山、celd.
re是 ds
speed 缸ld
ke:lboard input
c set_ticke r. c - 1 curses - 0 bounceld
./ 替 incl ude
< stdio. h>
捋 include
< curses. h>
将 inc: lude
< signa l. h>
/铃 sOIIe globëll settings main 回 d the handler use _/ 样 d.巳"'阻SSAGE 特 define
"hello"
阻'>'OK
int ro";
/*
int col;
/ * current col \llllIl * j
int dh;
j.
curr四 t row
where we a.r e
direction
旷
g01咱旷
int main( )
i nt del ay;
1* bigger '"> s lower . /
川军时elay ,
/幡 new de lay 如/
• 213 •
第 "霍事件驱动编程编写 一个 貌,第游戏
dir .. 1 else 迁 ( dir ." d 立r"
1
l 届四1
+ strl审议皿ESS'哩)
>- ∞国 〉
- 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 所 示.
,
。
边出
'
反弹
、\· '. E疆噩噩噩噩圈圈圈噩噩噩噩噩跑革 被迫
〈简未实现j
加速 因 7.21
两维动画
b o un ce2d 的 3 个由 ;1 部分与 bOU ß celd 相同. (1)计时器驱动 间隔计时蜡桂世置为产生固定的 SIGALRMS 信号施 . 响应一个信号,璋向前事动
一步 . (2) 等待键盘输入
程序阻噩等待键盘输入 . 根据用户按下的键的不同来取不同的动作 . (3) 状态噩噩
变量记录了球的速度和方向 . 用户输入悻政的变量值决定丁小璋的蓝蓝 . 计时器扯理
第 7 意事件理动编程编写一个 视频游戏
ωd 町
• 219
~data
=c二:Y 伽'"阻。
圄噩 O/I_a1 anoO
_
噩噩噩
图 7.
Z4
键盘和计时传部发送信号
来设置文件描述符 o 中的。 ASYNC 位来打开楠人信号 . 最后 , 循环调用 pause 等持来自计 时器重键盘的信号 . 当有
个从键盘来的字符到达 , 内核向进程盎遭到G I0 信号 .. $IGIO
的耻理函数使用标准的 curses 面世 getch 来键入这个字符 . 当计时器间隔超时 , 内核盎盖以 前已经处理的 S I GALRM 信号 . 以下是踵代码
/ * bounce_lI.sync. c wi th u.s er con trol ,
.. purpose
animati创1
.. note
set_ tick: er()
•
k. eyboard ser到ds SIGIO . main only cal 1s pause <>
如 .0恩.p i1 e
cc bo阳、c e_ lI.sync. c set_ticker. c - 1 curses - 0 b。因lCe_as yne:
9剧由 SIGAL血.
US :U哩。I_ASYNC
on fd
handler does animation
-/ 样 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
梓 def ine
BLANK
int row
-/
"t回llo ~
10;
/ - cu!"rent r ow
in t col
.0 ;
/ . cur rent
且nt
d ir
• 1;
int
delay 事 200 ;
/* /*
i ntdone
时
col umn
旷
where 四 are going 旷
hov 10呵 '0 帽忧- /
.. 0;
国 inO
void
On→1I.1arm(in时,
1* tandle.- for
alann 时
第 8 章近程和程序编写命令解释樨 , h
• 229 •
i量也
问
文件
因 8.
]
系统中的进程和程序
世据朝程序存储在磁盘文件中,程序在进程中运行 . 以下的几章里将学习进程幢;吉 从命令 p' 和 sh 开始,然后写一个自己的 Unix shell .
通过命令 ps 学习进程
8.2
进程存在于用户空间 . 用户空间是存脏运行的程序和它们的盘据的一部分内存空间 . 如图 8.2 所示,可以通过使用 ps(process status 进程状态的简写 〉命令来查看用户空间的 内 睿 . 且个命令告别出当前的进程 .
用 户 空间
I I 院副
容纳进程
嗣
-.J. I 萨 ]>>-< P画 4
lJ.j 文件 1f. t,宽容纳 文件和目是
E ....与11]'
国|
IS.8 Is-I
四日 .2
$ P' PID 1175 19自 1
'ITY
TIME
pts/ l pts/ l
00;00:17 bash 00:00:00 ps
这里有两个进程在运行
ps 命令列出当前进瞿
C凯D
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 ess 辑别 . 有些进程与某个终端相应,而其他一 些 贝'1 没有 . 这些属性存撞在什么地方呢 ? 曾对文件提过同样的问题 , 内核管理内存巾的进程 和磕盘上的文件 . 这些管理活动有什么相但之处吗?
文件包吉散据 , 进程包吉可执行代码 . 文件有 一些属性进程也有 一 些属性 . 内核埠立 和销毁文件,进程类似 . 就{掌管理磁盘的事个文件,内核管理内存中的事个进程 , 为它们分 配空间并记最内存分配情况 . 内存管理和键盘管理有什么相似之处?
8. 2. 3
内存和程序
进程这个帽念有些抽象·但是古代表了
些非常实际的主体内存中的 一 些字节 . 图
8.3 演示了计算机内存的 3 种模式 . 内存可以看作是一 个 曹纳内骸 翻进程的 空 问 .
fIl:i: ~民筑把内仔看作曲页面构 成的'皮细精选槐份制到不同 的页面 目 钩理上二 这些页丽可
且且』
能彼得"在回体的芯片 '1'
面勤勤
曹雪' 图 8. 3
计算机内存的 3 种模式
Unix 系统中的内存升为系统空间和用户空间 . 进程存在于用户空间 . 内存实际上就是
个宇节序列,或者
个很大的数组 . 如果肌器有 64 MB 的内存 , 那意味着直个数组有大约
6 7 00 万个内存位置 . J革中的 一 些用来存放组成内核的机器指令和数据 .
还有一些存融组成进程的凯器指令和盘据 . 一个进程不 一 定必须要占 一 段连续的内 存 . 就惶文件在瞄盘上被分成 '1、块,进程在内存也世分成小块 . 同样和文件有记录分配丁
的瞌盘块的列表相似,进程也有保存分配到的内存页面 (memory pages) 的数据结构 . 因此 , 将进程牵示为用户空间内的一个小万块只是某种程度的抽盘 . 特内存囊示为连续的字节数组也是一种抽血 . 理在的内布一般情况下是囱小电路植上
• 234 •
Uni 嚣/ Li nux 编程实践徽程
shell 同时也是带有变量和现程控制的描程语 盲 . 在上面的例子中 , 可以看到使用了两
个壁量 . 首先,直量 TZ 桂设置成茬示莞国西海岸时区的字符串 . 然后这个值瞌作为#盘传 蜡 dale 命令来打印当前的日期相时间 .
例子的后面部分,可以看到有汗。
then 语句 . 'l!'量 NAME 植置为字梓串 "Ip" .
$ NAME 的值在 grep 命令中被使用 . g'叩的结果自;r语句进行判断 . 如果在文件/etc/
P'臼wd. 中擅窜到 宇符 串 "Ip" , shell 就执行命令 echo hello l mail $ NAME . 否则跳 E 下一矗 命令 .
在本章中,先来看看 shell 是如何运行→个程序的 . 在后面的章节中将学习 shell 的脚本 语言和输入输出的重定向 .
8.4
shell 是如何运行程序的
shell 打印提示符,输入命令 . shell 就运行这个命令然后 shell 再眈打印提示符一一如此 li S! . 那么这些现靠的背后到底发生些付么?
一个 shell 的主循环执行下面的 4 步 〈 如图 8.4 所示) , (!)用户键入 a . o ut i
(2) s hell 建 立一个 新的进程来 运 行这个程序, (3) s hell 将程序从磁盘辑人; (4 )程序在它的进程中 运行 直到结束 .
shdJ
图 8.4
8. 4. 1
用户要求 shell 运行一 个程序
sheU 的主循环
s hell 由下面的循环组成:
while( get
!町、d_of_input) c叫
execut e
COIMI!II nd
wð i tforc叩 to
fin ish
考血下面这个与 s hell 典型的互动
Unix/ Linux 编程实是是教穰
• 236
Un;x 切问屋 1
i
个 Wj<
execvp < prOKnamc . 1
argl刚》
将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 的完整程序= / - exec l. c - shows how 倒可比 is for ð pl" og l"ðlD to run a p l" ogram
-/ 回 in()
0"0< 剑 ðrglist[3] ;
ð l" glist[O] " " >s "; arglist[l] .. " - } " arglist[2 ] .. 0 pr intf( "
*警*
About to exec 1s -1\n " )
execvp( "1.. " , argli..t ) print f(
"费*.
18 is done. bye\n")
exec叩有两个垂盘 z 要运行的瞿序,g和 Rß 个程序的命令行垂 数 数组 . 当程序运行时命
令行参盘 t.t argv[]传蜡程序 . 注意 · 将盘组的第一个元肃置为程序的 名 称 . 还要注意 , 最后 一个元章必须是 null . 编译并远行这个程序 z $
CC
el<ecl
$
. / execl .c
…Ahout
. c - 0 execl
t。时 ls
- 1
Uni x/ Un u x 编程实陇敏程
24 0 • Arg-[巧 'd恤 odir
Arg[ 3] ? tot~!ol
2
dnnrr_x一-
2 bruce
users
1024 Jul 14 21 ,02 a
d向 r _ x ___
3 bruce
users
1024 Jul 1603 , 16 c
bruce
users
OJul1 421 : 03y
_nl_r __r __
1
s 4. 它各么 i足11:丁 '
程序运行正常 , 但是就像设想的那样. e xec vp 用命 令 指定的程序代码覆盖了 sh el!的程 序代码,然 后 在命令指定的程序结束之后退出 . 这样 s hell 就不能再在接壁新的命令 . 为了 运行新的命令 , 用户不得不再在运行 ~he l1 .
s hell 如何能做到在运行程序的同时还能等待下 一个命令 呢?方怯之 一 就是启动一个新 的进程,囱这个进程来执行命令程序 .
问题 z
8.4.3
如何建立新的进程
事靠一个进程调用 fo rk 来草制自己 . 用量 l
forkO;
/骨 takes no a r gumenls*1
解绎 fork
继续用量因斯坦大脑思考的问题做比喻 . 就惶先前看到的,将量因斯坦的大脑撞到体
的脑壳里不但把量因斯坦的思想结了你,同时也晴除丁所有体原来大脑里的思想 .
解决的方法之 一 就是直制 一 个自己,某用 三 位lIi惶复制技术.罩个原于的直制 - 个完圭 等价的自己 . 当曲:建立了 自 己的重制品 , 将量因斯坦的大脑血到它的脑壳里 , 这样你就可且
继续你原来的计划和思考了 . 在你生命中的某个阶段 .世 界上只有 一个 你 . 当你挂下直制 机的直制捶钮,世界上有了两个体 . 对自己的草制有点惶马路上的岔口 . 开始只有 一 条路. 然后有了两条 . fork 之盲。
fork 之后
弱的避程拥有相父迸徨栩间的 代码彻敬饵 因 8.8
fork O 复制个进程
Unî x/ Lînux 编程实贱敏徨
• 242 •
打印的 .撞章进程 4171 波有 打印Be fore: 信息.为什么呢?用户空间的近照〈如图 8. 9 所 示〉显示了 进程 417 0 调用 fo rk 前后监生了什么 . fo~ 之前
f~ 之后
!-叶中干干| 个控制慌进入内核的 fork 憔埃 图 8. 9
。明闸完成时
从 fork 返国两个控制流
子进程执行 forkO 之后的代码
内核通过直制进程 417 0 辈创建进程 4111 ,它将 4170 的代码和当前运行到的位置都草 制结的 71 , 其中当前运行的位置是由随着代码向下蒂动的箭头表示的 . 新的进程 4171 从
fork 垣回的地方开始运行,而不是从开头开局运行 . 因为 4171 是且中间开始运行的,也就不 打印 Befo r e 3
信且丁 .
例号
子进程创建过程
for kdem u2. c
于进程不是从 maln 画酷的开始,而是从 fork 返回的地方开始它的生命之脏 . 预测
下
下面程序舍有几行输出
/*
f.创 kd画。2.
c - shows ho切 oh立 ld p:-oc田 se8 pick up at tl丑e r et urn ar回 can
execute any code they l ike ,
势
from
fodr; O
衡
even
for k.( ). Predict
nu凰.be:-
of lines of output
./ 回 inO
p:- i.nt剑"町 pid
i8 ‘d\ n" , getpid() );
fork () ; fork ( ) for k. O; pl"illt f(
"町 pid
is ‘d\ n , H
getpid 口) ;
捕译井运行这个程序 . 结果如何 9 4
例子
forkdemo3. c
分辨父赴程和子进程
从 forkdemol. c 可 以看到进程 4170 嗣 用 for k 创 立于进程 . 子进程 P lD是 4]71 . 两个进 程有相同的代码,运行到同 一 行有相同的数据和进程属性 . 那么如何才能分脾到底是立进
• 246 •
Uni lt/ Li nux 编程实量是被程
词用 e xlt 就像蛊造 一 个信号蜡立进程以唤醒古 . c业 ild
阳刚
坦白
0呻忖
l
-咀
eM ld
父 选碰在 W8' I 处阻鑫.
-M }
-J
然后在于进程退出 后 继 续屋行 . .dt! 时,
F 一 ___J
,
l 阳 "'to咱们
int .t il. t..."
V 四 t !Util.
!!i 8. 11
阳 omt_codl!
1
lnt stat Uil. 1 _it! ‘ It il. tus) I
调用 wail() 而后的控制流和选程间通信
waitdemo l. c 程序体现了 walt 的两个重要特征 ( 1) w ai t 阻事调用它的程序直到于进程结束
在这个简单的程序中,芷进程阻塞直到于进程调用 eXlt . 这 一特征使两个进程能曾同步 它们的行为 . 比如,j(进程用 fork 创建一个子进程来时一个文件排序 . 立进程必须等排序 结束后才能睡续扯理这个文件 . 革统调用 eXlI和 wa 1t是 种协调且些任务的方法 . ( 2 ) wait 远回结束进程的 PID
在这个简单的程序中. walt 的返回值是调用 e川的于进程的 PID . 就悻在 forkdemo2 . c 中看到的个进程可以创建多个子进程 . 带靡 一 个从两个不同的远程 数据库整合数据的 程序 . 革个程序可以使用 fork 来创建两个进程, 一个用来直接井从数据库中提取数据,另 个从其他散据库提胜数据 . 从第 一 个数据库提取来的数据需要做些后期扯理 ,而从第 二个 数据库提取来的数据则不需要这样的扯理.
walt 的垣回值告诉父进程那个任务结束了 . 这样它就可以继续有效的扯理丁 . 3
例子
w ai tdem o2. c
通信
walt 的目的之一 是通知立进程于进程结束运行了 . 它的第 二个目的是告诉立进程于进 程是如何结束的 .
一个选理山 3 种方式 〈 成劝,失陆或死亡)之一 结束 . 其 , 一 个进程可能顺利完成它的 任务 . 按照 Unix 惯例 , 成功的程序调用 exit C的 或者从 maln 函数中 return O. 其二 ,进程可能失败 . 比如进程可能由于内存能尽而提前退出程序 . 按 Unix 惯例,程 序遇到问题而要退出调用 eXlt 时传给它一个非零的值 . 程序且可以对不同的错误卦配革同 的值 , 手册中有详细的描述 .
第 8 章进覆柿程序描写命令解得稽 . h
• 247 •
最后.程序可能被 一 个信号杀死 〈 且第 6 和第 7j挺 ) . 信号可醋来自檀盘、间隔计时器、内
核或者其他进程 . 通常的情况下, 一个既世有酷坦略卫世有撞捕获信号舍杀死进程的 . WBlt 返回结束的于进程的 PID 结宜进程 . 立进程如何如置于进程是以何种方式退出 的呢 ?
替罪在传给 wa1t的垂数之中 . 立进程调用 Wðlt 时传 一 个整型置量地址蜻函数 . 内核
将于进程的退出拉在保存在过个变量中 . 如果于进程调用 ex!t坦出·那么内菌把 eXlt 的 i屋 回值存放到这个整数.-:ll'量中.如呆进程是桂杀死的,那么肉核将信号序号存敢在这个变量
中 . 这个整数由 3 部分组成一-8 个 bit 是记 录退出值 .7 个 bit 是记最信号序号,另 一 个 bit 用来指明宜生错误并产 生 了内核腆惶 ( cor c dump ) 0 图 B. 12 摘示了于进程壮志值的 3 个 部分 .
国B. 12
子进程状态值有 3 部分
例子 wa itdemo 2. c 是基于 waitdemol. c 的。它显示了于进程的退出状在 / .旧豆回国。 2 . c
- s ho回国"归 rent gets c hi l d S tð. t UB
时 P丰 incl ude
< 眈 dio.
转 define
DELA 'l' 5
h>
国 inO
in t
ne叩 id;
void c tj il d_code 口 , parent3 ode O prin t町 ~before
町p id is ‘ d飞 n ~ ,
if ( ( newp i d .. fork O> 归 rror( "fo响" )
else if ( newp i d : a 0 ) child_code( DElA Y) ; else
-1 )
get pidO) :
第8章
进程和程序:编写命令解,警棍 , h
• 253 •
如何做到。
psh2. c 工作正常 . 新的 shell 撞圭程序名称、垂数列毒、运行程序、报告结果 ,然后再重
新接圭和草行其他程序 . psh2. c 融少常用的 shell 的 一些 曹饰性功能,但可以作为 }个坚实 的基础开始了 .
下 ← 个版本中将做如下改进.
(1 ) 让用户可以通过按下 Ct rl - D 或者输入 "exit"坦出程序: (2) 让用户能够在行中输入所有垂数 .
在 F一 章主理的版本中加上这些功能 . 在那个版本中,将加上一些变量和控制植程使 它更惶一个捕程语盲 .
这之前必须E 正
个严重的错误 .
倍号和 psh2. c
从测试中可以看到,退出程序 psh2 的惟一 方法是按 Ctrl-C 键 . 如果在阴阳等持于进 程结束时键入 Ct rl-C 键告如何呢'比如 $ • / pah2
>
tr
.t.rq( l ]? [ .- z] Atq[ 2]? [ A - Z]
Ar g( 3] ? bell。 阻叹liJ
~掬 to
prtlU
NOII 四PRF.SS
ctrl- C 核 下"
S
于进程结束,但是 sheU 也结束 了 . 按下 Ctrl - c 所生成的 SIGINT 信号不但最死了运 行"的进程 . 而且最死了运行 psh2 的进程.为什么呢9 键盘信号监结所有直接的进程
程序 psh2 和忧郁连接到鳝端(如图 8. 1 5 所示 ) . 当按下 中断键, ttr 驱动告诉内核向所
回 8.
15
键盘倩号发向所有连簇的进程
• 254 •
Unix/ Li n UlC 编程实蝇披程
有白这个终端控制的进程盎送 SIGINT 借号"死了,在我们的程序里, psh 也死掉了 , 即使 它还在等待于进程的结束 . 如何才能让 shell 不彼用户接下的中断在退出键杀死?这个改动圄作习题 .
8.6
思考:用进程编程
为了理解 Unix 进程,运行了 P' 命令 , 还学习了 she l1如何使用 for k 、四川和 wa !t来控制 进程和运行程序 .
在蜡辑学习下一章有关在 shell 中增加变量和循环之前考虑一下面数和进程之间的相 似性 .
1.
exec vp / exit 就悻 clIlJ l rel urn
(] ) call1return 一个 C 程序由很多函数组成 .
个函量可以调用另一个画盘,同时传给它 一 些垂散 .
瞌间闸的面世执行 一 定的操作 . 然后温囚 一 个值 . 每个函数都有它的局部变量.不同的函数 通过 call / re lU rn ~统进行通信 .
这种通过盎盘和 i且回值在拥有私有数据的函数间通信的模式是结构化程序设计的基 础 . Unix 鼓励将这种应用于程序之内的模式扩展到程序之间 . 这种模式可剧用图 8. 1 6 来 理示 .
弓Z21tf哇 I ___c~ __
图 8.
16
函f<调用和瞿I!'调用
(2 ) exec/ exit 一个 C 程序可以 fork / exec 另 一 个程序 , 并传给它
些盎披 . 直个被调用的程序执行 一
茸的操作 , 酷后通过 exit( n) 来远回值 . 调用它的进程可以通过 wait(&.re s ult)来1Ji取 eX. ll 的 垣回 值 . 子程序的 exn 远回值可剧在 r e~u lt 的 8 - 15 位之间找到 .
函量调用所用到的难辑儿乎是世有限制的 . 一个髓调用的程序还可以调用其他程序 ,
一个通过 fork/ exec 阀用起来的程序可以通过 fork/ exec 调用别的程序 o Unîx 使创ll! 一 个 新进程方便而且快捷 . 用 fork /exit 和 exit / wait 来调用程序和迫团结果不仅适用于 sh e ll Unix 程序经常世设计成一组于程序,而不是一个带有很多函数的大程序 .
第 9 章
可 编徨的 s hdl 、 shell 变量和环挽:编写自己的 s he ll
• 287 •
aainO char
* tabl e[3] ;
tabl e[ O) - 咛酣 萄 吭 100~; tabl e[ l ]
- 咱咀E - /0旷 the/r.缸耻町
t由l e[2]
'" 0
四 v i ron
tabl e;
execl p( "四v" ,
/ * fill the
t.ab le 旷
/ * point to that tahl e */
~env " , N叽..1.) ;
/ .四ec
a
pr,句,u 旷
下面是示例 $
. / chaDgeenv
TERK = v t1 00 E睛也 = / on/ the/阳、go
s 仔细营营程序 . 在程序 changeenv 中创蛊→个字样南列茬,然后调用 ex eclp 来运行另 一 个程序 env . 第 二个程序能静读到这个字符串列茬 , 也就是说通过某些方法,将这个数组从 第一个程序 空 间复制到第 二个程序 空 间了 . 2
但是 e xe c 清除了所有的数据 1
在讨论 exec 系统调用时候知道,时它的调用就惶换脑,周国悻程序的代码和盘据替换调
用程序的代码和数据 . 但是 envlron 指针指向的数组是惟 一 的例外 , 当 内棋执行系统调用 e xecve 时 , 它将盘组和字符串复制到新的程序的数据空间,如图 9. 7 所示 .
f.愧 。
一「
一一 →
子进程栩 父进程有
相同的代码 l 数据| 栩栩vl ron . exec 戴入新的代码
初放组到选在 .
m9.7
en V lro n 指向的数据在执行 execO 时曾复制
在生成子进程的过程中,观事 envlro n 数姐的变化 . 可 以看到, fork 完整地豆制芷进程 , 包括代码和盘据,盘据中包括丁环境 . exec 清除原来进程中的所有代码和数据,插入新程序
U l1i J!;( L î l1 u J!编型实践敏程
• 300 •
一个 s hell 应用程序:监视系统用户
10.2
考血一下革个问题件的许多朋宜和你使用同 一 个 U nix ~统 . 你希望编写 一个程序 · 当 其他用户垂录系统或在销时通知体 . 过样你就可以了解朋直们的活动 .
可以写
个使用 utmp 士件和间隔计数器的 C 程序来完成任务 . 程序打开 utmp 文件,
记录下用户列罪,体眠一 段时间后再重新扫描此士件.井将变 化报告出来 . 个重简单的办法就是写一个 shell 脚本 . Unix 中有 一 个 列出 当前用户的命令
who .
Unix 中同样包吉丁 休眠和处理字特阜罗'J毒的程序 . 下面是一个 Unix 的脚本.用来报告所有 的垂录和注销情况 . 叫 ood,
Log ic
get lillt of
users(c啤11
"00 I sort >
it prev)
..hile
..hile t rue get list of l l1
由
s1eep 60
s1 ~p
C 回回 e
true
pre哑
'00
users(cð11 比 curr )
not in
curr
echo " 1明回 out ,
list s
pr酬,
I 白白 >
curr 一>
C叩
l O9out
-
"
23 prev curr
echo " 1 ogqed 凶
i l1 curr , not in prev 皿e
->
109in
C Ollllll
-
13
pr刷=,
mv curr prev
prev '" c urr
repeat
do"
,
此脚本使用丁 Unix *,统所提供的 7 个工具、 一 个 while 循耳和1 1/ 0 iIi定向,描写这个程
序解决了问题 . 仔细看 一下这些程序 的细节 ,以且 它们之间的连接 . 脚本中的第 一 行建 立 了 一 个在此脚本运行时已登录用户的则表 , 并按用户名进行排序 . wh o 命令输出用户列茬,而 国n 命令将列表作为输入读避,然后输出一个排好序的列费 .
命令 who
I 50rt >
prev 告诉 shell 同时执行 who 和I 50rt 将 who 的输出直接道到 50rt
的输入 。 如图 1 0 . 1 所示 . wh o 命令并不 一 定要在 50 rt 命令开始读取和排序之前完成肘
utmp 文件的分析 . 过两个进程以很小的时间间隔为单位来调度.它们相系统中的其他进程 起分享 CPU 时间 . 然后,四川 > prev 告际 5he ll 将 50 rt 的输出送至 prev :X: 件中 . 若此士 件不存在,则创建此文件:若已经存在,如l 替换其内窑 .
woo
回 1 0.
则
1
>
fi1 e
将 who 的输出连缩到 50 rt 的输入
Unilt/ Linult 编程实贱'安程
• 302 .
告给第三个班 . 如果扭略迫些幢幢班的去向问题, sort 工具的 基本原型就如图 1 0.3 所示 . 三 个数据现分别如下:
· 辑幢幢入一一需要处理的世据配 标准输出 -一一结果数据班
· 标准错误输出 一一 错误消且流
回 1 0, 3
10.3. 1
sort 工具将输入匮进并输出结果和错民消息
概 念 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
两个慨念 的结合
巳经介绍了两个基本的植企 . 首先. Unix:进程使用文件描述符 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 中显示了当前文件描述特数组中的第 一个元章现在址在主 闲状态 .
Unî x/ Lînux 编程实践敏程
• 306 •
图 10.6
典型的初始化配置
唰用 closc: (0) 之后
因 10.1
5tdin 披关闭
最后,使用 open (fi lename , O _ RDONL Y) 打开一个想连接到 stdin 上的文件 . 当前的最
低可用文件描述符是 0 ,因此所打开的文件将瞌连接到标准输入上去 . 如图 10. 8 所示,任何 从悻推输入读取盘据的函数都将从此文件中读入 .
。"" 调附创嘘了一个到 文件的直撞并煌宣指向 是低可能寝项由指针 .
图 1 0.8
,叫 m 现在已经连接到文件上 7
下丽的程序郎使用c1ose - th en - open 方法。 * 1 stdil'lred王宫1., ·阴~l"pOSe ,
.. .. ..
s OOw how to
red 豆 rect stardar唱'豆n阴~t
by replac l.ng tile
descriptor 0 with a connection to a file acti。罚
reads
clos回 fd
three
1 革 nea
0 , opens
i!I.
frooa
d且 sk
standard 皿 put ,由由3
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 ) 特立件描述符 fd 做了
个重制 . 此l!;!!制使用最低可用文件描述捋
号 . 因此 , 获得的文件描述骨是 O. 这样,就将磁盘文件与文件描述怦 o 连接在 一 起了 .
(4) c! ose (fd )
最 后 . 使用c! ose (f d) 来韭闭士件的原始连接,只留下文件描述符 o 的 连接 . 将这种方陆
与把电话从一个分饥转事到另一个分帆的技本做一个比较 . fd •
o"en('f 闹。UmONLY)
;
close(O)
c lose(fd) ;
dup(fd: ;
图 1 0.9
使用 dup. 定向
第 JO 章
• 311 •
1/0 重定肉和管道
子远程锺示了父返程指向打开
文件的指针 . 子透疆军定向标 准输出: d05t( >)
,
create( ~f " ) ..~υ l
被子 选程打开文件 图 1 0.
10
.'Ihell 为子送覆重定向其输出
看 一 下如何使用这个原则来重定向标准楠出 . 1
明始情况
如图 10.11 所示 ,进程运行在用户空间中 . 文件描述符 l 连接在打开的文件 I 上 . 为了 使这幅图清楚岛理解,其他打开的文件井未画出来 .
图 1 0 . 11
2
在调用 f。 此之间的进程以及官的标准输出
父进程调用 fork 之后
如图 10 . 12 所示 , 新的进程出现了 . 此进程与原始进程运行相同的代码 , 但它知道自己
是于进程 . 此进程包含了与立进程相同的代码、数据和打开文件的文件描述符 . 因此文件 描述符 1 依然指向的矗立件 f . 然后于进程调用了 clos e ( 1) 0
子选段
指向打开的艾件
盯开文件
图 1 0 . 12
3
子进程的标准'自由从父进程黯儿继承而得
在于进程调Jfl c! ose (l) 之后
如图 1 0 . 1 3 所示,父进程并世有调用c1ose ( 1) ,因此宜进程中的文件描述符 1 仍然指向 L 子进程调用 close (l ) 之后,文件描述符 l 变成了最低未用文件描述符 . 于进程现在试着
Unix/ Linux 编程实践敏程
• 312 • 打开丈件 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.
5
14
子进程打开一个额的文件得到 fd = 1
在于进程使用 exe c 执行惭税房之后
如图 10 . 1 5 所示 ,于进程执行了 who 程序 . 于是于进程中的代码和数据都植 wh o 程序 的代码和数据所替代 7 ,黯而文件描述特量保圄下来 . 打开的文件并非是程序的代码也不 是靠据,它们属于进程的属性,因此 exec 调用井不改变它们 . 子进瞿 新的包序 指向打开文件的指
针是选程的一部分 i 但此数组并不是艘
序中的政娼 . 图 1 0.
15
子进程运 行程序并将标准输 出重定向
who 命 令 将 当前 用户列表追至文件描述持 1 . 其实这组字节 已经 苗写到文件 g 巾去了
而 who 命令却毫不知晓 .
下面的程序 whotofile. C 展示了上面所诅的过种方挂
Unix/ Li nux 编理实践'虫覆
• 314 •
编写可以支持以上两种操作的代码就留给大辈作为练习去完成 .
10.6
管道编程
现在已经学 习了如何描写程序 将际准输出量定向到文件 . 下面将要讨论如何使用管盟
辈连接一 个进程的输出租另 一 个进程的输入 . 阻 1 0 . 16 展示了管道的工作原理 . 管ilI是内 核中的 一 个 单向的数据通道 . 管型有一个读取端朝 一 个写入端 . ~理 who I sort 这样的撞 作,需要两种植巧如何创建管遭.以且如何将标准输入和输出通过管道崖撞起来 .
网 1 0.
10.6.1
16
稠个造思囱管遭连接在一 起
创 建 管道
图lO. 17 所 jf- 即为 一 植1'f坦 . 可以使用如下的军统调用来创娃1'f逝 . P' P' 目标
创建管遭
头文件
$* indude < unÎsld. 11>
画 隙 '望
TC5Ult
… a 温圄僵
- 阴阳 ( Îm 盯 ray[ 2 ]> ,
M"Y
包含两个'"'类型'段揭露的'生组
- 1
发生'费泯
o
成功
P啊川
pipe
写入蟠
[01
11取蟠
图 1 0.17
i'f ill
调用户归来创童管道井将其阴端直接到两个文件描述特 . 盯 ray [O] 为读敏据睹的文件
第 10 章
1/ 0 重定向和管道
• 315 •
描述符,而 array[ l]则为写数据端的文件捕连带 . 惶 一 个打开的文件的内部情况一样 , 管道 的内部实现隐藏在内属中,进程只能看见两个主件描述符 . 图 1 0. 1 8 显 示 了进程创述一个管理前后的收配 . 前
张困 〈调 用 p !pe 之前 )显示丁标 准
立件描述符姐 .后一 张困(词用 plpe 之后〉显示了 内核中新创建的管道,以及进程到管道的 两个连接 . 注意 ,类似于 o pen 词用 .plpe 间用也使用最低可用文件描述符 . 惆用 P' P' 之前
耐用 P' P' 之后
进担打开 -些*蟹的文件
内接创建管道并没置文件描述符
固 1 0. 18
进程创建管理
下面的程序 pipedemo. c 展示了如何创盟管道并使用管道来向自己监造盘据 / * pip回国。 C
帽。回。nstra tes , how t o cr刨 出 and use a pi pe
* Effect , crea tes a pipe , wr ites into writ呵 怜巴nd , υ理nn皿, =。山ld and reads fr o. Teadi.r唱 •
end . A 1 此 tl e ..e i时. but deMo nstrates the idea
"
-/ 样 卫.nclude < stdio.
h>
得挝、c:1 ude < unistd , lIð.
h>
in( )
i nt char
l en , i , IIpipe [ 2J; buf [田目Z 旬,
/ . qet a
pipe 旷
if ( pipe
;*
two f ile descdptot"S
/ * for
read缸可"'"
旷 时
( 叩ipe)--- l) (
perror ( 吧。'I11d not 国ke pipe 叮, 回it (
1);
print f( "Got
ð
pipe! I t is f ile de 9CZ" i ptors ,
\ ‘ d ‘ d )\ n " .
'P且 pe[O] , ap且R【 1 J) ;
/ .. read fr:回 stdin , write 却to pipe , r嗣d fr OD. p i pe , p rint 旷 whil e ( fgets(buf ,田白血, stdin) ) ( l e n • strlen( buf )
• 316
U n ix/ Linux 编程实麟敏程
if (wdte( apipe[l ] , buf , len ) ! - l en) { perror( "胃口 tir咆
ω
- 0
/*
but[ i ]
i < 工曲; i
1回 - ..
=鸣
- 1 H
perror(n r田dil可怕泪 p ipe" )
;
break;
if
(盯 ite{
pipe 时
/ _ wipe _/
++ )
l回. ,ωd ( ap 孟 pe[ 町 , buf ,即fFSI Z
if (
send 旷
/镰由m 旷
to pipe 吟,
b,回<,
'0'
/*
)
/ * read */ / fI
fr四 时
/*
pi pe 时
/ . pipe 1 , but ,
1回 】
归口。氏"町让U唱 to
! '"len ){
stáout n)
b,田k ,
4/
/ . ser回./
/ 4 to" / / . pipe . /
图 10. 19 显示了从键盘到进程 · 从进程到管道,再从管盟到进程以且从进程回到费端的 数据传输配 .
图 1 0 .19
pipedemo.c 中的数据流
现在已经学巧了如何创建管坦,如何向管道 中 写墨宝据以及如何从曹迫中读取盘据 . 实
际上 , 很少会有程序用管道向自己芷选数据 . 将 plpe 和 fork 结合起来 . 就可以连接两个不同 的进程丁 .
第 10 章
10.6.2
• 317 •
1 / 0. 定向和管道
使用 fork 来共 享 管道
当进程创盟一个管道之后 . 该进程就有了连向管道两端的连接 . 当这个进程调用 fork
的时候,它的于进程也得到丁这两个连向营造的直接,如图 10. 20 所示 . 茸进程和于进程都 可且将数据写到管迫的写数据瑞口,井从撞撞据喘口将数据 i主出,如固 10 . 21 所示 . 两个进
程都可山语写管道.但是当 一个 进程埠 , 另一个进程写的时住.管盟的使用效率是最高的 .
其事管道
边观调用臂涩,内恢创建 - 个
'" iIl J~ 修细注接管;a精点的文 件细述符组针11<组
在资 .i墨程调 m fork. 内核刨缰 个..进程排队父避现复制造
罐管迫输点的文件描选符指针 数组
!I!e.
两个涯'啦'眼访问管i庄的两编
圄 1 0 . 20
组 10.21
共掌管道
近程之间的数据流
下面的程序 pipedemo2. c 说明了如何将 plpe 和 fork 结合起来.创建 一 时通过管道来通
信的进程 .
/*
pip回国。.2.c
。.
•
1)田。 nstrat田 阳帽 p i pe
-
P=四 t
is
dupli c:a t回 in fork门
continues to wri te 缸ld read
but child also wr i tes to the
斗"
-/ 4阵 incl uo:揭
< stdio.
样 define
OUID HF.SS
h> "I
wantac∞k:ie\n"
P卫'"
pi~.
k叫
4
,;
01
Unix/ Linux 编程实践敏程
• 326 •
成的工作就林立为服务 , 而自己则是服务的事户 .
上面的例子跟 Unix 有什么荣革呢? Unix 中的管道可以把数据从一个进程传盖到另外
一个进程 . 进程和管道元但类似于一条生产线来完路产品 , 还类似于一个大的服务产业 . 本意将关注于进程间的数据通信 , 这也是事户 / 服务器编程的基础知识 .
11. 2
一个简单的比喻:饮料机接口
程序处理信息就像人们消耗饮料一样 . 以图 1 1. 1 中 的自动碳酸饮料机为例,在你投 入硬币,按下按钮之后,饮料就自动由出 . 在此过程中 , 饮料机内的分配器在做什么工作
呢?在饮料机内,应该有 一 桶碳酸水和另 一 捅浓缩的世料什水,当接下按钮时,将撤活 一 个配制原材料井不断地传送户产生的碳酸性料的过程 . 另一种 方法则是只需要将
瓶颈
先配制奸的碳酣饮料辑到 一 个抽水泵上 , 缸下按钮这个动作就简单地将饮料抽出并送给 外面的杯于 . 累
很掘"求到脏停 阁 1
1. 1
来自存储部分 动态产生或来 自 静态饮料
就惶碳阪饮制分配器一样, Unix 提供
个接口来处理可能来自不同数据源的数据 , 如
图 1 1. 2 所示 .
4 种樊型的".匠,厚
1.
IU量文件
2
设备
3
管道
4. Soc keu 使用同 - 个1/0 镰口
图 1 1.
Z
一 个後口和不同的数黯源
( 1. 2) 磁盘 /设备士件 用叩 '0 命令连撞 . 用 read 和 w n le 传递数据 .
•
(3) 曹道
用 p l pe 命令创建 , 用 fo.r k 其事,用 read 和 wrlle 传递盘据 .
第门掌迄候到近蝙载运端的选程服务嚣与Soc k et(套後字 〉
327 •
• (4) Sockets 用 soc k et , listen 和 connect 连接,用 read 和 wnte 传递数据.
bc: Unix 中使用的计算器
11. 3
几乎每个版本的 Unix 都包啻 b,计算器 , 尽管这些计算器的版本有些盖异. b,计算器
中包含变量、循环和函数的功能,井如在第 1 章中所青到的那样主恃时1':整数的处理 . $ b ez4
茹扭
mm 曰 "且由
mm 盹
27292 23 <
2 240 228 095 225 262 393 196 2 92 207 65 33 35 565 976 359 091 503 76 75 73 747 ·8 21 29 62 66 59 31 37 59 07 39 93 82 36 33 70 \\ 47 03 21 28 38 73 19 17 7 62 7 6 肝 m
Mm
每行末尾的反制线罪示赞宇行的继续 .
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,是执行计算操作的
Unix/Li nUlc 编程实践敬程
• 328 •
这两个部分是根本上姐 立的程序 . 可且使用不同版本的 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创〈毒援字〉
F lLE.. fp;
fp"
/ .
S !IJIIe
• 333 •
type of struct 旷
/ .. arqs are pr吨 'UM圃, connection type */
pope:r汉 "18" , "宫" )
fgets(buf , len.f肘,
μexactly the 且me functions 旷
pclose( fp);
/ * close when
done 时
困 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 .呻Z 国 t
how t .D open 画
P'呵 ram
..
1. popen() returns a
..
2. the FlLE
•
s ta:回!lrd 1 1。
FILE 俘,
..此 returns
just like fopen O
can be
read/盯比t~
r
with all the standal: d f unc: tions
.. ./
3. you need
** include
< stdio. h>
将 include
< stdl 汕 h>
lnt
for
points ,
回国e pclose'口 when
done
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;
pc: lose( fp ) ; return 0;
; *pdnt 由回. /
1*
IMPOR'I'ANT! 时
. /
第 11 章
• 335 •
连接到近蟠或远峭的透湿 g 服务稽与Soc ket( 套援字〉
句 '" fdo四川P( O] . "r")
dup ( p[ 汀, 0
return fp
close{ p[l ]) exec( "/ bin/sh" ,"曲 ",
因 1 1.
6
" -c " , C回 mn且 ,
从 shell 命令中该取
下面的程序 popen.c 是上述班程的一个实现 / -阳 pen. c
a 飞ler!JÍon
-
FILE
_
揭阳pen (
of the Unix
,~叫 18
-
a
-
…
..
execls"sh"
"时e
todo
时回 t
r呵"lar
1 ibrary func tion
ct町.
...ode )
shell command
i9 "r" or "w" a
..
po胆n()
c har .. c Oll1laaIld,
str四巾 hod t。由 c叫, 0< 阻
about signal
" -c " c 。但..00 har划ling
for child process?
./ 将 include
<:std io .h:>
将皿clude
<s igllðL h>
4牢 def
i. ne RF.A Ð
1* define FlLE
0
ilRlπ
费 pop创刊 const
il> t
Fl LE
int
char ..
c。但mand ,
m圆的
pfp[2] , pid;
/*
the pÎpe 由、d the process
.. fdopen <). .. tp
/*
fdopen 皿.k:es
parent_er划
child_ e:r划,
p<<四飞回币d , h孟 ld_end
1 else if (
fiqure out direction
'" READ '" ilRI 'I'E :
幡凰锐je..'w' )l
parent_end
chlld else 国turn
酣m
eJ田d"READ;
NULL ;
i f ( pipe(pfp ) ",. - 1 ) return
I阻,
j "getap孟..旷
*/
a fd a stream * j
/ * of pipe */
1'"'
i f ( ..lIIOde "'" ' r ' ){
)
const char ..
*/
第 11 意
连拨到近峭或运峭的进程 g 服务据与Soc ke t ( 套磁字》
• 337 •
· 方法 2 ,从画撞在取数据
可以通过调用函撞来得到数据 .一 个库函数用标准的画数撞口辈封装数据的格式和
位置 . U nix 提供了读取 utmp 文件的函数撞口 . getu tent 的帮助信息描述了自取 utmp 数 据库函数的细节 . 这样的话,就算底层的存储结构变化了,使用这个接口的程序仍能正常 工作. 使用基于应用程序接口 (AP I)的倩血腥务也井不 一 定是最好的方法.有两种方法可 以使用库函数 . 一个 程序可以使用静牵连撞来包吉实际的函数代码 . 但是这些函量有可
能包吉的井不是正确的主件名或文件格式 . 另 - 方面, 一 个程序可以调用共享库中的面 世但是这些共享库也井不是辈辈在所有的革统上,或者其版本井不是程序所要使用的
版本 .
· 方法 3 ,从进程获取数据 第 三 种方法是从进程中璋取数据 . bc/ dc 和 popen 例子显示丁如何创建一个进程到另 外 一 个进程的连接 . 一个要得到用户列表的程序可以使用 popen 来建立与 who 程序的连 接 . 囱 wh o 命令来负责使用正确的文件名和文件格式以且正确的库函数,而不是你的程序 .
调用独立的程序获得数据还有其他的好处 . 服务器程序可以使用任何程序设计语言编
写
shell 脚本、 C ,J ava 或是 Perl 都可以 . 以独 立程序的方式实现革统服务的最大好处是在
户描程序和服务器端程序可山运行在不同的机器上 . 所有要做的只是和不同机器上的 一 个 进程相连接 .
11. 5
socket :与远端进程相连
管迫使得进程向其他进程盎量数据就悻向文件呈送数据一样事昂,但是管道具有两 个重大的融陷 . 骨道在 一 个进程中槛创建,通过 for k 来实现共享 . 因此 , 管理只能连接 相
关的进程 . 也只能连接同 一台 主凯上的进程 . Unix 提供了另外 一 种进程间的通信机制 四 c k et .
soc k et 允许 在不相关的进程间创建类似管道的连接,甚至可以通过 soc ket 连接其他主 机上的进程 〈如图 1 1. 7 所示 h 本节将学习 socket 的基础知识 , 理解如何用 socket 连接不同 主机上的客户端和服务器端 . 其思想就跟打电话查询当地时间一样简单 .
进程
进但
阳,,,
socket
图 1 1.
1
连撞到远峭的进程
Uni x./ I.inu x.编程实践数程
338 •
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 communicat ion) 0 下 一 眈如果需要在草里去裴电话线,可 ~:,( r闪电话局巾情辈革 一 个通信揣占. 为电话线申请号码
在户端需要通过呼叫 一个电话号码连接到体的通信端点 . 电话同蜡 U 电话号码来区分
每个墙上的 J'í座 e 从这个类比本身考店 , 由姐的5 是在 -个 大的商务罩在 中 ,除了时间服务以 外压需提供其他的服务 . 因此 , 每个插座要以电话号码附加1一个分机号码来标识 . 例如你的号码可能是 617→ 999- 1234 ,分凯号码是 8080 电 话号码标识了悻的办公室 所在的大峻的号码,扩展号码 8080 标识了撞楼叶'每个具体的电k'i . 一个号码来标识大幢 个分 m 号码米标识你的服务 , 这是→个电耍的机制 . · 处理接人也诏
你可能使用的是克来也显示的电话 . 体的服务不需要上述费型的电话服务 . 但必须通
• 350 •
Unix/ Li nux 编程实践被程
写入的数据特从该文件描述符读取 . · 步罪 3 和 4
传递数据和1 挂断
在成功连 接 之后,进程可山从班主件描述符自写数据 , 就惶与普通的文 件 或管道相连接 样 . 在时间服务的事户 / 服务器例子中. t imeclnt 只是从服务器读取一行数据 .
由取时间之后 , 在户端关闭文 件 描述符站后退出 . 若喜户端且出而不关闭描述符,内接 将完成关闭文件描述件的任务 , 测 试 limecln t. c
1 1. 5 . 7
大革已经有好几页世有霄J)J 插图了 . 大擅已经忘记这些 代 码的任务了吧 . 阁 1 1. 9 将
会提醒我们 . 服务器进程运行在
自 肌 器上 , 而喜户揣程序在另一台机器 上 通过网络与
服务器连撞 . 服#器通过 wnte 调用辈茸茸数据,在户端通过",,,d 调用辈接收泪且 .
'"户
因 1 1.
9
位于不同机锦上的进程
对上面这段代码真实的测试需要在平同 机 器上运行这两个程序 . 我不太确定下面写在 书上的测试情况是否足够明确,不过大事可以作为盎考 z $ hon . -
将位查当前机器
computerl . mys i te.net
样第 一 台机器
$
~ t 皿.\I~ .C
样建 立 殴务器
$
. / tJ....erY‘
[1 ]
-o t.皿 B 町
将并屋行它
1 们"
$ $ acp t.u..clD t . c bruc: e@ ec. put..-2
,
林 2发送客户代码 1日j 另
台机部
bruce@compu t. er2 ' s p!lSS嗣" t u.ecln t.. c
I
l llB
I
1. 8阻( ,
IETA, 00 , 00 , 00 1 1ω ‘
$ sah [email protected]. er2 bruce@c帽 puter2' s
No
password I
_il
00叩uter2 ,
bruces cc t. illeclnt. ç
-
0 t.UeclD t
00凰.puter2 , hr皿 e S.!t圃tclnt. α_puted
The
ti盹 here
c。但puter2:
13000 Wow! Go t a cal l !
18 .. T回归g 14 02 ,44:31 2001
h nx:e$
第 J J 素造簇到近瑞或远峭的选程服务指与Soc ket ( 套徨字 )
• 351
服主于器编译好后 , 运行在凯器 l 上 a 然后把客户端程序 E 制到机器 2 上再壁最到机器 2 . 在机器 2 上,编译好事户瑞后 ,然后让事户端请求与运行在凯器 1 上监听在 1 3 000 揣口
的服务器相连接 . 这里墙到的消且就是从机器 1 上的服务器通过罔蝠盎送给机器 2 上的事 户的 . 而在户再把悄且主选至标准输出 .
从上面的拥试结果是否能茸的看到机器 2 上的输出 ' 我是从机器 l 连接到机器 2 的,所
以显示桐且的路端实际上是直接到机器! 上 的 . 后面的→些练习合让你仔细考血其运作 原理 .
通过 timese rv / timeclnt 程序,可以得到另 - 台机器上的时间 . 检查另 一 台机器上的时
间可以保证不同机器的时钟同步 . 网络 t 的某俞机器可以作为战威时间服务器,而其他的 机器可以利用上面的程序周期性地来调整自己的时钟 .
11. S. 8
另-种服务器远程的 1 ,
下 个项目是编写一个可以打印远端凯器上文件列翠的程序 · 件可能在两台机器上拥 有账号 . 若想着另 一 台机器上的文件 J'J 击时如何去世呢 ? 可以暨最到另 一 台机器上,然后 运行 1 , . 一个快璋的且更加方便的盘径是运行一个远程的 1 ,程序 · 这里称它为r! s ( remo te 1 ,λ 可以指定主机名和目录, $
rls
c帽p!.I ter2.
site. net
jbol酶;_jcode
当然. rls 需要在另一台饥器上的一个服务进程接收请求,仕理请求和返回结果 . 该系统 看上去盎似于图 1 1. 1 0. 服务器运行在 台机器上客户运行在另 一 台机器上井与服务器连
接,把目量的名字盎选结服务器 . 服#器将位于该目录下的文件列者信息温回给客户端 . 喜户端将结果道主际准蜻出把它们显示出来 ‘ 两进程系统提供了对于不同机器上的目最访 问的支持 . 客户
/I!务 III
网络 图门
l
设计远程 1 ,系统
这里需要 3 个要素 来实现r! s 系统 (J)胁拙 (2)窑户端程序 (3) 服务器端程序
1 。一 l'远峭的 J ,系统
第 11 拿
连接到后编或远稽的选程 t 服务樨与Socket ( 套後字 〉
if ( write {s∞k_ ìd , av[巧 , strlen(av[2J)) 三左 。ops (
回'ps ( "盯 ite" )
if
川
"write " ) ;
i t ( write(sock_ i d , "\"" .υ
while (
• 353 •
-
I I
:
(n_ read 鑫 read{sock_ id , buffer ,应JFSIZ川 > 0)
buffer , n_ read) .... - 1 )
( 町 i te口,
∞ps { "wr ite " ) ;
close ( sock_id )
世章该在户端与时间服务喜户端的不同之处 . 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 / .. r lsd. c - a reaote 1s 5crv", r - with parano ia 时 将inc:lu血
< stdio.
样 include
< unistd.h>
林立 nclude
<町lI/ typeS .
捋忌地l回e
< sysj socket. h>
梓inc: lude
h>
h>
h>
第 lZ 拿连援和协议编 写 W ,b 服务器
• 36 1 •
客户
客户
二二二 :2; 1
连拢到圆务媲 获取服务
1.1晰 i茎扫毒 因 1 2,
1
客户 /服务黯交互巾的主要步骤
下面将分别讨论每个操作 .
12.3
操作 1 和操作 2 :建立连接
基于班的军统需要建立连接 . 这里特回回一下建立连接的步罪 , 陆后将这些步骤抽血
且 一些 库函数 . 操作 I
12.3. 1
建立服务器端 socket
首先,如图 12. 2 所示 ,描述了服务器设 立 一个服务的过程 . 设 立 一个服务一般需要如下 3 个步骤:
(!)创 建 一个 sock et
socket
socket (PF_I NET ,
SOCK_STREAM ,的
(2) 给 soc ket 摒定 一 个地址
b i nd(sock , &addr , sizeof(addr)) (3) 监昕 接入请求
listen(sock ,
queue_si~e)
步骤 1
图 1 2. 2
创埠服务器瑞 socket
362 •
UlllldLin~x 编程实践放程
为了避免在编写服务器时lIi:!!输入上述代码将过 3 个$黯组合成一个函数
make
server_soc kel . 该函散的代码位于本章后丽将给出的 socklil、 c 文件巾.在描写服务器的时
候只要嗣用在函数就可 U 创埠 sock" nake_server_socket(int
个服务器喘 soc ket . 具体如下 portn四〉
re turn - 1 if error , 。,r
a server socket listening at port
操作 2
12.3. 2
"portn四"
建 立到服蛊器的连接
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 connect_to• s erver( hostna圈, portnum)
fd
return -
1 江'"饵,
。 r a fd open for readi呵 ~d 盯立ting connec ted to th陪 socl< et 此 port "portn四" on host Hhostname
!Ji1l 2 岱',jj!并 主帽务揭
因 12.3
12. 3. 3 /.
连撞到服务器
socklib. c
soc业lih.c
' • ThÜ! file contllins functions US tlÚ
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 章
i f( fd "',.
连 篷 和协议 l 编写 W.b 服务锦
• 365 •
四川
回且 t {l)
; . or
;
talk_with_server(fd) ; close(fd) ;
d且.. /
/ _ c hat
..ith 四凹er
*/
/ . ho呵 up when done 旷
函数 l a l k_ wit h _ se rve r 处理与服务稽的生话 . 具体的内窑取决于特定应用 . 例如 ,
e- m a il 客户和邮件服务器吏模的是邮件,而天气预报事户租服务器吏模的则是天气 . 2
一傲的服务嚣棉
一个 典 型 的服务器如下 : 皿 1nO
int soclc , fd;
sock .,
内,民kot 缸田d
connection . /
make_se凹 er_ socket(por时 ,
if(s ock. ""
-1)
H迅 t (l ) .h立le (1)
fd • i!tCCept < sock. N1lLL, NULL) ; if( fd .", - 1)
/ _ t&k:e 睡xt 四 11 . j
/ . or dic . j
break;
proceS$ r明uest( fd) ; cl Ol:< e( fω ,
/. chat ..ith cli ent 旷 / _ ha咱 up when done ,, /
函数 proces s_ re que s t 处理事户的请求 . 具体的内窑取决于特定应用 . 例如 , 邮 件 服务 器告诉窑户信件信 息, 天气服务器则告诉窑户天气情况 .
12.4. 1
使用 soc klib . c 的 tim国e rvj tim eclnt
如何利用上面的模桓来建 立 在户 / 服务器系统呢?例如,在西框架下本主的时间罩统事 户 / 服务器是怎 样 的呢 ' 因 1 2 . 4 对此做丁解黯 . 为了使用 soc k lib. c 重写时间客户和服务 器 , 这里描写了扯理会话的函盘 talk with serve r 用于在户端 , 而 p rocess_ fcquc s t 用于服务
图 1 2. 4
时伺服务嚣和客户编版本 1
• 366
Unix/ Li nux
b副理实战披程
器瑞 . talk_ "此 h_server(fd)
proces8_request(td}
C 尊 r buf(皿NJ
U臼e_t
no ,,;
int n;
char
n. read( fd , buf ,LEIf)
cp t ctime( &now) ;
IIr ite口 .buf. 时,
wr ite ( 阳,叩, strl国 (0'川,
* cp
time ( &now) ;
服务器调用 time .h人 内属中我得时间, 棉后用 ctlme 将时间 转换成可以打印的字符串 .
服务器将该字符臣和写到 socket 中,盎选结害户端的 socket . 事尸J:A socket tþ 由取班宇符亭, 然后写到标准输出巾 . 革个新的版本遵循丁先前版本的程序逻辑 , 但是世计更加模块化,代 码更加清崎 .
12.4.2
第 2 版的服量器使用 fork
现在考虑第 二 版服务器的设计 . 第二眶中程序由有通过调用 tlme 函数来!x得代表时间 的散据 , 而是1[接使用了一个 shell 命令 (date 命令) .如图 1 2.5 所示 .
图 12 . 5
跟务费使用 fotk 运行 d8te
代码如下 proceS !I _request ( fd)
;*
send the date out to the client via fd
-; i nt pid '" fork( ) H比 ch (pid)
case - 1 , return 国 se O , dup2( 阳,叶,
c! ose (t d) ;
1* can not provide
f . child runs date */ ,例 bp回irec::: ting st由此- /
四ocl( 町/ bm/由国","由国 " , NULL) 国ps (
"execlp " ) ;
service 旷
/倍。 r qults 旷
• 369 •
第 12 拿连徒和协议编写 W, b 服务帮
w a itpid 提供了 wa l1函盘姐挠的功植 . 其第 一 个盎散表示它所要等待的进程 l 口 号 . 值 - 1 表示等待所有的于进槌 . 第 二 个参数是指 1 0 J 整型的的指针用来在取状在 . 服务器井不
关心子进程中;业生了 1 1 么不过一个他壮的服务器可能用国信且来阻踪柑误 . w ai tpid 的监后 一 介参量表示选项 . W NO H ANG 盎敢告诉 waitpid: 如果没有僵尸进 程则不必等恃 .
西循耳直到所有 i且由的子il!程都酷等待了才停止.即使多个于进程同时 ill 出井产生了 多个 S I GCHLD. 所有的这些伯号郁金融处理 .
12.5
编 写 Web 服务器
至此 . 已经学巧了编写 W eh 服务嚣的必 备 知识 . W eb 服务器是已经描写的目录服务器
的扩展 .主 要的扩展是一个 " 1 服务器和
12.5. I
个 exec 服务器 .
W e b 服务器功能
W e b 服务器通常要具备 3 种用户操作 (1)列举 目录 信息 . (2) cat 文 件 .
(3) 运行程序.
Web
呻陪伴装
l23
图 12.6
配段文臼 叶行 示→不
IliBí R!J
时运显且
、战eb
"自
wcb 浏览也
III 务器握供远程 1~ , CIl1凹"
W eb 服务器通过基于丽的 soc ket 连接为客户 提供 上 埠 3 种操 作.如图 12 . 6 所示,用户 连撞到l 服务器后,监造请求,棋后服务器逅回事户请求的信息 . 具体过程如下: 在严咄
服务器啕 :
用户选挥 一 个链幢 庄撞服务器
写晴草
• •
提收请求
i韭取晴束 处理请求: 目录显示目录列表
文件显示内窑
cgi :t 件 屋行 不存在错误消息 读取应答
可应窑
第 13 1l
13.3
.Mi子数缸很 ( Dalagram ) 的编程=编写诈可证服务嚣 ③
383 •
一个非计算机系统实例:轿车管理系统
一 个公司购买了许可证 . 该许可证限制了同时使用程序的用户数 . 公司可能拥有比许 可 iÆ 所允许的用户提辜的应且 . f.ê.:hl:并非所 布 的辰1 且要 [nj 时使用惶序 . 怎样的一个世 ì l 才
能满足上面的市草呢 9 现实世界中有很辈革统 . 通常山一大群人来Jt享其中的资源.而这些自班是有限的 . 这
里将分析 一 个模型府且共 '1 公司轿车的问题 . 一 个公司拥有特定数量的轿车 , 同时还拥有 更事盘量的~i!.m . 如何控制对辅车的使用呢。
13.3. 1
轿车钥匙管理描述
控制轿车的使用是通过控制对轿车钥匙的访问 . 当扭用轿车的时候 , 必须先仰到一把 钥匙 . 如果在钥匙盘中世有钥匙了 , 将不能使用轿车 . 如果有可用钥也 . 可以先拿一把钥 耻 . 签名,鼎后使用轿车 . 使用完毕后,把钥匙放回钥匙盒 , 在使用轿车名单叶'如l 去自己的名 字 . 过程描述如图 13. 2 所示 . 司机
钥匙管理员钥匙盒
定数量的军
。
&咀 Y恕γ
年 0华
因 13 . 2
-
‘国·幅画· 控制对轿享的的使用
签名罗'1 茬有何作用呢?该牵统的目的是通过控制对轿车的使用 , 值得可用的钥匙一直
植克足 . 人并非是完莹的·有时司机会在记归正钥匙 . 钥匙管理且可以根据鉴名列表!I<到 在司机,确定他是否仍在使用车 .
轿车使用管理果统是控制软件使用的 -1 现实模型 。 在将直系统转性成软件革班之 前 . 需要更为昨细 地描述班系统 . 钥匙管组系统的构成如下 (J)钥匙管理 中 . L、的地点 (2) 钥匙甘理员
(3)胡匙
执行策略的人
需要得到的东西
(4)盎名列表 13.3.2
到哪儿可以在得钥匙
保存钥匙和找回钥匙的记录
用客户 /服务器方式管理轿草
在结出轿车钥匙骨理系班的构成后,下面将用事户 /服务器语百来描述该系统 . (l)1!U务器和1 在户
谁是服务器和l 谁盐在户 。 钥监管理 M 拥有司机需要的朝匙 . 用网络术语来描述,钥匙
绪~ ! 3 肃
A庄子数据报 ( D"tagram) 的编程:编写作可证服务骨,ε
• 385 •
务
器
证
阻刷
"何
榄
可 件
V+阳伊』守
时 也可伽可
胡也1<
socket 阁! 3.
3
控制软件的使用
件可证服务革统与轿车钥匙服务革统睛有不同 . 在轿草草统 巾. 司机向钥匙管理员请
求昨日J
而在过啦 , 程序 lÎiJ 脱#器请求内可 . 这就悔司机谓'"轿萃 , 而轿车向钥匙管理员请
求钥匙 ① .
E 序的创建者要编写所有的程序应用且序相服务器 . 这两个程序作为一个军统 . 服 务器结于应用程序运行许可和实施许可 iIE 条:在 . 如果许可证服务器不在运行 , 应用程序将 畔不到 i年可不能运行 .
这里以轿车钥匙服务系统作为懊型进行丁讨论 . 那么如何将这 种 思扭运用到软件系统 中呢 9 骸内可理 11<" 何从服务器呻到许可?服务器如何措予许百l'软件系统和轿车钥眶系 盟的等价之处在哪里呢 9
13.~.
2
许可证服务军统。如何工作
(!)J;l据慎型
钥匙管理员负责分'Ji.钥匙 , 许可证服务器监布什么呢? J:l 电配院和梅球场为例,付钱获 得进场许可 。 如后得到一张人场票据 . 与此提阻 . 这里的件可证服务器监布的~数字票据 . 这件票据丑是 1j 么样于的呢 9 窑户和I 服务器吏换字符串 , 所以票据是字恃净 , 其格式如下 pid.
ticketno.æber 例如:
6589.3
每张票据包括恃有该票据进腥的 PID 以且票据蝙号 . 在票据巾包吉 P ID 的原因其实就 和在飞帆架上印上名字的进理是 一 样的 . 照据中的 P ID 标识黑掘的使用者,在票据丢失时 可 WII 助找回票据 . 进程可能丢失票据吗? (2) 服务器和在户
谁是在户和i1I址服务器?峙可证服务器持有程序需要的班源票据 . 用网络术语.许可 证服务器是服务器而且用程叩是在户 . (3 ) t;/> ìJ1
胁议是iI么 9 吏互的事务是什么?迫里的事据管理 胁 世包括 F 丽的两个主要事务 ①
这个慢念并不难理'阿
帽'附注备越来越先进 , 连徨锺来锺方便很可能在不久例将来就可以通过你的收音机询问
殴务"是 2/41 娼妓你勤部"歌跳的作町.
第 13 章
基 于数据报
• 387
班 socket 使用的网络协议叫 TCP 即传输控制胁~ ( Transmi ssio n Contro! Protoco l). 盘据报 socket 叫 UDP 即用户数据报协出 (User Da tagram Prot oco [) . 它们的区别是什么 呢? 何时选择何种 s ocket 是较好的呢 ? 在程序中如何使用数据报 socket 呢? 13.5.1
流与数据报的比较
socket 是如何工作的 ' 数据如何在 Internet 上传输的 9 当用户向班 回 c ket 写数据时内 核要做些什么?这与向数据报 socket 写盘据有何区别?
从一个配 socket 传输到另一个班 socket 的盘据班,看上去是连续的、王缝的,实际上这 是→种错觉 . Internet 连横要把盘据分割成植立的敏偌包 . 网络盘据传输过程如图 1 3.
<1
所示 .
图 l J .4
在现实世界中,有很事特
ln te rn ct 包传戴数倍
大块数据分割成若干较小的数据挠的例子 . 假设事通过惧
递传送 1 00 页的文档 . 当快递公司要草你使用只能靠 20 页的借封时·班怎么办?可以把土 档卦割成 5 个包囊,每个正在烛地打包和附上地址 . 照后把这 5 个强 立 的包襄腊到邮箱中 , 运 - 输 E 统将负责把它们送到目的地 . 在接收端·接圭者打开这 5 个包囊,并把它们按顺序重组 成原来的主档 .
Inlernet 就像上述运输系统一样 . 1;上面传输的盘据必须符合大小的限制 . 大块的数据
世分割成4 块的数据来传输,接收端必须以正确的顺序重组数据块 . 但通信过程可以世连 接和中断,如图 13.5 所示 .
.... .,
",. ," ‘.... .0> ",~..
因 1 3. 5
•
通馆可以披连接和中断
流 socket 负责分割排序、重组的所有工作 . 数据报 socket 则不会 . 下表给出了它们之 间的区别 .
第 13 拿
基于曼史据报(Ða lllgram) 的编程 E 描写许可证'民务帮@
• 389 •
- 合主机可能有多个雄收5O<: ket . 每个5O<:ket 撞衔'段给
个特定的精口号 . ."止用主机上的喝口来区分 . 因 13. 7
1
使用 ~ndlO 初 r..,vfrnm
接收数据报
程序 dgrecv . c 是一个简单的 基于数据报的眼身器 . dgrec v. c 使 用命令行传过来的端口 号建立 socke t ,然后进入循环,接收和打印从事户端盘来的数据报 /………·譬.."...... ..铮 铮~......~....~.*...... .........*曾饵. .相.. ·啕E缸'v .c - 由 tagr咀 recelVer
啕军民 'P。此n四
uSðge 揭
action ,
listens at the specfied port and repo z: ts lIl esBages
.j #旦'lC lude
< atdio. h>
** include ** :inc: lude
< sys / types. h>
再 include
< sys/ socltet. h>
#虹x::lude
< stdlib. h>
#- define ∞.ps( 圃,对 int
( perror( lI ) 回此〈剖,
lIIM::e 啕rð.ll se凹" ,民ket(
I
int);
int get_ internet_aódress(char
链,
int ,
int 臂
struct sockaddr_in 怪) •
void say_who_called(struct sockaddr_in .. ) '"'咀皿(int
ac , char .. 叫 l>
in t
port;
/锦 use this 归此时
int
s民k
/ * for this
char
buf [即SIZ] ;
/ * to
…'"
/ * store its length here .. j
旦回 t
.$gl四
struct s础len_t
if ( ac
~
"曲,
a 1
11
(po r: t
i t.!I
ato孟 (av[ l ] ) )< -
fprintf(stden , "\lsage ,啕Te<:v 自比 (1)
/ * put sender' s add ress 归缸xl
"addr len;
s出k" 旷
receive 由ta here 旷
le吨th
O){
port.'l.咀ber\n")
;
he四旷
here */
.. .. .. ..倒昏.
.器"…
第 13 章
提于激据报 (Datagram ) 的编程编写诗可证版务帮③
• 4 07 •
SERVER ,GOT: GBYE 25915. 2( 10.200. 7~. 200 ,1055 ) SERVER , SAID
Se e ya! (10. 200. 75. 200 , 1055)
τ1iNX
CU四T[2591S ] , rele臼ed
t:cket Ol'i
实际运行的程序可能有非撑不同的结果 .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 i cket
recla皿
• 90 through a11 t i clc: ets and reclaim 0随, bd。可口哩 tod田d processes
.. Results , none
./ voi d t icket_recla illlO
Uni x/ Linux 编程实践敏程
• 410 •
通过上丽的悻础 , 叶可证服 JHi 就 "f VJ 周期性地检查原据了 。 确实市 ~i在样周期性地 险盒吗?为付么不只在~酷刑 二丧描 f 并且有在户的哨求世拒绝的时候检查呢?这样会更
好吗 '
13. 7. 2
处理服务器崩溃
服务器 lûi 攒通常有两个严重的后果 . 白先.辈出罗 '1 表丢失.失去迸程持有票据的记最 . 其在 . 新客户不可 U 再后行.因为分左许可证的程序已经不存在 . Jil简单的解决方法是重新 启动服务器.如国 13. 1 0 所示.
1Ii l年严'可 以得到l一
a野启动萨厦
张票据
务器篮 III 列及为空
图 13 .
10
服务费在崩攒后重启
重启服务器使得前的客户可以运行 , 但会带来两个新的问题 .
首先 重 M 服 务器的J;.!据敛组是 莹的 .服务器吉有新的来桩取走的黑据列表 . 崩酣的 n~ 卦嚣的票据数细可能已 经 满了而重启服务器仍给其他在户盎道件可
1主样草草地关闭服
务器再重自服务器就悻印钞机一样可以产生更多的事据 .
Jl:次,恃有旧的服务器的栗世的事 户在 归正t1据时,将会被认为是伪造事据 .
]
单据验证
上述问题的
个解陡方法是票据验证 . 票据验证表示每个在户周期性地向服务器盎选
票据的副本 . 客户监逝世时包,对服务器且
这是我的事据 . .&\合法的吗。",如图 13. 11
所示 .
图 13.
11
客 户 验证禀倡
票据含有盘组描号和 PID . 服务器检查盛出如j 毒 . 如果jJ<项为主 , 服好器可以认为撞
事据是自己先前的主例赋于的 . 服务器将会把草原据加到列表中 . 逐步地,在户提供票据 来验证辈出夕 '1 牵酷重新填人 .
服务器置建甚出页。表解决 丁在 丢失问题 , 但可能导致其他问题 . 如果 一 个新的事户在 表重建野之前,请求票掘,服务器可能分盎→个已经结予其他在户的票据结该在户 . 当持有 旧的事 据的事户时其罪掘进行验证时,服虫子器会拒绝它 .
第 13 意
另
基于敛据报(D,剖 agram ) 的编徨编写许可证服务樨 @
• 411 •
种方桂是服务器拒绝袋中世有的所有的票据 . 恃有被拒琪掘的客户尝试再申请新
的 . 这种方桂是否 E 好 ' z
协议中增加股证
票据验证是协议中的 一 个新的事务 U酣 VALD
t i ckid
SERVER ,GOOD or FAIL i nvalid ticket 这里必须改变客户和服务器以支持验证 . 3
在户端增加验证
客户瑞增加验证 , 需要编写
个函数井在主函数中词用它 ,其配程如图 1 3 . 12 所示 .
clm ,~
CJ:…
… 一 _
TI
--
GB Y! 'ickid ____
…一 一 叩
副 1 3.
12
客户定期验证票据
在户可以根据系统的 需要以 一 定的间隔足期验证票据,可以匪置一个计时器辈定期验 证 . 如果在户是 一 个电子制表程序,嘘证可以在一定量的计算结束后进行 .一个 许可证服 #器舍响应验证请求 .
SuperSleep 客户程序可以把 1 0 静的睡眠时间分割成两个 5 耻,在这期间进行验证 . 这 作为课后蝶习4
服务器揭槽加#.-据验证
服务器端增却l 验证 需要做两处改动.i!!c功后的程序位于新的文 件 l serv funcs2 . c 中 . 首先 , 增加 一 个函数来验证票据 /…………·晏…"………'"医必钝………... ........刽..................键
.. do
vali由国
.. Val idateclient 's ticket " I N mS9_P message received from client .. Results , ptr to response ..σTE ,
return is in static buffer
overwritt田 hy
each ca11
./ stat ic:
char 指由,_ vali date (char
intp主d ,
slot;
.. 1118g)
/"恤归 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. s e t icket 01': . /1c l nt2
[2] Do ne [ 4]- Do ne $ P' PIO
/lclnt 2
TrY
Tl l!E
。吃D
00 , 00 , 00
bas h
23509
pts/ 3
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据的表中肯有 PIO 和离 据捕号 . 在图 1 3 . 1 3 的情况巾 , 许可证服务将认为给同 个进程分配了 3 张票 . 每个进程只要一 张票就可以运行 , 所以这是错误的 . 请求 E辜的票 据 , 可山酷认为是喜 尸 端的→个漏洞 .
这里可以扩展票据表项的棉 式和内容 , 使其包吉标识运行程序的主凯从而解决lIUl
P IO
问题 . ·
问题 2 . 回收票据
服务器通过调用 kil l( pid .的 命令 向客户 回 收票据 . kill ( pid. 的监量信号 o 给持 有 票据 的进程 . 通过幢订过的事据茬 , 服务器现在可以知道事户 运行在哪台主机上 . 但是 , 服务器不能给其他机器上的进程盘盘信号.如图 1 3 . 14 所示 . 如果许可证服务器 想蜡主机 3 上的进程韭送信号 . 1IIi身器必 须产生主机 3 上的请求 .
纳 13 意
鉴于数据报 ( Dalllgrllm) 的编程到自写诗可证服务稽 @
窗口 M
• 4 15 •
逃程不能给其他主机发送信号
为什么不在每白机器上都运行一个服务嚣的实例 9 如因 13. 15 所示 , 每个本地的服务 器可以盹控吾失的I:l据 . 许回I 证服务器
回 13. 15
件可证帽务 帮
件可证幅画务器
运行 本地直制的 IserT
本地服务器解决了向主但 l :!t量信号的问题,但是卫带来新的问题 :哪个服务器 盎布票 据?主服务器如何和本地服务器通信 。 在户把tI据盎结谁验证 9 · 问题 3
主机崩酣
如果其中的 一 台机器停止运行 ,将 会 E 生 什么 ?主 服务器如果还在运行的话,如何收回
事据 9 喜户端程序如何验证票据 φ 如果运行主服务嚣的主机停止运行 .谁来分盎票据。 如何建立
个分布式许可证罩统来同时支持多台机器?这 里有 3 种方桂.试考虑它们
的设计细节以及民融占 , 并考虑当事户、服务器、计算机革者罔描崩酣时每个方案的后躲 .
•
1J 挂]
客户瑞服务器和中央服务器通信
每台机器都有 一 个本地服务嚣,就像本主描写的那样 .每个 客户且本地的服务器通信 . 本地服务器把请求转主措中央服务姆 . 中央服务器远回票lI!或者拒绝 . 本地服#器记 最井 把应答转盎蜻客户 . 本地服务器也可以强制执行 -些 时本地在户的限制 , 例如该舰器上 可 U 屋行的程序实倒数,或者程序可以运行的时刻 . · 方哇 2
每个事户都和 中央服务 器通信
在户直接给特定主 凯上的服务器皮革请求 .本 地耻务器运行在每个主舰上,但这 些 服 封器不初在户迦倍,它们在重新声明栗酷的时佳作为 中央』国务器的代理 . · 方击 3
客户服务器和在户服务器通信
每台机器 都有本地服务器 , 每个在户眼本地服务器通信 . 没有中央服务器 . 所有的本 地服务器问 互相通俗 . 每 IX 个在户请求时,本地服务器而J 问其他所有的服务器目前已经 用掉了多少张票 . 如果所用票据盘小于所允叶的屈、盘,本地服务器分配一张票据给喜户 .
• 425
第 14 章钱程机制 z 并发函敏的使用
上面的每 一 行输出之间都有 一 个 l 静钟的时廷,程序共运行了 1 0 砂 . 因 14. 1 展示了此 程序的执行配程 .
一↑进侄 陶个 函1!1 个线瞿
因 14.
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 拿钱程机制并发函数的使用
• 429 •
1nt counter :: 0;
main( ) ptlrr国 d_ t 口,
void int
/ * one
.. print_ count(void 叶 i;
thr国d 旷
/ * its function
*/
pthread_create(&tl , N1JLL, print_c。回 t ,肌江川, for ( i - 0
i <肌.1M; i+t ){
counter + t "四 p(l)
pthreacU。皿《口,
void .. 皿t
NULL);
print_count{void 幡回〉
i;
for ( i"O
H
i < 肌润; i++
printf("c。国生 ·
‘ d飞 n" , counte叶,
sleep(1)
return NULL;
程序Incpnnt. c 使用了两个线程 . 韧始辑程执行了一个循环来使计量器值每秒钟增 1 •
初始线程在进入循环之前,创建了 一个新 的线程 . 新的缉程运行了一个函数来将 counter 的 值打印出来 . ma m 画敬和 prmt_count 画数量行在同一个进程中,所以都有时于 counter 的 访问权 . 固 14. 3 展示了 两个函数和圭局变量的内在逻辑 .
四 ]4.3
两个线程共享全局变量
当 mam 画世改型l: T counter 值之后. prmt_coun ter 画触立 即可山访问到新的值 . 因此
并不需要通过营造或者毒接字等方法传造新的值 . 编译这个程序,然后运行宫,结果如下 z S cc incprint. c - 1院hread $ . / inçprint
0
incprint
430 • counl:
Uni x/ Linux 编程实践被程
,.
1
count .. 2 count • J count ., 4 count '" 5
程序显然可以正常工作 .一 个画散佳 ilr 了变量,另 - 个函敢自取并显示丁壁量的值 .
这个例于展示丁如何使运行在不同线程中的函数共草圭局变量.不过下个例于还要有趣 . 14.3.2
例 2: twordcoun t. 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 号;
inc: lude
辖立 ~1回e
< stdio. h> < pthr回d.h>
< ctype. h>
int total_ IIOrds Øla in(int
ac , char •
pthread_t
av口 }
tl ,口,
/ * two threads
时
第】"脏
p'皿tf ( " U8呵e
• 433 •
线程机制:并发函数的使用
‘ 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 ,
vo i d " count
tota l words飞 n" , total_"ords) ;
words ( 币。基 d
0 ,,",慢 f il ename
rr u:
11
..
" f)
(char 铃 )
t;
fp;
int c , prevc if ( ( fp ..
. . \0' ;
fopen ( filer田e , n r 胃 口 ! ..
"hi> e ( ( c " getc( fp) 川 =
if (
回.F
N1:血.)(
}{
isaln咀 (0) 且 isalnum ( prevc)
)l
ptkud--Utu-LEMKαmt 缸 1忧的,
total_words +令, pt trr回 d_~utex_ unl ock ( &counter_l ock ) ;
pr evc " c; f close( fp ) ; I el se perror( f il e啤a的, ret= 即毡,
此程序的逻辑类似于图 14. 6 所示 . - 个进程
个 计敏l!.!
细心的读者可以监现,在原来的程序中仅仅如I
7=-行代码 . 首先定且了一个 pthread _ mutex _ l 类 型的全局噩噩 counter lock. 然 后赋结它一 个初值 .
另外改动的地方就是把对于 count words 的操作亮 在对两个函数 pthread _ mutex _ lock 以及 pthread _ mutex unlock 的调用之间 .
现在西个辑程可以安全地共享计散器 7 . 当 个线理调用 pthread _ mutex _ loc k 的时 候 ,如果另 一 图 14. 6
两 个 线程使用互斥锁来
共享 ltlt 器
个线程已经将这个互斥量锁住了部这个线程只好阻 事等恃着这个锁植另
个线程解开后,才可以对汁数
Unix/ Linux 编程实践敏程
• 4 36 •
struct a咱I_set
* fp; c , p l'evc
* IlrqS
-
/ * oast arg
a;
back to correct 叮归 时
FILE int
' \0 ';
卫 f(( fp - fop审议""1'一 >f n.!lJle , "r"川 , . 阻lLL ){ Ifhile(
( c = getc( fp)) !,.
if ( ! 缸"gs
i且lnUØl (c) &&
E(到')(
isalnum(prevc) )
- > count ++
prevc - C; fclose( fp);
I
el se pecror(args -
> fnam时,
因 turn 阻,
这里通过定义一个 以文件名和该文件中字数为成员的结构体解决了同时传递两个垂靠 的问题 . maln 函融定且了两个这种类型的局部变量,井将这两个壶量的地址传给线程 (如图 14. 7 所示 L 传递本地结构体指针的方法既量免了时E 斥量的依赖,卫悄除了圭局变量 . 一个进程
注意这里,宁波有销
两个结蟹
细 14. 7
每个线覆都拥有 一 个指向自己结 构体的指针
每眈调用函数 count_ words 之后都会接收到 一个指向不同结构体的指针,因此线程从不 同的文件中醒目主信息,并时不同的计量武器进行增 l 操作 . 因为结构体是 mam 中的局部变量, 所以分配给各计数器的内存空间在 mam 画盘边回前一直保存着 . 14.3.3
线程内部的分工合作 · 小结
进程的数据空间包吉了所有属于它的壶量 . 此进程中运行的所有钱程都拥有对这些变 量访问的仅限 . 如果这些变量值不聋的话,线程可以主误地读取井使用它们的值 . 不过如果进程中的任何线程修改了一个 E 量缸,所有使用此变量的钱程必须呆用某种 策略来量免访问冲吏 . 在某一时刻,只有惟 的线程可以对变量进行访问 . 字数统计程序的 三 个不同版本显示了三种不同的方法来进行线程间的变量共草.在 tw o rdcount 1. c 中使用的第
身存在着很大问题 .
种方法,先许辑程主任何告作来蜡改同一个噩噩 . 这个程序本
第 1. 章线程机制并l<画敛的使用
• 439 •
一个文件要比第 二 个文件的时间茸的事 . 那么在第 二 个钱程完成之后,如何来通知原线 程呢?
一 个线程是如何与另一个线程互通消息的呢?在一个计数线程完成任务之后,它是如 何通知原线程它的结果已经产生了呢?对于进程来说 , 当于进程终止后,系统调用 wa Jt返 回 . 是不是对于线理的仕理也有类似的机制呢?某个钱程是否也可以等待其他线程完成? 事果是否定的 . 线程主法按照这种方佳工作 . 因为肘子钱程而吉井没有立线程、子线程的 砸在 , 因此井不存在某 一 个明显的线程可以去通知.
14. 5. 1
通知选举中心
某选区完成计事后,将其结果盎送给管理中心着
下下面的这个方击,此方法是用来从
选区得到选票 , 然后将其监送给管理中心 ( 也许营起来有些古怪 , 不过钱程确实就是用这种 方法来与另外的事件互通消且 λ
(1)在选举中心有 一 个世迪选举报告的邮箱 . 这个邮箱在某 一 时刻只能撞收
份票数
报告 . 此邮箱前有 一 商旗帜.它可阻桂升起来.但很慎就会撞恢直至原位 .
(2)选举中心等待这面旗帜升起来 . (3) 某选区负责人将选区统计结果脏入邮箱中 .
(4)某选区负责人将邮箱的旗帜升起〈盎造倩号) , (5)选区 中心看到脱朝升起来了,便执行下列步骤: . 从邮箱中取出击 E 的统计报告$
· 处理此统计报告+ · 回到原来的地方继续等待 , 即循环转至步事 (2) , 上面的黄略起韧看起来有些古怪,但确ll:很有童且 . 直选方将数据存入睿器中,然 后升起
面旗帜来通知接收方盘据已经准备好了 .
图 14.8 槽楚地展现了选举中心与两个选区之间的荣革 . 每 一 个选区将自己的报告放入 邮箱中,然后通知选举中心来取 . 最后选举中心处理了报告 . 在这个例子 中 升旗帜的挂本
术语叫做主选信号 , 即接收方在等待信号的到来 . 当然 , 这里只是个比咱,对旗帜的操作与 U n ix 里的信号量机制
点主革也世有,两者仅仅是基本思路相同而已 . 条件标记
报告 倩箱 筒箱锁
选举咿心 囱 14 .8
选区 1
选区 2
使用加锁的邮箱来传递数据
• 441
第 14 意线程凯倒并发函敏的使用
lf储戴帽的变量
豆 f辛糊
通知 ,也.hreaι"卧. . .' 民 ruct Il t !LlòQt
"mb l 。εk
两个接程
初始线夜 因 14.9
用加锁的变量机制来传递数据
中去 . 然后原线程草出信号 , 以防别的计曲线程正在哥待 . 且后原线程循环回去,继续调用 pthread_ co nd _ wait 函数,自动将互斥量解锁 , 井将自己挂起直到下
民的信号到来 .
上 面一段所讨论的步骤恰好对应于投票革统的原型 .也同样 ~;t 应于下面将君到的 twordcount4. c 中的代 码
;*
tllordc。山nt4.c
"
-
- Version 4:
threaded 四 rd co回国 r
for two f iles
COf}(到 tion variabl嘘 .11。司, co山lter
~
fuoctions to report resul ts
国rly
• #:
ir田clude
< stdio. h>
林 include < pthr回 d.h> 样 豆 oclude
拭目ct
< ctype. h>
arg_set
/ * two val ues in
!
one 缸g
eXðllline 时
cmr "fnarne
/ " f ile to
intc。回 t ,
/ * nUJQber of ..ords */
,,=乞町,.t 叫 ilbol<;
pthr国d__u恤x_t P 二hread_COlv:t t
旧 in( int
lock ,.凹'HREAD MUTEX I N ITIALIZ阻,
flag
~凹lfREAl)_ COND_INITI ALl ZER;
ac. çhar 部 av[])
pthr e<'l d_t t >, t2
/>t盹 threads
struct arg_set argsl , arlJS2;
1'"
void
镰 c。 ωt_ ..ords(void 帽〉
int
reports_in " 0
.1
two argsets 时
旷
第 14 拿线程4乳制 f 并发函数的使用
• 453 •
动画 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
h>
< pthread. h>
将挝、clude < stdl 恼h> 拌 include
< unistd. h>
/ * shaI"ed
v缸 iables both 乞Irr国由国e. Th ese ne回 a .utex 旷
符 define 阻ESS'咀 n hello~
int row
j*
int col;
/ * cun-,四 t c01咀n . j / * where we ð l"e gOll可时 / * ôe l 町 b.".使n~吨• . j
int dir; int deloy;
cuu四t rOIl时
且a 矶。
int 回elay;
j . 睛.. del町 .j
基盹 0 ,
归国0< ill胆 t
pthread_t Q(Lthread
/ _ i!l
void ..
thread
.j
,, /
..ovir可圃", 0
皿拉配 r O ;
c OlOde() ; noecho()
; * init curs自由"'t叮时
455 •
第 1 . 掌线程机制:并发函敛li'J使用
盘 中 创建了 - 个新的线程果执行 m o vlng_msg 函数 . movmg_ msg 函数执行了
个简单的循
耳,\,叩 , m Q Ve ,检查跳跃, repeat o 同时,在同 一 个进程的另 一 部分 .maln 函散也执行
个简
单的循环 :get 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.a te 'Bu y this' 'Orive
t h主scar"S洁白d
Money here '
C。因 ume '1 阳y!
将产生 一 个包吉事种幌动的动画消息 , 如图 14. 1 2 所 示.
剧Iy
this! ".山e thi~
Spe nd
mon~y
ca!'! herel COIIS WlI C!
Buy!
圈 14.
12
『 卢
严
0' t o qu it , '0'. , '4- to bounce
多仲躁动的动画消息
用户可以通过挂键 " 0 " 、 "1 "等使处在该行上的消息跳动 . 也可 以 使一组字持南活动起来,甚至是那些 U n ix 的工具,可以尝试下面的命令 : t 8JI i aate
I11 I
国皿恤
'users'
t ani Q t.
' date'
tammate 在不同的线程中运行控制动画的函数 . 这个画 盟 的每
个事例都 接 收到原始
线程所传的 一组不同的盎数 . 参数指定丁消息名林、行号、事动方向以及且!lt. /.
tan u跑回
·四 leep【》
c , animate severa1
str iI啕 S UBU唱 tlrr幽幽,
curses ,
• 460 •
Uni x/ Linux 编程实践敏在
每个 侈动的图片 由 一个独立的钱段但制
这些
线限将对屏稿 画面的夏'听请求政 入俏箱中
一个 战侄接收 到对扉幕的更
一个钱穰读取并
新 晴求 。 然后
处理用户 输 入
调用 屏幕处理 函数
图 14.
13
貌 立处 理屏幕控制的线 程
可 以把革个屏幕控制线程营成在 一 个大公司中的公共荣革部门 . 任何想要向媒体主选 消且的部门都必须向公共关系部门左道请求 . 公共荒草部门里的员工只茸 'C'、如何将消息盎 造出去 . 其实且个扉幕控制线程在功能上就悻这个公关部 一 样 , 任何希望向屏幕监消息的线程 都必须向这个扉幕管理线程盎邀请求 .
果用这种饥制之后,每个 线程所监迭的情草就应该包吉这些信息行 号、 列哥和消息字 持申 . 控制动画的线程盎出消息 , 扉事管理辑程接到消息后 , 将其显示在屏幕上 . 这种生产者〈监消息结程 〉
捐费者《接收悄且钱程 〉 的设计类似于前面学习的事线程
版的字散统计程序: 需 要 - 个存储置量来肢置消且,通过互斥量来避免对此存储置量的访问
冲突,以且 一 个条件查量 , 在动圆辑程韭追捕息之后,画Hl 通过这个条件墅量将屏幕控制线 寝唤醒 .
对于扉幕控制功能的盘中化和抽盘化世汁使得程序更加里话 . 就算屏幕显示系统直接 了,也只需改变屏幕控制线程中使用的函数 . 扉幕控制统程甚至还可以直起同远蹦显示服
务器的 一 个舍话,通过管理或茸撞宇将消息!,[t 过去,然而这一切对于控制动画的线程来说都
是造明的 . 改造这个程序就留给大事作为练习完成 .
小结 l
主要内在
· 执行线路即为程序的控制配程 . pthreads 的线程库允许程序在同 一 时刻运行多个 函盟 .
同时执行的各函数都拥有自己的局部变量,但共草所有的圭局变量和动在分配的世 据空间 .
当 线程共事变量时,必须惺证它们不会主生共享 冲 突 . 线程使用互斥锁来保证在某 一 时刻只有 一 个线程在时共事变量前问 .
· 线程间通过条件变量来 互相通知和同步数据 . 一个钱程挂起井等带着条件变量挂照 某种特定方式变化而另
个钱程则监信号使得条件变量监生变化 .
· 线程需要使用互斥量来避免对于共享贵国操作函数的访问忡费 . 非重人的画量必须
第 15 章
15.2
i!!程间通倍。陀〉
• 465 •
talk 命令:从多个数据源读取数据
Un i x 的 talk 命令是 一 种通信工具 . talk 允许人们在路蝙间传递信息 . talk 甚至可以跨 越 I nternet 来连接不同机器的路端,如图 1 5 . 1 所示 .
图 1 5 .1
talk
j皇锋网络上的两个终蝙
使用 tal k 命令的时候 · 屏幕瞌分为上下雨个部分 , 用户以字样终端模式来操作 . 输入字
符的时候,字符合同时显示在两个窗口中 . 体所输入的字符合出现在上丽的窗口中 , 而对方 输入的字出现在下面的窗口中 . talk 使用 sockel 进行通信 ,如图 1 5 . 2 所示 .
因 1 5. 2
I8. lk 命令的工作方式
talk 命令读取字符井将它们写到目的地 . 但与所学过的其他程序不间. talk 同时等待从 南个文件描述符的输入 . 15.2.1
同时从两个立件描述符读取数据
talk 从键盘租 socket 接收数据 . 从键盘输入憧取的字符瞌直制到扉幕中上半个窗口,
井通过 socket 'll造出去 . 同样J}..,配 k" 读人的丰符酣睡加到肝幕 F 面的窗口巾 . tall‘ 的用户可以以任意速度和任意顺序输入字符 . talk 程序就 ~ø 须在任何时刻都准备 好从任一数据晖接收宇符,而不懂其他的程序可以佳靠明确的协 1J/. . 服务器等待着 read 或 recvfrom 的请求,井用 wnte 茸 scndto jt 回
个应替 . 用户不可能 一 直在切换自己的角色
(输入完之后等待别人的应答 . 然后再输入
) ,那么 talk 程序如何解民这个问题呢。 talk
当然也不能这样做,如下述 ft 码 while (1 ){ ,.甜(fd_Itbd,&c , 川, 咱佬:lch(toplI' in ,叶, 盯 ite (
fd_ll' OCk , &c, 口 ,
1* read fr帽 keybo!u:回 旷 1* add to screen */ / ~ SeJ回国。theJ:"
read ( fd_ sock ,缸,1)
/ * read
waddch( botwi n. c) ;
/ .. add to
peraon _;
fr帽。 ther sc: re剧 时
person */
• 470 •
Unìx/ Lìnux 篇程实践敏程
惯写文件描述得
键入文阳俑迷符
因 15. 3
15.3.1
三 个文件描述符
一个问题的三种解决方案
问题从服务器得到数据 , 将其传给害户端,如何来决定选择哪一种通信方桂呢?想
想前面使用菌 ""k崎过?时间/ 日期服务器 . 茶一进程知道当前的时间 , 而另-进程想班
取时间信息 (如图 1 5 .4 所用 ) . 如何让一个进程从另 一个进程得到盘踞呢 ? 客户耀
服务111
图】 5.4
某进程拥有另 一进程所要获得的偏怠
三种解决方盎文件、营造、共享内存· 图 1 5. 5 屉示了 三 种平同的方法 一种是已经学习
过的,但另外的两种方法却是新的 . 大事所熟悉的方章是使用文件,而另外的两种新方案是命
名管道 (named pipe) ,&共享内存段 (share m
emory se阴ent)策略 . 这些方桂分别通过磁盘、内瞎
以及用户空间进行数据的传输 . 那么每种方桂具体如何应用 ? 各有何优融点呢,
因 1 5.5
15.3. 2
三 种传输数据的方法
通过文件的进程间通信
中 量:?间可以通过士件来进行通信 · 某进程将数据写人士件,别的进程再将数据从文件 口〉使用文件进行通信的时间 / 日 期服务器
过里不必写一个完整的 C 程序,下面的一个 shell 脚本就可 U 完成任务了 . 样! /b三n/9h
梓"皿 ."凹 er - file veui on
第 15 章进程间通信 ( IPC )
while da te
true
>
• 471 •
由
I r em p/ curre nt_da t e
sleep 1 done
此服务吉普每周 1 非钟将 当 前的时间和日期写入文件中 . 输出重定向符" > " 每眈将该文 件内容删除然后重写 . (2)使用文件进行通信的时间 / 日期客户器 害户瑞撞取文件的内睿 将
/ bin/ sh
将 t ime :::1 且 ent
- f ile ve rsion
ç
date
(3) 使用文件的 1PC 小结
面问控制
客户瑞必须能事读取文件 . 通过使用标准文件访问极限,可 H 给予服务器 写
杠限并且限制在户墙只有读仅限 .
事在户端任意盘目的事户可以同时从文件中撞取'I!<据 . Unix 并不限制同时打开同 一 士件的进程耻目 .
竟志条件服务器通过晴空内在再重写的方陆来重新文件 . 如果某窑户恰好在晴空和 重写之间在取文件 . 那么它碍到的将是 -个主 的或只有部分的内窑 . 避免置在条件服务器和事户端可以使用草种类型的互斥量来避免竟在条件 . 后面的 章节中大家将会学副主件顿的方法 . 另外 , 如果服务器在程序中使用 lseek 和 wrne 函数来
替换 create ,过样文件永Jii;都不可能为空,因为 wrlte 是一个原子操作,它不会在执行中融 打断 .
15.3.3
命名曹道
通常的管道只能连接相茧的进程 . 常规曹迥由进程创建,井囱最后 一个进程关闭 .
使用命名管道可以连接不相共的进程,井且可以独立于进程存在〈如图 15. 6 所示 L 排 这样的命名管道为 FIFO(先进先出队列) . FIFD 盎似于放在草押上的-段给花园浇水的水 曹 . 任何人都可以将此水臂的 一 端盘在自己的耳朵边 1 而另-个人通过水管向时方说话 . 人们可山通过此水管进行交班,而在没有人使用的时候,水啻仍辑是存在的 . FIFO 可 U 看 作囱文件名悻志的 一 根水管 .
因 1 5. 6
FJFO 与进程独 立
• 473 •
第 15 难逃程问道自 ([PCJ
15. 3. ~
共享内存
字节流是如何通过文件或 F I FO 来恃捕的。 wrll t'将数据从内存盟制到内核缰存中 . read 将数据从内植缀存坦制到 内 再中 .
如躲进应运行在阳尸空间的不 luJ 部分.进植间址虫 i'f 特散据从内在现存中 E 制进草制 出的呢?同一个革统型的两个进程通过使用共草的内存段来歪扭散据 . 共草的内存段是用 户内存的
部分 . 每个进居都有
个指向此内存段的指针〈如图 15 . 7 所示) . 佳靠访问权
限的世盟所有进程都可山庄取这
块主问中的盘据 . 因此进程间的资源是共享的 , 而不是
瞌豆制来 E 制去的 . 共草内存段时于进程而百 , 就类似于共草蛮量时于钱腥-悻 . 共巧在内存可以
允作一个边但 直拨将政惚放 入另一?进毯 的内存空!faJ 巾
使用管道需要两
次复制 11拥 因 15.
1
7
两个滋程共事 一 块肉存区城
共享内存段的一些基本慨念
共享内存段在内存中不假鞭子进程的存在而存在. . 共享内存段有自己的名字 , 称为韭键字 (key) . · 关键字是 一 个盟'四世 . · 共享内存段有自己的拥有者 ~1 .!l扭阳也 . · 进程百m 连接到某共享内fI-段 , 并且在甜甜向此段的指针 . 2
使用共享内存段
(])如何得到共草内存段 9
im segjd
s n mget (key , Si lC -of - segment .
f! ags)
如果内存段存在函数 s hm gcl 找到它的位置 . 如果不存在 , 可 以通过在 flags 缸中指定 一 个创建此由剧tJJ 始化极限模式的请求 . (2) 如何将进程连接到某个共享内存段 q
yoid plr = 领 5hmal (segjd. NULL . flags)
shmat 在进程的地址空间 中 øllr共事内存耻的部分,井返回一个指向 此 段的指针 . flags 垂盘用来指定此内再段是否为只踵 . ( 3) 如何与共享内存段进行在写 5C!i '
sl rcpy( plr. "hello " ) memcpy() 、 pt r [i J 且 其他 一 些通用的指针操作3
使用共卒内存段的时闯/ 日期服务器 吁国 ts. C
乞hc ti ~,c
ser"er
usi呵..,.,国 lleJOory.
a bizarre
ðpplication 旷
'在 15 章进程问通信 (]PC)
• 481
借号量集
周 1 5 .8
信号,监设置
num_ readers 、 num wrlters
在告改共享内存之前 , 服务器必须先对这组活动组进行操作 • [OJ 等候 nurn readers 变成 。 •
[1J 将 num wrlters 加 1
当服务器完成写操作之后,它必须再对下面迫组活动靠进行据作: • [OJ 将 num wr 1t e r s 减 l
在害户读取共享内存之前,必须对下商量组活动靠进行操作 • [OJ 等待 num wnters 变成 o • [ 1J 棉 num readers 加 1
当事户完成任鼻之后 , 需要时下而这组活动靠进行操作 • [OJ 将 num readers 瞄 1
3
服务器版本
shm 152. c
蜻原来的程序 shm 15. C 添加倍号量得到 shm ts2. c / - IIhm_ts2. c - t ÌJo e se凹 er shar田,但 ver2
* prograa
use B咀aphores for 1民.k ing
uses shared memory ..ith key 99
.. progrilll uses
se国 phore
set with key 9900
-/
** include
< s tdio. h>
# include # include
< sysf shm. h> < time. h>
** include ** include
< 5Y5/ S咀 . h>
# l.ndude
< sigr咀l.h>
梓 define
Tl民".,吨 KEY
梓 define
< sys/ typeS. h>
99
/ * lilcea
f ilename 时
'I'lME_ S:四ttEy 9900
待 defin.啤
SEG_SlZE (何主lt c_t)100)
样de.fine
∞阴阳,对 / peuor (叫
union semun 1 in定 val
/ .. size of s呵回回t 旷 田比 (对 , }
s truct s国 id_ds ..以涩,回国 rt .array; 1;
int
S呵_ id , S四set_id;
,.立d
cleanup( int);
j * qlobal fOf cl eanup O 揭/
• 488 •
Unix/ Lin 川编程实践放程
草内存和文件的版本看上去很简单,但E; 锢相信号量也是相 当 且在的 一 件 'J '
111 10 要锁相信号盘机制来保护盘据.要知监川人
.
然而文件和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 /l pt
• 489 •
!!<15 意继霞间温情。陀》
迫里 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
!locket . 有些盹用命名管坦,而另外 一 些仅使用 fork 和文件 . 是否使用靠中化的事户 / JIl.虫'器模式就可以不使闸锁机制来避免冲突了 '苟 且把罩统
世计成 一个通 过构件进行通信相合作的模型来打印 一 台机器上的文件也可 U 用来打印 l nterne t 上的文件 . 可以将你自己的思路和不同版本的 Unix 打印革统的世计血路做一个