系列文章目录
文章目录
前言
这个lab可以说是我投入最认真的的一个。
前提是要把pdf所有的内容都熟读标注,把书上相应内容反复看几遍。按照书上的例子敲进去,在参照其他博客改改,就能基本满足lab的要求。
lab原始代码链接:
http://csapp.cs.cmu.edu/3e/shlab-handout.tar
lab的pdf链接:
http://csapp.cs.cmu.edu/3e/shlab.pdf
一、作业解释和要求
这个Lab主要要求实现一个最简单的Unix shell程序(貌似是tinyshell)
需要在原有框架下实现:
- eval:主程序,用于分析、解释命令行
- builtin_cmd:识别、解释 bulit-in 命令:如quit,fg,bg 和jobs
- do_bgfg:执行 bg 和 fg 这些bulit-in命令
- waitfg:等待前台程序(foreground job)完成
- sigchld_handler:获取 SIGCHILD 信号
- sigint_handler:获取 SIGINT(ctrl-c)信号
- sigtstp_handler:获取SIGTSTP(ctrl-z) 信号
Unix Shells的大致介绍
Shell是一个交互的命令行解释器,并且其在用户面前运行程序。一个shell反复地打印提示,等待stdin的命令行(command line),随后按照命令行执行相应的操作。
命令行是一个以空格分隔的ASCII文本单词序列。命令行中的第一个单词是内置命令的名称或可执行文件的路径名。剩下的单词是命令行参数。如果第一个单词是内置命令,shell会立即在当前进程中执行该命令。否则,该字被假定为可执行程序的路径名。在这种情况下,shell会fork一个子进程,然后在该子进程的上下文中加载并运行该程序。由于解释单个命令行而创建的子进程统称为作业。通常,一个作业可以由多个通过Unix管道连接的子进程组成。
例如输入命令行:
tsh> jobs
这使得 shell 执行 built-in 中的 jobs 指令。
输入命令行:
tsh> /bin/ls -l -d
使得在前台运行ls程序。按照约定,shell确保当程序开始执行它的主例程时
int main(int argc, char *argv[])
其中 args 和 argv 参数有如下的值:
argc == 3
argv[0] == ‘‘/bin/ls’’
argv[1]== ‘‘-l’’
argv[2]== ‘‘-d’’
或者,键入命令行:
tsh> /bin/ls -l -d
使得ls程序运行在后台。
Shell支持作业控制(jobs contorl)的概念,它允许用户在后台和前台之间来回移动作业,并更改作业中进程的状态(运行、停止或终止)。键入 ctrl-c 将导致一个 SIGINT 信号传递给前台作业中的每个进程。SIGINT 的默认操作是终止进程。类似地,键入 ctrl-z 将导致向前台作业中的每个进程发送 SIGTSTP 信号。SIGTSTP的默认操作是将进程置于停止状态,直到被接收方唤醒为止。比如:
- jobs:列出正在运行和已停止的后台任务
- bg:将已停止的后台作业更改为正在运行的后台作业。
- fg:将已停止或正在运行的后台作业更改为正在前台运行的作业。
- kill:杀死一个作业(进程)
二、代码
0. main主程序
/*
* 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