简介
CSAPP实验介绍
学生实现他们自己的带有作业控制的Unix Shell程序,包括Ctrl + C和Ctrl + Z按键,fg,bg,和 jobs命令。这是学生第一次接触并发,并且让他们对Unix的进程控制、信号和信号处理有清晰的了解。
什么是Shell?
? Shell就是用户与操作系统内核之间的接口,起着协调用户与系统的一致性和在用户与系统之间进行交互的作用。
? Shell最重要的功能是命令解释,从这种意义上说,Shell是一个命令解释器。
? Linux系统上的所有可执行文件都可以作为Shell命令来执行。当用户提交了一个命令后,Shell首先判断它是否为内置命令,如果是就通过Shell内部的解释器将其解释为系统功能调用并转交给内核执行;若是外部命令或实用程序就试图在硬盘中查找该命令并将其调入内存,再将其解释为系统功能调用并转交给内核执行。在查找该命令时分为两种情况:
(1)用户给出了命令的路径,Shell就沿着用户给出的路径进行查找,若找到则调入内存,若没找到则输出提示信息;
(2)用户没有给出命令的路径,Shell就在环境变量Path所制定的路径中依次进行查找,若找到则调入内存,若没找到则输出提示信息。
关于本次实验
本次实验需要我们熟读CSAPP第八章异常控制流。
需要设计和实现的函数:
- eva l 函数:解析命令行。
eva luate the command line that the user has just typed in.
- builtin_cmd:判断是否为内置 shell 命令
If the user has typed a built-in command then execute it immediately.
- do_bgfg:实现内置命令bg,fg。
Execute the builtin bg and fg commands.
- waitfg:等待前台作业完成。
Block until process pid is no longer the foreground process
- sigchld_handler:捕获SIGCHLD信号。
- sigint_handler:捕获SIGINT信号。
- sigtstp_handler:捕获SIGTSTP信号。
TinyShell辅助函数:
/* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, char **argv); //解析命令行参数
void sigquit_handler(int sig);//退出的处理函数
/*jobs是全局变量,存储每一个进程的信息。*/
/*jid为job编号ID,pid为进程ID*/
void clearjob(struct job_t *job);//清除所有工作
void initjobs(struct job_t *jobs);//初始化工作结构体
int maxjid(struct job_t *jobs); //返回jobs中jid的最大值
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);//添加job
int deletejob(struct job_t *jobs, pid_t pid); //删除job
pid_t fgpid(struct job_t *jobs);//返回前台运行job的pid
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);//返回对应pid的job
struct job_t *getjobjid(struct job_t *jobs, int jid); //返回jid对应的job
int pid2jid(pid_t pid); //pid转jid
void listjobs(struct job_t *jobs);//遍历
void usage(void);//帮助信息
void unix_error(char *msg);//报错unix-style error routine
void app_error(char *msg);//报错application-style error routine
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);//信号设置
实验要求
-
tsh的提示符:tsh>
-
用户输入的命令行应该包括一个名字、0或多个参数,并用一个或多个空格分隔。
-
如果名字是内置命令,tsh立即处理并等待用户输入下一个命令行。否则,假定这个名字是一个可执行文件的路径,tsh在初始子进程的上下文中加载和运行它。
-
tsh不需要支持管(|)或I/O重定向(<和>)。
-
键入ctrl-c(ctrl-z)应该导致SIGINT(SIGTSTP)信号被发送到当前的前台作业,及其该作业的子孙作业(例如,它创建的任何子进程)。如果没有前台工作,那么信号应该没有效果。
-
如果命令行以&结尾,则tsh在后台运行该作业;否则,在前台运行该作业
-
可以用进程ID(PID)或tsh赋予的正整数作业ID(job ID,JID)标识一个作业。JID用前缀%,例如%5标识作业ID为5的作业,5表示PID为5的作业。
-
已经提供了处理作业列表所需的所有函数
-
tsh支持以下内置命令:
- quit:终止tsh程序
- jobs:列出所有后台job
- bg:后台运行程序
- fg:前台运行程序
回顾
fork
pid_t fork(void)
在函数调用处创建子进程。
父进程函数返回子进程的PID。
子进程函数返回0。
waitpid
一个进程可以通过waitpid函数来等待它的子进程终止或者停止。
pid_t waitpid(pid_t pid, int *statusp, int options);
pid:判定等待集合的成员
- 当pid > 0时,waitpid等待进程ID为pid的进程;
- 当pid = -1时,waitpid等待所有它的子进程。
options:修改默认行为
options中有如下选项:
- WNOHANG:若当前没有等待集合中的子进程终止,则立即返回0
- WUNTRACED:等待直到某个等待集合中的子进程停止或返回,并返回这个子进程的pid。
- WCONTINUED:等待直到某个等待集合中的子进程重新开始执行或终止。
- 组合WNOHANG | WUNTRACED:立即返回,如果等待集合中的子进程都没有被停止或终止,则返回0。如果有,则返回PID。
statusp:检查已回收子进程的退出状态
如果statusp参数非空,那么waitpid就会在status中放入关于导致返回的子进程的状态信息,status是statusp指向的值。
- WIFEXITED(status):如果子进程通过调用exit或者返回(return)正常终止,就返回真。
- ········
kill函数
int kill(pid_t pid, int signo);
- pid > 0,信号发送给pid进程