csapp:shelllab
一、实验要求
实现一个简单的shell,能够执行内置命令,执行前端任务、后端任务,以及任务控制。
需要实现如下几个功能:
•支持以下内置命令:
–quit命令终止shell。
–jobs命令列出所有后台作业。
–bg<job>命令通过发送SIGCONT信号重新启动<job>,然后在后台运行。<job>参数可以是PID或JID。
–fg<job>命令通过发送SIGCONT信号重新启动<job>,然后在前台运行它。<job>参数可以是PID或JID。
•键入ctrl-c(ctrl-z)应该会将SIGINT(sigtsp)信号发送到当前前台作业以及该作业的任何子进程(例如,它派生的任何子进程)。如果没有前台作业,则该信号应无效。
•如果命令行以&结束,则tsh应在后台运行作业。否则,它应该在前台运行作业。
二、预备知识
进程控制
创建子进程
pid_t fork()
-
调用一次返回两次,子进程返回0,父进程返回子进程pid;
-
并发执行,父进程和子进程并发执行,没有先后顺序;
-
相同但独立的地址空间,父进程和子进程的地址空间内容完全一致,但是相互独立。
-
共享文件,子进程继承父进程的打开文件表。
加载并运行程序
int execve(const char* filename,const char* argv[],const char *envp[ ]);
-
调用一次不返回;
-
地址空间替换为新程序的。
回收子进程
pid_t waitpid(pid_t pid, int *statusp, int option)
1.确定等待集的成员,等待集的成员由pid参数确定:
-
如果pid>0,那么等待集就是进程ID为等于pid。
-
如果pid=-1,那么等待集由父进程的所有子进程组成。
2.修改默认行为
可以通过将选项设置为各种组合来修改默认行为WNOHANG和WUNTRACED常数:
-
WNOHANG:如果等待集中的子进程尚未终止,则立即返回(返回值为0)。默认行为将挂起调用进程,直到子进程终止。如果希望在等待子项终止时继续执行有用的工作,则此选项非常有用。。
-
WUNTRACED:暂停调用进程的执行,直到等待集中的进程被终止或停止。返回导致返回的已终止或已停止子级的PID。默认行为只返回已终止的子进程。如果要同时检查已终止子级和已停止子级,则此选项非常有用。
-
WNOHANG | WUNTRACED:立即返回,返回值为0,如果等待集中的所有子项都没有停止或终止,或者返回的值等于某个已停止或终止的子级的PID。
3.检查子进程的退出状态
如果status参数为非空,则waitpid将对导致status参数返回的子进程的状态信息进行编码。
-
WIFEXITED(status) :如果子进程通过调用exit或return正常终止,则返回true。
-
WEXITSTATUS(status) :返回正常终止的子进程的退出状态。仅当WIFEXITED返回true时才定义此状态。
-
WIFSIGNALED(status) :如果子进程因未捕获到信号而终止,则返回true。
-
WTERMSIG(status) :返回导致子进程终止的信号数。仅当WIFSIGNALED(status)返回true时才定义此状态。
-
WIFSTOPPED(status) :如果导致返回的子级当前已停止,则返回true。
-
WSTOPSIG(status) :返回导致子进程停止的信号的数目。仅当WIFSTOPPED(status)返回true时才定义此状态。
信号控制
发送信号
int kill(pid_t pid, int sig)
-
pid>0,发送信号sig给进程pid;
-
pid=0,发送信号sig给调用进程所在进程组的每个进程,包括调用进程自己;
-
pid<0,发送信号sig给进程组|pig|中的每个进程;
阻塞和解阻塞信号
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum); Returns: 0 if OK, −1 on error
int sigismember(const sigset_t *set, int signum); Returns: 1 if member, 0 if not, −1 on error
sigprocmask函数更改当前被阻止的信号集。具体行为取决于以下方式的值:
-
SIG_BLOCK :将set中的信号添加到blocked(blocked=blocked | set)。
-
SIG_UNBLOCK:从blocked中移除set中的信号。
-
SIG_SETMASK: blocked=set。
如果oldset为非空,则阻塞位向量的之前的值存储在oldset中。使用以下函数操作信号集(如set)。
sigemptyset将集合初始化为空集合。sigfillset函数添加要设置的每个信号。sigaddset函数将signum添加到set,sigdelset删除来自集合的signum,如果signum是集合的成员,则sigismember返回1,并且如果不是,则为0。
三、实验代码
eval()
void eval(char *cmdline)
{
char *argv[MAXARGS];
// char cmdlinestr[MAXLINE];
pid_t pid;
int is_bg;
sigset_t mask_all, mask, pre;
is_bg = parseline(cmdline, argv);
if (argv[0] == NULL)
{ //空的命令行
return;
}
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTSTP);
sigfillset(&mask_all);
//判断是否为内置命令
if (!builtin_cmd(argv))
{ //不是内置命令
//阻塞sigchild信号,防止sigchild信号处理函数提前处理,避免竞态
sigprocmask(SIG_BLOCK, &mask, &pre);
//fork子进程执行
if ((pid = fork()) == 0)
{
//子进程继承父进程的阻塞信号集合,解锁子进程的sigchild信号
sigprocmask(SIG_SETMASK, &pre, NULL);
//设置进程组,创建新的进程组,用于SIG——INT
setpgid(0, 0);
//execve执行错误返回-1
if (execve(argv[0], argv, environ) < 0)
{
unix_error(argv[0]);
}
}
//
sigprocmask(SIG_BLOCK, &mask_all, NULL);
//判断是否为前端命令
int state = is_bg ? BG : FG;
addjob(jobs, pid, state, cmdline);
//解锁sigchild信号
sigprocmask(SIG_SETMASK, &pre, NULL);
//判断是否为前端命令
if (!is_bg)
{
//等待前端命令执行完,或者终端发信号终止前端命令
waitfg(pid);
}
else
{
//后端命令,直接打印命令信息
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
}
return;
}
sigchld_handler
void sigchld_handler(int sig)
{
int olderrno = errno;
pid_t pid;
int status;
sigset_t mask, pre;
sigfillset(&mask);
//使用WNOHANG来避免shell程序因等待子进程终止而被挂起
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
{
sigprocmask(SIG_BLOCK, &mask, &pre); //阻塞所有信号
int jid = pid2jid(pid);
//正常退出
if (WIFEXITED(status))
{
// printf("Job [%d] (%d) exited\n", jid, pid);
deletejob(jobs, pid);
}
//进程被不能捕获的信号终止(sigkill,sifINT)
else if (WIFSIGNALED(status))
{
int sig_num = WTERMSIG(status);
printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, sig_num);
deletejob(jobs, pid);
}
//进程被停止sigTSTP
else if (WIFSTOPPED(status))
{
int sig_num = WSTOPSIG(status);
printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, sig_num);
struct job_t *job = getjobpid(jobs, pid);
job->state = ST;
}
sigprocmask(SIG_SETMASK, &pre, NULL); //解锁所有信号
}
errno = olderrno;
return;
}
sigint_handler
void sigint_handler(int sig)
{
int olderrno = errno;
//获取前端的进程pid
pid_t pid = fgpid(jobs);
if (pid != 0)
{
//给进程组为[pid]的发送sigkill信号
kill(-pid, SIGINT);
}
errno = olderrno;
return;
}
sigtstp_handler
void sigtstp_handler(int sig)
{
int olderrno = errno;
//获取前端的进程pid
pid_t pid = fgpid(jobs);
if (pid != 0)
{
//给进程组为[pid]的发送SIGTSTP信号
kill(-pid, SIGTSTP);
}
errno = olderrno;
return;
}
do_fpbg
void do_bgfg(char **argv)
{
int jid;
pid_t pid;
if (argv[1] == NULL)
{
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return;
}
char *id = argv[1];
//检查参数合法性
if (id[0] != '%' && (id[0] < '0' || id[0] > '9'))
{
printf("%s argument must be a PID or %%jobid\n", argv[0]);
return;
}
//获取jid,pid信息
if (id[0] == '%')
{
jid = atoi(id + 1);
struct job_t *job = getjobjid(jobs, jid);
if (job == NULL)
{
printf("%%%d: No such job\n", jid);
return;
}
else
{
pid = job->pid;
}
}
else
{
pid = atoi(id);
jid = pid2jid(pid);
if (jid == 0)
{
printf("(%d): No such process\n", pid);
return;
}
}
struct job_t *job = getjobjid(jobs, jid);
kill(-pid, SIGCONT);
//把stop的后端命令命令变成running后端
if (!strcmp(argv[0], "bg"))
{
job->state = BG;
printf("[%d] (%d) %s", jid, pid,job->cmdline);
}
else
{
job->state = FG;
waitfg(pid);
}
return;
}
浙公网安备 33010602011771号