📄 appendix_c.htm
字号:
<p>父进程 </p>
<p>的锁,另一个字节用作为子进程的锁。WAIT-CHILD使得父进程等待子进程的</p>
<p>锁,T </p>
<p>ELL_PARENT使得子进程释放子进程的锁。但是问题在于调用fork后,子进程</p>
<p>释放了 </p>
<p>所有的锁导致子进程不能具有任何它自己的锁而开始执行。 </p>
<p>12.8 用select的方法见程序C.11,使用poll的情况类似。 </p>
<p>#include <sys/types.h> </p>
<p>#include <sys/time.h> </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int i, n, fd[2]; </p>
<p>fd_set writeset; </p>
<p>struct timeval tv; </p>
<p>if (pipe(fd) < 0) </p>
<p>err_sys("pipe error"); </p>
<p>FD_ZERO(&writeset); </p>
<p>for (n = 0; ; n++) { /* write 1 byte at a time until pipe is</p>
<p>full */ </p>
<p>FD_SET(fd[1], &writeset); </p>
<p>tv.tv_sec = tv.tv_usec = 0; /* don't wait at all */ </p>
<p>if ( (i = select(fd[1]+1, NULL, &writeset, NULL, &tv)) < 0) </p>
<p>err_sys("select error"); </p>
<p>else if (i == 0) </p>
<p>break; </p>
<p>if (write(fd[1], "a", 1) != 1) </p>
<p>err_sys("write error"); </p>
<p>} </p>
<p>printf("pipe capacity = %d\n", n); </p>
<p>exit(0); </p>
<p>} </p>
<p>程序C.11 用select计算管道的性能 </p>
<p>在SVR4和SunOS 4.1.1中使用select和poll计算出的结果等于图2.6的值。</p>
<p>在4.3B </p>
<p>SD中使用select计算的结果为3073。 </p>
<p>12.9 在SVR4、4.3+BSD和SunOS 4.1.2中,程序12.14确实修改了输入文件</p>
<p>的最近一 </p>
<p>次访问时间。 </p>
<p> </p>
<p>第十三章 </p>
<p>13.1 如果进程调用chroot就不能打开/dev/log,解决的办法是在chroot</p>
<p>之前调用 </p>
<p>选项为LOG_NDELAY的openlog。这样即使调用了chroot之后,仍然可以打开</p>
<p>特定的 </p>
<p>设备文件(UNIX与数据包套接口)并生成一个有效的描述符。 </p>
<p>13.3 程序C.12是一种解决方案。 </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>char *ptr, buff[MAXLINE]; </p>
<p>daemon_init(); </p>
<p>close(0); </p>
<p>close(1); </p>
<p>close(2); </p>
<p>ptr = getlogin(); </p>
<p>sprintf(buff, "login name: %s\n", </p>
<p>(ptr == NULL) ? "(empty)" : ptr); </p>
<p>write(3, buff, strlen(buff)); </p>
<p>exit(0); </p>
<p>} </p>
<p>程序C.12 调用daemon_init获得注册名 </p>
<p>结果依赖于不同的系统实现和是否关闭文件描述符1、2和3。关闭描述符影响</p>
<p>结果 </p>
<p>的原因是:当程序开始执行时与控制终端连接,调用deamon_init后关闭3个</p>
<p>描述符 </p>
<p>就意味着getlogin没有控制终端,所以不能在utmp文件中看到登录项。
</p>
<p>但是在4.3+BSD中,登录名是由进程表维护的,并且可以通过fork复制。也</p>
<p>就是说 </p>
<p>除非其父进程没有登录名(如系统自引导时调用init),否则进程总能获得其</p>
<p>登录名 </p>
<p>。 </p>
<p> </p>
<p>第十四章 </p>
<p>14.1
如果写管道端总是不关闭,则读者就决不会看到文件的结束符。页面调</p>
<p>度程 </p>
<p>序就会一直阻塞在读标准输入。 </p>
<p>14.2
父进程向管道写完最后一行以后就终止了,然后读者读到管道的结尾时</p>
<p>自动 </p>
<p>关闭管道。但是由于子进程(页面调度程序)要等待输出的页,所以父进程可</p>
<p>能比子 </p>
<p>err_sys ( "setvbuf error" ) ; </p>
<p>while中的read和write用下面的语句代替。 </p>
<p>if (fputs ( line, fpout ) == EOF ) </p>
<p>err_sys ( "fputs error to pipe" ) ; </p>
<p>if ( fgets ( line, MAXLINE, FPIN ) == NULL ) { </p>
<p>err_msg ( "child close pipe" ) ; </p>
<p>break ; </p>
<p>} </p>
<p>14.6 虽然system函数调用了wait,但是终止的第一个子进程是由popen产</p>
<p>生的,所 </p>
<p>以它将再次调用wait并一直阻塞到sleep完成,然后system返回。当pclose</p>
<p>调用wa </p>
<p>it时,由于没有子进程可等待所以返回出错,导致pclose也返回出错。
</p>
<p>14.7 select表明描述符是可读的。调用read读完所有的数据后返回0就表明</p>
<p>到了文 </p>
<p>件尾。对于poll(假设管道是一个流设备)来说,若返回POLLHUP也许仍有数</p>
<p>据可以 </p>
<p>读。但是一旦读完了所有的数据read就返回0,即表明到了文件尾。poll读</p>
<p>完了所 </p>
<p>有的数据后并不返回POLLIN。 </p>
<p>对于被读者关闭的指向管道的输出描述符来说,select表明描述符是可写</p>
<p>的,调 </p>
<p>用write时产生SIGPIPE信号量,如果我们忽略该信号量或从信号量处理程序</p>
<p>中返回 </p>
<p>时write就返回EPIPE错误。而对于poll,如果管道是一个流设备,poll就</p>
<p>对该描述 </p>
<p>符返回POLLHUP。 </p>
<p>14.8
子进程向标准出错写的内容同样也在父进程的标准出错中出错。只要在</p>
<p>cmds </p>
<p>tring包含重定向2 > &1命令,就可以将标准出错发送给父进程。
</p>
<p>14.9 popen生成一个子进程,子进程通过exec执行Bourne shell。然后</p>
<p>Shell再调 </p>
<p>用fork,最后由shell的子进程执行命令串。当cmdstring终止时shell恰好</p>
<p>在等待 </p>
<p>该事件,然后shell退出,而这一事件又是pclose中waitpid所等待的。 </p>
<p>14.10 解决的办法是打开FIFO两次,一次读一次写。一般我们不会对打开的</p>
<p>描述符 </p>
<p>进行写,我们绝不会使用为写而打开的描述符,但是使该描述符打开就可在客</p>
<p>户数 </p>
<p>从1变为0时,阻止产生文件终止。打开FIFO两次需要注意下列操作方式:第</p>
<p>一次以 </p>
<p>非阻塞、只读方式open,第二次以阻塞、只写方式open。(如果用非阻塞、</p>
<p>只写方 </p>
<p>式打开将返回错误。)然后关闭读描述符的非阻塞属性。参见程序C.13。
</p>
<p>#include <sys/types.h> </p>
<p>#include <sys/stat.h> </p>
<p>#include <fcntl.h> </p>
<p>#include "ourhdr.h" </p>
<p>#define FIFO "temp.fifo" </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int fdread, fdwrite; </p>
<p>unlink(FIFO); </p>
<p>if (mkfifo(FIFO, FILE_MODE) < 0) </p>
<p>err_sys("mkfifo error"); </p>
<p>if ( (fdread = open(FIFO, O_RDONLY | O_NONBLOCK)) < 0) </p>
<p>err_sys("open error for reading"); </p>
<p>if ( (fdwrite = open(FIFO, O_WRONLY)) < 0) </p>
<p>err_sys("open error for writing"); </p>
<p>clr_fl(fdread, O_NONBLOCK); </p>
<p>exit(0); </p>
<p>} </p>
<p>程序C.13 以非阻塞方式打开FIFO进行读写操作 </p>
<p>14.11 随意读取现行的队列中的消息会干扰客户/服务器协议,导致丢失客户</p>
<p>的请 </p>
<p>求或者服务器的响应。由于队列允许所有的用户读,所以进程只要知道队列的</p>
<p>标识 </p>
<p>符就可以读队列。 </p>
<p>14.13
由于服务器和客户都可能将段连接到不同的地址,所以在共享的内存</p>
<p>段中不 </p>
<p>存放实际物理地址。相反,当在共享的内存段中建立链表时,指针的值设置为</p>
<p>共享 </p>
<p>内存段内的偏移。偏移量为所指目标的实际地址减去共享内存段的起始地址。</p>
<p>? </p>
<p>14.14 图C.5显示了相关的事件。 </p>
<p> </p>
<p>第十五章 </p>
<p>15.3
说明指定了标识符集合的属性(例如数据类型),如果说明的同时分配</p>
<p>了存 </p>
<p>储单元就是定义。 </p>
<p>在头文件<open.h>中用extern说明了三个全局变量,这时并没有为它们分配</p>
<p>存储 </p>
<p>单元,在文件<main.c>中定义了三个全局变量,有时会在定义时就初始化全</p>
<p>局变量 </p>
<p>,但通常使用C的缺省值。 </p>
<p>15.5 select和poll都返回就绪的描述符个数。当将这些就绪描述符都处理</p>
<p>完后, </p>
<p>操作client数组的循环就可以结束。 </p>
<p> </p>
<p>第十六章 </p>
<p>16.1 _db_dodelete中保守的上锁操作是为了避免和db_nextrec发生竞态</p>
<p>条件。如 </p>
<p>果没有使用写锁保护_db_writedat调用,则有可能在_db_nextrec读某个记</p>
<p>录时擦 </p>
<p>去该记录:db_nextrec首先读入一个索引记录,发现该记录非空,则接着读</p>
<p>入记录 </p>
<p>内容,但是在它调用_db_readidx后_db_readdat前,该记录却给</p>
<p>_db_dodelete删除 </p>
<p>了。 </p>
<p>16.2 假定db_nextrec调用了_db_readidx,它将记录的关键字读入索引缓</p>
<p>存进行处 </p>
<p>理。但该处理过程被内核调度进程打断,另一个执行的进程刚好调用</p>
<p>db_delete删 </p>
<p>除了这一条记录,使得索引文件和记录文件中对应的内容都被清空。当第一个</p>
<p>进程 </p>
<p>恢复执行并调用_db_readdat(在db_nextrec函数体中)时,返回的是空记</p>
<p>录。所以 </p>
<p>db_nextrec中的读锁使得读入记录索引的过程和读入记录内容的过程是一个</p>
<p>原子操 </p>
<p>作(至少对其它操作同一数据库的并发进程中的写操作而言)。 </p>
<p>16.3 强制的锁定对其它的读者和写者产生了影响--其它的读和写操作都被阻</p>
<p>塞, </p>
<p>直到_db_writeidx和_db_writedat设置的锁被解除。 </p>
<p> </p>
<p>第十七章 </p>
<p>17.1 psif必须读取文件的前两个字节并且与%!进行比较。如果文件是可以</p>
<p>随机定 </p>
<p>位的,则可以rewind文件,并调用lprps或textps。如果文件不可随机定</p>
<p>位,则只 </p>
<p>能将该两个字节重新放回标准输入设备。此时一个可行的办法是:建立一个管</p>
<p>道并 </p>
<p>fork一个子进程。然后父进程将其标准输入设备设置为管道,并执行textps</p>
<p>或lpr </p>
<p>ps。然后子进程将它读到的两个字节写入管道,再接着将文件的其它部分输</p>
<p>出。 </p>
<p> </p>
<p>第十八章 </p>
<p>18.2 通常getopt只用来处理单个参数列表。在getopt函数的初始化数据</p>
<p>段,全局 </p>
<p>变量optind被初始化为1。但是在我们的服务器中,我们调用getopt来处理</p>
<p>多个参 </p>
<p>数列表--每个client一个,所以我们必须在为每个client首次调用getopt</p>
<p>前均重新 </p>
<p>初始化optind。 </p>
<p>18.3 我们使用了Client结构维护系统文件的偏移值。如果在保存了偏移值</p>
<p>后、再 </p>
<p>次使用之前修改了该文件,则有可能上次保存的偏移值不再指向他以前指向的</p>
<p>行。 </p>
<p>虽然服务器可以检测到文件是否被修改了(如何检测?),但是我们无法再将</p>
<p>偏移值 </p>
<p>恢复到原来的正确位置。当文件修改后,我们唯一的办法是不让有关用户在登</p>
<p>录进 </p>
<p>来。 </p>
<p>18.4 只有当client_add被调用时,我们才能通过realloc移动client数</p>
<p>组。因为c </p>
<p>lient_add是在select后,而不是在使用cliptr的循环中。 </p>
<p>18.5 发送到远端系统的不同命令可能会被混淆起来。可以在</p>
<p>take_put_args中加入 </p>
<p>一些检查功能实现命令的区分。 </p>
<p>18.6
一个常用的方法是:要求用户在修改任何文件后通知服务器,以使服务</p>
<p>器可 </p>
<p>以重新读取文件。SIGHUP命令就是经常用来完成这项任务的。 </p>
<p>18.9 可以在远端执行stty命令,然后分析其输出结果。但是考虑到不同</p>
<p>UNIX平台 </p>
<p>的stty命令输出结果差别很大,这种方式实现起来较为困难。 </p>
<p> </p>
<p>第十九章 </p>
<p>19.1 telnetd和rlogind两个服务器均以超级用户的权限运行,所以它们都</p>
<p>可以成功 </p>
<p>地调用chown和chmod。 </p>
<p>-</font></p>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -