《unix高级编程22.pdf》由会员分享,可在线阅读,更多相关《unix高级编程22.pdf(19页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、下载下载附录C 习 题 答 案第1章1.1 利用l s(1)命令中的下面两个选择项:-i显示文件或目录的i节点数目(关于i节点在4.1 4节中会详细讨论);-d如果参数是一目录,只列出其名字,而不是目录中的所有文件。执行命令的结果为:$l s-ldi/etc/./etc/.-i要求打印i节点的数量3077 drwxr-sr-x 7 bin 2048 Aug 5 20:12/etc/./2 drwxr-xr-x 13 root 512 Aug 5 20:11/etc/./$ls-ldi/./.和.的i节点数均为22 drwxr-xr-x 13 root 512 Aug 5 20:11/./2 d
2、rwxr-xr-x 13 root 512 Aug 5 20:11/./1.2 UNIX是多任务系统,所以,在程序1-4运行的同时其他两个进程也在运行。1.3 假如p e r r o r的p t r参数是一个指针,则p e r r o r就可以改变p t r所指串的内容。所以利用限定词c o n s t使得p e r r o r不能修改p t r所指的串。而s t r e r r o r的参数是错误号,由于其是整数类型并且C传递的是参数值,因此s t r e r r o r不能修改参数的值,也就没有必要使用c o n s t属性。(如果C中函数参数的处理不是很清楚,可参见K e r n i g
3、 h a n和R i t c h i e1 9 9 85.2节。)1.4 调用ff l u s h,f p r i n t f和v p r i n t f函数可修改e r r n o的值。如果它的值变了但没有保存,则最终显示的错误信息是不正确的。在过去开发的许多程序中,都可以发现不保存 e r r n o的情况,典型的错误信息是“Not at y p e w r i t e r(打字机不存在)”。5.4节中标准I/O库根据标准I/O流是否指向终端设备而改变流的缓存器。i s t t y(见11.9节)通常用来判断流是否指向终端设备,如果流不指向终端设备,e r r n o可能置为E N O T
4、 T Y,从而引起该错误。程序C-1显示了这一特性。程序C-1 errno和p r i n t f的交互作用执行上面的程序,结果为:$grep BSD/etc/motd4.3 BSD UNIX#29:Thu Mar 29 11:14:13 MST 1990$a.o u topen error:error=2 工作正常,s t d o u t是一个终端$a.out temp.foo$cat temp.fooopen error:error=25错误1.5 2038年。1.6 大约2 4 8天。第2章2.1 下面是4.3+B S D中使用的技术。在 中,用大写字母定义可在多个头文件中出现的基本数据
5、类型。例如:#i f n d e f_ A N S I _ H _#d e f i n e_ A N S I _ H _#d e f i n e_CLOCK_T_ unsigned long#d e f i n e_SIZE_T_ unsigned int.#e n d i f/*_ANSI_H_*/以下面的顺序可以在这6个头文件中分别定义s i z e _ t。#ifdef _SIZE_T_typedef _SIZE_T_ size_t;#undef _SIZE_T_#e n d i f这样,实际上只执行一次t y p e d e f。第3章3.1 所有的磁盘I/O都要经过内核的块缓存器,唯
6、一例外的是对原始磁盘设备的 I/O,但是我们不考虑这种情况(B a c h1 9 8 6的第3章讲述了这种缓存器的操作)。既然r e a d或w r i t e的数据都要被内核缓存,那么术语“无缓存装置的 I/O”指的是在用户的进程中对这两个函数不会自动缓存,每次r e a d或w r i t e就要进行一次系统调用。3.3 每次调用o p e n函数就分配一个文件表项,如果两次打开的是相同的文件,则两个文件表项指向相同的v节点。调用d u p引用已存在的文件表项(此处指 f d 1的文件表项),见图C-1。当F _ S E T F D作用于f d 1时,只影响f d 1的文件描述符标志;F
7、_ S E T F L作用于f d 1时,则影响f d 1及f d 2的文件描述符标志。3.4 如果f d是1,执行d u p 2(f d,1)后返回1,但是没有关闭描述符1(见3.1 2节)。调用3次d u p 2后,3个描述符指向相同的文件表项,所以不需要关闭描述符。如果f d是3,调用3次d u p 2后,有4个描述符指向相同的文件表项,所以需要关闭描述符3。附录C 习 题 答 案5 1 9下载图C-1 open和d u p的结果3.5 shell从左到右处理命令行,所以a.out outfile 2&1首先设置标准输出到o u t f i l e,然后执行d u p s将标准输出复制到
8、描述符2(标准错误)上,其结果是将标准输出和标准错误设置为相同的文件,即描述符 1和2指向相同的文件表项。而对于命令行a.out 2&1 outfile由于首先执行d u p s,所以描述符2成为终端(假设命令是交互执行的),标准输出重定向到o u t f i l e。结果是描述符1指向o u t f i l e的文件表项,描述符2指向终端的文件表项。3.6 这种情况之下,仍然可以用l s e e k和r e a d函数读文件中任意一处的内容。但是w r i t e函数在写数据之前会自动将文件位移量设置为文件尾,所以写文件时只能从文件尾开始,不能在任一位置。第4章4.1 stat函数总是顺一个
9、符号连接向前,所以修改后的程序不会显示文件类型是“符号连接”。例如:/b i n是/u s r/b i n的一个符号连接,但是 s t a t函数的结果只显示/b i n是一个目录,而不说明它是一个符号连接。若一个符号连接指向一不存在的文件,则 s t a t出错返回。4.2 将下面的几行语句加入#if defined(S_IFLNK)&!defined(S_ISLNK)#define S_ISLNK(mode)(mode)&S_IFMT)=S_IFLNK)#e n d i f这是一个我们编写的头文件如何屏蔽某些系统差别的实例。4.3 关闭了该文件的所有存取许可权。$umask 777$dat
10、a temp.foo$ls-l temp.foo-l stevens 29 Jan 14 06:39 temp.foo4.4 下面的命令表示关闭用户读许可权的情况。$data foo$c h m o d u-r foo关闭用户读许可权$ls-l foo验证文件的许可权-w-r w-r-l stevens29 Jul 31 09:00 foo$cat foo读文件5 2 0U N I X环境高级编程下载进程表项文件表文件状态标志当前文件位移v节点指针f d标志 p t r文件状态标志当前文件位移v节点指针v节点表v节点信息i节点信息当前文件长度cat:foo:Permission denied4
11、.5 如果用o p e n或c r e a t创建已经存在的文件,则该文件的存取许可权不变。程序 4-3可以验证这点。$rm foo bar删除文件$data foo创建文件$data bar$chmod a-r foo bar关闭所有的读许可权$ls-l foo bar验证其许可权-w-w-l stevens29 Jul 31 10:47 bar-w-w-l stevens29 Jul 31 10:47 foo$a.o u t运行程序4-3$ls-l foo bar检查文件的许可权和大小-w-w-ls t e v e n s0 Jul 31 10:47 bar-w-w-ls t e v e
12、n s0 Jul 31 10:47 foo可以看出存取许可权没有改变,但是文件长度缩短了。4.6 目录的长度从来不会是0,因为它总是包含.和.两项。符号连接的长度指其路径名包含的字符数,由于路径名中至少有一个字符,所以长度也不为 0。4.8 当创建新的c o r e文件时,内核对其存取许可权有一个默认设置,在本例中是 r w-r-r-。这一默认值可能会可能不会被 u m a s k的值修改。s h e l l对创建的重定向的新文件也有一个默认的访问许可权,本例中为r w-r w-r w-。这个值总是被当前的u m a s k修改,在本例中u m a s k为0 2。4.9 不能使用d u的原因
13、是它需要文件名,如:du tempfile或目录名,如:du.只有当u n l i n k函数返回时才释放t e m p f i l e的目录项,d u.命令没有计算仍然被t e m p f i l e占用的空间。本例中只能使用d f命令察看文件系统中实际可用的自由空间。4.10 如果被删除的链接不是该文件的最后一个链接,则该文件不会删除。此时,文件的状态改变时间被更新。如果是最后一个链接被删除,则该文件将被物理删除。这时再去更新文件的状态改变时间就没有意义,因为包含文件所有信息的i节点将会随着文件的删除而被释放。4.11 用o p e n d i r打开一个目录后,循环调用函数d o p a
14、 t h。假设o p e n d i r使用一个文件描述符,并且只有处理完目录后调用 c l o s e d i r才释放描述符,这就意味着每次打开目录就要降一级使用另外一个描述符。所以系统可打开的描述符数就限制了文件系统中树的深度。S V R 4中的f t w允许调用者指定使用的描述符数,这隐含着该实现可以关闭描述符并且重用它们。4.13 chroot函数用于辅助因特网文件传输程序(F T P)中的安全性。系统中没有帐号的用户(也称为匿名F T P)放在一个单独的目录下,利用 c h r o o t将此目录当作新的根目录就可以阻止用户访问此目录以外的文件。c h r o o t也用于在另一台
15、机器上构造一文件系统层次结构的一个副本,然后修改此副本,但不更改原来的文件系统。这可用于测试新软件包的安装。c h r o o t只能由超级用户使用,一旦更改了一个进程的r o o t,该进程及其后代进程就再也不能恢复至原先的r o o t。4.14 首先调用s t a t函数取得文件的三个时间值,然后调用 u t i m e设置期望的值。我们不希望在调用u t i m e时改变的值就是s t a t中相应的值。4.15 finger(1)对邮箱调用s t a t函数,最近一次的修改时间是上一次接收邮件的时间,最近存取时间是上一次读邮件的时间。附录C 习 题 答 案5 2 1下载4.16 对c
16、 p i o来说,既可以改变文件的访问时间(s t _ a t i m e)和修改时间(s t _ m t i m e),也可以都不改变。c p i o的-a选项可以在读文件后重新设置文件的存取时间,改变文件的存取时间。另一方面,-m将文件的修改时间和存取时间保存为归档时的值。对t a r来说,在抽取文件时,其默认方式是复原归档时的修改时间,但是-m选择项则将修改时间设置为抽取文件时的时间。无论t a r在何种情况,文件的存取时间均是抽取文件时的时间。由于不能修改状态改变时间(u t i m e也只能改变访问时间和修改时间),所以没有将其保存在文档上。4.17 read改变了文件存取时间,为了
17、消除这一影响,有些版本的 f i l e(1)调用u t i m e恢复文件的存取时间,但是这样做会修改文件的状态改变时间。4.18 内核对目录的深度没有内在的限制,但是如果路径名的长度超出了 PAT H _ M A X,则有许多命令会失败。程序 C-2创建了一个深度为 1 0 0的目录树,每一级目录名有 4 5个字符。利用g e t c w d可以得到第1 0 0级目录的绝对路径名(需要多次调用 r e a l l o c申请一个足够大的缓存)。程序C-2 创建深目录树5 2 2U N I X环境高级编程下载运行后得到:$a.o u tgetcwd failed,size=1025:Resu
18、lt too largegetcwd failed,size=1125:Result too large.3 3行getcwd failed,size=4525:Result too largelength=4613显示4 6 1 3字节的路径名但是由于文件名太长了,不能用t a r或c p i o对该目录建立档案文件,而且也不能用rm-r命令删除该目录。(怎样才能删除该目录树?)4.19 /dev目录关闭了一般用户的写许可权,所以用户不能删除目录中的文件,即 u n l i n k失败。第5章5.2 fgets函数读入数据,直到行结束或缓存满(当然会留出一个字节存放终止字符)。同样,f p
19、u t s只负责将缓存的内容输出,而并不考虑缓存中是否包含换行符。所以,如果将M A X L I N E设得很小,这两个函数仍然会正常工作,只不过被执行的次数要比 M A X L I N E值较大的时候多。如果这些函数删除或添加换行符(如g e t s和p u t s),则必需保证缓存足够大。5.3 当p r i n t f没有输出任何字符时,如:p r i n t f(),则返回0。5.4 这是一个比较常见的错误。g e t c以及g e t c h a r的返回值是整型,而不是字符型。由于E O F经常定义为1,那么如果系统使用的是有符号的字符类型,程序还可以正常工作。但如果使用的是无符号
20、字符类型,那么返回的E O F被保存到字符c后将不再是1,所以,程序会进入死循环。5.5 5个字符长的前缀、4个字符长的进程内唯一标识再加 5个字符长的系统内唯一标识(进程I D)刚好组成1 4位的U N I X传统文件长度限制。5.6 使用方法为:先调用ff l u s h后调用f s y n c,f s y n c所使用的参数由f i l e n o函数获得。如果不调用ff l u s h,所有的数据仍然在内存缓存中,此时调用f s y n c将没有任何效果。5.7 当程序交互运行时,标准输入和输出设备均为行缓存方式。每次调用 f g e t s时标准输出设备将自动刷清。第6章6.1 在S
21、 V R 4系统中,用户手册中讲述了存取阴影口令文件的函数。我们不能使用 6.2节所述函数返回的p w _ p a s s w d变量来比较加密口令。正确的方法是使用阴影口令文件中对应用户的加密口令来进行比较。在4.3+B S D系统中,口令文件的阴影是自动建立的。仅当调用者的用户I D为0时,g e t p w n a m或g e t p w u i d函数返回的p a s s e d结构中的p w _ p a s s w d字段才包含有加密口令。6.2 在S V R 4系统中,程序C-3将输出加密口令。当然,除非有超级用户许可权,否则调用g e t s p n a m将返回E A C C
22、E S错误。附录C 习 题 答 案5 2 3下载程序C-3 在S V R 4系统中输出加密口令在4.3+B S D系统中,具有超级用户许可权时,程序 C-4将输出加密口令。否则p w _ p a s s e d的返回值为星号(*)。程序C-4 在4.3+BSD系统中输出加密口令6.4 程序C-5以d a t e格式输出日期。程序C-5 以d a t e(1)的格式输出日期和时间5 2 4U N I X环境高级编程下载程序C-5的运行结果如下:$echo$TZ作者使用的默认值M S T 7$a.o u tWed Jan 15 06:48:57 MST 1992$TZ=EST5EDT a.out美
23、国东海岸Wed Jan 15 08:49:06 EST 1992$TZ=JST-9 a.out日本Wed Jan 15 22:49:12 JST 1992第7章7.1 原因在于p r i n t f的返回值(输出的字符数)变成了 m a i n函数的返回码。当然,并不是所有的系统都会出现该情况。7.2 当程序处于交互运行方式时,标准输出设备通常处于行缓存方式,所以当输出换行符时,上次的结果才被真正输出。如果标准输出设备被定向到一个文件而处于完全缓存方式,则当标准I/O清理操作执行时,结果才真正被输出。7.3 由于a g r c和a rg v不像e n v i r o n一样保存在全局变量中,所
24、以在大多数 U N I X系统中没有其他办法。7.4 当C程序复引用一个空指针出错时,执行该程序的进程将终止,于是可以利用这种方法终止进程。7.5 定义如下:typedef void Exitfunc(void);int atexit(Exitfunc*f u n c);7.6 calloc将分配的内存空间初始化为0。但是ANSI C并不保证0值与浮点0或空指针的值相同。7.7 只有通过e x e c函数执行一个程序时,才会分配堆和堆栈。7.8 可执行文件包含了用于调试c o r e文件的符号表信息,用s t r i p(1)可以删除这些信息,对两个a.o u t文件执行这条命令,它们的大小减
25、为98 304和16 384。7.9 没有使用共享库时,可执行文件的大部分都被标准I/O库所占用。7.10 这段代码不正确。因为在i f语句中定义了自动变量v a l,所以当i f中的复合语句结束时,该变量就不存在了,但是在i f语句之外又用指针引用已经不存在的自动变量v a l。第8章8.1 用下面几行代替程序8-2中调用p r i n t f的语句:i=printf(pid=%d,glob=%d,var=%dn,getpid(),glob,var);sprintf(buf,%dn,i);附录C 习 题 答 案5 2 5下载write(STDOUT_FILENO,buf,strlen(buf
26、);注意要定义变量i和b u f。这里假设子进程调用 e x i t时只关闭标准 I/O流,并不关闭与标准输出相关的文件描述符S T D O U T _ F I L E N O。有些版本的标准I/O库会关闭与标准输出相关的文件描述符从而引起写失败,这种情况就调用d u p将标准输出复制到另一个描述符,w r i t e则使用新复制的文件描述符。8.2 可以通过程序C-6来说明这个问题。程序C-6 错误使用v f o r k的例子当函数f 1调用v f o r k时,父进程的堆栈指针就指向 f 1的栈帧,见图C-2。v f o r k使得子进程先执行然后从f 1返回,接着子进程调用f 2并且覆盖
27、了f 1的堆栈区间,在f 2中子进程将自动变量b u f的值置为0,即将堆栈中的1 0 0 0个字节的值都置为0。从f 2返回后父进程继续执行调用_ e x i t,这时堆栈中m a i n以下的内容已经被f 2修改了,但是父进程仍然以为调用了v f o r k后从f 1返回。返回信息虽然保存在堆栈中,但是可能已经被子进程修改了。对这个例子,父进程继续执行的结果要依赖于实际的U N I X系统。(如:返回信息保存在堆栈的具体位置,修改动态变量时覆盖了哪些信息等等。)通常的结果是一个c o r e文件。8.3 在程序8-7中我们先让父进程输出,但是当父进程输出完毕子进程要输出时,要让父进程终止。
28、是父进程先终止还是子进程先执行输出要依赖5 2 6U N I X环境高级编程下载图C-2 调用v f o r k时的栈帧栈延伸的方向栈顶m a i n函数的堆栈区f l函数的堆栈区于内核对两个进程的调度。s h e l l在父进程终止后会开始执行其他程序,这样也许仍会影响子进程。要避免这种情况就是在子进程完成输出后才终止父进程。用下面的语句替换程序中 f o r k后面的代码。由于只有终止父进程才能开始下一个程序,所以不会出现上面的情况。8.4 对a rg v 2 打印的是相同的值(/h o m e/s t e v e n s/b i n/t e s t i n t e r p)。原因是e x
29、 e c l p在结束时调用了e x e c v e,并且与直接调用e x e c l的路径名相同。8.5 不提供返回保存的设置-用户-I D的函数,我们必须在进程开始时保存有效的用户I D。8.6 程序C-7创建了一个僵死进程。程序C-7 创建一个僵死进程并用p s查看其状态执行程序结果如下(p s(1)用Z表示僵死进程):$a.o u tPID TT STATTIME COMMAND5940 p3 S0:00 a.out5941 p3 Z0:00 僵死进程5942 p3 S0:00 sh-c ps5943 p3 R0:00 ps第9章9.1 因为i n i t是login shell的父进
30、程,当登录s h e l l终止时它收到S I G C H L D信号量,所以i n i t进程知道什么时候终端用户注销。网络登录没有包含i n i t,相应的注销项是由一个处理登录并监测注销的进程写的(本例中为t e l n e t d)。附录C 习 题 答 案5 2 7下载第1 0章10.1 当程序第一次接收到发送给它的信号量时就终止了。因为一捕捉到信号量 p a u s e函数就返回。10.2 程序C-8实现了r a i s e函数。程序C-8 raise函数的实现10.3 见图C-3。图C-3 longjmp前后的堆栈状态从s i g _ a l r m通过l o n g j m p返
31、回m a i n,有效地避免了继续执行s i g _ i n t。10.4 如果进程在调用a l a r m和s e t j m p之间被内核阻塞了,a l a r m时间走完之后就调用信号量处理程序,然后调用l o n g j m p。但是由于没有调用s e t j m p,所以没有设置e n v _ a l r m缓存区。如果l o n g j m p的跳转缓存没有被s e t j m p初始化,则说明没有定义l o n g j m p的操作。10.5 参见Don Libes的“Implementing Software Ti m e r s”(C Users Journal,Vol.8,
32、no.11,N o v.1990)中的例子。10.7 如果仅仅调用_ e x i t,则进程终止状态就不能表示该进程是由于S I G A B RT信号量而终止的。10.8 如果信号量是由其他用户的进程发出的,进程必须设置用户的 I D为根或者是接收进程的所有者,否则k i l l不能执行。所以实际的用户I D为信号量的接收者提供了更多的信息。10.10 对于本书中所用的系统,大约每 6 0 9 0分钟增加一秒,这个误差是因为每次调用s l e e p都要调度一次将来的时间事件,但是由于C P U调度,有时我们并没有在事件发生时被叫醒。另外一个原因是开始运行进程和调用s l e e p都需要一定
33、的时间。B S D中的c r o n每分钟都要取当前时间,它首先设置一个休眠周期,然后在下一分钟开始时唤醒。大多数调用是s l e e p(6 0),偶尔有一个s l e e p(5 9)用于在下一分钟同步。但是若在进程中花费了许多时间执行命令或者系统的负载重调度慢,这时休眠值可能远小于 6 0。5 2 8U N I X环境高级编程下载栈顶处理S I G I N T时处理S I G I N T时函数m a i n的堆栈区函数m a i n的堆栈区函数m a i n的堆栈区函数s i g _ i n t的堆栈区进行l o n g j m p之后函数m a i n的堆栈区函数s i g _ i n
34、 t的堆栈区函数s i g _ a l r m的堆栈区1 0.11 在S V R 4中,从来没有调用过 S I G X F S Z的信号量处理程序,一旦文件的大小达到1 0 2 4时,w r i t e就返回2 4。在4.3+B S D中,文件大小达到1 5 0 0字节时调用该信号处理程序,w r i t e返回1并且e r r n o设置为E F B I G。S u n O S 4.1.2的情况与S V R 4类似,但是调用了该信号量处理程序。系统V在文件大小达到软资源限制时无错返回一个较小的数,而 B S D判断文件超出限制时错误返回,没有写任何数据。10.12 结果依赖于标准I/O库的实
35、现f w r i t e如何处理一个被中断的写。第11章11.1 注意由于终端是非正规模式,所以要用换行符而不是回车终止 r e s e t命令。11.2 它为1 2 8个字符建了一张表,根据用户的要求设置奇偶校验位。然后使用 8位I/O处理奇偶位的产生。11.3 在S V R 4中运行stty-a,并且将标准输入重定向到运行v i的终端,结果显示v i设置M I N为1,T I M E为1。r e a d s等待至少敲入一个字符,但是该字符输入后,只对后继的字符等待十分之一秒即返回。11.4 在S V R 4中使用扩展的通用终端接口。参见 AT&T1 9 9 1手册中的t e r m i o
36、 x(7)。在4.3+B S D中使用c _ c f l a g字段的C C T S _ O F L O W和C RT S _ I F L O W标志,参见表11-1。第1 2章12.1 程序运行正常,不会发生 E N O L C K的错误。第一次循环调用 w r i t e w _ l o c k、w r i t e和u n _ l o c k。调用u n _ l o c k后只保留了第一个字节的锁,第二次循环时,调用 w r i t e w _ l o c k使得新锁与第一个字节的锁合并,图C-4是第二次循环的结果。每循环一次就扩展一个字节的锁,内核将这些锁合并后就只保持了一个锁,因此符合
37、锁结构的定义。12.2 在S V R 4和4.3+B S D中,f d _ s e t是只包含一个成员的结构,该成员为一个长整型数组。数组中每一位对应于一个描述符。四个 F D _宏通过开关或测试指定的位来操纵这个数组。将之定义为一个包含数组的结构而不仅仅是一个数组的原因是:通过 C语言的赋值语句,可以使f d _ s e t类型变量相互赋值。12.3 在S V R 4和4.3+B S D中允许用户在头文件 前定义常数F D _ S E T S I Z E。例如下面的代码可以使f d _ s e t数据类型包含2 0 4 8个描述符。#define FD_SETSIZE 2048#i n c
38、l u d e12.4 下面列出了功能类似的函数。附录C 习 题 答 案5 2 9下载图C-4 第二次循环后锁的状态已加锁第一个字节第二个字节F D _ Z E R Os i g e m p t y s e tF D _ S E Ts i g a d d s e tF D _ C L Rs i g d e l s e tF D _ I S S E Ts i g i s m e m b e r没有与s i g f i l l s e t对应的F D _ x x x函数。对信号量来说,指向信号量集合的指针是第一个参数,信号量数是第二个参数;对于描述符来说,描述符数是第一个参数,指向描述符集合的指针
39、是第二个参数。12.5 最多五种信息:数据,数据长度,控制信息,控制信息的长度和标志。12.6 利用s e l e c t实现的程序见C-9,利用p o l l实现的程序见C-1 0。程序C-9 用s e l e c t实现s l e e p _ u s函数程序C-10 用p o l l实现s l e e p _ u s函数B S D中的u s l e e p(3)使用s e t i t i m e r设置间隔计时器,并且执行8个系统调用。它可以正确地和调用进程设置的其他计时器交互作用,而且即使捕捉到信号量也不会被中断。12.7 不行。我们可以使T E L L _ WA I T创建一个临时文件
40、,其中一个字节用作为父进程的锁,另一个字节用作为子进程的锁。WA I T _ C H I L D使得父进程等待子进程的锁,T E L L _ PA R E N T使得子进程释放子进程的锁。但是问题在于调用 f o r k后,子进程释放了所有的锁导致子进程不能具有任何它自己的锁而开始执行。12.8 用s e l e c t的方法见程序C-11,使用p o l l的情况类似。程序C-11 用s e l e c t计算管道的性能5 3 0U N I X环境高级编程下载在S V R 4和SunOS 4.1.1中使用s e l e c t和p o l l计算出的结果等于表2-6的值。在4.3+B S D
41、中使用s e l e c t计算的结果为3 0 7 3。12.9 在S V R 4、4.3+B S D和SunOS 4.1.2中,程序1 2-1 4确实修改了输入文件的最近一次存取时间。第1 3章13.1 如果进程调用c h r o o t就不能打开/d e v/l o g,解决的办法是在c h r o o t之前调用选择项为L O G _ N D E L AY的o p e n l o g。这样即使调用了c h r o o t之后,仍然可以打开特定的设备文件(U N I X与数据报套接口)并生成一个有效的描述符。13.3 程序C-1 2是一种解决方案。程序C-12 调用d a e m o n
42、_ i n i t获得注册名结果依赖于不同的系统实现和是否关闭文件描述符 1、2和3。关闭描述符影响结果的原因是:当程序开始执行时与控制终端连接,调用 d e a m o n _ i n i t后关闭3个描述符就意味着g e t l o g i n没附录C 习 题 答 案5 3 1下载有控制终端,所以不能在u t m p文件中看到登录项。但是在4.3+B S D中,登录名是由进程表维护的,并且可以通过 f o r k复制。也就是说除非其父进程没有登录名(如系统自引导时调用i n i t),否则进程总能获得其登录名。第1 4章14.1 如果写管道端总是不关闭,则读者就决不会看到文件的结束符。页面
43、调度程序就会一直阻塞在读标准输入。14.2 父进程向管道写完最后一行以后就终止了,然后读者读到管道的结尾时自动关闭管道。但是由于子进程(页面调度程序)要等待输出的页,所以父进程可能比子进程领先一个管道缓存器。如果在一个交互式命令行s h e l l上运行,当父进程终止时s h e l l会改变终端的模式并提示用户。由于大部分页面调度程序在等待处理下一个页面时将终端设置为非正规模式,所以终止父进程就会影响页面调度程序。14.3 因为执行了s h e l l,所以p o p e n返回一个文件指针。但是s h e l l不能执行不存在的命令,因此在标准错误上显示下面信息后终止,其退出状态为 1,调
44、用p c l o s e就将该退出状态返回。sh:a.out:not found14.4 当父进程终止时,用s h e l l看它的终止状态。对于Bourne shell和K o r n S h e l l所用的命令是echo$?打印的结果是1 2 8加信号量数。14.5 首先加入下面的定义,FILE *fpin,*fpout;然后用f d o p e n关联管道描述符和标准I/O流,并将流设置为行缓存的,在 w h i l e循环从标准输入读之前作下面的工作。w h i l e中的r e a d和w r i t e用下面的语句代替:14.6 虽然s y s t e m函数调用了w a i t
45、,但是终止的第一个子进程是由p o p e n产生的,所以它将再次调用w a i t并一直阻塞到s l e e p完成,然后s y s t e m返回。当p c l o s e调用w a i t时,由于没有子进程可等待所以返回出错,导致p c l o s e也返回出错。14.7 select表明描述符是可读的。调用r e a d读完所有的数据后返回0就表明到达了文件尾端。对于p o l l(假设管道是一个流设备)来说,若返回P O L L H U P也许仍有数据可以读。但是一旦读完了所有的数据 r e a d就返回0,即表明到达了文件尾端。p o l l读完了所有的数据后并不返回P O L L
46、 I N。5 3 2U N I X环境高级编程下载对于被读者关闭的指向管道的输出描述符来说,s e l e c t表明描述符是可写的,调用 w r i t e时产生S I G P I P E信号量,如果忽略该信号量或从信号量处理程序中返回时 w r i t e就返回E P I P E错误。而对于p o l l,如果管道是一个流设备,p o l l就对该描述符返回P O L L H U P。14.8 子进程向标准出错写的内容同样也在父进程的标准出错中出错。只要在 c m d s t r i n g中包含重定向2&1命令,就可以将标准出错发送给父进程。14.9 popen生成一个子进程,子进程通过
47、e x e c执行Bourne shell。然后s h e l l再调用f o r k,最后由s h e l l的子进程执行命令串。当c m d s t r i n g终止时s h e l l恰好在等待该事件,然后s h e l l退出,而这一事件又是p c l o s e中w a i t p i d所等待的。14.10 解决的办法是打开F I F O两次,一次读一次写。我们绝不会使用为写而打开的描述符,但是使该描述符打开就可在客户数从 1变为0时,阻止产生文件终止。打开 F I F O两次需要注意下列操作方式:第一次以非阻塞、只读方式 o p e n,第二次以阻塞、只写方式 o p e n。
48、(如果用非阻塞、只写方式打开将返回错误。)然后关闭读描述符的非阻塞属性。参见程序C-1 3。程序C-13 以非阻塞方式打开F I F O进行读写操作1 4.11 随意读取现行队列中的消息会干扰客户机-服务器协议,导致丢失客户机请求或者服务器的响应。由于队列允许所有的用户读,所以进程只要知道队列的标识符就可以读队列。14.13 由于服务器和客户机都可能将段连接到不同的地址,所以在共享存储段中不存放实际物理地址。相反,当在共享存储段中建立链表时,指针的值设置为共享存储段内的位移。位移量为所指目标的实际地址减去共享存储段的起始地址。14.14 表C-1显示了相关的事件。表C-1 程序1 4-1 2中
49、父子进程间的切换过程附录C 习 题 答 案5 3 3下载父进程中i的值子进程中i的值共享变量的值u p d a t e的返回值备注0由m m a p初始化1子进程先运行,然后阻塞第1 5章15.3 说明(d e c l a r a t i o n)指定了标识符集合的属性(例如数据类型),如果说明的同时分配了存储单元就是定义(d e f i n i t i o n)。在头文件o p e n.h中用e x t e r n说明了三个全局变量,这时并没有为它们分配存储单元,在文件m a i n.c中定义了三个全局变量,有时会在定义时就初始化全局变量,但通常使用 C的默认值。15.5 select和p
50、o l l都返回就绪的描述符个数。当将这些就绪描述符都处理完后,操作 c l i e n t数组的循环就可以结束。第1 6章16.1 _db_dodelete中保守的加锁操作是为了避免和d b _ n e x t r e c发生竞态条件。如果没有使用写锁保护_ d b _ w r i t e d a t调用,则有可能在_ d b _ n e x t r e c读某个记录时擦去该记录:d b _ n e x t r e c首先读入一个索引记录,发现该记录非空,则接着读入记录内容,但是在它调用 _ d b _ r e a d i d x后_ d b _ r e a d d a t前,该记录却给_