一、预备知识

阅读课本CSAPP的第八章后完成本次实验,要求熟练掌握以下内容:

  1. 进程的概念、状态以及控制进程的几个函数(fork,waitpid,execve)。
  2. 信号的概念,会编写正确安全的信号处理程序。
  3. shell的概念,理解shell程序是如何利用进程管理和信号去执行一个命令行语句。

二、实验要求

本次实验要求完成框架中一些必要函数从而实现一个简单shell程序。

tsh.c中给出了本次实验中需要实现的shell的框架,大部分函数已经写好了,需要完成的函数已经写在了shlab.pdf中。完成空缺的函数,使shell支持的功能也已写在了shlab.pdf中。

三、实验思路

shell进程解析一条命令行语句后会创建数个子进程(这些进程被称为作业)加载不同的程序去完成该命令行语句,由于shell是所有作业的父进程,因此需要管理所有的子进程,进程间的通信需要靠信号。具体来说,为了执行一条命令行语句,shell进程要完成以下工作:

  1. 解析一条命令行,判断是内置命令还是可执行文件路径,如果是后者就需要创建多个子进程。

  2. 维护作业列表

  3. 接收信号(当子进程终止、停止、继续执行时都会给shell进程发送信号,用户还可以通过键盘向shell进程发送信号,别的进程可以通过kill发送信号),并做出正确响应。

    正确响应意味着安全正确的信号处理程序,书上对这部分有详细的叙述。在本次实验中存在共享全局变量jobs,编写信号处理程序应注意。

四、实验中遇到的问题

整个实验最令人困惑的地方应当是sigchld_handler的编写方式,因为当子进程终止、停止、继续执行时都会给shell进程发送SIGCHLD,在该函数中必须对这三种情况加以区分。

最初的版本只考虑了子进程终止的情况,导致shell表现出奇怪的状态,后来使用WIFSTOPPED判断进程是否被停止。导致键入ctrl+Z时子进程被挂起,而,但子进程已经被挂起而不可能会终止/停止,shell进程将被永远挂起。

第二个版本只考虑子进程终止、停止的情况,没有考虑子进程继续执行的情况,因为shell进程调用了waitpid(-1,&status,0)而陷入等待子进程终止/停止的挂起状态,导致shell进程必须等待继续执行的进程终止或被停止,修改后调用waitpid(-1,&status,WNOHANG|WUNTRACED),让shell进程不等待直接返回,根据返回值来判断子进程是终止、停止、继续执行中的哪种情况。

五、实验代码

和原来的代码相比,除了填充了空缺函数外还引入了一些自己写的、CSAPP上的包装函数

/*
* 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 */ /* bool type */
typedef char bool;
#define true 0
#define false 1 /*
* 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); /* Wrapped function */
static size_t sio_strlen(char s[])
{
int i = 0; while (s[i] != '\0')
++i;
return i;
} ssize_t sio_puts(char s[]) /* Put string */
{
return write(STDOUT_FILENO, s, sio_strlen(s)); //line:csapp:siostrlen
} void Sio_error(char s[]) /* Put error message and exit */
{
sio_puts(s);
_exit(1); //line:csapp:sioexit
} pid_t Fork(void)
{
pid_t pid; if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
} void Sigfillset(sigset_t *set)
{
if (sigfillset(set) < 0)
unix_error("Sigfillset error");
return;
} void Sigemptyset(sigset_t *set)
{
if (sigemptyset(set) < 0)
unix_error("Sigemptyset error");
return;
} void Sigaddset(sigset_t *set, int signum)
{
if (sigaddset(set, signum) < 0)
unix_error("Sigaddset error");
return;
} void Sigdelset(sigset_t *set, int signum)
{
if (sigdelset(set, signum) < 0)
unix_error("Sigdelset error");
return;
} void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{
if (sigprocmask(how, set, oldset) < 0)
unix_error("Sigprocmask error");
return;
} /*
* 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 *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
sigset_t mask_all, prev_all, mask_chld; Sigfillset(&mask_all);
Sigemptyset(&mask_chld);
Sigaddset(&mask_chld, SIGCHLD); strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return; // Ignore empty line if (!builtin_cmd(argv))
{
Sigprocmask(SIG_BLOCK, &mask_chld, &prev_all); // Block SIGCHLD
if ((pid = Fork()) == 0) { // Child process
// Change the pgid and restore sigset
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
setpgid(0, 0);
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
Sigprocmask(SIG_BLOCK, &mask_all, NULL); // Block all signals
if (!bg)
addjob(jobs, pid, FG, cmdline);
else
addjob(jobs, pid, BG, cmdline);
Sigprocmask(SIG_SETMASK, &prev_all, NULL); // Restore blocked signals if (!bg)
waitfg(pid);
else
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
} return;
} /*
* 判断一个字符是否为数字或'%'
*/
bool isId(char c)
{
if ((c >= '0' && c <= '9') || c == '%')
return true;
else
return false;
}
/*
* 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], "jobs"))
{
listjobs(jobs);
return 1;
}
if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) { // 如果是fg命令需要等待前台作业
if (argv[1] == NULL) // 参数不能为空
printf("%s command requires PID or %%jobid argument\n", argv[0]);
else if (isId(argv[1][0]) == false) // 判断参数是否为PID或%JID的形式
printf("argument must be a PID or %%jobid\n");
else // 调用函数,由该函数处理进程或者作业是否存在
do_bgfg(argv);
return 1;
} return 0; /* not a builtin command */
} /*
* do_bgfg - Execute the builtin bg and fg commands
* 没有对输入做合法性检查
*/
void do_bgfg(char **argv)
{
int jid;
sigset_t mask_all, prev_all;
struct job_t *job; Sigfillset(&mask_all);
if (argv[1][0] == '%') // 得到对应作业的jid
jid = atoi(argv[1]+1);
else
jid = pid2jid(atoi(argv[1])); Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); //上锁,防止其它中断处理程序对jobs的操作
job = getjobjid(jobs, jid);
if (job == NULL) { // 作业不存在,输出报错信息
if (argv[1][0] == '%')
printf("%s: No such job\n", argv[1]);
else
printf("(%s): No such process\n", argv[1]);
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
return;
} if (kill(-job->pid, SIGCONT) < 0) // 发送继续执行信号
Sio_error("sigint error\n"); if (!strcmp(argv[0], "bg")) { // 把进程从停止状态变为后台运行状态
job->state = BG;
printf("[%d] (%d) %s", jid, job->pid, job->cmdline);
}
else { // 把进程从停止状态变为前台运行状态
job->state = FG;
}
Sigprocmask(SIG_SETMASK, &prev_all, NULL); waitfg(fgpid(jobs)); return;
} /*
* waitfg - Block until process pid is no longer the foreground process
*/
void waitfg(pid_t pid)
{
while (fgpid(jobs)) // Loop while existing foreground job
{
sleep(1);
} 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.
* 这个函数并不只是用于处理终止进程,由于进程停止、继续执行时也会发送SIGCHLD,必须识别出停止和终止两种情况
* 1. 进程停止
* 直接从jobs中删除子进程对应作业
* 如果被信号终止,打印终止信号
* 2. 进程停止
* 修改进程的状态
* 打印停止信号
* 3. 进程继续执行
* 直接返回
*/
void sigchld_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
int status, jid; Sigfillset(&mask_all);
if ((pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) < 0) { // 等待进程终止或者停止,如果返回-1说明waitpid出错,如果返回0说明没有进程处于停止或终止只是有子进程继续执行
Sio_error("waitpid error");
}
else if (pid == 0)
return;
jid = pid2jid(pid); // 得到jobid Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); //上锁,防止其它中断处理程序对jobs的操作
if (WIFEXITED(status)) // 正常终止
deletejob(jobs, pid);
if (WIFSIGNALED(status)) { // 被信号终止
deletejob(jobs, pid);
printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, WTERMSIG(status));
}
if (WIFSTOPPED(status)) {// 被信号停止
struct job_t *job = getjobpid(jobs, pid);
job->state = ST;
printf("Job [%d] (%d) stopped by signal %d\n", job->jid, pid, WSTOPSIG(status));
}
Sigprocmask(SIG_SETMASK, &prev_all, 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_all;
Sigfillset(&mask_all); Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); // 上锁,防止其它中断处理程序对jobs的操作
if (fgpid(jobs))
if (kill(-fgpid(jobs), SIGINT))
Sio_error("sigint error\n");
Sigprocmask(SIG_SETMASK, &prev_all, 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_all;
pid_t pid;
Sigfillset(&mask_all); Sigprocmask(SIG_BLOCK, &mask_all, &prev_all); // 上锁,防止其它中断处理程序对jobs的操作
if ((pid = fgpid(jobs)) != 0)
if (kill(-pid, SIGTSTP) < 0)
printf("kill error\n");
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
errno = olderrno; return;
}

shellLab实验报告的更多相关文章

  1. 北京电子科技学院(BESTI)实验报告5

    北京电子科技学院(BESTI)实验报告5 课程: 信息安全系统设计基础 班级:1452.1453 姓名:(按贡献大小排名) 郑凯杰.周恩德 学号:(按贡献大小排名) 20145314.20145217 ...

  2. 北京电子科技学院(BESTI)实验报告4

    北京电子科技学院(BESTI)实验报告4 课程: 信息安全系统设计基础 班级:1452.1453 姓名:(按贡献大小排名)周恩德 .郑凯杰 学号:(按贡献大小排名)20145217 .201453 指 ...

  3. 20145215&20145307信息安全系统设计基础实验报告

    20145215&20145307信息安全系统设计基础实验报告 PART1 一.实验原理 交叉编译,简单地说,就是在一个平台上生成另一个平台上的可执行代码.同一个体系结构可以运行不同的操作系统 ...

  4. 北京电子科技学院(BESTI)实验报告1

    北京电子科技学院(BESTI)实验报告1 课程: 信息安全系统设计基础 班级:1452.1453 姓名:(按贡献大小排名)郑凯杰 .周恩德 学号:(按贡献大小排名)20145314 .20145217 ...

  5. 北京电子科技学院(BESTI)实验报告3

    北京电子科技学院(BESTI)实验报告3 课程: 信息安全系统设计基础 班级:1452.1453 姓名:(按贡献大小排名)周恩德 .郑凯杰 学号:(按贡献大小排名)20145217 .201453 指 ...

  6. 20145205 《Java程序设计》实验报告五:Java网络编程及安全

    20145205 <Java程序设计>实验报告五:Java网络编程及安全 实验要求 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.客户端中输入明文,利用DES算法加密,D ...

  7. 20145220&20145209&20145309信息安全系统设计基础实验报告(5)

    20145220&20145209&20145309信息安全系统设计基础实验报告(5) 实验报告链接: http://www.cnblogs.com/zym0728/p/6132249 ...

  8. 20145220&20145209&20145309信息安全系统设计基础实验报告(3)

    20145220&20145209&20145309信息安全系统设计基础实验报告(3) 实验报告链接: http://www.cnblogs.com/zym0728/p/6132243 ...

  9. 20145220&20145209&20145309信息安全系统设计基础实验报告(4)

    20145220&20145209&20145309信息安全系统设计基础实验报告(4) 实验报告链接: http://www.cnblogs.com/zym0728/p/6132246 ...

随机推荐

  1. P3400【仓鼠窝 】

    思路清奇,代码简洁的好题 问题大体分两部: 记录子矩阵个数,统计每一个点作为右下角时可以得到多少矩形,加起来就是答案 剪掉墙挡住的地方 考虑从右下角开始,如果有0已经挡住了矩阵,那么更靠左.上的0都卵 ...

  2. 1、Linux基础--相关软件安装与网络配置

    1.虚拟机(VM安装) 2.网络配置 3.Linux操作系统安装 4.xshell安装

  3. 暑假撸系统6- Thymeleaf ajax交互!

    本来用Thymeleaf也没想着深度使用ajax,就是用也是非常传统的ajax方式提交然后js控制修改下变量.闲来无事的时候看Thymeleaf的教程发现一哥们的实现方式,以及实现思路,堪称惊奇,先说 ...

  4. python3监控网站状态

    前面已经写过Python3发邮件,Python发微信的文章了.直接导入即可. import configparser,requests from time import sleep import We ...

  5. go 互斥锁实现原理

    目录 go 互斥锁的实现 1. mutex的数据结构 1.1 mutex结构体,抢锁解锁原理 1.2 mutex方法 2. 加解锁过程 2.1 简单加锁 2.2 加锁被阻塞 2.3 简单解锁 2.4 ...

  6. 设计DFA接受{0,1}上的字符串ω,且ω是3倍数的二进制表示

    DFA设计 设计DFA接受{0,1}上的字符串ω,且ω是3倍数的二进制表示 先叙述下思路: 要想证明某数是3的倍数可以让其除以3看余数是否为零即可,现在我们的问题就是如何计算一串二进制数除以3所得的余 ...

  7. [Matlab]求解线性方程组

    转自:http://silencethinking.blog.163.com/blog/static/911490562008928105813169/ AX=B或XA=B在MATLAB中,求解线性方 ...

  8. 深入理解Cache工作原理

    内容来源:https://zhuanlan.zhihu.com/p/435031232 内容来源:https://zhuanlan.zhihu.com/p/102293437 本文主要内容如下,基本涉 ...

  9. Linux运维实战——如何利用文件节点删除乱码文件

    引言 linux系统中删除文件可以用rm [filename] 命令,然而有些系统或程序自动生成的文件或者文件夹名称却是乱码. 虽然部分文件/文件夹可以通过复制粘贴名字的方式来删除,但是仍然有些文件无 ...

  10. Python:读取Excel 不带第一行标题

    #根据第0到第1列进行重建 0-X 1-Y PX=sheet_name.col_values(0)[1:] PY=sheet_name.col_values(1)[1:] 读取的某一列后在后边加[1: ...