9299.net
大学生考试网 让学习变简单
当前位置:首页 >> 其它课程 >>

unix高级编程apue习题答案

unix高级编程apue习题答案


下载

下载

附录C 习 题 答 案
第1章
1.1 利用ls(1)命令中的下面两个选择项:-i—显示文件或目录的 i节点数目(关于 i节点在 4.14节中会详细讨论);-d—如果参数是一目录,只列出其名字,而不是目录中的所有文件. 执行命令的结果为:
$ ls - l d i / e t c / . 3077 drwxr-sr-x 2 drwxr-xr-x 13 $l s - l d i / . / . . 2 drwxr-xr-x 13 2 drwxr-xr-x 13 / e t c / . . - i要求打印i节点的数量 7 bin 2048 Aug 5 20:12 /etc/./ root 512 Aug 5 20:11 /etc/../ .和. .的i节点数均为 2 root 512 Aug 5 20:11 /./ root 512 Aug 5 20:11 /../

1.2 UNIX是多任务系统,所以,在程序1-4运行的同时其他两个进程也在运行. 1.3 假如perror的ptr参数是一个指针,则 perror就可以改变 ptr所指串的内容.所以利用限 定词const使得perror不能修改ptr所指的串.而 strerror的参数是错误号,由于其是整数类型并且 C传递的是参数值,因此 strerror不能修改参数的值,也就没有必要使用 const属性. (如果C中函 数参数的处理不是很清楚,可参见 Kernighan和Ritchie〔1998〕5.2节. ) 1.4 调用fflush,fprintf和vprintf函数可修改errno的值.如果它的值变了但没有保存,则最 终显示的错误信息是不正确的. 在过去开发的许多程序中,都可以发现不保存 e r r n o的情况,典型的错误信息是" Not a t y p e w r i t e r(打字机不存在) .5 . 4节中标准 I / O库根据标准I / O流是否指向终端设备而改变流的 " 缓存器.istty(见11.9节)通常用来判断流是否指向终端设备,如果流不指向终端设备, errno 可能置为ENOTTY,从而引起该错误.程序 C-1显示了这一特性.
程序C-1 errno和printf的交互作用

下载
执行上面的程序,结果为:

附录C 习 题 答 案

519

$ grep BSD /etc/motd 4.3 BSD UNIX #29: Thu Mar 29 11:14:13 MST 1990 $ a.out open error: error = 2 工作正常, s t d o u t是一个终端 $ a.out > temp.foo $ cat temp.foo open error: error = 25 错误

1.5 2038年. 1.6 大约248天.

第2章
2.1 下面是4.3+BSD中使用的技术.在<machine/ansi.h> 中,用大写字母定义可在多个头 文件中出现的基本数据类型.例如:
#ifndef #define #define #define ... #endif /* _ANSI_H_ */ _ANSI_H_ _ANSI_H_ _CLOCK_T_ unsigned long _SIZE_T_ unsigned int

以下面的顺序可以在这6个头文件中分别定义size_t.
#ifdef _SIZE_T_ typedef _SIZE_T_ size_t; #undef _SIZE_T_ #endif

这样,实际上只执行一次typedef.

第3章
3.1 所有的磁盘I/O都要经过内核的块缓存器,唯一例外的是对原始磁盘设备的 I/O,但是 我们不考虑这种情况( Bach〔1986〕的第3章讲述了这种缓存器的操作) .既然read或write的数 据都要被内核缓存,那么术语"无缓存装置的 I / O"指的是在用户的进程中对这两个函数不会 自动缓存,每次read或write就要进行一次系统调用. 3.3 每次调用open函数就分配一个文件表项,如果两次打开的是相同的文件,则两个文件 表项指向相同的 v节点.调用 d u p引用已存在的文件表项(此处指 f d 1的文件表项) ,见图 C - 1. 当F_SETFD作用于fd1时,只影响 fd1的文件描述符标志; F_SETFL作用于fd1时,则影响 fd1及 fd2的文件描述符标志. 3.4 如果fd是1,执行dup2(fd,1)后返回1,但是没有关闭描述符 1(见3.12节) .调用3次 dup2后,3个描述符指向相同的文件表项,所以不需要关闭描述符. 如果fd是3,调用3次dup2后,有4个描述符指向相同的文件表项,所以需要关闭描述符 3.

520

UNIX环境高级编程

下载
文件表 文件状态标志 当前文件位移 v节点指针 v节点表 v节点信息 i节点信息 文件状态标志 当前文件位移 v节点指针 当前文件长度

进程表项 fd标志 ptr

图C-1 open和dup的结果

3.5 shell从左到右处理命令行,所以
a.out > outfile 2>&1

首先设置标准输出到outfile,然后执行dups将标准输出复制到描述符2(标准错误)上,其 结果是将标准输出和标准错误设置为相同的文件,即描述符 1和2指向相同的文件表项.而对于 命令行
a.out 2 >&1 >outfile

由于首先执行 dups,所以描述符 2成为终端(假设命令是交互执行的) ,标准输出重定向到 outfile.结果是描述符1指向outfile的文件表项,描述符 2指向终端的文件表项. 3.6 这种情况之下,仍然可以用lseek和read函数读文件中任意一处的内容.但是 write函数 在写数据之前会自动将文件位移量设置为文件尾,所以写文件时只能从文件尾开始,不能在任 一位置.

第4章
4.1 stat函数总是顺一个符号连接向前,所以修改后的程序不会显示文件类型是"符号连 接" .例如: / b i n是/ u s r / b i n的一个符号连接,但是 s t a t函数的结果只显示 / b i n是一个目录,而不 说明它是一个符号连接.若一个符号连接指向一不存在的文件,则 stat出错返回. 4.2 将下面的几行语句加入<ourhdr.h>
#if defined (S_IFLNK) && !defined(S_ISLNK ) #define S_ISLNK(mode) (((mode) & S_IFMT ) == S_IFLNK) #endif

这是一个我们编写的头文件如何屏蔽某些系统差别的实例. 4.3 关闭了该文件的所有存取许可权.
$ umask 777 $ data > temp.foo $ ls -l temp.foo ---------- l s t e v e n s

29 Jan 14 06:39 temp.foo

4.4 下面的命令表示关闭用户读许可权的情况.
$ data > foo $ chmod u - r f o o 关闭用户读许可权 $ ls -l foo 验证文件的许可权 --w-rw-r-- l s t e v e n s 29 Jul 31 09:00 foo $ cat foo 读文件

下载
cat: foo: Permission denied

附录C 习 题 答 案

521

4.5 如果用open或creat创建已经存在的文件,则该文件的存取许可权不变.程序 4-3可以 验证这点.
$ rm foo bar $ data > foo $ data > bar $ chmod a-r foo bar $ ls -l foo bar --w--w---l stevens --w--w---l stevens $ a.out $ ls -l foo bar --w--w---lst e v e n s --w--w---lst e v e n s 删除文件 创建文件 关闭所有的读许可权 验证其许可权 29 Jul 31 10:47 bar 29 Jul 31 10:47 foo 运行程序4 - 3 检查文件的许可权和大小 0 Jul 31 10:47 bar 0 Jul 31 10:47 foo

可以看出存取许可权没有改变,但是文件长度缩短了. 4.6 目录的长度从来不会是0,因为它总是包含 .和 ..两项.符号连接的长度指其路径名包 含的字符数,由于路径名中至少有一个字符,所以长度也不为 0. 4.8 当创建新的core文件时,内核对其存取许可权有一个默认设置,在本例中是 rw-r--r--. 这一默认值可能会可能不会被 umask的值修改. shell对创建的重定向的新文件也有一个默认的 访问许可权,本例中为rw-rw-rw-.这个值总是被当前的umask修改,在本例中umask为02. 4.9 不能使用du的原因是它需要文件名,如:
du tempfile

或目录名,如:
du .

只有当unlink函数返回时才释放 tempfile的目录项, du.命令没有计算仍然被 tempfile占用的 空间.本例中只能使用df命令察看文件系统中实际可用的自由空间. 4.10 如果被删除的链接不是该文件的最后一个链接,则该文件不会删除.此时,文件的 状态改变时间被更新.如果是最后一个链接被删除,则该文件将被物理删除.这时再去更新文 件的状态改变时间就没有意义,因为包含文件所有信息的 i节点将会随着文件的删除而被释放. 4.11 用opendir打开一个目录后,循环调用函数 dopath.假设opendir使用一个文件描述符, 并且只有处理完目录后调用 c l o s e d i r才释放描述符,这就意味着每次打开目录就要降一级使用 另外一个描述符.所以系统可打开的描述符数就限制了文件系统中树的深度. SVR4中的ftw允 许调用者指定使用的描述符数,这隐含着该实现可以关闭描述符并且重用它们. 4.13 chroot函数用于辅助因特网文件传输程序( FTP)中的安全性.系统中没有帐号的用 户(也称为匿名 F T P)放在一个单独的目录下,利用 c h r o o t将此目录当作新的根目录就可以阻 止用户访问此目录以外的文件. chroot也用于在另一台机器上构造一文件系统层次结构的一个副本,然后修改此副本,但 不更改原来的文件系统.这可用于测试新软件包的安装. chroot只能由超级用户使用,一旦更 改了一个进程的root,该进程及其后代进程就再也不能恢复至原先的 root. 4.14 首先调用stat函数取得文件的三个时间值,然后调用 utime设置期望的值.我们不希 望在调用utime时改变的值就是stat中相应的值. 4.15 finger(1)对邮箱调用stat函数,最近一次的修改时间是上一次接收邮件的时间,最近 存取时间是上一次读邮件的时间.

522

UNIX环境高级编程

下载

4.16 对cpio来说,既可以改变文件的访问时间( st_atime)和修改时间(st_mtime) ,也可 以都不改变. c p i o的- a选项可以在读文件后重新设置文件的存取时间,改变文件的存取时间. 另一方面,-m将文件的修改时间和存取时间保存为归档时的值. 对tar来说,在抽取文件时,其默认方式是复原归档时的修改时间,但是 -m选择项则将修改 时间设置为抽取文件时的时间.无论 tar在何种情况,文件的存取时间均是抽取文件时的时间. 由于不能修改状态改变时间( utime也只能改变访问时间和修改时间) ,所以没有将其保存 在文档上. 4.17 read改变了文件存取时间,为了消除这一影响,有些版本的 file(1)调用utime恢复文 件的存取时间,但是这样做会修改文件的状态改变时间. 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 创建深目录树

下载

附录C 习 题 答 案

523

运行后得到:
$ a.out getcwd getcwd ... getcwd length failed, size = 1025: Result too large failed, size = 1125: Result too large 3 3行 failed, size = 4525: Result too large = 4613 显示4 6 1 3字节的路径名

但是由于文件名太长了,不能用 tar或cpio对该目录建立档案文件,而且也不能用 rm -r命令删除 该目录. (怎样才能删除该目录树?) 4.19 /dev目录关闭了一般用户的写许可权,所以用户不能删除目录中的文件,即 nlink失败. u

第5章
5.2 fgets函数读入数据,直到行结束或缓存满(当然会留出一个字节存放终止字符) .同 样, f p u t s 只负责将缓存的内容输出,而并不考虑缓存中是否包含换行符.所以,如果将 M A X L I N E设得很小,这两个函数仍然会正常工作,只不过被执行的次数要比 M A X L I N E值较 大的时候多. 如果这些函数删除或添加换行符(如 gets和puts) ,则必需保证缓存足够大. 5.3 当printf没有输出任何字符时,如:printf(""),则返回0. 5.4 这是一个比较常见的错误.getc以及getchar的返回值是整型,而不是字符型.由于EOF经 常定义为-1,那么如果系统使用的是有符号的字符类型,程序还可以正常工作.但如果使用的 是无符号字符类型,那么返回的EOF被保存到字符c后将不再是-1,所以,程序会进入死循环. 5.5 5 个字符长的前缀, 4个字符长的进程内唯一标识再加 5个字符长的系统内唯一标识 (进程ID)刚好组成14位的UNIX传统文件长度限制. 5.6 使用方法为:先调用 fflush后调用fsync,fsync所使用的参数由 fileno函数获得.如果 不调用fflush,所有的数据仍然在内存缓存中,此时调用 fsync将没有任何效果. 5.7 当程序交互运行时,标准输入和输出设备均为行缓存方式.每次调用 fgets时标准输出 设备将自动刷清.

第6章
6.1 在SVR4系统中,用户手册中讲述了存取阴影口令文件的函数.我们不能使用 6.2节所 述函数返回的pw_passwd变量来比较加密口令.正确的方法是使用阴影口令文件中对应用户的 加密口令来进行比较. 在 4 . 3 + B S D 系统中,口令文件的阴影是自动建立的.仅当调用者的用户 I D 为 0 时, getpwnam或getpwuid函数返回的passed结构中的pw_passwd字段才包含有加密口令. 6.2 在SVR4系统中,程序C-3将输出加密口令.当然,除非有超级用户许可权,否则调用 getspnam将返回EACCES错误.

524

UNIX环境高级编程

下载

程序C-3 在SVR4系统中输出加密口令

在4.3+BSD系统中,具有超级用户许可权时,程序 C-4将输出加密口令.否则 pw_passed的 返回值为星号(* ).
程序C-4 在4.3+ BSD系统中输出加密口令

6.4 程序C-5以date格式输出日期.
程序C-5 以date(1)的格式输出日期和时间

下载

附录C 习 题 答 案

525

程序C-5的运行结果如下:
$ echo $TZ 作者使用的默认值 MST7 $ a.out Wed Jan 15 06:48:57 MST 1992 $ TZ=EST5EDT a.out 美国东海岸 Wed Jan 15 08:49:06 EST 1992 $ TZ=JST-9 a.out 日本 Wed Jan 15 22:49:12 JST 1992

第7章
7.1 原因在于printf的返回值(输出的字符数)变成了 main函数的返回码.当然,并不是 所有的系统都会出现该情况. 7.2 当程序处于交互运行方式时,标准输出设备通常处于行缓存方式,所以当输出换行符 时,上次的结果才被真正输出.如果标准输出设备被定向到一个文件而处于完全缓存方式,则 当标准I/O清理操作执行时,结果才真正被输出. 7.3 由于agrc和a rg v不像environ一样保存在全局变量中,所以在大多数 UNIX系统中没有 其他办法. 7.4 当C程序复引用一个空指针出错时,执行该程序的进程将终止,于是可以利用这种方 法终止进程. 7.5 定义如下:
typedef void Exitfunc(void) ; f * i n t a t e x i t ( E x i t f u n c unc) ;

7.6 calloc将分配的内存空间初始化为 0.但是ANSI C并不保证0值与浮点0或空指针的值 相同. 7.7 只有通过exec函数执行一个程序时,才会分配堆和堆栈. 7.8 可执行文件包含了用于调试core文件的符号表信息,用strip(1)可以删除这些信息,对 两个a.out文件执行这条命令,它们的大小减为 98 304和16 384. 7.9 没有使用共享库时,可执行文件的大部分都被标准 I/O库所占用. 7.10 这段代码不正确.因为在if语句中定义了自动变量val,所以当if中的复合语句结束时, 该变量就不存在了,但是在if语句之外又用指针引用已经不存在的自动变量 val.

第8章
8.1 用下面几行代替程序8-2中调用printf的语句:
i = printf("pid = %d, glob = %d, var = %d\n", getpid( ), glob, var); sprintf (buf,"%d\n", i);

526

UNIX环境高级编程

下载

write (STDOUT_FILENO, buf, strlen(buf));

注意要定义变量i和buf. 这里假设子进程调用 e x i t时只关闭标准 I / O流,并不关闭与标准输出相关的文件描述符 S T D O U T _ F I L E N O.有些版本的标准 I / O库会关闭与标准输出相关的文件描述符从而引起写失 败,这种情况就调用dup将标准输出复制到另一个描述符, write则使用新复制的文件描述符. 8.2 可以通过程序C-6来说明这个问题.
程序C-6 错误使用 vfork的例子

当函数f1调用vfork时,父进程的堆栈指针就指向 f1的栈帧,见图 C-2.vfork使得子进程先 执行然后从 f1返回,接着子进程调用 f2并且覆盖了 f1的堆栈区间,在 f2中子进程将自动变量 buf 的值置为0,即将堆栈中的1000个字节的值都置为0.从f2返回后父进程继续执行调用 _exit,这 时堆栈中main以下的内容已经被f2修改了,但是父进程仍然以为调用了 vfork后从f1返回.返回 信息虽然保存在堆栈中,但是可能已经被子进程修 改了.对这个例子,父进程继续执行的结果要依赖 栈顶 main函数的堆栈区 于实际的UNIX系统. (如:返回信息保存在堆栈的 具体位置,修改动态变量时覆盖了哪些信息等等. ) 通常的结果是一个core文件. fl函数的堆栈区 栈延伸的方向 8.3 在程序8 - 7中我们先让父进程输出,但是 当父进程输出完毕子进程要输出时,要让父进程终 图C-2 调用vfork时的栈帧 止.是父进程先终止还是子进程先执行输出要依赖

下载

附录C 习 题 答 案

527

于内核对两个进程的调度. shell在父进程终止后会开始执行其他程序,这样也许仍会影响子进 程.要避免这种情况就是在子进程完成输出后才终止父进程.用下面的语句替换程序中 fork后 面的代码.由于只有终止父进程才能开始下一个程序,所以不会出现上面的情况.

8.4 对argv[2]打印的是相同的值(/home/stevens/bin/testinterp) .原因是execlp在结束时调 用了execve,并且与直接调用 execl的路径名相同. 8.5 不提供返回保存的设置-用户-ID的函数,我们必须在进程开始时保存有效的用户 ID. 8.6 程序C-7创建了一个僵死进程.
程序C-7 创建一个僵死进程并用 ps查看其状态

执行程序结果如下( ps(1)用Z表示僵死进程) :
$ a.out PID TT 5940 p3 5941 p3 5942 p3 5943 p3 S T A TT I M E S 0:00 Z 0:00 S 0:00 R 0:00 COMMAND a.out <defunct> sh -c ps ps

僵死进程

第9章
9.1 因为init是login shell的父进程,当登录shell终止时它收到 SIGCHLD信号量,所以 init 进程知道什么时候终端用户注销. 网络登录没有包含 i n i t,相应的注销项是由一个处理登录并监测注销的进程写的(本例中 为telnetd) .

528
第10章

UNIX环境高级编程

下载

10.1 当程序第一次接收到发送给它的信号量时就终止了.因为一捕捉到信号量 pause函数 就返回. 10.2 程序C-8实现了raise函数.
程序C-8 raise函数的实现

10.3 见图C-3.
处理SIGINT时 处理SIGINT时 进行longjmp之后

栈顶

函数main 的堆栈区

函数main 的堆栈区 函数sig_int 的堆栈区

函数main 的堆栈区 函数sig_int 的堆栈区 函数sig_alrm 的堆栈区

函数main 的堆栈区

图C-3 longjmp前后的堆栈状态

从sig_alrm通过longjmp返回main,有效地避免了继续执行 sig_int. 10.4 如果进程在调用 alarm和setjmp之间被内核阻塞了,alarm时间走完之后就调用信号量 处理程序,然后调用longjmp.但是由于没有调用 setjmp,所以没有设置env_alrm缓存区.如果 longjmp的跳转缓存没有被 setjmp初始化,则说明没有定义 longjmp的操作. 10.5 参见Don Libes的"Implementing Software Timers" Users Journal, Vol. 8, no. 11, (C Nov. 1990)中的例子. 10.7 如果仅仅调用_exit,则进程终止状态就不能表示该进程是由于 SIGABRT信号量而终 止的. 10.8 如果信号量是由其他用户的进程发出的,进程必须设置用户的 ID为根或者是接收进 程的所有者,否则kill不能执行.所以实际的用户ID为信号量的接收者提供了更多的信息. 10.10 对于本书中所用的系统,大约每 6 0 ~ 9 0分钟增加一秒,这个误差是因为每次调用 sleep都要调度一次将来的时间事件,但是由于CPU调度,有时我们并没有在事件发生时被叫醒. 另外一个原因是开始运行进程和调用 sleep都需要一定的时间. BSD中的cron每分钟都要取当前时间,它首先设置一个休眠周期,然后在下一分钟开始时 唤醒.大多数调用是 sleep(60),偶尔有一个sleep(59)用于在下一分钟同步.但是若在进程中花 费了许多时间执行命令或者系统的负载重调度慢,这时休眠值可能远小于 60.

下载

附录C 习 题 答 案

529

1 0 . 11 在S V R 4中,从来没有调用过 S I G X F S Z的信号量处理程序,一旦文件的大小达到 1024时,write就返回24. 在4.3+BSD中,文件大小达到 1500字节时调用该信号处理程序, write返回-1并且errno设 置为EFBIG. SunOS4.1.2的情况与SVR4类似,但是调用了该信号量处理程序. 系统V在文件大小达到软资源限制时无错返回一个较小的数,而 BSD判断文件超出限制时 错误返回,没有写任何数据. 10.12 结果依赖于标准 I/O库的实现 —fwrite如何处理一个被中断的写.

第11章
11.1 注意由于终端是非正规模式,所以要用换行符而不是回车终止 reset命令. 11.2 它为128个字符建了一张表,根据用户的要求设置奇偶校验位.然后使用 8位I/O处理 奇偶位的产生. 11.3 在SVR4中运行stty -a,并且将标准输入重定向到运行 vi的终端,结果显示 vi设置MIN 为1,TIME为1.reads等待至少敲入一个字符,但是该字符输入后,只对后继的字符等待十分 之一秒即返回. 11.4 在S V R 4中使用扩展的通用终端接口.参见 AT & T〔1 9 9 1〕手册中的t e r m i o x ( 7 ).在 4.3+BSD中使用c_cflag字段的CCTS_OFLOW和CRTS_IFLOW标志,参见表11-1.

第12章
12.1 程序运行正常,不会发生 E N O L C K的错误.第一次循环调用 w r i t e w _ l o c k,w r i t e和 un_lock.调用un_lock后只保留了第一个字节的锁,第二次循环时,调用 writew_lock使得新锁 已加锁 与第一个字节的锁合并,图C-4是第二次循环的结果. 每循环一次就扩展一个字节的锁,内核将这些锁合 第二个 第一个 并后就只保持了一个锁,因此符合锁结构的定义. 字节 字节 12.2 在SVR4和4.3+BSD中,fd_set是只包含一个成 员的结构,该成员为一个长整型数组.数组中每一位对 图C-4 第二次循环后锁的状态 应于一个描述符.四个 F D _宏通过开关或测试指定的位 来操纵这个数组.将之定义为一个包含数组的结构而不仅仅是一个数组的原因是:通过 C语言 的赋值语句,可以使fd_set类型变量相互赋值. 12.3 在SVR4和4.3+BSD中允许用户在头文件<sys/types.h> 前定义常数FD_SETSIZE.例 如下面的代码可以使fd_set数据类型包含2048个描述符.
#define FD_SETSIZE 2048 #include <sys/types.h>

12.4 下面列出了功能类似的函数.
FD_ZERO FD_SET FD_CLR FD_ISSET sigemptyset sigaddset sigdelset sigismember

530

UNIX环境高级编程

下载

没有与sigfillset对应的FD_xxx函数.对信号量来说,指向信号量集合的指针是第一个参数, 信号量数是第二个参数;对于描述符来说,描述符数是第一个参数,指向描述符集合的指针是 第二个参数. 12.5 最多五种信息:数据,数据长度,控制信息,控制信息的长度和标志. 12.6 利用select实现的程序见C-9,利用poll实现的程序见C-10.
程序C-9 用select实现sleep_us函数

程序C-10 用poll实现sleep_us函数

BSD中的usleep(3)使用setitimer设置间隔计时器,并且执行 8个系统调用.它可以正确地和 调用进程设置的其他计时器交互作用,而且即使捕捉到信号量也不会被中断. 12.7 不行.我们可以使TELL_WAIT创建一个临时文件,其中一个字节用作为父进程的锁, 另一个字节用作为子进程的锁. WAIT_CHILD使得父进程等待子进程的锁, TELL_PARENT使 得子进程释放子进程的锁.但是问题在于调用 fork后,子进程释放了所有的锁导致子进程不能 具有任何它自己的锁而开始执行. 12.8 用select的方法见程序C-11,使用poll的情况类似.
程序C-11 用select计算管道的性能

下载

附录C 习 题 答 案

531

在SVR4和SunOS 4.1.1中使用select和poll计算出的结果等于表 2-6的值.在4.3+BSD中使用 select计算的结果为3073. 12.9 在SVR4,4.3+BSD和SunOS 4.1.2中,程序12-14确实修改了输入文件的最近一次存 取时间.

第13章
13.1 如果进程调用 chroot就不能打开/dev/log,解决的办法是在 chroot之前调用选择项为 LOG_NDELAY的openlog.这样即使调用了 chroot之后,仍然可以打开特定的设备文件( UNIX 与数据报套接口)并生成一个有效的描述符. 13.3 程序C-12是一种解决方案.
程序C-12 调用daemon_init获得注册名

结果依赖于不同的系统实现和是否关闭文件描述符 1,2和3.关闭描述符影响结果的原因 是:当程序开始执行时与控制终端连接,调用 deamon_init后关闭3个描述符就意味着getlogin没

532

UNIX环境高级编程

下载

有控制终端,所以不能在 utmp文件中看到登录项. 但是在4 . 3 + B S D中,登录名是由进程表维护的,并且可以通过 f o r k复制.也就是说除非其 父进程没有登录名(如系统自引导时调用 init) ,否则进程总能获得其登录名.

第14章
14.1 如果写管道端总是不关闭,则读者就决不会看到文件的结束符.页面调度程序就会 一直阻塞在读标准输入. 14.2 父进程向管道写完最后一行以后就终止了,然后读者读到管道的结尾时自动关闭管 道.但是由于子进程(页面调度程序)要等待输出的页,所以父进程可能比子进程领先一个管 道缓存器.如果在一个交互式命令行 shell上运行,当父进程终止时 shell会改变终端的模式并提 示用户.由于大部分页面调度程序在等待处理下一个页面时将终端设置为非正规模式,所以终 止父进程就会影响页面调度程序. 14.3 因为执行了 shell,所以popen返回一个文件指针.但是 shell不能执行不存在的命令, 因此在标准错误上显示下面信息后终止,其退出状态为 1,调用pclose就将该退出状态返回.
sh: a.out: not found

14.4 当父进程终止时,用shell看它的终止状态.对于Bourne shell和KornShell所用的命令是
echo $?

打印的结果是128加信号量数. 14.5 首先加入下面的定义,
FILE *fpin, *fpout;

然后用f d o p e n关联管道描述符和标准 I / O流,并将流设置为行缓存的,在 w h i l e循环从标准 输入读之前作下面的工作.

while中的read和write用下面的语句代替:

14.6 虽然system函数调用了wait,但是终止的第一个子进程是由 popen产生的,所以它将 再次调用wait并一直阻塞到sleep完成,然后system返回.当pclose调用wait时,由于没有子进程 可等待所以返回出错,导致 pclose也返回出错. 14.7 select表明描述符是可读的. 调用read读完所有的数据后返回0就表明到达了文件尾端. 对于poll(假设管道是一个流设备)来说,若返回 POLLHUP也许仍有数据可以读.但是一旦读 完了所有的数据 r e a d就返回 0,即表明到达了文件尾端. p o l l读完了所有的数据后并不返回 POLLIN.

下载

附录C 习 题 答 案

533

对于被读者关闭的指向管道的输出描述符来说, s e l e c t表明描述符是可写的,调用 w r i t e时 产生SIGPIPE信号量,如果忽略该信号量或从信号量处理程序中返回时 write就返回EPIPE错误. 而对于poll,如果管道是一个流设备, poll就对该描述符返回POLLHUP. 14.8 子进程向标准出错写的内容同样也在父进程的标准出错中出错.只要在 cmdstring中 包含重定向2 > &1命令,就可以将标准出错发送给父进程. 14.9 popen生成一个子进程,子进程通过 exec执行Bourne shell.然后shell再调用fork,最 后由shell的子进程执行命令串.当 cmdstring终止时shell恰好在等待该事件,然后 shell退出,而 这一事件又是pclose中waitpid所等待的. 14.10 解决的办法是打开 FIFO两次,一次读一次写.我们绝不会使用为写而打开的描述 符,但是使该描述符打开就可在客户数从 1变为0时,阻止产生文件终止.打开 F I F O两次需要 注意下列操作方式:第一次以非阻塞,只读方式 o p e n,第二次以阻塞,只写方式 o p e n. (如果 用非阻塞,只写方式打开将返回错误. )然后关闭读描述符的非阻塞属性.参见程序 C-13.
程序C-13 以非阻塞方式打开 FIFO进行读写操作

14.11 随意读取现行队列中的消息会干扰客户机 -服务器协议,导致丢失客户机请求或者 服务器的响应.由于队列允许所有的用户读,所以进程只要知道队列的标识符就可以读队列. 14.13 由于服务器和客户机都可能将段连接到不同的地址,所以在共享存储段中不存放实 际物理地址.相反,当在共享存储段中建立链表时,指针的值设置为共享存储段内的位移.位 移量为所指目标的实际地址减去共享存储段的起始地址. 14.14 表C-1显示了相关的事件.
表C-1 程序14-12中父子进程间的切换过程
父进程中i的值 子进程中 i的值 共享变量的值 0 1 update的返回值 备 注

由mmap初始化 子进程先运行,然后阻塞

534
父进程中i的值 0

UNIX环境高级编程

下载
(续)
共享变量的值 备 父进程运行 1 0 2 1 父进程阻塞 子进程恢复 子进程阻塞 父进程恢复 3 2 4 3 父进程阻塞 注

子进程中i的值

update的返回值

3 2

5 4

子进程阻塞 父进程恢复

第15章
15.3 说明(declaration)指定了标识符集合的属性(例如数据类型) ,如果说明的同时分 配了存储单元就是定义( definition) . 在头文件 o p e n . h中用e x t e r n说明了三个全局变量,这时并没有为它们分配存储单元,在文 件main.c中定义了三个全局变量,有时会在定义时就初始化全局变量,但通常使用 C的默认值. 15.5 select和poll都返回就绪的描述符个数.当将这些就绪描述符都处理完后,操作 client 数组的循环就可以结束.

第16章
16.1 _db_dodelete中保守的加锁操作是为了避免和 db_nextrec发生竞态条件.如果没有使 用写锁保护_db_writedat调用,则有可能在_db_nextrec读某个记录时擦去该记录: db_nextrec首 先读入一个索引记录,发现该记录非空,则接着读入记录内容,但是在它调用 _ d b _ r e a d i d x后 _db_readdat前,该记录却给_db_dodelete删除了. 16.2 假定db_nextrec调用了_db_readidx,它将记录的关键字读入索引缓存进行处理.但该处 理过程被内核调度进程打断,另一个执行的进程刚好调用db_delete删除了这一条记录,使得索引 文件和记录文件中对应的内容都被清空.当第一个进程恢复执行并调用 _ d b _ r e a d d a t(在 db_nextrec函数体中)时,返回的是空记录.所以db_nextrec中的读锁使得读入记录索引的过程和 读入记录内容的过程是一个原子操作(至少对其他操作同一数据库的并发进程中的写操作而言) . 16.3 强制锁对其他的读者和写者产生了影响 — 其他的读和写操作都被阻塞,直到 _db_writeidx和_db_writedat设置的锁被解除.

第17章
17.1 psif 必须读取文件的前两个字节并且与 % !进行比较.如果文件是可以随机定位的, 则可以r e w i n d文件,并调用 l p r p s或t e x t p s.如果文件不可随机定位,则只能将该两个字节重新 放回标准输入设备.此时一个可行的办法是:建立一个管道并 fork一个子进程.然后父进程将 其标准输入设备设置为管道,并执行 textps或lprps.然后子进程将它读到的两个字节写入管道, 再接着将文件的其他部分输出.

下载
第18章

附录C 习 题 答 案

535

18.2 通常 g e t o p t只用来处理单个参数列表.在 g e t o p t函数的初始化数据段,全局变量 optind被初始化为 1.但是在我们的服务器中,调用 getopt来处理多个参数列表 — 每个客户机 一个,所以必须在为每个客户机首次调用 getopt前均重新初始化optind. 18.3 我们使用了 Client结构维护Systems文件的位移量.如果在保存了位移量后,再次使 用之前修改了该文件,则有可能上次保存的位移量不再指向以前指向的行.虽然服务器可以检 测到文件是否被修改了(如何检测?) ,但是我们无法再将位移量恢复到原来的正确位置.当 文件修改后,我们唯一的办法是不让有关用户再登录进来. 18.4 只有当 c l i e n t _ a d d被调用时,才能通过 r e a l l o c移动c l i e n t数组.因为 c l i e n t _ a d d是在 select后,而不是在使用 cliptr的循环中. 18.5 发送到远端系统的不同命令可能会被混淆起来.可以在 take_put_args中加入一些检 查功能实现命令的区分. 18.6 一个常用的方法是:要求用户在修改任何文件后通知服务器,以使服务器可以重新 读取文件.SIGHUP命令就是经常用来完成这项任务的. 18.9 可以在远端执行stty命令,然后分析其输出结果.但是考虑到不同 UNIX平台的stty命 令输出结果差别很大,这种方式实现起来较为困难.

第19章
19.1 telnetd和rlogind两个服务器均以超级用户的许可权运行 ,所以它们都可以成功地调用 chown和chmod. 19.3 执行:
pty -n stty -a

以避免伪终端从设备的termios 结构和winsize 结构初始化. 19.5 很不幸,fcntl 的F_SETFL 命令不允许改变读-写状态. 19.6 有三个进程组: (1)登录shell, (2)pty 父进程和子进程, (3)cat进程.前两个进 程组与登录shell组成了一个对话期,其中,登录 shell为对话期首进程.第二个对话期仅包含 cat 进程.第一个进程组(登录shell)是后台进程组,其他两个进程组是前台进程组. 19.7 当接受到文件终止符时,首先是 cat终止,然后是伪终端从设备及伪终端主设备.接 着,正从伪终端主设备读取的 pty父进程产生一个文件终止符,该父进程将 SIGTERM信号发送 给子进程,子进程终止(子进程不捕捉该信号) .最后,父进程调用main 函数尾端的exit(0). 程序8-17中相关的输出为:
cat pty pty e = e = e = 270, chars = 262, chars = 288, chars = 274, stat = 40, stat = 188, stat = 0: 15: F 0: X

19.8 这可通过使用shell的echo和date(1)命令实现:
#!/bin/sh (echo "Script started on " `date`; pty "${SHELL:-/bin/sh}"; echo "Script done on " `date`) | tee typescript

19.9 伪终端上的行规程能够回应,故 pty可以读取其标准输入,并写向伪终端主设备.尽 管程序(ttyname)从不读取输入,该回应也可通过伪终端上的行规程模块实现.


推荐相关:
网站首页 | 网站地图
All rights reserved Powered by 大学生考试网 9299.net
文档资料库内容来自网络,如有侵犯请联系客服。zhit325@qq.com