练习参考答案#
课后练习#
编程题#
* 分别编写基于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}
** 分别编写基于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}
** 参考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}
** 扩展内核,实现共享内存机制。
略
*** 扩展内核,实现signal机制。
略,设计思路可参见问答题2。
问答题#
* 直接通信和间接通信的本质区别是什么?分别举一个例子。
本质区别是消息是否经过内核,如共享内存就是直接通信,消息队列则是间接通信。
** 试说明基于UNIX的signal机制,如果在本章内核中实现,请描述其大致设计思路和运行过程。
首先需要添加两个syscall,其一是注册signal handler,其二是发送signal。其次是添加对应的内核数据结构,对于每个进程需要维护两个表,其一是signal到handler地址的对应,其二是尚未处理的signal。当进程注册signal handler时,将所注册的处理函数的地址填入表一。当进程发送signal时,找到目标进程,将signal写入表二的队列之中。随后修改从内核态返回用户态的入口点的代码,检查是否有待处理的signal。若有,检查是否有对应的signal handler并跳转到该地址,如无则执行默认操作,如杀死进程。需要注意的是,此时需要记住原本的跳转地址,当进程从signal handler返回时将其还原。
** 比较在Linux中的无名管道(普通管道)与有名管道(FIFO)的异同。
同:两者都是进程间信息单向传递的通路,可以在进程之间传递一个字节流。异:普通管道不存在文件系统上对应的文件,而是仅由读写两端两个fd表示,而FIFO则是由文件系统上的一个特殊文件表示,进程打开该文件后获得对应的fd。
** 请描述Linux中的无名管道机制的特征和适用场景。
无名管道用于创建在进程间传递的一个字节流,适合用于流式传递大量数据,但是进程需要自己处理消息间的分割。
** 请描述Linux中的消息队列机制的特征和适用场景。
消息队列用于在进程之间发送一个由type和data两部分组成的短消息,接收消息的进程可以通过type过滤自己感兴趣的消息,适用于大量进程之间传递短小而多种类的消息。
** 请描述Linux中的共享内存机制的特征和适用场景。
共享内存用于创建一个多个进程可以同时访问的内存区域,故而消息的传递无需经过内核的处理,适用在需要较高性能的场景,但是进程之间需要额外的同步机制处理读写的顺序与时机。
** 请描述Linux的bash shell中执行与一个程序时,用户敲击 Ctrl+C 后,会产生什么信号(signal),导致什么情况出现。
会产生SIGINT,如果该程序没有捕获该信号,它将会被杀死,若捕获了,通常会在处理完或是取消当前正在进行的操作后主动退出。
** 请描述Linux的bash shell中执行与一个程序时,用户敲击 Ctrl+Zombie 后,会产生什么信号(signal),导致什么情况出现。
会产生SIGTSTP,该进程将会暂停运行,将控制权重新转回shell。
** 请描述Linux的bash shell中执行 kill -9 2022 这个命令的含义是什么?导致什么情况出现。
向pid为2022的进程发送SIGKILL,该信号无法被捕获,该进程将会被强制杀死。
** 请指出一种跨计算机的主机间的进程间通信机制。
一个在过去较为常用的例子是Sun RPC。