练习参考答案#

课后练习#

编程题#

  1. * 分别编写基于UNIX System V IPC的管道、共享内存、信号量和消息队列的Linux应用程序,实现进程间的数据交换。

    管道

     1#include <unistd.h>
     2#include <stdio.h>
     3#include <stdlib.h>
     4#include <string.h>
     5
     6int main(void) {
     7  int pipefd[2];
     8  // pipe syscall creates a pipe with two ends
     9  // pipefd[0] is the read end
    10  // pipefd[1] is the write end
    11  // ref: https://man7.org/linux/man-pages/man2/pipe.2.html
    12  if (pipe(pipefd) == -1) {
    13    perror("failed to create pipe");
    14    exit(EXIT_FAILURE);
    15  }
    16
    17  int pid = fork();
    18  if (pid == -1) {
    19    perror("failed to fork");
    20    exit(EXIT_FAILURE);
    21  }
    22
    23  if (pid == 0) {
    24    // child process reads from the pipe
    25    close(pipefd[1]); // close the write end
    26    // read a byte at a time
    27    char buf;
    28    while (read(pipefd[0], &buf, 1) > 0) {
    29      printf("%s", &buf);
    30    }
    31    close(pipefd[0]); // close the read end
    32  } else {
    33    // parent process writes to the pipe
    34    close(pipefd[0]); // close the read end
    35    // parent writes
    36    char* msg = "hello from pipe\n";
    37    write(pipefd[1], msg, strlen(msg)); // omitting error handling
    38    close(pipefd[1]); // close the write end
    39  }
    40
    41  return EXIT_SUCCESS;
    42}
    

    共享内存

     1#include <unistd.h>
     2#include <stdio.h>
     3#include <stdlib.h>
     4#include <string.h>
     5#include <sys/shm.h>
     6
     7int main(void) {
     8  // create a new anonymous shared memory segment of page size, with a permission of 0600
     9  // ref: https://man7.org/linux/man-pages/man2/shmget.2.html
    10  int shmid = shmget(IPC_PRIVATE, sysconf(_SC_PAGESIZE), IPC_CREAT | 0600);
    11  if (shmid == -1) {
    12    perror("failed to create shared memory");
    13    exit(EXIT_FAILURE);
    14  }
    15
    16  int pid = fork();
    17  if (pid == -1) {
    18    perror("failed to fork");
    19    exit(EXIT_FAILURE);
    20  }
    21
    22  if (pid == 0) {
    23    // attach the shared memory into child process's address space
    24    char* shm = shmat(shmid, NULL, 0);
    25    while (!shm[0]) {
    26      // wait until the parent signals that the data is ready
    27      // WARNING: this is not the correct way to synchronize processes
    28      // on SMP systems due to memory orders, but this implementation
    29      // is chosen here specifically for ease of understanding
    30    }
    31    printf("%s", shm + 1);
    32  } else {
    33    // attach the shared memory into parent process's address space
    34    char* shm = shmat(shmid, NULL, 0);
    35    // copy message into shared memory
    36    strcpy(shm + 1, "hello from shared memory\n");
    37    // signal that the data is ready
    38    shm[0] = 1;
    39  }
    40
    41  return EXIT_SUCCESS;
    42}
    

    信号量

     1#include <unistd.h>
     2#include <stdio.h>
     3#include <stdlib.h>
     4#include <string.h>
     5#include <sys/sem.h>
     6
     7int main(void) {
     8  // create a new anonymous semaphore set, with permission 0600
     9  // ref: https://man7.org/linux/man-pages/man2/semget.2.html
    10  int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    11  if (semid == -1) {
    12    perror("failed to create semaphore");
    13    exit(EXIT_FAILURE);
    14  }
    15
    16  struct sembuf sops[1];
    17  sops[0].sem_num = 0; // operate on semaphore 0
    18  sops[0].sem_op  = 1; // increase the semaphore's value by 1
    19  sops[0].sem_flg = 0;
    20  if (semop(semid, sops, 1) == -1) {
    21    perror("failed to increase semaphore");
    22    exit(EXIT_FAILURE);
    23  }
    24
    25  int pid = fork();
    26  if (pid == -1) {
    27    perror("failed to fork");
    28    exit(EXIT_FAILURE);
    29  }
    30
    31  if (pid == 0) {
    32    printf("hello from child, waiting for parent to release semaphore\n");
    33    struct sembuf sops[1];
    34    sops[0].sem_num = 0; // operate on semaphore 0
    35    sops[0].sem_op  = 0; // wait for the semaphore to become 0
    36    sops[0].sem_flg = 0;
    37    if (semop(semid, sops, 1) == -1) {
    38      perror("failed to wait on semaphore");
    39      exit(EXIT_FAILURE);
    40    }
    41    printf("hello from semaphore\n");
    42  } else {
    43    printf("hello from parent, waiting three seconds before release semaphore\n");
    44    // sleep for three second
    45    sleep(3);
    46    struct sembuf sops[1];
    47    sops[0].sem_num = 0; // operate on semaphore 0
    48    sops[0].sem_op  = -1; // decrease the semaphore's value by 1
    49    sops[0].sem_flg = 0;
    50    if (semop(semid, sops, 1) == -1) {
    51      perror("failed to decrease semaphore");
    52      exit(EXIT_FAILURE);
    53    }
    54  }
    55
    56  return EXIT_SUCCESS;
    57}
    

    消息队列

     1#include <unistd.h>
     2#include <stdio.h>
     3#include <stdlib.h>
     4#include <string.h>
     5#include <sys/msg.h>
     6
     7struct msgbuf {
     8  long mtype;
     9  char mtext[1];
    10};
    11
    12int main(void) {
    13  // create a new anonymous message queue, with a permission of 0600
    14  // ref: https://man7.org/linux/man-pages/man2/msgget.2.html
    15  int msgid = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
    16  if (msgid == -1) {
    17    perror("failed to create message queue");
    18    exit(EXIT_FAILURE);
    19  }
    20
    21  int pid = fork();
    22  if (pid == -1) {
    23    perror("failed to fork");
    24    exit(EXIT_FAILURE);
    25  }
    26
    27  if (pid == 0) {
    28    // child process receives message
    29    struct msgbuf buf;
    30    while (msgrcv(msgid, &buf, sizeof(buf.mtext), 1, 0) != -1) {
    31      printf("%c", buf.mtext[0]);
    32    }
    33  } else {
    34    // parent process sends message
    35    char* msg = "hello from message queue\n";
    36    struct msgbuf buf;
    37    buf.mtype = 1;
    38    for (int i = 0; i < strlen(msg); i ++) {
    39      buf.mtext[0] = msg[i];
    40      msgsnd(msgid, &buf, sizeof(buf.mtext), 0);
    41    }
    42    struct msqid_ds info;
    43    while (msgctl(msgid, IPC_STAT, &info), info.msg_qnum > 0) {
    44      // wait for the message queue to be fully consumed
    45    }
    46    // close message queue
    47    msgctl(msgid, IPC_RMID, NULL);
    48  }
    49
    50  return EXIT_SUCCESS;
    51}
    
  2. ** 分别编写基于UNIX的signal机制的Linux应用程序,实现进程间异步通知。

     1#include <unistd.h>
     2#include <stdio.h>
     3#include <stdlib.h>
     4#include <signal.h>
     5
     6static void sighandler(int sig) {
     7  printf("received signal %d, exiting\n", sig);
     8  exit(EXIT_SUCCESS);
     9}
    10
    11int main(void) {
    12  struct sigaction sa;
    13  sa.sa_handler = sighandler;
    14  sa.sa_flags = 0;
    15  sigemptyset(&sa.sa_mask);
    16  // register function sighandler as signal handler for SIGUSR1
    17  if (sigaction(SIGUSR1, &sa, NULL) != 0) {
    18    perror("failed to register signal handler");
    19    exit(EXIT_FAILURE);
    20  }
    21
    22  int pid = fork();
    23  if (pid == -1) {
    24    perror("failed to fork");
    25    exit(EXIT_FAILURE);
    26  }
    27
    28  if (pid == 0) {
    29    while (1) {
    30      // loop and wait for signal
    31    }
    32  } else {
    33    // send SIGUSR1 to child process
    34    kill(pid, SIGUSR1);
    35  }
    36
    37  return EXIT_SUCCESS;
    38}
    
  3. ** 参考rCore Tutorial 中的shell应用程序,在Linux环境下,编写一个简单的shell应用程序,通过管道相关的系统调用,能够支持管道功能。

     1#include <stdio.h>
     2#include <stdlib.h>
     3#include <string.h>
     4#include <sys/wait.h>
     5#include <unistd.h>
     6
     7int parse(char* line, char** argv) {
     8  size_t len;
     9  // read a line from stdin
    10  if (getline(&line, &len, stdin) == -1)
    11    return -1;
    12  // remove trailing newline
    13  line[strlen(line) - 1] = '\0';
    14  // split line into tokens
    15  int i = 0;
    16  char* token = strtok(line, " ");
    17  while (token != NULL) {
    18    argv[i] = token;
    19    token = strtok(NULL, " ");
    20    i++;
    21  }
    22  return 0;
    23}
    24
    25int concat(char** argv1, char** argv2) {
    26    // create pipe
    27    int pipefd[2];
    28    if (pipe(pipefd) == -1)
    29      return -1;
    30
    31    // run the first command
    32    int pid1 = fork();
    33    if (pid1 == -1)
    34      return -1;
    35    if (pid1 == 0) {
    36      dup2(pipefd[1], STDOUT_FILENO);
    37      close(pipefd[0]);
    38      close(pipefd[1]);
    39      execvp(argv1[0], argv1);
    40    }
    41
    42    // run the second command
    43    int pid2 = fork();
    44    if (pid2 == -1)
    45      return -1;
    46    if (pid2 == 0) {
    47      dup2(pipefd[0], STDIN_FILENO);
    48      close(pipefd[0]);
    49      close(pipefd[1]);
    50      execvp(argv2[0], argv2);
    51    }
    52
    53    // wait for them to exit
    54    close(pipefd[0]);
    55    close(pipefd[1]);
    56    wait(&pid1);
    57    wait(&pid2);
    58    return 0;
    59}
    60
    61int main(void) {
    62  printf("[command 1]$ ");
    63  char* line1 = NULL;
    64  char* argv1[16] = {NULL};
    65  if (parse(line1, argv1) == -1) {
    66    exit(EXIT_FAILURE);
    67  }
    68  printf("[command 2]$ ");
    69  char* line2 = NULL;
    70  char* argv2[16] = {NULL};
    71  if (parse(line2, argv2) == -1) {
    72    exit(EXIT_FAILURE);
    73  }
    74  concat(argv1, argv2);
    75  free(line1);
    76  free(line2);
    77}
    
  4. ** 扩展内核,实现共享内存机制。

  5. *** 扩展内核,实现signal机制。

    略,设计思路可参见问答题2。

问答题#

  1. * 直接通信和间接通信的本质区别是什么?分别举一个例子。

    本质区别是消息是否经过内核,如共享内存就是直接通信,消息队列则是间接通信。

  2. ** 试说明基于UNIX的signal机制,如果在本章内核中实现,请描述其大致设计思路和运行过程。

    首先需要添加两个syscall,其一是注册signal handler,其二是发送signal。其次是添加对应的内核数据结构,对于每个进程需要维护两个表,其一是signal到handler地址的对应,其二是尚未处理的signal。当进程注册signal handler时,将所注册的处理函数的地址填入表一。当进程发送signal时,找到目标进程,将signal写入表二的队列之中。随后修改从内核态返回用户态的入口点的代码,检查是否有待处理的signal。若有,检查是否有对应的signal handler并跳转到该地址,如无则执行默认操作,如杀死进程。需要注意的是,此时需要记住原本的跳转地址,当进程从signal handler返回时将其还原。

  3. ** 比较在Linux中的无名管道(普通管道)与有名管道(FIFO)的异同。

    同:两者都是进程间信息单向传递的通路,可以在进程之间传递一个字节流。异:普通管道不存在文件系统上对应的文件,而是仅由读写两端两个fd表示,而FIFO则是由文件系统上的一个特殊文件表示,进程打开该文件后获得对应的fd。

  4. ** 请描述Linux中的无名管道机制的特征和适用场景。

    无名管道用于创建在进程间传递的一个字节流,适合用于流式传递大量数据,但是进程需要自己处理消息间的分割。

  5. ** 请描述Linux中的消息队列机制的特征和适用场景。

    消息队列用于在进程之间发送一个由type和data两部分组成的短消息,接收消息的进程可以通过type过滤自己感兴趣的消息,适用于大量进程之间传递短小而多种类的消息。

  6. ** 请描述Linux中的共享内存机制的特征和适用场景。

    共享内存用于创建一个多个进程可以同时访问的内存区域,故而消息的传递无需经过内核的处理,适用在需要较高性能的场景,但是进程之间需要额外的同步机制处理读写的顺序与时机。

  7. ** 请描述Linux的bash shell中执行与一个程序时,用户敲击 Ctrl+C 后,会产生什么信号(signal),导致什么情况出现。

    会产生SIGINT,如果该程序没有捕获该信号,它将会被杀死,若捕获了,通常会在处理完或是取消当前正在进行的操作后主动退出。

  8. ** 请描述Linux的bash shell中执行与一个程序时,用户敲击 Ctrl+Zombie 后,会产生什么信号(signal),导致什么情况出现。

    会产生SIGTSTP,该进程将会暂停运行,将控制权重新转回shell。

  9. ** 请描述Linux的bash shell中执行 kill -9 2022 这个命令的含义是什么?导致什么情况出现。

    向pid为2022的进程发送SIGKILL,该信号无法被捕获,该进程将会被强制杀死。

  10. ** 请指出一种跨计算机的主机间的进程间通信机制。

    一个在过去较为常用的例子是Sun RPC。