文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>附录C 习题答案

附录C 习题答案

时间:2005-08-31  来源:mantou

  

  
   
第一章  
1.1 利用ls命令中的下面两个选项:-i--显示文件或目录的i节点数目(关于i节点  
在4.14节中会详细讨论);-d--如果参数是一目录,只列出其名字,而不是目录中  
的所有文件。  
执行下面命令的结果为:  
$ ls -ldi   /etc/.   /etc/..  
                        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 /.   /..  
   2 drwxr-xr-x 13 root  512 Aug 5 20:11 /./  
                   2 srwxr-xr-x 13 root  512 Aug 5 20:11 /../           {.和..的的i节点数均为2  
的i节点数均为2  
1.3 假如perror的ptr参数是一个指针,则perror就可以改变ptr所指串的内容。所  
以利用限定词const使得perror不能修改ptr所指的串。而strerror的参数是错误号  
,由于其是整数类型并且C传递的是参数值,因此strerror不能修改参数的值,也  
   
   
就没有必要使用const属性。  
1.4 调用fflush,fprintf和vprintf函数会改变errno的值。如果它的值变了但没  
有保存,则最终显示的错误信息是不正确的。  
在过去开发许多程序中都可以发现不保存errno的情况,典型的打印出的错误信息  
是"Not a typewriter."。在5.4节中标准I/O库根据标准I/O流是否指向终端设备而  
改变流的缓存器。istty(11.9节)通常用来判断流是否指向终端设备,如果流不指  
向终端设备,errno可能置为ENOTTY,从而引起该错误。程序C.1表现了这个属性。  
   
#include        <stdio.h>  
/*  
 * The following prints errno=25 (ENOTTY) under 4.3BSD and SVR2,  
 * when stdout is redirected to a file.  
 * Under SVR4 and 44BSD it works OK.  
 */  
int  
main()  
{  
        int                     fd;  
        extern int      errno;  
        if ( (fd = open("/no/such/file", 0)) < 0) {  
                printf("open error: ");  
                printf("errno = %d ", errno);  
        }  
        exit(0);  
}  
程序 C.1        errno和printf的交互作用  
执行上面的程序,结果为:  
$ grep BSD /etc/motd  
4.3  BSD  UNIX  #29:  Thu Mar 29  11:14:13  MST  1990  
$ a.out  
open error:   error = 2                 {工作正常,stdout是一个终端}  
$ a.out > temp.foo  
$ cat temp.foo  
open error:   error = 25                {错误}  
1.5 2038年。  
1.6 大约248天。  
   
   
第二章  
2.1 下面是4.3+BSD中使用的技术。在<machine/ansi.h> 中,用大写字母定义可以  
在多个头文件中出现的基本数据类型。例如:  
        #ifndef _ANSI_--H_  
        #define _ANSI_H_  
   
        #define _CLOCK_T_       unsigned long  
        #define _SIZE_T_                unsigned int  
        …  
        #endif                  /*_ANSI_H_*/  
        以下面的顺序可以在这6个头文件中分别定义size_t。  
        #ifdef  _SIZE_T_  
        typedef _SIZE_T_        size_t;  
        #undef  _SIZE_T_  
        #endif  
        这样,实际上只执行一次typedef。  
   
   
第三章  
3.1 所有的磁盘I/O都要经过内核的块缓存器,唯一例外的是对原始磁盘设备的I/  
O,但是我们不考虑这种情况(Bach [1986] 中的第三章讲述了这种缓存器的操作)  
。既然read或write的数据都要被内核缓存,那么术语"无缓存装置的I/O"指的是在  
用户的进程中对这两个函数不会自动缓存,每次read或write就要进行一次系统调  
用。  
3.3 每次调用open函数就分配一个文件表项,如果两次打开的是相同的文件,则两  
个文件表项指向相同的v节点。调用dup引用已存在的文件表项(此处指fd1的文件表  
项),见图C.1。当F_SETFD作用于fd1时,只影响fd1的文件描述符标志;F_SETFL作  
用于fd1时,则影响fd1及fd2的文件描述符标志。  
图C.1 open和dup的结果  
3.4 如果fd是1,执行dup2(fd,1)后返回1,但是没有关闭描述符1(参见3.12节)。  
调用3次dup2后,3个描述符指向相同的文件表项,所以不需要关闭描述符。  
        如果fd是3,调用3次dup2后,有4个描述符指向相同的文件表项,所以就需要关闭  
描述符3。  
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.1 stat函数总是顺一个符号连接向前,所以修改后的程序不会显示文件类型是"  
符号连接"。例如:/bin是/usr/bin的一个符号连接,但是stat函数的结果只显示  
/bin是一个目录,而不说明它是一个符号连接。若一个符号连接指向一不存在的文  
件,则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       stevens 29      Jan     14  
6:39   temp.foo  
4.4 下面的命令表示关闭用户读权限的情况。  
                $ data > foo  
                $ chmod u-r foo                                         {关闭用?nbsp; 
读权限}  
                $ ls -l foo  
验证文件的权限}  
                --w-rw-r--      l       stevens 29      Jul     31      09:00  
oo  
                $ cat foo  
       {读文件}  
                cat: foo: Permission denied  
4.5 如果用open或creat创建已经存在的文件,则该文件的存取权限不变。通过程  
序4.3可以验证这点。  
                $ rm foo bar  
删除文件}  
                $ data > foo  
创建文件}  
                $ data > bar  
                $ chmod a-r foo bar                                     {关闭所?nbsp; 
的读权限}  
                $ ls -l foo bar                                         {验证其?nbsp; 
限}  
                --w--w----      l       stevens 29      Jul     31      10:47  
ar  
                --w--w----      l       stevens 29      Jul     31      10:47  
oo  
                $ a.out  
运行程序4.3}  
        $ ls -l foo bar                                         {检查文件的权限?nbsp; 
大小}  
        --w--w----      l       stevens 0       Jul     31      10:47   bar  
                --w--w----      l       stevens 0       Jul     31      10:47  
oo  
可以看出存取权限没有改变,但是文件长度缩短了。  
4.6 目录的长度从来不会是0,因为它总是包含 .和 ..两项。符号连接的长度指其  
路径名的包含的字符数,由于路径名中至少有一个字符,所以长度也不为0。  
4.8 当创建新的core文件时,内核对其存取权限有一个默认设置,在本例中是:r  
w-r--r--。这一缺省值可能会也许不会被umask的值修改。Shell对创建的重定向的  
新文件也有一个默认的访问权限,本例中为rw-rw-rw-。这个值总是被当前的umas  
k修改,在本例中umask为02。  
4.9 不能使用du的原因是它需要文件名或目录名作为参数,例如:  
                du tempfile  
                du .  
        只有当unlink函数返回时才释放tempfile的目录项,du命令没有计算仍然被temp  
file占用的空间。在本例中只能使用df命令察看文件系统中实际可用的自由空间。  
   
4.10 如果被unlink的链接不是该文件的最后一个链接,该文件不会删除。此时,  
unlink函数更新文件的状态改变时间。如果是最后一个链接,该文件将被物理删除  
。这时再去更新文件的状态改变时间就没有意义,因为包含文件所有信息的i节点  
将会随着文件的删除而被释放。  
4.11 用opendir打开一个目录后,循环调用函数dopath。假设opendir使用一个文  
件描述符,并且只有处理完目录后调用closedir才释放描述符,这就意味着每次打  
开目录就要降一级使用另外一个描述符。所以系统可打开文件数就限制了文件系统  
中树的深度。SVR4中的ftw允许调用者指定使用的描述符数,这隐含着该实现可以  
关闭描述符并且重用它们。  
4.13 chroot函数用于辅助Internet文件传输程序(FTP)中的安全性。系统中没有帐  
号的用户(也称为匿名FTP)放在一个单独的目录下,利用chroot将此目录当作新的  
根目录就可以阻止用户访问此目录以外的文件。  
        chroot也用于在另一台机器上构造一文件系统层次结构的一个副本,然后修改此  
副本,但不更改原来的文件系统。这可用预测是新软件包装等场合。Chroot只能有  
超级用户使用,一旦更改了一个进程的root,该进程及其后代进程就再也不能恢复  
至原先的root。  
4.14 首先调用stat函数取得文件的三个时间值,然后调用utime设置期望的值。我  
们不希望在调用utime改变的值就是stat中相应的值。  
4.15 finger(1)对邮箱调用stat函数,最近一次的修改时间是上一次接收邮件的时  
间,最近访问时间是上一次读邮件的时间。  
4.16 对cpio来说,既可以改变文件的访问时间(st_atime)和修改时间(st_mtime)  
也可以都不改变。cpio的-a选项可以在读文件后重新设置文件的访问时间为读文件  
前的时间,所以cpio可以不改变文件的访问时间。另一方面,-m选项将文件的修改  
时间保存为原来的修改时间。  
对tar来说,在抽取文件时,其缺省方式是复原归档时的修改时间,但是-m选项则  
将修改时间设置为抽取文件时的时间。无论tar在何种情况,文件的访问时间均是  
抽取文件时的时间。  
        由于不能修改状态改变时间(utime也只能改变访问时间和修改时间),所以没有将  
其保存在文档上。  
4.17 read改变了文件访问时间,为了消除这一影响,有些版本的file(1)调用uti  
me恢复文件的访问时间,但是这样做会修改文件的状态改变时间。  
4.18 内核对目录的深度没有内在的限制,但是如果路径名的长度超出了PATH_MAX  
有许多命令会失败。程序C.2创建了一个深度为100的目录树,每一级目录名有45个  
字符。利用getcwd可以得到第100级目录的绝对路径名(需要多次调用realloc申请  
一个足够大的缓存)。  
#include        <sys/types.h>  
#include        <sys/stat.h>  
#include        <fcntl.h>  
#include        "ourhdr.h"  
#define DEPTH   100                     /* directory depth */  
#define MYHOME  "/home/stevens"  
#define NAME    "alonglonglonglonglonglonglonglonglonglongname"  
int  
main(void)  
{  
        int             i, size;  
        char    *path;  
        if (chdir(MYHOME) < 0)  
                err_sys("chdir error");  
        for (i = 0; i < DEPTH; i++) {  
                if (mkdir(NAME, DIR_MODE) < 0)  
                        err_sys("mkdir failed, i = %d", i);  
                if (chdir(NAME) < 0)  
                        err_sys("chdir failed, i = %d", i);  
        }  
        if (creat("afile", FILE_MODE) < 0)  
                err_sys("creat error");  
        /*  
         * The deep directory is created, with a file at the leaf.  
         * Now let's try and obtain its pathname.  
         */  
        path = path_alloc(&size);  
        for ( ; ; ) {  
                if (getcwd(path, size) != NULL)  
                        break;  
                else {  
                        err_ret("getcwd failed, size = %d", size);  
                        size += 100;  
                        if ( (path = realloc(path, size)) == NULL)  
                                err_sys("realloc error");  
                }  
        }  
        printf("length = %d %s ", strlen(path), path);  
        exit(0);  
}  
程序C.2  创建深度目录  
运行后得到:  
                $ a.out  
                getcwd failed, size = 1025: Result too large  
                getcwd failed, size = 1125: Result too large  
                …                                              { 33行}  
                getcwd failed, size = 4525: Result too large  
                length = 4613  
                {显示4613字节的路径名}  
        但是由于文件名太长了,不能用tar或cpio对该目录建立档案文件,而且也不能用  
rm -r命令删除该目录。(我们怎样才能删除该目录树?)  
4.19 /dev目录关闭了一般用户的写权限,所以用户不能删除目录中的文件,即un  
link失败。  
   
   
第五章  
5.2 fgets函数读入数据,直到行结束或缓冲区满(当然会留出一个字节存放'')  
。同样,fputs只负责将缓冲区的内容输出,而并不考虑缓冲区中是否包含换行符  
。所以,如果将MAXLINE设得很小,这两个函数仍然会正常工作,只不过被执行的  
次数要比MAXLINE值较大的时候多。  
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.1 在SVR4系统中,用户手册中讲述了获取阴影密码文件的函数。我们不能使用6  
.2节所述函数返回的pw_passwd变量来比较加密的口令。正确的方法是使用阴影密  
码文件中对应用户的加密口令来进行比较。  
        在4.3+ BSD系统中,密码文件的阴影是自动建立的。仅当调用者的用户ID为0时,  
getpwnam或getpwuid函数返回的passed结构中的pw_passwd字段才包含有加密的口  
令。  
6.2 在SVR4系统中,程序C.3将输出加密的口令。当然,除非有超级用户权限,否  
则调用getspnam将返回EACCESS错误。  
#include        <sys/types.h>  
#include        <shadow.h>  
#include        "ourhdr.h"  
int  
main(void)              /* SVR4 version */  
{  
        struct spwd     *ptr;  
        if ( (ptr = getspnam("stevens")) == NULL)  
                err_sys("getspnam error");  
        printf("sp_pwdp = %s ",  
                                ptr->sp_pwdp == NULL || ptr->sp_pwdp[0] == 0 ?  
                                "(null)" : ptr->sp_pwdp);  
        exit(0);  
}  
程序C.3  在SVR4系统中输出加密的口令  
        在4.3+ BSD系统中,具有超级用户权限时,程序C.4将输出加密的口令。否则pw_  
passed的返回值为星号(*)。  
#include        <sys/types.h>  
#include        <pwd.h>  
#include        "ourhdr.h"  
int  
main(void)              /* 44BSD version */  
{  
        struct passwd   *ptr;  
        if ( (ptr = getpwnam("stevens")) == NULL)  
                err_sys("getpwnam error");  
        printf("pw_passwd = %s ",  
                                ptr->pw_passwd == NULL || ptr->pw_passwd[0] == 0  
?  
                                "(null)" : ptr->pw_passwd);  
        exit(0);  
}  
程序C.4  在4.3+ BSD系统中输出加密的口令  
6.4  
#include        <time.h>  
#include        "ourhdr.h"  
int  
main(void)  
{  
        time_t          caltime;  
        struct tm       *tm;  
        char            line[MAXLINE];  
        if ( (caltime = time(NULL)) == -1)  
                err_sys("time error");  
        if ( (tm = localtime(&caltime)) == NULL)  
                err_sys("localtime error");  
        if (strftime(line, MAXLINE, "%a %b %d %X %Z %Y ", tm) == 0)  
                err_sys("strftime error");  
        fputs(line, stdout);  
        exit(0);  
}  
程序C.5  以date(1)的格式输出日期和时间  
程序C.5的运行结果如下:  
        $ echo $TZ  
MST7  
$ a.out  
Wed Jan 15 06:48:57 MST 1992  
$ TZ=EST5EDT a.out                      U.S.East Coast  
Wed Jan 15 08:49:06 EST 1992  
$ TZ=JST-9 a.out                                Japan  
Wed Jan 15 22:49:12 JST 1992  
   
   
第七章  
7.1 原因在于printf的返回值(输出的字符数)变成了main函数的返回码。当然,并  
不是所有的系统都会出现该情况。  
7.2 当程序处于交互运行方式时,标准输出设备通常处于行缓冲方式,所以当键入  
新行符时,上次的结果才被真正输出。如果标准输出设备被定向到一个文件而处于  
完全缓冲方式,则当标准I/O清理操作执行时,结果才真正被输出。  
7.3 由于agrc和argv不象environ一样保存在全局变量中,所以在大多数Unix系统  
中没有其它办法。  
7.4 当C程序复引用一个空指针出错时,执行该程序的进程将终止,于是可以利用  
这种方法终止进程。  
7.5 定义如下:  
                typedef void    Exitfunc ( void ) ;  
                int  atexit ( Exitfunc *func ) ;  
7.6 calloc将分配的内存空间初始化为0。但是ANSI C并不保证0值与浮点0或空指  
针的值相同。  
7.7 只有通过exec函数执行一个程序时,才会分配堆和堆栈。  
7.8 可执行文件包含了用于调试core文件的符号表信息,用strip(1)可以删除这些  
信息,对两个a.out文件执行这条命令,它们的大小减为98304和16384。  
7.9 没有使用共享库时,可执行文件的大部分都被标准I/O库所占用。  
7.10 这段代码不正确。因为在if语句中定义了自动变量val,所以当if中的复合语  
句结束时,该变量就不存在了,但是在if语句之外又用指针引用已经不存在的自动  
变量val。  
第七章  
7.1 原因在于printf的返回值(输出的字符数)变成了main函数的返回码。当然,并  
不是所有的系统都会出现该情况。  
7.2 当程序处于交互运行方式时,标准输出设备通常处于行缓冲方式,所以当键入  
新行符时,上次的结果才被真正输出。如果标准输出设备被定向到一个文件而处于  
完全缓冲方式,则当标准I/O清理操作执行时,结果才真正被输出。  
7.3 由于agrc和argv不象environ一样保存在全局变量中,所以在大多数Unix系统  
中没有其它办法。  
7.4 当C程序复引用一个空指针出错时,执行该程序的进程将终止,于是可以利用  
这种方法终止进程。  
7.5 定义如下:  
                typedef void    Exitfunc ( void ) ;  
                int  atexit ( Exitfunc *func ) ;  
7.6 calloc将分配的内存空间初始化为0。但是ANSI C并不保证0值与浮点0或空指  
针的值相同。  
7.7 只有通过exec函数执行一个程序时,才会分配堆和堆栈。  
7.8 可执行文件包含了用于调试core文件的符号表信息,用strip(1)可以删除这些  
信息,对两个a.out文件执行这条命令,它们的大小减为98304和16384。  
7.9 没有使用共享库时,可执行文件的大部分都被标准I/O库所占用。  
7.10 这段代码不正确。因为在if语句中定义了自动变量val,所以当if中的复合语  
句结束时,该变量就不存在了,但是在if语句之外又用指针引用已经不存在的自动  
变量val。  
   
   
第八章  
8.1 用下面几行代替程序8.2中调用printf的语句。  
        i = printf ("pid = %d, glob = %d, var = %d ", getpid( ), glob, var);  
        sprintf (buf, "%d ", i);  
                write (STDOUT_FILENO, buf, strlen(buf));  
        注意要定义变量i和buf。  
        这里假设子进程调用exit时只关闭标准I/O流,并不关闭与标准输出相关的文件描  
述符STDOUT_FILENO。有些版本的标准I/O库会关闭与标准输出相关的文件描述符从  
而引起写失败,这种情况就调用dup将标准输出复制到另一个描述符,write则使用  
新复制的文件描述符。  
8.2 我们可以通过程序C.6来说明这个问题。  
#include        <sys/types.h>  
#include        "ourhdr.h"  
static void     f1(void), f2(void);  
int  
main(void)  
{  
        f1();  
        f2();  
        _exit(0);  
}  
static void  
f1(void)  
{  
        pid_t   pid;  
        if ( (pid = vfork()) < 0)  
                err_sys("vfork error");  
        /* child and parent both return */  
}  
static void  
f2(void)  
{  
        char    buf[1000];              /* automatic variables */  
        int             i;  
        for (i = 0; i < sizeof(buf); i++)  
                buf[i] = 0;  
}  
程序C.6  错误使用vfork的例子  
图C.2   调用vfork时的栈帧  
当函数f1调用vfork时,父进程的堆栈指针就指向f1的栈帧,见图C.2。vfork使得  
子进程先执行然后从f1返回,接着子进程调用f2并且覆盖了f1的堆栈区间,在f2中  
子进程将自动变量buf的值置为0,即将堆栈中的1000个字节的值都置为0。从f2返  
回后父进程继续执行调用_exit,这时堆栈中main以下的内容已经被f2修改了,但  
是父进程仍然以为调用了vfork后从f1返回。返回信息虽然保存在堆栈中,但是可  
能已经被子进程修改了。对这个例子,父进程继续执行的结果要依赖于实际的Uni  
x系统。(如:返回信息保存在堆栈的具体位置,修改动态变量时覆盖了哪些信息等  
等。)通常的结果是一个core文件。  
8.3 程序8.7中我们先让父进程输出,但是当父进程输出完毕子进程要输出时,我  
们要让父进程终止。是否父进程先终止或是子进程先执行输出要依赖于内核对两个  
进程的调度。shell在父进程终止后会开始执行其它程序,这样也许仍会影响子进  
程。要避免这种情况就是在子进程完成输出后才终止父进程。用下面的语句替换程  
序中fork后面的代码。由于只有终止父进程才能开始下一个程序,所以不会出现上  
面的情况。  
                else if ( pid == 0 ) {  
                        WAIT_PARENT( ) ;  
       /*parent goes first */  
                        charatatime ( "output form child " ) ;  
                        TELL_PARENT ( getppid( )) ;  
* tell parent we're done */  
                } else {  
                        charatatime ( "output from parent " ) ;  
                        TELL_CHILD ( pid ) ;  
* tell child we're done */  
                        WAIT_CHILD ( ) ;  
       /* wait for child to finish */  
   
8.4 对argv[2]打印的是相同的值(/home/stevens/bin/testinterp)。原因是ex  
eclp在结束时调用了execve,并且与直接调用execl的路径名相同。  
8.5不提供返回保存的用户ID的函数,我们必须在进程开始时保存有效的用户ID。  
   
8.6 程序C.7创建了一个zombie。  
#include        "ourhdr.h"  
int  
main(void)  
{  
        pid_t   pid;  
        if ( (pid = fork()) < 0)  
                err_sys("fork error");  
        else if (pid == 0)              /* child */  
                exit(0);  
        /* parent */  
        sleep(4);  
        system("ps");  
        exit(0);  
}  
程序C.7  创建一个zombie并用ps查看其状态  
        执行程序结果如下(ps(1)用"Z"表示Zombie):  
                $ a.out  
                 PID TT STAT    TIME COMMAND  
                5940 p3 S       0:00 a.out  
                5941 p3 Z       0:00 <defunct>                  the zombie  
                5942 p3 S       0:00 sh -c ps  
                5943 p3 R       0:00 ps  
   
   
   
第九章  
9.1 因为init是login shell的父进程,当login shell终止时它收到SIGCHLD信号  
量,所以init进程知道什么时候终端用户注销。  
        网络login没有包含init,相应的logout项是由一个处理login并监测logout的进  
程写的。(本例中为telnetd)  
   
   
第十章  
10.1 当程序第一次接到我们发送给它的信号量就终止了。因为一捕捉到信号量pa  
use函数就返回。  
10.2 程序C.8实现了raise函数。  
#include        <sys/types.h>  
#include        <signal.h>  
#include        <unistd.h>  
int  
raise(int signo)  
{  
        return( kill(getpid(), signo) );  
}  
程序C.8  raise函数的实现  
10.3 见图C.3。  
图C.3 longjump前后的堆栈状态  
        从sig_alrm通过longjmp返回main,有效地避免了继续执行sig_int。  
10.4 如果进程在调用alarm和setjmp之间被内核阻塞了,alarm时间走完之后就调  
用信号量处理程序,然后调用longjmp。但是由于没有调用setjmp,所以没有设置  
env_alrm缓存区。如果longjmp的跳转缓存区没有被setjmp初始化,则说明没有定  
义longjmp的操作。  
10.5 参见" Implementing Software Timers " by Don Libers ( C user Journa  
l, Vol. 8, no. 11, Nov. 1990 )中的例子。  
10.7 如果仅仅调用_exit,则进程终止状态就不能表示该进程是由于SIGABRT信号  
量而终止的。  
10.8 如果信号量是由其它用户的进程发出的,进程必须设置用户的ID为根或者是  
接收进程的拥有者,否则kill不能执行。所以实际的用户ID为信号量的接收者提供  
了更多的信息。  
10.10 对于本书中所用的系统,大约每60-90分钟增加一秒,这个误差是因为每次  
调用sleep都要调度一将来的时间事件,但是由于CPU调度,有时我们并没有在事件  
发生时被叫醒。另外一个原因是开始运行进程和调用sleep都需要一定的时间。  
        BSD中的cron每分钟都要取当前时间,它首先设置一个休眠周期,然后在下一分钟  
开始时唤醒。大多数调用是sleep(60),偶尔有一个sleep(59)用于在下一分钟同步  
。但是若在进程中花费了许多时间执行命令或者系统的负载重调度慢,这时休眠值  
可能远小于60。  
10.11 在SVR4中,从来没有调用过SIGXFSZ的信号量处理程序,一旦文件的大小达  
到1024时,write就返回24。  
        在4.3+BSD中,文件大小达到1500字节时调用该信号处理程序,write返回-1并且  
errno设置为EFBIG。  
        SunOS4.1.2的情况与SVR4类似,但是调用了该信号量处理程序。  
        System V在文件大小达到软资源限制时无错返回一个较小的数,而BSD判断文件超  
出限制时错误返回,没有写任何数据。  
   
10.12 结果依赖于标准I/O库的实现--fwrite如何处理一个被中断的写。  
   
第十一章  
11.1 注意由于终端是非正规模式,所以要用换行符而不是回车终止reset命令。  
11.2 它为128个字符建了一张表,根据用户的要求设置奇偶校验位。然后使用8位  
I/O处理奇偶位的产生。  
11.3 在SVR4中运行stty -a,并且将标准输入重定向到运行vi的终端,结果显示v  
i设置MIN为1、TIME为1。reads等待至少敲入一个字符,但是该字符输入后,只对  
后继的字符等待十分之一秒即返回。  
11.4 在SVR4中使用扩展的通用终端接口。参见AT&T[1991]手册中的temiox(7)。在  
4.3+BSD中使用c_cflag字段的CCTS_OFLOW和CRTS_IFLOW标志,参见图11.3。  
   
   
第十二章  
12.1 程序运行正常,不会发生ENOLCK的错误。第一次循环调用writew_lock、wri  
te和un_lock。调用un_lock后只保留了第一个字节的锁,第二次循环时,调用wri  
tew_lock使得新锁与第一个字节的锁合并,图C.4是第二次循环的结果。  
图C.4 第二次循环后锁的状态  
        每循环一次就扩展一个字节的锁,内核将这些锁合并后就只保持了一个锁,因此  
符合锁结构的定义。  
12.2 在SVR4、4.3+BSD中,fd_set是只包含一个成员的结构,该成员为一个长整型  
数组。数组中每一位对应于一个描述符。四个FD_宏通过开关或测试指定的位来操  
纵这个数组。将之定义为一个包含数组的结构而不仅仅是一个数组的原因是:通过  
C语言的赋值语句,可以使fd_set类型变量相互赋值。  
12.3 在SVR4、4.3+BSD中允许用户包含头文件<sys/types.h> 前定义常数FD_SETS  
IZE。例如下面的代码可以使fd_set数据类型包含2048个描述符。  
                #define FD_SETSIZE      2048  
                #include                <sys/types.h>  
12.4 下面的表列出了功能类似的函数。  
                FD_ZERO         sigemptyset  
                FD_SET          sigaddset  
                FD_CLR          sigdelset  
                FD_ISSET                sigismember  
        没有与sigfillset对应的FD_XXX函数。对信号量来说,指向信号量集合的指针是  
第一个参数,信号量数是第二个参数;对于描述符来说,描述符数是第一个参数,  
指向描述符集合的指针是第二个参数。  
12.5 最多五种信息:数据,数据长度,控制信息,控制信息的长度和标志。  
12.6 利用select实现的程序见C.9,利用poll实现的程序见C.10。  
#include        <sys/types.h>  
#include        <sys/time.h>  
#include        <stddef.h>  
#include        "ourhdr.h"  
void  
sleep_us(unsigned int nusecs)  
{  
        struct timeval  tval;  
        tval.tv_sec = nusecs / 1000000;  
        tval.tv_usec = nusecs % 1000000;  
        select(0, NULL, NULL, NULL, &tval);  
}  
程序C.9 用select实现sleep_us函数  
#include        <sys/types.h>  
#include        <poll.h>  
#include        <stropts.h>  
#include        "ourhdr.h"  
void  
sleep_us(unsigned int nusecs)  
{  
        struct pollfd   dummy;  
        int                             timeout;  
        if ( (timeout = nusecs / 1000) <= 0)  
                timeout = 1;  
        poll(&dummy, 0, timeout);  
}  
程序C.10 用poll实现sleep_us函数  
        BSD中的usleep(3)使用setitimer设置间隔计时器,并且执行8个系统调用。它可  
以正确的和调用进程设置的其它计时器交互作用,而且即使捕捉到信号量也不会被  
中断。  
12.7 不行。我们可以使TELL_WAIT创建一个临时文件,其中一个字节用作为父进程  
的锁,另一个字节用作为子进程的锁。WAIT-CHILD使得父进程等待子进程的锁,T  
ELL_PARENT使得子进程释放子进程的锁。但是问题在于调用fork后,子进程释放了  
所有的锁导致子进程不能具有任何它自己的锁而开始执行。  
12.8 用select的方法见程序C.11,使用poll的情况类似。  
#include        <sys/types.h>  
#include        <sys/time.h>  
#include        "ourhdr.h"  
int  
main(void)  
{  
        int                             i, n, fd[2];  
        fd_set                  writeset;  
        struct timeval  tv;  
        if (pipe(fd) < 0)  
                err_sys("pipe error");  
        FD_ZERO(&writeset);  
        for (n = 0; ; n++) { /* write 1 byte at a time until pipe is full */  
                FD_SET(fd[1], &writeset);  
                tv.tv_sec = tv.tv_usec = 0;             /* don't wait at all */  
                if ( (i = select(fd[1]+1, NULL, &writeset, NULL, &tv)) < 0)  
                        err_sys("select error");  
                else if (i == 0)  
                        break;  
                if (write(fd[1], "a", 1) != 1)  
                        err_sys("write error");  
        }  
        printf("pipe capacity = %d ", n);  
        exit(0);  
}  
程序C.11 用select计算管道的性能  
        在SVR4和SunOS 4.1.1中使用select和poll计算出的结果等于图2.6的值。在4.3B  
SD中使用select计算的结果为3073。  
12.9 在SVR4、4.3+BSD和SunOS 4.1.2中,程序12.14确实修改了输入文件的最近一  
次访问时间。  
   
   
第十三章  
13.1 如果进程调用chroot就不能打开/dev/log,解决的办法是在chroot之前调用  
选项为LOG_NDELAY的openlog。这样即使调用了chroot之后,仍然可以打开特定的  
设备文件(UNIX与数据包套接口)并生成一个有效的描述符。  
13.3 程序C.12是一种解决方案。  
#include        "ourhdr.h"  
int  
main(void)  
{  
        char    *ptr, buff[MAXLINE];  
        daemon_init();  
        close(0);  
        close(1);  
        close(2);  
        ptr = getlogin();  
        sprintf(buff, "login name: %s ",  
                                        (ptr == NULL) ? "(empty)" : ptr);  
        write(3, buff, strlen(buff));  
        exit(0);  
}  
程序C.12 调用daemon_init获得注册名  
结果依赖于不同的系统实现和是否关闭文件描述符1、2和3。关闭描述符影响结果  
的原因是:当程序开始执行时与控制终端连接,调用deamon_init后关闭3个描述符  
就意味着getlogin没有控制终端,所以不能在utmp文件中看到登录项。  
但是在4.3+BSD中,登录名是由进程表维护的,并且可以通过fork复制。也就是说  
除非其父进程没有登录名(如系统自引导时调用init),否则进程总能获得其登录名  
。  
   
   
第十四章  
14.1 如果写管道端总是不关闭,则读者就决不会看到文件的结束符。页面调度程  
序就会一直阻塞在读标准输入。  
14.2 父进程向管道写完最后一行以后就终止了,然后读者读到管道的结尾时自动  
关闭管道。但是由于子进程(页面调度程序)要等待输出的页,所以父进程可能比子  
                        err_sys ( "setvbuf error" ) ;  
        while中的read和write用下面的语句代替。  
if (fputs ( line, fpout ) == EOF )  
        err_sys ( "fputs error to pipe" ) ;  
if ( fgets ( line, MAXLINE, FPIN ) == NULL ) {  
        err_msg ( "child close pipe" ) ;  
        break ;  
}  
14.6 虽然system函数调用了wait,但是终止的第一个子进程是由popen产生的,所  
以它将再次调用wait并一直阻塞到sleep完成,然后system返回。当pclose调用wa  
it时,由于没有子进程可等待所以返回出错,导致pclose也返回出错。  
14.7 select表明描述符是可读的。调用read读完所有的数据后返回0就表明到了文  
件尾。对于poll(假设管道是一个流设备)来说,若返回POLLHUP也许仍有数据可以  
读。但是一旦读完了所有的数据read就返回0,即表明到了文件尾。poll读完了所  
有的数据后并不返回POLLIN。  
        对

相关阅读 更多 +
排行榜 更多 +
地狱摩托游戏最新版下载

地狱摩托游戏最新版下载

赛车竞速 下载
小猫快来钓鱼游戏下载

小猫快来钓鱼游戏下载

休闲益智 下载
殴打氪金大佬昊天手游下载

殴打氪金大佬昊天手游下载

休闲益智 下载