Unix Linux 编程实践教程 (Understanding UNIX LINUX  Programming: A Guide to Theory and Practice)
 0130083968, 9780130083968 [PDF]

  • 0 0 0
  • Suka dengan makalah ini dan mengunduhnya? Anda bisa menerbitkan file PDF Anda sendiri secara online secara gratis dalam beberapa menit saja! Sign Up
File loading please wait...
Citation preview

第 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 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



梓 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 回 睹过所有介绍性的内在,直撞来着 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



utmp_close( ) ; return 0;



/•



. how 皿 fo ()



悻改后的主函数世有直接对 open 、 read 和 close 进行调用,而是调用与之等价的具有缓 冲模式的面l!



通过联机帮助可以知道,从目录读数据与从文件读数据是类似的, 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, 目标



得到文件的属饺



'"文件



咎 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 Ó







函 戴' 缸



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' 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



使用默认民置运行这个程序 ( 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 的 执行过程 .



阳晒 alarm(unsigncd



等待的时间《钞〉 如 S展出错



计时嚣剩余时间



secondρ



'院 7 司监事件驱动编程,描写



个很 11111 戏



• 191



a!arm 世置本进程的 H 时器到 seconds 秒眉目世盎信号 . 当设定的时间过去之后,内核监 埠 SIGALRM 到这个进程 . 如果汁时器已蛊醒世置 .al a rm 温回剩最非数〈注童.调用 ølørm (0) 童 峰精失掉闹钟 ) . ,..~



自鲁露



等待俯号



头文 件



科 includ l!



画 11. 型



r;



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




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,回



林立 nclude



梓inc: lude



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



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 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



• 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



ç



# 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 •



!!