《Unix环境高级编程》课后习题(1-6章)

来源:互联网 发布:佛山房地产数据 编辑:程序博客网 时间:2024/06/01 09:54

  今天有时间来做一下课后习题,下面的答案是自己的理解,有一些也参考的网上的解答,并不保证正确性。

第一章

  1. ls命令查看文件信息,-l选项以长格式的形式查看文件详细信息,-i选项打印文件的i结点编号,-d选项,表示如果文件是目录文件,则只列出该目录本身的信息,不列出该目录内文件的信息,如果文件是符号链接,则打印该链接本身的信息,不打印所指向文件的信息。在Ubuntu上进行验证:
       还在路上,稍等...
      
  2. Unix是多任务系统,执行程序时,ID为852和853的进程也在运行。

  3. strerror没必要用const,因为是赋值传递,所以不会影响原来参数的值,而perror使用的是指针,如果不加const限制,则可能影响指针所指对象的值。

  4. 调用fflush,vsprintf,fprintf等函数可能改变errno的值,如果值改变了,没有保存先前的值,则会导致输出的信息是错误的。

  5. (2^31-1)/(365*24*60*60) ≈ 68, 1970+68 = 2038年

  6. 约248天

第二章

  1. 采用宏条件编译,先在一个单独的文件定义一个宏SIZE_T表示实际类型,然后在每个头文件中加入宏条件编译:

        #ifdef _SIZE_T_    typedef _SIZE_T_ size_t;    #undef _SIZE_T_    #endif

    这样可以保证实际只执行了一次typedef命令。

  2. 我使用的系统是Ubuntu14.04,系统头文件是在/usr/include/x86_64-linux-gnu/bits下的types.h,下图是里面的一部分内容:

    还在路上,稍等...

  3. 没太懂题目意思。。

第三章

  1. 没有缓冲机制,因为本章介绍的函数都是直接进行的系统调用,在函数内部不会对数据进行缓冲处理。系统每次读写磁盘数据都会经过内核的块缓存器,但这并不指的是缓冲,缓冲指的是进程内部对读写的数据做缓冲处理,是软件方面定义的而不是指硬件的缓存。

  2. 本题参考APUE习题 3.2 浅析
    dup2函数原型是int dup2(int filedes, int filedes2>,功能是复制filedes描述符,并将新描述符命名为filedes2,如果filedes2已存在,则将其关闭。不能使用fcntl,则只能想办法使用dup函数,dup每次复制的新描述符值为当前系统中最小可用描述符值,所以可以循环dup函数,直至结果等于filedes2。出错处理主要是针对filedes2,判断filedes2是否是无效的描述符(小于0或者大于系统定义的最大描述符值)。代码如下:

        #include <unistd.h>    #include <limits.h>    #include <stdio.h>    #include <fcntl.h>    #define MAX_LEN 4096int my_dup2(int filedes, int filedes2){    int s[MAX_LEN];    int i=0, top=0;    int n;    if(filedes2 > sysconf (_SC_OPEN_MAX) || filedes2 < 0){        printf("Invalid filedes2 %d\n", filedes2);        return -1;    }    if(filedes == filedes2)        return filedes2;    while((n = dup(filedes)) < filedes2){        if(n==-1){            printf("System can't make file \n");            return -1;        }        s[top++] = n;    }    //循环执行完后,有两种情况,一是返回的描述符大于filedes2,二是返回的描述符刚好等于filedes2,无论哪种情况,都说明filedes2已打开,所以可以先关闭,再执行dup一次,返回的描述符就是filedes2    close(filedes2);    if(dup(filedes) == -1){        printf("dup function error \n");        return -1;    }    for(i=0; i<top; i++){        close(s[i]);    }    return filedes2;}int main(int argc, char *argv[])  {      int filedes, filedes2;          if(argc != 3) {          printf("Parameter error!\n");          return -1;      }         filedes = open(argv[1], O_RDWR);      if(filedes == -1) {          printf("Error! System cannot open %s\n", argv[1]);          return -1;      }             filedes2 = atoi(argv[2]);      if(my_dup2(filedes, filedes2) != -1) {          write(filedes2, "test", sizeof("test"));      }                     return 0;  }  

    执行dup函数只是复制原描述符,即在当前进程的文件描述符表中增加一条记录,但新描述符和原描述符仍指向同一个文件表项,即仍指向同一个文件

  3. 每次调用open函数都会生成一个新的文件表项,对同一个文件调用两次open函数,则生成两个文件表项,这两个文件表项指向同一个v结点。dup函数并不生成新文件表项,只是在进程文件描述符表中增加一条记录。F_SETFD(设置文件描述符标记)只影响fd1,F_SETTL(设置文件状态标志)更改fd1文件表项的内容,因为fd2和fd1指向一个文件表项,所以fd2也会受到影响。
    还在路上,稍等...

  4. 如果fd是1,执行dup2(fd,1)后,没有关闭描述符1,执行完程序后有三个描述符项指向同一个文件表项,不需要关闭描述符。如果fd是3,执行完三次dup2函数后,有四个描述符指向同一个文件表项,而fd描述符不再使用,所以需要关闭它。

  5. a.out > outfile 2 > &1 这条命令从左到右执行,可分为两部分:a.out > outfile 将程序a.out的标准输出重定向到outfile,2 > &1 将标准出错重定向到标准输出所指向的文件,相当于指向dup2(1, 2),结果描述符1和2指向同一个文件;
    a.out 2 > &1 >outfile 命令先将标准出错重定向至标准输出,然后将标准输出重定向至outfile,结果描述符1和2指向不同文件。

  6. 经测试发现可以使用lseek定位至文件任一位置,并用read读任一位置的内容,但是write函数会自动将文件偏移量设置在文件结尾,所以写文件时只能从文件尾开始,不能在任一位置。

        #include <fcntl.h>    #include <unistd.h>    #include <stdio.h>int main(int argc, char* argv[]){    int fd;    if(argc !=2){        printf("input error\n");        return -1;    }    if((fd=open(argv[1], O_RDWR | O_APPEND))<0){        printf("open error\n");        return -1;    }    if(lseek(fd, 10, SEEK_SET)<0){        printf("lseek error\n");        return -1;    }    char buf[100];    if(read(fd,buf, 20)<0){        printf("read error\n");        return -1;    }    printf("%s\n", buf);     if(write(fd, buf, 20)!=20){    printf("write error\n");    return -1;    }    int new_offset = lseek(fd, 0, SEEK_CUR);    printf("new offset : %d\n", new_offset);    return 0;}

第四章

  1. stat函数会追随符号链接向前,返回的是该符号链接所指向文件的信息,所以修改后的程序不会显示文件类型是“符号链接”。若符号链接指向一个不存在的文件,则stat会报错。
    还在路上,稍等...

  2. 新创建的文件失去所有读写权限。

  3. chmod u-r *.txtcat *.txt

  4. 如果用open或creat创建已存在的文件,则该文件的存取权限不变,但文件的内容被清空了。
    还在路上,稍等...

  5. 目录的长度永远不会是0,因为它始终包含...项,符号链接的长度指其文件路径名中包含的字节数,所以长度也不可以为0。

  6. 因为read函数读空洞的内容时,读的字节是0,所以可以写时做判断,如果当前字节是0,就不写入文件。

        #include "apue.h"    #include <fcntl.h>    #include <stdio.h>    int main(int argc, char* argv[]){        if(argc !=2)            return -1;        int fd, fd1;        if((fd=open(argv[1], O_RDONLY)) <0){            printf("open error\n");            return -1;        }           if((fd1=open("temp", O_CREAT | O_WRONLY, FILE_MODE)) <0){            printf("create error 1\n");            return -1;        }           char buf;        while(read(fd, &buf, 1)==1){            if(buf!=0){                if(write(fd1, &buf, 1)!=1){                    printf("write error\n");                    return -1;                      }                       }               }           return 0;    }

    还在路上,稍等...

  7. 当创建新的core文件时,内核对其存写权限有一个默认设置,在本例中是rm-r–r–,这一默认值可能会也可能不会被umask值修改。shell对创建的重定向文件也有一个默认的访问许可权,本例中是rw-rw-rw,这个值总是被umask修改,在本例中为02。

  8. 不能使用du命令的原因是因为du命令必须使用文件名和目录名,因为unlink释放了tempfile的目录项,如果用du . 来判断的话,则不会计算tempfile文件所占的内容,所以只能用df命令查看文件系统中实际可用的自由空间。

  9. unlink删除一个目录项,会导致文件i结点计数减一,如果i结点计数不为0,则文件状态更改时间会被更新,如果i结点计数为0,则文件被删除,此时文件i结点也会被删除。

  10. 假设打开一个目录后,只有当遍历完目录内所有的文件时,才关闭该目录描述符,则对打开文件描述符数目的限制会影响文件系统中目录树的深度。

  11. chroot用于辅助因特网文件传输程序(FTP)中的安全性,系统中没有账户的用户(也称匿名FTP)放在一个单独的目录下,利用chroot将当前目录当作根目录,就可以阻止此用户访问根目录以外的文件。chroot只能由超级用户使用,一旦更改了一个进程的root,该进程及进程的后代再也不能恢复至原先的root。

  12. 首先调用stat函数获取文件的三个时间值,然后用utime设置想更改的值,不改变的值保持为原值。

  13. finger对邮箱调用stat函数,最近一次修改时间是上次接收邮件的时间,最近一次访问时间是上次读文件的时间。

  14. 内核对目录树的深度没有内在的限制,但如果路径名长度超过了PATH_MAX,则很多命令不能正确执行,在linux上当到达路径长度限制值时,getcwd不能再继续正常工作了。

  15. /dev目录关闭了一般用户的写权限,所以不能删除目录项,所以unlink会失败。

第五章

  1. setbuf对流设置缓冲区,若该流指向标准输入或输出,则将流设置为行缓冲,若指向标准出错,则设置为不带缓冲,若指向其它文件,则设置为全缓冲。

        #include "stdio.h"    #include "apue.h"    void my_setbuf(FILE *restrict fp,char *restrict buf){    if(buf == NULL){        if(setvbuf(fp,buf,_IONBF,BUFSIZ) != 0){            err_quit("setvbuf error!");        }        printf("no buf\n");    }else{        if(fp == stderr){            if(setvbuf(fp,buf,_IONBF,BUFSIZ) != 0){                err_quit("setvbuf error");            }            printf("no buf\n");        }else if(fp == stdin || fp == stdout){            if(setvbuf(fp,buf,_IOLBF,BUFSIZ) != 0){                err_quit("setvbuf error");            }            printf("line buf\n");        }else{            if(setvbuf(fp,buf,_IOFBF,BUFSIZ) != 0){                err_quit("setvbuf error");            }            printf("fully buf\n");        }    }    exit(0);}
  2. fgets函数读入数据,直至缓冲区满(会留一个字符存NULL)或者遇到换行符,fputs函数负责输出数据,并不一定输出一行,只是输出以NULL结尾的字符串,最后的NULL不输出。因此将MAXLINE改为4后,程序仍能正确执行,只是循环的次数将增多。如果是gets和puts,则不能正确执行,必须保证缓存区足够大。

  3. 表明printf没有输出任何东西。

  4. 因为getc或getchar返回的是整型结果,即输入字符的ASCII码值,由于EOF经常被定义为-1,如果系统使用的是有符号字符类型,则程序可以正常工作,如果使用无符号字符类型,则将EOF赋给字符c后将不再是-1,所以程序会进入死循环。

  5. 5个字符长的前缀,4个字符长的进程内唯一标识,再加上5个字符长的系统内唯一标识(进程ID)刚好构成一个14位长的UNIX传统文件长度限制。

  6. fsync是将内存缓存中的数据同步到磁盘上,对于标准I/O流,因为存在用户缓冲区,所以应该先调用fflush将用户缓冲区中的内容冲洗到内存中,然后调用fsync将内存缓存中的数据同步到磁盘上。

第六章

  1. 调用对应的函数,读取阴影口令文件中的加密口令字段。

  2. 具有超级用户权限时,可以调用getspnam读取阴影文件内容。

  3. uname命令输出的信息多了两条

    还在路上,稍等...

  4. 和time_t类型的实际数据类型有关,如果超出了所能表示的最大时间值,则应该会重新从0开始。

  5.     #include "../apue.h"    #include <stdio.h>    #include <time.h>    int main(){    time_t timeval;    struct tm *t;     char s[MAXLINE];        if((timeval = time(NULL))==-1)        err_sys("time error");    if((t = gmtime(&timeval))==NULL)        err_sys("gmtime error");    if(strftime(s, MAXLINE, "%a %b %d %X %Z %Y\n", t)==0)        err_sys("strftime error");    fputs(s, stdout);    exit(0); }