官网

http://csapp.cs.cmu.edu/3e/labs.html

Students implement simple logical, two’s complement, and floating point functions, but using a highly restricted subset of C. For example, they might be asked to compute the absolute value of a number using only bit-level operations and straightline code. This lab helps students understand the bit-level representations of C data types and the bit-level behavior of the operations on data.

参考

CSAPP实验之shell lab

实验要求和可选辅助函数见参考链接。

void eval(char *cmdline)

解析并执行命令。

void eval(char *cmdline) {
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
int state;
pid_t pid;
sigset_t mask_all, mask_one, prev;

// 在函数内部加阻塞set
sigfillset(&mask_all);
sigemptyset(&mask_one);
sigaddset(&mask_one, SIGCHLD);

strcpy(buf, cmdline);
bg = parseline(buf, argv);

// 没有参数就退出
if (argv[0] == NULL)
return;

// 如果不是内置指令
if (!builtin_cmd(argv)) {
// 为了避免父进程运行到addjob之前子进程就退出了,所以在fork子进程前阻塞sigchld信号,addjob后解除
sigprocmask(SIG_BLOCK, &mask_one, &prev);
if ((pid = fork()) == 0) {
// 子进程继承了父进程的阻塞向量,也要解除阻塞,避免收不到它本身的子进程的信号
sigprocmask(SIG_SETMASK, &prev, NULL);
// 改进程组与自己pid一样
if (setpgid(0, 0) < 0) {
perror("SETPGID ERROR");
}
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
}
// 正常运行execve函数会替换内存,不会返回/退出,所以必须要加exit,否则会一直运行下去,子进程会开始运行父进程的代码
exit(0);
}
// 父进程
state = bg ? BG : FG;
// 依然是加塞,阻塞所有信号
sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, state, cmdline);
sigprocmask(SIG_SETMASK, &prev, NULL);

// 后台任务则打印,前台则等待子进程结束
if (state == FG)
waitfg(pid);
else {
sigprocmask(SIG_BLOCK, &mask_all, NULL); // 操纵全局变量,阻塞信号
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
sigprocmask(SIG_SETMASK, &prev, NULL);
}
return;
}

int builtin_cmd(char **argv)

检测命令是否为内置命令quitfgbgjobs

int builtin_cmd(char **argv) {
sigset_t mask_all, prev;
sigfillset(&mask_all);

// 判断是不是内置函数,不是就返回,注意内置命令有要继续操作的一定要返回1
if (!strcmp(argv[0], "quit"))
exit(0);
else if (!strcmp(argv[0], "jobs")) {
sigprocmask(SIG_BLOCK, &mask_all, &prev);
listjobs(jobs);
sigprocmask(SIG_SETMASK, &prev, NULL);
return 1;
} else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
do_bgfg(argv);
return 1;
} else if (!strcmp(argv[0], "&"))// 对单独的&不处理
return 1;
return 0; /* not a builtin command */
}

void do_bgfg(char **argv)

实现bgfg命令。

void do_bgfg(char **argv) {
// 没有参数,其实应该也加上判断参数个数的语句才比较完整
if (argv[1] == NULL) {
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return;
}

int id;
sigset_t mask_all, prev;
sigfillset(&mask_all);
struct job_t *job;

// bg %5 和bg 5不一样,一个是对一个作业操作,另一个是对进程操作,而job代表了一个进程组。
// 根据tshref的样例输出看有多少种情况
if (sscanf(argv[1], "%%%d", &id) > 0) {
sigprocmask(SIG_BLOCK, &mask_all, &prev);
job = getjobjid(jobs, id);
if (job == NULL) {
printf("%%%d: No such job\n", id);
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}
} else if (sscanf(argv[1], "%d", &id) > 0) { // 读到pid
sigprocmask(SIG_BLOCK, &mask_all, &prev);
job = getjobpid(jobs, id);
if (job == NULL) {
printf("(%d): No such process\n", id);
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}
} else { // 格式错误
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}

// 因为子进程单独成组,所以kill很方便
// 进程组是负数pid,发送信号并更改状态
if (!strcmp(argv[0], "bg")) {
kill(-(job->pid), SIGCONT);
job->state = BG;
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
} else { // 如果fg后台进程,那么将它的状态转为前台进程,然后等待它终止
kill(-(job->pid), SIGCONT);
job->state = FG;
waitfg(job->pid);
}
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}

void waitfg(pid_t pid)

等待前台命令执行完成。

void waitfg(pid_t pid) {
// 进程回收不需要做,只要等待前台进程就行
sigset_t mask_temp;
sigemptyset(&mask_temp);
// 设定不阻塞任何信号,或者sleep显式等待信号
while (fgpid(jobs) > 0)
sigsuspend(&mask_temp);
return;
}

void sigchld_handler(int sig)

处理SIGCHLD信号,即子进程停止或终止。

void sigchld_handler(int sig) {
int olderrno = errno; // 保存旧errno
int status;
pid_t pid;
sigset_t mask_all, prev;
sigfillset(&mask_all); // 设置全阻塞

while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { // WNOHANG | WUNTRACED 是立即返回
sigprocmask(SIG_BLOCK, &mask_all, &prev);
// 用WIFEXITED(status),WIFSIGNALED(status),WIFSTOPPED(status)等来捕获终止或者被停止的子进程的退出状态
if (WIFEXITED(status)) // 正常退出 delete
{
deletejob(jobs, pid);
} else if (WIFSIGNALED(status)) {// 信号退出 delete
struct job_t *job = getjobpid(jobs, pid);
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
deletejob(jobs, pid);
} else { // 停止 只修改状态
struct job_t *job = getjobpid(jobs, pid);
printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
job->state = ST;
}
sigprocmask(SIG_SETMASK, &prev, NULL);
}
errno = olderrno; // 恢复
return;
}

void sigint_handler(int sig)

处理SIGINT信号,即来自键盘的中断ctrl-c

void sigint_handler(int sig) {
// 向子进程发送信号即可
int olderrno = errno;
pid_t pid;
sigset_t mask_all, prev;
sigfillset(&mask_all); // 设置全阻塞

sigprocmask(SIG_BLOCK, &mask_all, &prev);
pid = fgpid(jobs);
sigprocmask(SIG_SETMASK, &prev, NULL);
if (pid != 0)
kill(-pid, sig);
errno = olderrno;
return;
}

void sigtstp_handler(int sig)

处理SIGTSTP信号,即终端停止信号ctrl-z

void sigtstp_handler(int sig) {
// 向子进程发送信号即可
int olderrno = errno;
pid_t pid;
sigset_t mask_all, prev;
sigfillset(&mask_all); // 设置全阻塞

sigprocmask(SIG_BLOCK, &mask_all, &prev);
pid = fgpid(jobs);
sigprocmask(SIG_SETMASK, &prev, NULL);
if (pid != 0)
kill(-pid, sig);
errno = olderrno;
return;
}

部分测试结果