深入理解计算机系统_3e 第八章家庭作业 CS:APP3e chapter 8 homework

8.9

关于并行的定义我之前写过一篇文章,参考:

并发与并行的区别 The differences between Concurrency and Parallel

+----------------------------+
| Process pair    Concurrent?|
+----------------------------+
|  AB                 N      |
|                            |
|  AC                 Y      |
|                            |
|  AD                 Y      |
|                            |
|  BC                 Y      |
|                            |
|  BD                 Y      |
|                            |
|  CD                 Y      |
+----------------------------+

8.10

A.

fork

B.

execve longjmp

C.

setjmp

8.11

4次

                      +-------------->   printf("hello\n")
                      |
                      | Fork
                      | i = 1
         +------------+-------------->   printf("hello\n")
         |
         |
         |            +-------------->   printf("hello\n")
         |            |
         |            |
         |            |
+--------+------------+-------------->   printf("hello\n")
       Fork          Fork
       i = 0         i = 1

8.12

8次



                                                  main
                   +-------> printf("hello\n")+--------> printf("hello\n")
                   |                              main
             +-----+-------> printf("hello\n")+--------> printf("hello\n")
             |     Fork
         Fork|     Fork                           main
     +-------------+-------> printf("hello\n")+--------> printf("hello\n")
     |             |                              main
+----+             +-------> printf("hello\n")+--------> printf("hello\n")
    doit()

8.13

保证x=4在x=3之前即可(拓扑排序),有三种情况:

A.

x=2 x=4 x=3

B.

x=4 x=2 x=3

C.

x=4 x=3 x=2

                           "x=4"                         "x=3"

          +-------> printf("%d\n", ++x) +---> printf("%d\n", --x) +-->
          |
          |
          |
+---------+-------> printf("%d\n", --x) +---------------------------->
 x = 3   Fork
                           "x=2"

8.14

3次

                   +-------> printf("hello\n") +--->
                   |
             +-----+-------> printf("hello\n") +--->
             |     Fork
         Fork|                                 main
     +-------------------------------------------------> printf("hello\n")
     |
+----+
    doit()

8.15

5次

                                                 main
                   +-------> printf("hello\n")+--------> printf("hello\n")
                   |                             main
             +-----+-------> printf("hello\n")+--------> printf("hello\n")
             |     Fork
         Fork|                                   main
     +-------------------------------------------------> printf("hello\n")
     |
+----+
    doit()

8.16

counter = 1

          +--> counter-- +--+
          |                 |
counter=1 |                 v
       +--+-------------->Wait(NULL)+--> printf("counter = %d\n", ++counter);
           Fork

8.17

假设子进程正常退出;构成拓扑排序即可,有三种情况:

A.

Hello 1 Bye 0 2 Bye

B.

Hello 1 0 Bye 2 Bye

C.

Hello 0 1 Bye 2 bye

8.18

构成拓扑排序即可,ACE正确。

B中的第一个不可能是2。D中的第一个1后面不可能有两个2。

                   +-->printf("0")+--->printf("2")
                   |
    +----->atexit+-+-->printf("1")+--->printf("2")
    |              Fork
    |              Fork
+---+--------------+-->printf("1")
   Fork            |
                   +-->printf("0")

8.19

2^n

每次Fork都会使原来的进程数翻倍,最后每一个进程都会输出一行,所以是2^n行。

8.20

书上说改变COLUMNS环境变量会使得ls改变输出的宽度,但是在我的机器上即使用export改变该环境变量后,如果我再调用ls ,其依然按照终端的宽度输出,而且COLUMNS被改变回原来的值,我怀疑是调用ls的时候系统重新探测终端宽度并设置了新的COLUMNS。

#include <unistd.h>
#include <errno.h>
#include <stdio.h>

int main(int argc, const char *argv[], const char *envp[])
{
    if (execve("/bin/ls", argv, envp))
    {
      perror("Failed to execve /bin/ls:\n");
    }
    return 0;
}

类型不一致可能会报警,这里不会有问题。

8.21

满足拓扑排序即可,两种情况:

A.

abc

B.

bac

    +----->printf("a")+-------+
    |                         |
    |                         v
+---+----->printf("b")+--->waitpid+--->printf("c")+-->
   fork

8.22

根据man 3 system 的部分描述:

The  system()  library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
           execl("/bin/sh", "sh", "-c", command, (char *) 0);

我们可以得到execl调用后的进程关系图:

根据man sh的部分描述:

EXIT STATUS
     Errors that are detected by the shell, such as a syntax error, will cause
     the shell to exit with a non-zero exit status.  If the shell is not an
     interactive shell, the execution of the shell file will be aborted.  Oth‐
     erwise the shell will return the exit status of the last command exe‐
     cuted, or if the exit builtin is used with a numeric argument, it will
     return the argument.

可以看到Otherwise the shell will return the exitstatus of the last command executed这句话,也就是说,command执行的状态会称为sh的返回状态,所以我们回收sh并判断其返回状态即可。

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int mysystem(char *command)
{
    pid_t sh_pid;
    int sh_status;

    if ((sh_pid = fork()) == 0)
    {
        execl("/bin/sh", "sh", "-c", command, (char *) 0);
    }

    else
    {
        if ((waitpid(sh_pid, &sh_status, 0)) == sh_pid)
        {
            if (WIFEXITED(sh_status))
            {
                return WEXITSTATUS(sh_status);
            }

            else if (WIFSIGNALED(sh_status))
            {
                fprintf(stderr, "command terminated by signal number %d.\n", WTERMSIG(sh_status));
                if (WCOREDUMP(sh_status))
                {
                    fprintf(stderr, "core dumped...\n", );
                }
                return WTERMSIG(sh_status);
            }

            else
            {
                fprintf(stderr, "command terminated abnormally.\n");
                fprintf(stderr, "return status information...\n");
                return sh_status;
            }
        }

        else
        {
            fprintf(stderr, "Failed to reap /bin/sh.\n");
            return EXIT_FAILURE;
        }
    }
}

8.23

一个典型的信号不能累加的问题。

当子进程连续向父进程发送5个SIGUSR2信号时,第一个信号传送过程如下,其中A代表子进程,C代表父进程:

当父进程C接到信号后,它进入信号处理函数,并暂时将这个信号屏蔽(设置block位),这时子进程还在不断的向父进程发送所有剩下的同类信号,pending位被再次置1,而接下来的信号则会被遗弃(只有一个pending位,没办法计数),当父进程C的信号处理函数退出后,block位被置零,刚刚pending的信号再次被送入父进程C,父进程再次进入信号处理函数,这时子进程已经完成所有的信号发送,所以父进程不会再次进入信号处理函数了。综上,父进程C只会进入两次信号处理函数,即counter只会被加2而非5。

8.24

书上说要使用库函数psignal信号的描述,man 3 psignal描述如下:

#include <signal.h>
void psignal(int sig, const char *s);

The  psignal()  function  displays a message on stderr consisting of the string s, a colon, a space, a string describing the signal number sig, and a  trailing  newline. If  the  string  s  is  NULL  or  empty, the colon and space are omitted. If sig is invalid, the message displayed will indicate an unknown signal.

将Figure 8.18的代码改为:

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>

#define N 2

int main()
{
    int status;
    pid_t pid;

    for (int i = 0; i < N; i++)
    {
        if (((pid = fork()) == 0))
        {
            int *p = 0;
            *p = 0; /* Segmentation fault (core dumped) */

            return 0;
        }
    }                                                   

    while ((pid = wait(&status)) > 0)
    {
        if (WIFEXITED(status))
        {
            printf("child %d terminated normally with exit status=%d\n"
                , pid, WEXITSTATUS(status));
        }                   

        else if (WIFSIGNALED(status))
        {
            fprintf(stderr, "child %d terminated by signal %d"
                , pid, WTERMSIG(status));
            psignal(WTERMSIG(status), " ");
        }

        else
        {
            fprintf(stderr, "child %d terminated abnormally with status information=%d\n"
                , pid, status);
        }
    }

    if (errno != ECHILD)
    {
        fprintf(stderr, "waitpid error");
    }                        

    return 0;
}

运行输出:

8.25

倒计时可以用alarm实现,其到指定时间后会raise一个SIGALRM信号, man 2 alarm部分描述:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.

If seconds is zero, any pending alarm is canceled.

In any event any previously set alarm() is canceled.

我们收到这个信号后,就要想办法终止等待中的读入并返回NULL。其中一个办法是使用setjmp.h ,我们在第一次使用setjmp(buf)时进入正常的读入(此时setjmp返回值为0),但当信号出现(时间截止),信号处理函数就会longjmp(buf,1) (此时setjmp返回值为1),根据返回值的不同,这时我们便进入return NULL语句。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>

#define TIMEOUT ((unsigned int)5)
#define SIZEOFBUF 1024

jmp_buf buf;

void SIGALRM_handler(int signum)
{
    longjmp(buf, 1);
}

char *tfgets(char *s, int size, FILE *stream)
{
    if (signal(SIGALRM, SIGALRM_handler) == SIG_ERR)
    {
        perror("Failed to install SIGALRM_handler");
        return NULL;
    }
    else
    {
        alarm(TIMEOUT); /* raise SIGALRM after TIMEOUT seconds */
    }

    if (!setjmp(buf))
    {
        return fgets(s, size, stream);
    }
    else    /* longjmp from SIGALRM_handler */
    {
        return NULL;
    }
}

int main(int argc, char const *argv[])
{
    char temp_bufer[SIZEOFBUF];
    char *result = tfgets(temp_bufer, SIZEOFBUF, stdin);

    if (result)
    {
        printf("Input : %s\n", result);
    }
    else
    {
        fprintf(stderr, "Time out!\n");
    }

    return 0;
}

运行输出(第二次输入超时):

8.26

这个四星的题目实际上就是本章对应的ShellLab(tsh)实验,我做了以后会把该实验对应的writeup链接发上来。

时间: 2024-11-14 12:22:27

深入理解计算机系统_3e 第八章家庭作业 CS:APP3e chapter 8 homework的相关文章

深入理解计算机系统_3e 第十一章家庭作业 CS:APP3e chapter 11 homework

注:tiny.c csapp.c csapp.h等示例代码均可在Code Examples获取 11.6 A. 书上写的示例代码已经完成了大部分工作:doit函数中的printf("%s", buf);语句打印出了请求行:read_requesthdrs函数打印出了剩下的请求报头,但是要注意书上写的是: void read_requesthdrs(rio_t *rp) { char buf[MAXLINE]; Rio_readlineb(rp, buf, MAXLINE); while

深入理解计算机系统_3e 第四章家庭作业(部分) CS:APP3e chapter 4 homework

4.52以后的题目中的代码大多是书上的,如需使用请联系 [email protected] 流水线部分只写了偶数题号的,这几天太浮躁,落下了好多课... 4.45 A. 不正确,当REG为%rsp时,这样会压入%rsp - 8而非%rsp B. 对于 pushq REG: movq REG, -8(%rsp) subq $8, %rsp 4.46 A. 不正确,当REG为%rsp是,这样会使得%rsp的值为(%rsp) + 8 而非(%rsp) B. 对于popq REG: addq $8, %

5233杨光--第八章家庭作业

8.18(2分) 由进程图可得 112002   102120  100212是可能的  所以为ACE 8.19 由于fork函数调用一次返回两次,所以总共会输出2^n行

家庭作业二

家庭作业二(Chapter 3) P206 3.60 考虑下面的源代码,这里R,S,T都是用#define声明的常数 int A[R][S][T]; int store_ele(int i,int j,int k,int *dest) { *dest=A[i][j][k]; return sizeof(A); } 编译这个程序,GCC产生下面的汇编代码:(i at %ebp+8,j at %ebp+12,k at %ebp+16,dest at %ebp+20) movl 8(%ebp),%ec

《深入理解计算机系统(原书第三版)》pdf

下载地址:网盘下载 内容简介  · · · · · · 和第2版相比,本版内容上*大的变化是,从以IA32和x86-64为基础转变为完全以x86-64为基础.主要更新如下: 基于x86-64,大量地重写代码,首次介绍对处理浮点数据的程序的机器级支持. 处理器体系结构修改为支持64位字和操作的设计. 引入更多的功能单元和更复杂的控制逻辑,使基于程序数据流表示的程序性能模型预测更加可靠. 扩充关于用GOT和PLT创建与位置无关代码的讨论,描述了更加强大的链接技术(比如库打桩). 增加了对信号处理程序

信息安全系统设计基础家庭作业

<深入理解计算机系统>家庭作业 * 8.9 答案: 进程对 是否并发 AB 否 AC 是 AD 是 BC 是 BD 是 CD 是 * 8.10 答案: A. 调用一次,返回两次: fork B. 调用一次,从不返回: execve, longjmp C. 调用一次,返回一次或者多次: setjmp * 8.11 答案: 这个程序会输出4个“hello”输出行. 因为Fork()函数的作用是调用一次返回两次.根据条件i<2,当 i = 0 时,输出2个hello,当 i = 1 时,输出2

《深入理解计算机系统(原书第2版)》pdf

下载地址:网盘下载 内容简介  · · · · · · 本书从程序员的视角详细阐述计算机系统的本质概念,并展示这些概念如何实实在在地影响应用程序的正确性.性能和实用性.全书共12章,主要内容包括信息的表示和处理.程序的机器级表示.处理器体系结构.优化程序性能.存储器层次结构.链接.异常控制流.虚拟存储器.系统级I/O.网络编程.并发编程等.书中提供大量的例子和练习,并给出部分答案,有助于读者加深对正文所述概念和知识的理解. 本书的最大优点是为程序员描述计算机系统的实现细节,帮助其在大脑中构造一个

读完了csapp(中文名:深入理解计算机系统)

上个星期最终把csapp看完了. 我买的是中文版的,由于除了貌似评价不错以外,由于涉及到些自己不了解的底层东西,怕是看英文会云里雾里.如今看来,大概不能算是个长处,可是的确可以加快我的看书速度,否则一星期还真不大可能把这书搞定. 对csapp慕名已久,主要在于据说这本书尽量的做到相对实用,不去讲那些和实际编程没多大关系的计算机原理(毕竟是著名计算机院校里面最偏软件的cmu的作品),重点很得当,像我这样的没有本科科班出生又不想去死读些不知道以后有没实用的东西的人来说,最是适合了.感兴趣的东西就行再

第四章家庭作业4.45

第四章家庭作业---4.45 题目要求: A 用指针索引的方式编写相同的数组索引的冒泡程序C代码 B 将所得的C程序用Y86程序表述出来 解题思路: 1 将题目所给的数组元素代码转换成指针索引的代码 具体方法为:将指针赋给数组的头地址,然后按位移动指代 C格式:int *data=a; *(data+i); 2 将程序复写,并加入头文件以及主函数使其能正常调用以及相应传参 3 将所得的C程序汇编一下获得相应的汇编代码 格式:gcc -S xxx.c  -o  xxx 4 利用所得的汇编代码,用正