下面代码中,LINE-A的输出什么??

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

int value = 5;

int main()
{
    pid_t pid;

    pid = fork();

    if (pid == 0) { /* child process */
        value += 15;
        printf ("subProcess: value = %d\n",value); 
        return 0;
    }
    else if (pid > 0) { /* parent process */
        wait(NULL);
        printf ("parentProcess: value = %d\n",value); /* LINE A */
        return 0;
    }
}

fork()是一个系统调用,它会创建一个新的进程,这个系统调用会将一个进程分为两个进程,这两个进程分别是父进程和子进程。

原始进程所使用的内存页在fork()系统调用之后会被复制。因此父进程和子进程将会具有相同的镜像,因为父子进程具有与原始进程相同的内存。

这两个进程的不同点是当调用fork()的返回值:

  • 如果返回时在父进程中、),则返回值将是子进程的进程id

  • 如果返回s会在子进程红,返回值是0

  • 如果调用黑白,没有进程被创建出来,返回-1

如果进程创建成功,由于内存复制,那么父进程和子进程将会从相同的地方执行(即fork调用的地方)。这里有两种可能:

  • 父进程在子进程之前结束
  • 子进程在父进程之前结束

如果在父进程中调用额wait()那么会被暂停,直到子进程结束

因此,这里 输出是5.子进程只是更新了复制的值,并在之后将控制权返回给父进程,而父进程 值是5,因此打印值是5

下面的代码一共有创建了多少进程?

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

int main()
{
    fork();

    fork();

    fork();

    return 0;
}

8个进程,如下:

                            main()
                             |
                             |
                      fork()  ------------------
                              |                 |
                     fork()   -------        -------
                              |     |        |      |
                   fork()   ----   ----     ---    ---
                            |  |   |   |    |  |   |  |

Sun UltraSPACRC处理器有多组寄存器集合,如果新的上下文已经被加载进寄存器列表中时候,会发生什么??如果新的上下文在内存中,而不是在寄存器中,且寄存器都在使用中,这时候会发生什么??

CPU当前寄存器集合(current-register-set)的指针被更改为指向包含这个新的上下文的集合,这只需要非常短的时间。如果上下文是在内存中,必须选择寄存器集合中的一个上下文,并将其转移到内存,让这个新的上下文会被加载到寄存器集合中。这一过程花费的时间,比只有一组寄存器的处理器操作所花费的时间要多,取决于如何选择替换者。

当进程会用fork()创建了一个新的子进程,在父进程和子进程之间共享以下哪些状态?

A Stack B Heap C Shared memory segments

当进程使用fork()创建一个新进程时,将创建与父进程对应的所有新进程,并将其加载到操作系统的子进程的单独内存位置。父进程和新创建的子进程只共享内存。

考虑到RPC机制的“只执行一次(exactly once)”语义。假设返回给客户端的ACK消息由于网络问题而丢失,实现此语义的算法是否正确执行?

只执行一次(exactly once)的语义保证了一个重复的过程将被精确地执行一次并且只执行一次。确保这一方法的通用算法结合了一个确认(ACK)方案和时间戳(或者其他一些允许服务器区分重复消息的增量计数器)。通常的策略是客户端将RPC发送到服务器,并使用时间戳。客户端还将启动一个超时时钟。然后,客户机将等待两个事件中的一个:(1)它将从服务器接收一个ACK,指示执行远程过程,或者是(2)它将超时。如果客户机超时,则假定服务器无法执行远程过程,因此客户机将第二次调用RPC,然后发送一个较晚的时间戳。

客户端可能没有收到ACK,原因有二:(1)原始RPC从未被服务器接收,或者(2)RPC被服务器正确地接收和执行,但是ACK丢失了。在情况(1)中,ack的使用允许服务器最终接收和执行RPC。在情况(2)中,服务器将接收一个重复的RPC,它将使用时间戳将其标识为副本,以便第二次不执行RPC。需要注意的是,服务器必须向客户机发送第二个ACK,以通知客户机RPC已经执行。

假设分布式系统易受服务器故障的影响。需要什么样的机制来保证只执行一次(exactly once)的语义来执行rpc ?

服务器应该跟踪稳定的存储(例如磁盘日志)信息,了解是否接收了RPC操作,是否成功执行了这些操作,以及与操作相关的结果。当服务器发生崩溃并接收到RPC消息时,服务器可以检查RPC是否已经执行,为已执行的RPCs保证只执行一次(exactly once)的语义

描述长期、中期、短期调度程序的区别

长期调度程序( long-term scheduler):决定将哪些作业引入到系统中进行处理

调度程序( long-term scheduler):从已准备好的或阻塞的队列中选择进程,并将它们从内存中删除,然后在之后再恢复它们以继续运行。

短期调度程序( long-term scheduler):从内存中等待执行的进程中选择一个,并将CPU分配给它。

描述内核在进程的上下文切换之间所采取的操作:

  • 1.为了响应时钟中断,操作系统将保存当前执行进程的PC和用户堆栈指针,并将控制转移到内核时钟中断处理程序。

  • 2.时钟中断处理程序将其余的寄存器,以及其他机器状态,如浮点寄存器的状态,保存到进程的PCB中。

  • 3.操作系统调用调度程序/调度器来确定要执行的下一个进程

  • 4.然后,操作系统从PCB中检索下一个进程的状态,并重新存储寄存器。这个恢复操作将处理器带回之前所在的状态。

10.

说明在处理进程终止中,init进程在UNIX和Linux系统中的作用

当一个进程被终止时,它会暂时地移动到“僵尸状态”并保持在那个状态,直到父调用调用wait()。当这发生时,PID和进程表中的条目都被释放。但是,如果父进程不调用wait(),那么在父进程仍然存在时,子进程仍然是一个僵尸。一旦父进程终止,init进程将成为僵尸程序的新的父进程。init进程周期性地调用wait(),它最终会在僵尸进程的进程表中释放PID和条目。

下面的程序创造了多少进程?

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

int main()
{
    int i;

    for (i = 0; i < 4; i++)
        fork();

    return 0;
}

16个

解释下面代码中,在printf("LINE J")发生的事:

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

int main()
{
    pid_t pid;
    /* fork a child process */
       pid = fork();

    if (pid < 0) { /* error occurred */ 
                   fprintf(stderr, "Fork Failed"); 
               return 1;
    }
    else if (pid == 0) { /* child process */
                execlp("/bin/ls","ls",NULL);
                printf("LINE J");
    }
    else { /* parent process */
              /* parent will wait for the child to complete */
                wait(NULL);
                printf("Child Complete");
    }

    return 0;
}

调用exec()将进程的地址空间替换为指定为exec()的参数的程序。如果对exec()的调用成功,则新程序现在就在运行, 控制权会从调用端到exec(),而不会返回。在这种情况下,printf("Line J")永远不会被执行。但是,如果在调用exec()时发生错误,则函数将返回控制,并因此而执行printf("Line J")

判断下面程序中A,B,C,D的值(假设真实情况下父进程的PID为2600,子进程的是PID是2603)

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

  int main()
  {
    pid_t pid, pid1;

    /* fork a child process */
    pid = fork();
    if (pid < 0) { 
      /* error occurred */ 
      fprintf(stderr, "Fork Failed"); 
      return 1;
    }
    else if (pid == 0) { /* child process */
      pid1 = getpid();
      printf("child: pid = %d\n",pid); /* A */
      printf("child: pid1 = %d\n",pid1); /* B */
    }
    else { /* parent process */
      pid1 = getpid();
      printf("parent: pid = %d\n",pid); /* C */
      printf("parent: pid1 = %d\n",pid1); /* D */
      wait(NULL);
    }

    return 0;
  }

A = 0 B = 2603 C = 2603 D = 2600

getpid()获取当前进程 PID,所以不难确定D是2600,而B是2603

而对于A和B中所指的pid变量,因为pid是初始化为0,父进程创建进程而已赋值pid为子进程PID,但是子进程将pid的0初始值复制后,并没有再赋值,所以还是0.

举个例子,来说明在这种情况下,普通管道比具名管道更合适,并举例说明命名管道比普通管道更合适的情况。

在以下情况下,普通管道比命名管道更合适:

  • 如果我们想在同一台机器上两个特定进程之间建立通信,那么使用普通管道比使用命名管道更合适,因为命名管道在这种情况下需要更多的开销。

  • 在这种情况下,当通信完成后,我们不允许其他进程来访问这个管道时,普通管道要更好

以下情况下更适合具名管道:

  • 希望多个进程间通信时候,我们可以使用具名管道
  • 当通信完成后,并不希望销毁管道时候

考虑RPC机制。描述没有遵循“最多执行一次”或“只执行一次”语义可能产生的不良后果。描述一个没有这些保证的机制的可能用途。

当局部过程在极端情况下失败时,RPC可能会失败,或者由于网络错误而多次被复制和执行。这就是所谓的调用语义。

如果RPC机制不能支持“最多执行一次”或“只执行一次”语义,那么RPC服务器不能保证远程过程不会被多次调用事件的发生。如果一个从一个系统的银行帐户中取出钱的远程过程并不支持这些语义,那么远程过程的单次调用可能导致服务器上的多次取款。

对于一个支持这两种语义的系统,通常需要服务器维护某种形式的客户机状态,比如时间戳。如果一个系统无法支持其中任何一个语义,这样的系统只能安全地提供不改变数据或提供时间敏感结果的远程过程。

以我们的银行帐户为例,我们当然需要“最多执行一次”或“只执行一次”的语义来执行取款(或存款!)但是,然而对帐户余额或其他帐户信息(如名称、地址等)的查询却并不需要这些语义。

下面代码中Line X和Line Y的输出??

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

  #define SIZE 5

  int nums[SIZE] = {0,1,2,3,4};

  int main()
  {
    int i; 
    pid_t pid;

    pid = fork();

    if (pid == 0) {
      for (i = 0; i < SIZE; i++) {
        nums[i] *= -i;
        printf("CHILD: %d ",nums[i]); /* LINE X */
      } 
    }
    else if (pid > 0) { 
      wait(NULL);

      for (i = 0; i < SIZE; i++)
        printf("PARENT: %d ",nums[i]); /* LINE Y */
    }

    return 0;
  }

因为子进程是父进程的副本,因此子进程所做的任何更改都将发生在它的数据副本中,并且不会在父进程中反映出来。结果,在Line X输出的值是0,-1,-4,-9,-16。父节点所执行 Line Y上的值为0 1 2 3 4。

下列各项的好处和坏处是什么? 从系统级别和程序员级别来考虑。

(a)同步与异步通信:

  • 同步通信的好处是它允许发送方和接收方之间有一个集合点(rendezvous )。阻塞发送的一个缺点是集合点(rendezvous) 可能根本不需要,消息可以异步传递。因此,消息传递系统通常提供两种形式的同步。

  • 从程序角度而言,如果这两个进程都不能互相阻止其执行,这就可以导致更好的性能

(b)自动和显式缓冲:

自动缓冲提供了一个具有无限长度的队列,从而确保发送方在等待复制消息时不需要阻塞。没有关于自动缓冲如何提供的规范;一个方案可以保留足够大的内存,代价就是很可能其中大部分内存都被浪费了(消息太少时)。显式缓冲需要指定缓冲区的大小。在这种情况下,发送方可能在等待队列中的可用空间时被阻塞。但是,在显式缓冲的情况下,不太可能浪费内存。

(c)发送拷贝还是引用??:

使用拷贝的话不允许接收方改变参数的状态;通过引用的话允许修改参数状态。通过引用发送的好处是,它允许程序员编写一个集中式应用程序的分布式版本。虽然Java RMI也提供了这功能;但是,通过引用传递参数需要将参数声明为远程对象。

通过复制发送可能会增加安全性,因为传递的是值,而不是对值的地址的引用被传递,因此原始文件不会被损坏。但是,如果值是像struct或二进制对象那样大的东西,则通过引用来保持堆栈较小是有利的。此外,有时需要或有必要改变原有的价值,这需要通过引用发送。

(d)固定大小和可变大小的消息:

这其中的含义大多与缓冲问题有关;对于固定大小的消息,具有特定大小的缓冲区可以容纳已知数量的消息。 可以由这样的缓冲区保存的可变大小的消息的数量却是未知的。考虑一下Windows 2000如何处理这种情况:使用固定大小。 消息(任何小于256字节的消息)都是从发送方的地址空间复制到接收进程的地址空间。更大的消息(即可变大小的消息)使用共享内存来传递消息。

results matching ""

    No results matching ""