过年很常时间没有开 lab 了,这个 lab 做了两天(不包括写文档),第一天就是读文档和测试各种 demo,第二天前半天也是测试各种 demo,晚上才把 lab 写完。大概这就是做这个 lab 的体验吧。
做这个 lab 的时候 CSAPP 下载 lab 材料的网站还炸了,一度以为是不开放了再也上不去了
Shell Lab 介绍
Shell Lab 要求实现一个带有作业控制的 Unix Shell 程序,需要考虑基础的并发,进程控制以及信号和信号处理。做这个实验之前一定要将 CSAPP 的第八章(异常控制流)仔细研读。
执行下面的命令,开启 shell lab 之旅
tar xvf shlab-handout.tar
cd shlab-handout/
make
接下来我花了一上午的时间来研读文档,了解实验的要求
Demo 与猜想
读完文档后,就开始尝试通过写代码来印证各种猜想。
首先需要注意区分进程的停止(stopped)和终止(terminated) 状态,其实停止这个词翻译得不太好,暂停表达的意思更合理,所以后文如果提到暂停或者 stopped 都表示停止(stopped)状态。
WNOHANG 与 WUNTRACED
WNOHANG
和 WUNTRACED
信号的区别(CSAPP上关于 WUNTRACED
的解释有点难理解它想表达的意思)
上面这个回答还比较正确,我也经过多次实验验证,WUNTRACED
参数下遇到等待集合中的一个子进程停止或终止状态就会返回。(有一些博客上说 WUNTRACED
是遇到停止但没有终止才会返回,这是错误的)
promask2.c 和 sigsuspend.c 的理解
这是书上的两个关于信号部分比较重要且值得理解的示例程序
promask2.c
在 P543,核心就是在fork()
之前需要阻塞SIGCHLD
,然后做完addjob(pid)
等创建子进程后需要做的有关的操作完成后,再解除阻塞SIGCHLD
sigsuspend.c
在 P546,核心就是讲了显示地等待信号的正确方法,先将需要等待地信号屏蔽,然后使用sigsuspend
让进程进入休眠状态,直到信号到达触发handler
,在handler
中完成信号到达后应该做的任务,从handler
返回之后程序苏醒继续运行,再解除信号屏蔽。(关于sigsuspend
见书或上一篇文章)
pause 的第一个坑
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
int main() {
printf("Hello\n");
pause();
printf("After pause\n");
return 0;
}
如上面的代码,在终端发送 SIGCONT
信号,没有反应
如果想达到期望的效果,代码应该这样写:
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
void sigcont_handler(int sig) {
}
int main() {
signal(SIGCONT, sigcont_handler);
printf("Hello\n");
pause();
printf("After pause\n");
return 0;
}
原因是这个,pause 返回当且仅当一个信号到达且从信号处理函数返回。
僵尸进程的猜想验证
如下的情况为什么是仅有 2 个僵尸进程,而不是 3 个呢 ?
可以通过 printf("pid1 = %d pid2 = %d\n", pid1, pid2);
来验证,
void sigcont_handler(int sig) {
}
int main() {
int pid1 = fork();
int pid2 = fork();
signal(SIGCONT, sigcont_handler);
printf("pid1 = %d pid2 = %d\n", pid1, pid2);
printf("Hello\n");
if(pid1 && pid2) {
pause();
}
return 0;
}
回收进程的 demo
下面是一个使用 sigsuspend
回收僵尸进程的实验
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
volatile sig_atomic_t pid, Cnt;
void sigchld_handler(int sig) {
int olderrno = errno, status;
while((pid = waitpid(-1, &status, 0)) > 0)
printf("Reaped pid = %d\n", pid), ++Cnt;
errno = olderrno;
}
int main() {
pid_t pid;
sigset_t mask, prev;
signal(SIGCHLD, sigchld_handler);
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &prev);
for(int i = 1; i <= 4; ++i)
if((pid = fork()) == 0) {
//子进程
sigprocmask(SIG_SETMASK, &prev, NULL);
printf("child process %d\n", getpid());
sleep(getpid() % 5 + 1);
_exit(0);
}
while(Cnt < 4)
sigsuspend(&prev);
sigprocmask(SIG_SETMASK, &prev, NULL);
return 0;
}
waitpid 会浪费 CPU 资源吗
下面是 ChatGPT 的回答,
我也经过多次实验证明,waitpid
阻塞的过程中,进程处于 可中断睡眠状态(S)
,和 pause
的效果是一样的,不会浪费 CPU 资源
pause 的第二个坑 与 Linux 进程状态
下图是当时遇到的一个问题,
这是一个值得注意的坑, pause()
不会将进程变成 stopped
状态,而是变成可中断的睡眠状态(S),T 才代表暂停或跟踪状态,具体见 Linux进程状态说明
R (task_running) : 可执行状态
S (task_interruptible): 可中断的睡眠状态
D (task_uninterruptible): 不可中断的睡眠状态
T(task_stopped or task_traced):暂停状态或跟踪状态
Z (task_dead - exit_zombie):退出状态,进程成为僵尸进程
X (task_dead - exit_dead):退出状态,进程即将被销毁
下面这样就达到了想要的结果了,成功测试了 WUNTRACED
这个可选参数,当然 WNOHANG
我也测试过了
遗留的 waitpid 的困惑与猜想
我写了 3 个 waitpid{1-3}.c
,这里就懒得放出来了,大致总结出两个猜想:
waitpid
返回值为 0 的话status
的结果是不可信的(对应waitpid1.c,waitpid2.c
)-
waitpid
只在子进程状态改变时才能正常返回,返回的结果才有效,或者说对同一子进程调用两次waitpid
可能会出问题(waitpid3.c
是两次对一个已经stopped
进程调用waitpid
,选项为WUNTRACED
,按理说WUNTRACED
选项的情况下遇到stopped
进程是什么都没做吧,也不会回收它,但第二次调用它就阻塞了)
代码实现
在进行了诸多猜想验证和 demo 实验后,那么完成这个 shell lab 问题不是太大了。但写的时候还发现了一个新知识:
/*
sigchld_handler - The kernel sends a SIGCHLD to the shell whenever a child job terminates (becomes a zombie), or stops because it received a SIGSTOP or SIGTSTP signal. The handler reaps all available zombie children, but doesn't wait for any other currently running children to terminate.
*/
从 lab 上的注释中我发现了 CSAPP 上一个漏讲的东西,就是在子进程收到 SIGSTOP
等停止相关的信号时也会向父进程发送 SIGCHLD
。
此外写代码的时候还有一个很烦的就是得多次查看文档要求,因为要求实在太多了,容易漏掉😭
下面直接上最后的代码:
/*
* tsh - A tiny shell program with job control
*
* <Put your name and login ID here>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
/* Misc manifest constants */
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1<<16 /* max job ID */
/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1 /* running in foreground */
#define BG 2 /* running in background */
#define ST 3 /* stopped */
/*
* Jobs states: FG (foreground), BG (background), ST (stopped)
* Job state transitions and enabling actions:
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* At most 1 job can be in the FG state.
*/
/* Global variables */
extern char **environ; /* defined in libc */
char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
int verbose = 0; /* if true, print additional output */
int nextjid = 1; /* next job ID to allocate */
char sbuf[MAXLINE]; /* for composing sprintf messages */
struct job_t { /* The job struct */
pid_t pid; /* job PID */
int jid; /* job ID [1, 2, ...] */
int state; /* UNDEF, BG, FG, or ST */
char cmdline[MAXLINE]; /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */
/* Function prototypes */
/* Here are the functions that you will implement */
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);
void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);
/* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, char **argv);
void sigquit_handler(int sig);
void clearjob(struct job_t *job);
void initjobs(struct job_t *jobs);
int maxjid(struct job_t *jobs);
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *jobs, pid_t pid);
pid_t fgpid(struct job_t *jobs);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid);
int pid2jid(pid_t pid);
void listjobs(struct job_t *jobs);
void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
/*
* main - The shell's main routine
*/
int main(int argc, char **argv)
{
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; /* emit prompt (default) */
/* Redirect stderr to stdout (so that driver will get all output
* on the pipe connected to stdout) */
dup2(1, 2);
/* Parse the command line */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h': /* print help message */
usage();
break;
case 'v': /* emit additional diagnostic info */
verbose = 1;
break;
case 'p': /* don't print a prompt */
emit_prompt = 0; /* handy for automatic testing */
break;
default:
usage();
}
}
/* Install the signal handlers */
/* These are the ones you will need to implement */
Signal(SIGINT, sigint_handler); /* ctrl-c */
Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
/* This one provides a clean way to kill the shell */
Signal(SIGQUIT, sigquit_handler);
/* Initialize the job list */
initjobs(jobs);
/* Execute the shell's read/eval loop */
while (1) {
/* Read command line */
if (emit_prompt) {
printf("%s", prompt);
fflush(stdout);
}
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
app_error("fgets error");
if (feof(stdin)) { /* End of file (ctrl-d) */
fflush(stdout);
exit(0);
}
/* Evaluate the command line */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}
exit(0); /* control never reaches here */
}
/*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline)
{
char buf[MAXLINE];
char *argv[MAXARGS];
int bg;
pid_t pid;
sigset_t mask, prev, mask_all;
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if(argv[0] == NULL) return;
int is_builtin_cmd = builtin_cmd(argv);
if(is_builtin_cmd) return; //在当前进程中执行内置命令
//fork之前先 block SIGCHLD
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask, &prev);
//fork一个新进程执行非内置命令
if((pid = fork()) == 0) {
setpgid(0, 0); //将子进程的进程组ID改成子进程ID
sigprocmask(SIG_SETMASK, &prev, NULL);
if(execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
sigprocmask(SIG_BLOCK, &mask_all, NULL); //访问全局数据结构前阻塞所有信号
addjob(jobs, pid, (bg ? BG : FG), cmdline);
sigprocmask(SIG_SETMASK, &prev, NULL);
if(bg) printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline);
else waitfg(pid);
return;
}
/*
* parseline - Parse the command line and build the argv array.
*
* Characters enclosed in single quotes are treated as a single
* argument. Return true if the user has requested a BG job, false if
* the user has requested a FG job.
*/
int parseline(const char *cmdline, char **argv)
{
static char array[MAXLINE]; /* holds local copy of command line */
char *buf = array; /* ptr that traverses command line */
char *delim; /* points to first space delimiter */
int argc; /* number of args */
int bg; /* background job? */
strcpy(buf, cmdline);
buf[strlen(buf)-1] = ' '; /* replace trailing '\n' with space */
while (*buf && (*buf == ' ')) /* ignore leading spaces */
buf++;
/* Build the argv list */
argc = 0;
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
}
else {
delim = strchr(buf, ' ');
}
while (delim) {
argv[argc++] = buf;
*delim = '\0';
buf = delim + 1;
while (*buf && (*buf == ' ')) /* ignore spaces */
buf++;
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
}
else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL;
if (argc == 0) /* ignore blank line */
return 1;
/* should the job run in the background? */
if ((bg = (*argv[argc-1] == '&')) != 0) {
argv[--argc] = NULL;
}
return bg;
}
/*
* builtin_cmd - If the user has typed a built-in command then execute
* it immediately.
*/
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0], "quit")) exit(0);
if(!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) {
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0], "jobs")) {
listjobs(jobs);
return 1;
}
return 0; /* not a builtin command */
}
/*
* do_bgfg - Execute the builtin bg and fg commands
*/
void do_bgfg(char **argv)
{
pid_t pid = 0;
int jid = 0, bg = 0;
struct job_t *job;
sigset_t mask_all, prev;
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
if(argv[1] == NULL) {
printf("%s command requires PID or %%jobid argument\n", argv[0]);
goto END;
}
if(argv[1][0] == '%') {
sscanf(&argv[1][1], "%d", &jid);
job = getjobjid(jobs, jid);
if(job == NULL) {
printf("%%%d: No such job\n", jid);
goto END;
}
} else
if(isdigit(argv[1][0])) {
sscanf(&argv[1][0], "%d", &pid);
job = getjobpid(jobs, pid);
if(job == NULL) {
printf("(%d): No such process\n", pid);
goto END;
}
} else {
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
goto END;
}
if(!strcmp(argv[0], "bg")) bg = 1;
kill(-job->pid, SIGCONT);
job->state = (bg ? BG : FG);
sigprocmask(SIG_SETMASK, &prev, NULL); //调用waitfg之前解除信号屏蔽
if(bg) printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
else waitfg(pid);
return;
END:
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}
/*
* waitfg - Block until process pid is no longer the foreground process
*/
void waitfg(pid_t pid)
{
// int status;
// waitpid(pid, &status, 0); //感觉返回值是啥都行,没有必要检查
sigset_t mask, prev;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &prev);
while(fgpid(jobs) > 0)
sigsuspend(&prev);
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}
/*****************
* Signal handlers
*****************/
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn't wait for any other
* currently running children to terminate.
*/
void sigchld_handler(int sig)
{
int olderrno = errno, status;
sigset_t mask_all, prev;
pid_t pid;
struct job_t *job;
sigfillset(&mask_all);
while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
sigprocmask(SIG_BLOCK, &mask_all, &prev); //阻塞所有信号,保护对共享全局数据结构的访问
if(WIFEXITED(status)) {
deletejob(jobs, pid);
}
if(WIFSIGNALED(status)) {
printf ("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
deletejob(jobs, pid);
}
if(WIFSTOPPED(status)) {
job = getjobpid(jobs, pid);
job->state = ST;
printf ("Job [%d] (%d) stoped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
}
sigprocmask(SIG_SETMASK, &prev, NULL);
}
errno = olderrno;
return;
}
/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev;
pid_t pid;
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
if((pid = fgpid(jobs)) > 0) {
kill(-pid, SIGINT); //发给整个进程组,为因子进程可能fork了新进程,新进程和子进程都属于子进程的进程组
}
sigprocmask(SIG_SETMASK, &prev, NULL);
errno = olderrno;
return;
}
/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev;
pid_t pid;
// struct job_t *job;
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
if((pid = fgpid(jobs)) > 0) {
kill(-pid, SIGTSTP); //发给整个进程组,因为子进程可能fork了新进程,新进程和子进程都属于子进程的进程组
//下面可以省略,因为子进程收到SIGTSTP后kernel会给父进程发SIGCHLD,父进程在sigchld_handler完成了下面的修改
// job = getjobpid(jobs, pid);
// job->state = ST;
}
sigprocmask(SIG_SETMASK, &prev, NULL);
errno = olderrno;
return;
}
/*********************
* End signal handlers
*********************/
/***********************************************
* Helper routines that manipulate the job list
**********************************************/
/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\0';
}
/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
clearjob(&jobs[i]);
}
/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs)
{
int i, max=0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid > max)
max = jobs[i].jid;
return max;
}
/* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)
{
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == 0) {
jobs[i].pid = pid;
jobs[i].state = state;
jobs[i].jid = nextjid++;
if (nextjid > MAXJOBS)
nextjid = 1;
strcpy(jobs[i].cmdline, cmdline);
if(verbose){
printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
}
return 1;
}
}
printf("Tried to create too many jobs\n");
return 0;
}
/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid)
{
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == pid) {
clearjob(&jobs[i]);
nextjid = maxjid(jobs)+1;
return 1;
}
}
return 0;
}
/* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs) {
int i;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return jobs[i].pid;
return 0;
}
/* getjobpid - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid)
return &jobs[i];
return NULL;
}
/* getjobjid - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid)
{
int i;
if (jid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid == jid)
return &jobs[i];
return NULL;
}
/* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid)
{
int i;
if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid) {
return jobs[i].jid;
}
return 0;
}
/* listjobs - Print the job list */
void listjobs(struct job_t *jobs)
{
int i;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid != 0) {
printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
switch (jobs[i].state) {
case BG:
printf("Running ");
break;
case FG:
printf("Foreground ");
break;
case ST:
printf("Stopped ");
break;
default:
printf("listjobs: Internal error: job[%d].state=%d ",
i, jobs[i].state);
}
printf("%s", jobs[i].cmdline);
}
}
}
/******************************
* end job list helper routines
******************************/
/***********************
* Other helper routines
***********************/
/*
* usage - print a help message
*/
void usage(void)
{
printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n");
printf(" -p do not emit a command prompt\n");
exit(1);
}
/*
* unix_error - unix-style error routine
*/
void unix_error(char *msg)
{
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1);
}
/*
* app_error - application-style error routine
*/
void app_error(char *msg)
{
fprintf(stdout, "%s\n", msg);
exit(1);
}
/*
* Signal - wrapper for the sigaction function
*/
handler_t *Signal(int signum, handler_t *handler)
{
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
action.sa_flags = SA_RESTART; /* restart syscalls if possible */
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
/*
* sigquit_handler - The driver program can gracefully terminate the
* child shell by sending it a SIGQUIT signal.
*/
void sigquit_handler(int sig)
{
printf("Terminating after receipt of SIGQUIT signal\n");
exit(1);
}
测试
总结
- 通过学习 Linux 信号以及信号程序编写后,我不再能像以前那样能够自信地确保程序的正确性,任何时候都可能插入一个中断,插入一个信号处理,使得编写程序的时候得格外小心,我也没有什么异步程序编写经验,还需要更多地学习和练习
- 上面的程序一些不足的地方,比如没有使用包装函数(wrapper),可以像 CSAPP 那样为那些系统函数写个包装函数,不过我懒得写了。还有包括在信号处理程序中使用了
printf
异步不安全函数。还有在对共享全局数据结构访问的时候需要阻塞所有信号,我在很多处都这样做了,但还有少数地方我没有这样做。这也看出使用全局变量真是麻烦,最好在信号处理程序中少用吧 - 这次 lab 的测试程序竟然没有实现自动化测试,还得一个一个人工比对。这或许也是因为信号处理程序的错误很难发现和复现。
Comments | NOTHING