实验报告
实 验(七)
题 目 TinyShell
微壳
专 业 计算机类
学 号 **********
班 级 1903003
学 生 李*涵
指 导 教 师 郑**
实 验 地 点 G709
实 验 日 期 2021.6.4
计算机科学与技术学院
目 录
第1章 实验基本信息... - 4 -
1.1 实验目的... - 4 -
1.2 实验环境与工具... - 4 -
1.2.1 硬件环境... - 4 -
X64 CPU; 1.80GHz; 12G RAM; 512GHD SSD; - 4 -
1.2.2 软件环境... - 4 -
windows10 家庭中文版;Vmware 15; Ubuntu 20.04LTS 64位... - 4 -
1.2.3 开发工具... - 4 -
codeblocks; vim+gcc; gdb; - 4 -
1.3 实验预习... - 4 -
第2章 实验预习... - 6 -
2.1 进程的概念、创建和回收方法(5分)... - 6 -
2.2信号的机制、种类(5分)... - 6 -
2.3 信号的发送方法、阻塞方法、处理程序的设置方法(5分)... - 7 -
2.4 什么是shell,功能和处理流程(5分)... - 9 -
第3章 TinyShell的设计与实现... - 10 -
3.1.1 void eval(char *cmdline)函数(10分)... - 10 -
3.1.2 int builtin_cmd(char **argv)函数(5分)... - 11 -
3.1.3 void do_bgfg(char **argv) 函数(5分)... - 11 -
3.1.4 void waitfg(pid_t pid) 函数(5分)... - 11 -
3.1.5 void sigchld_handler(int sig) 函数(10分)... - 12 -
第4章 TinyShell测试... - 43 -
4.1 测试方法... - 43 -
4.2 测试结果评价... - 43 -
4.3 自测试结果... - 43 -
4.3.1测试用例trace01.txt - 43 -
4.3.2测试用例trace02.txt - 44 -
4.3.3测试用例trace03.txt - 44 -
4.3.4测试用例trace04.txt - 44 -
4.3.5测试用例trace05.txt - 45 -
4.3.6测试用例trace06.txt - 45 -
4.3.7测试用例trace07.txt - 45 -
4.3.8测试用例trace08.txt - 46 -
4.3.9测试用例trace09.txt - 46 -
4.3.10测试用例trace10.txt - 47 -
4.3.11测试用例trace11.txt - 48 -
4.3.12测试用例trace12.txt - 48 -
4.3.13测试用例trace13.txt - 49 -
4.3.14测试用例trace14.txt - 51 -
4.3.15测试用例trace15.txt - 53 -
第5章 评测得分... - 54 -
第6章 总结... - 55 -
5.1 请总结本次实验的收获... - 55 -
5.2 请给出对本次实验内容的建议... - 55 -
参考文献... - 56 -
理解现代计算机系统进程与并发的基本知识
掌握linux 异常控制流和信号机制的基本原理和相关系统函数
掌握shell的基本原理和实现方法
深入理解Linux信号响应可能导致的并发冲突及解决方法
培养Linux下的软件系统开发与测试能力
上实验课前,必须认真预习实验指导书(PPT或PDF)
了解实验的目的、实验环境与软硬件工具、实验操作步骤,复习与实验有关的理论知识。
了解进程、作业、信号的基本概念和原理
了解shell的基本原理
熟知进程创建、回收的方法和相关系统函数
熟知信号机制和信号处理相关的系统函数
Kill命令
kill –l:列出信号
kill –SIGKILL 17130: 杀死pid为17130的进程
kill -9 17130 :杀死pid为17130的进程,或者:
kill -9 -17130:杀死进程组17130中的每个进程
killall -9 pname: 杀死名字为pname的进程进程状态
D 不可中断睡眠 (通常是在IO操作) 收到信号不唤醒和不可运行, 进程必须等待直到有中断发生
R 正在运行或可运行(在运行队列排队中)
S 可中断睡眠 (休眠中, 受阻, 在等待某个条件的形成或接受到信号)
T 已停止的 进程收到SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU信号后停止运行
W 正在换页(2.6.内核之前有效)
X 死进程 (未开启)
Z 僵尸进程a defunct (”zombie”) process
< 高优先级(not nice to other users)
N 低优先级(nice to other users)
L 页面锁定在内存(实时和定制的IO)
s 一个信息头
l 多线程(使用 CLONE_THREAD,像NPTL的pthreads的那样)
+ 在前台进程组
总分20分
进程的经典定义就是一个执行中程序的实例。
一个执行程序中的实例,提供给我吗一种错觉:我们的程序好像是系统中当前运行的唯一程序,我们的程序独占使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序的代码和数据好像是系统中内存唯一的对象。
每次用户通过向shell输人一个可执行目标文件的名字,运行程序时,shell 就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。
创建进程:
使用fork函数,父进程调用fork函数创建一个新的运行的子进程,子进程得到和父进程用户及虚拟地址空间完全相同的一个副本。
回收方法:
(1)当一个进程由于某种原因终止时,进程保持在一种已经终止的状态,直到被他的父进程回收,当父进程回收他的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程,从此开始,该进程就不存在了。一个僵死了但还未被会回收的进程被称为僵死进程。
(2)如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养分,init进程将会回收这些孤儿进程。
使用waitpid函数来等待子进程终止或者停止。
信号:一个信号就是一条消息,它通知进程系统中发生了一个某种类型的事件。每种信号类型都对应某种系统的事件。
以Linux信号为例,它时一种软件层面的异常。
信号提供了一种机制,通知用户进程发生了由内核异常处理程序处理的底层的硬件异常。
发送信号:内核通过更新目的进程上下文的某种状态,发送一个信号给目的进程。
接收信号:目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了这种信号。
Linux信号如下:
信号的发送方法:
/bin/kill程序可以向另外的进程发送任意的信号
在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程中的每个进程,默认情况下,结果是是终止前台作业。
在键盘上输入Ctrl+Z会发送一个SIGTSTP信号到前台进程中的每个进程,默认情况下,结果是是停止(挂起)前台作业。
(3) 用kill函数发送信号给其他进程(包括他们自己)
(4) 使用alarm函数发送信号
进程可以通过调用 alarm 函数在指定 secs 秒后发送一个 SIGALRM 信号给调用进程。
信号的阻塞方法:
处理程序的设置方法:
signal函数可以通过下列三种方法之- .来改变和信号signum相关联的行为:
如果handler是SIG_ IGN,那么忽略类型为signum 的信号。
如果handler是SIG_ DFL,那么类型为signum的信号行为恢复为默认行为。
否则,handler 就是用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序。调用信号处理程序被称为捕获信号。执行信号处理程序被称为处理信号。
当一个进程捕获了一个类型为k的信号时,会调用为信号k设置的处理程序,一个整数参数被设置为k。这个参数允许同一个处理函数捕获不同类型的信号。
shell:一个交互型应用级程序,代表用户运行其他程序。
功能:shell应用程序提供了一个界面,用户通过访问这个界面访问操作系统内核的服务。
处理流程:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序执行
5)shell 应该接受键盘输入信号,并对这些信号进行相应处理
总分45分
3.1 设计
函数功能:解析和解释命令行的主例程。
参 数:char *cmdline
处理流程:
(1) 调用 parseline 函数。将 cmdline 字符串切分为参数数组 argv,返回 bg得知该操作是否需要后台运行。
(2) 调用 buildin_cmd 判断该函数是否为内置函数。如果是内置函数则立即执 行,执行后返回。
(3) 如果不是,先设置阻塞信号集,阻塞预先识别的信号,防止shell捕获并处理信号。
(4) 创建一个子进程,解锁子进程的阻塞,将子进程放进一个进程组,在子进程中运行命令行输入的可执行文件(调用 execve 函数)。
(5) 将进程放进jobs列表。
(6) 判断是否是后台程序,不是后台程序,shell界面暂时挂起,执行waitfg;
如果是后台程序,打印部分信息。
要点分析:
解决办法:
函数功能:识别并解释内置命令: quit, fg, bg和jobs
参 数:char **argv
处理流程:数个并列的判断语句,其中有一个符合,则运行相应函数,并返回1(退出不返回);若均不符合,则在函数尾返回0。
要点分析:每次只是简单的函数调用,除了退出的处理不同,其他都是类似的实现。函数模块化思想及其突显。
函数功能:实现内置命令bg和fg
参 数:char **argv
处理流程:
要点分析:
函数功能:等待一个前台作业结束
参 数:pid_t pid
处理流程:调用fgpid函数,判断pid有没有被改变,即所给进程是否结束。
要点分析:
函数功能: 父进程中接收到SIGCHLD信号的处理函数
参 数:int sig
处理流程:
要点分析:
3.2 程序实现(tsh.c的全部内容)(10分)
重点检查代码风格:
/*
* tsh - A tiny shell program with job control
*
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 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 */
/*
* 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 *