哈工大计算机系统lab7——微壳

 

 

 

实验报告

 

验(七)

 

 

题     目      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 -

 

第1章 实验基本信息

 

1.1 实验目的

理解现代计算机系统进程与并发的基本知识

掌握linux 异常控制流和信号机制的基本原理和相关系统函数

掌握shell的基本原理和实现方法

深入理解Linux信号响应可能导致的并发冲突及解决方法

培养Linux下的软件系统开发与测试能力 

1.2 实验环境与工具

1.2.1 硬件环境

X64 CPU; 1.80GHz; 12G RAM; 512GHD SSD;

1.2.2 软件环境

windows10 家庭中文版;Vmware 15; Ubuntu 20.04LTS 64位

1.2.3 开发工具

codeblocks; vim+gcc; gdb;

1.3 实验预习

上实验课前,必须认真预习实验指导书(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的那样)

 +   在前台进程组

 

 

第2章 实验预习

总分20

 

2.1 进程的概念、创建和回收方法(5分)

进程的经典定义就是一个执行中程序的实例

一个执行程序中的实例,提供给我吗一种错觉:我们的程序好像是系统中当前运行的唯一程序,我们的程序独占使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序的代码和数据好像是系统中内存唯一的对象。

每次用户通过向shell输人一个可执行目标文件的名字,运行程序时,shell 就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。

创建进程:

使用fork函数,父进程调用fork函数创建一个新的运行的子进程,子进程得到和父进程用户及虚拟地址空间完全相同的一个副本。

回收方法:

(1)当一个进程由于某种原因终止时,进程保持在一种已经终止的状态,直到被他的父进程回收,当父进程回收他的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程,从此开始,该进程就不存在了。一个僵死了但还未被会回收的进程被称为僵死进程。

(2)如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养分,init进程将会回收这些孤儿进程。

使用waitpid函数来等待子进程终止或者停止。

2.2信号的机制、种类(5分)

信号:一个信号就是一条消息,它通知进程系统中发生了一个某种类型的事件。每种信号类型都对应某种系统的事件。

以Linux信号为例,它时一种软件层面的异常。

信号提供了一种机制,通知用户进程发生了由内核异常处理程序处理的底层的硬件异常。

发送信号:内核通过更新目的进程上下文的某种状态,发送一个信号给目的进程。

接收信号:目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了这种信号。

Linux信号如下:

2.3 信号的发送方法、阻塞方法、处理程序的设置方法(5分)

信号的发送方法

 

  • 用/bin/kill程序发送信号

/bin/kill程序可以向另外的进程发送任意的信号

  • 从键盘发送信号

在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程中的每个进程,默认情况下,结果是是终止前台作业。

在键盘上输入Ctrl+Z会发送一个SIGTSTP信号到前台进程中的每个进程,默认情况下,结果是是停止(挂起)前台作业。

(3) 用kill函数发送信号给其他进程(包括他们自己)

(4) 使用alarm函数发送信号

进程可以通过调用 alarm 函数在指定 secs 秒后发送一个 SIGALRM 信号给调用进程。

信号的阻塞方法

  • 隐式阻塞机制:内核默认阻塞任何当前处理程序正在处理信号类和待处理信号。
  • 显示阻塞进制:应用程序可以调用 sigprocmask 函数和它的辅助函 数,明确地阻塞和解除阻塞选定的信号。

处理程序的设置方法

  • 进程可以通过使用signal函数修改和信号相关联的默认行为。唯一的例外是SIGSTOP和SIGKILL,它们的默认行为是不能修改的。

signal函数可以通过下列三种方法之- .来改变和信号signum相关联的行为:

如果handler是SIG_ IGN,那么忽略类型为signum 的信号。

如果handler是SIG_ DFL,那么类型为signum的信号行为恢复为默认行为。

否则,handler 就是用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序。调用信号处理程序被称为捕获信号。执行信号处理程序被称为处理信号。

当一个进程捕获了一个类型为k的信号时,会调用为信号k设置的处理程序,一个整数参数被设置为k。这个参数允许同一个处理函数捕获不同类型的信号。

  • 因为 signal 的语义各有不同,系统调用可以被中断。要解决这些问题,Posix 标准定义了sigaction 函数,它允许用户在设置信号处理时,明确指定他们想要的信号处理语义。
  • 一个更简洁的方式,最初是由 Richard Stevens提出的,就是定义一个包装函数,称为Signal,它调用sigaction。

2.4 什么是shell,功能和处理流程(5分)

shell:一个交互型应用级程序,代表用户运行其他程序。

功能:shell应用程序提供了一个界面,用户通过访问这个界面访问操作系统内核的服务。  

处理流程:

1)从终端读入输入的命令。

2)将输入字符串切分获得所有的参数

3)如果是内置命令则立即执行

4)否则调用相应的程序执行

5)shell 应该接受键盘输入信号,并对这些信号进行相应处理

 

第3章 TinyShell的设计与实现

总分45

3.1 设计

3.1.1 void eval(char *cmdline)函数(10分)

函数功能:解析和解释命令行的主例程。

参    数:char *cmdline

处理流程:

(1) 调用 parseline 函数。将 cmdline 字符串切分为参数数组 argv,返回 bg得知该操作是否需要后台运行。

(2) 调用 buildin_cmd 判断该函数是否为内置函数。如果是内置函数则立即执 行,执行后返回。

(3) 如果不是,先设置阻塞信号集,阻塞预先识别的信号,防止shell捕获并处理信号。

(4) 创建一个子进程,解锁子进程的阻塞,将子进程放进一个进程组,在子进程中运行命令行输入的可执行文件(调用 execve 函数)。

(5) 将进程放进jobs列表。

(6) 判断是否是后台程序,不是后台程序,shell界面暂时挂起,执行waitfg;

如果是后台程序,打印部分信息。

要点分析:

  • 将命令行的处理封装并集中
  • 阻塞shell的预识别信号,防止shell捕获并处理,避免竞争。
  • 父进程先记录addjob,后解除了阻塞,在阻塞期间捕获到的sigchild在此时被处理,同时在任务列表中删除,避免了竞争现象的出现。
  • ctrl-c会给shell(bash)下前台进程组中的所有进程发送SIGINT信号,包括tsh和tsh创建的进程——这样显然不对。

解决办法:

  • 在fork后、execve前,子进程调用函数setpgid(0, 0)将自己放到一个新的进程组中(进程组ID与子进程的PID相同)。在前台进程组中只有一个进程tsh。
  • 当键入Ctrl-C, shell(bash)将捕获产生的SIGINT,然后转发给tsh, tsh收到SIGINT后,转发给适当的前台作业(更准确的说法:前台作业的进程组)
  • tsh是在Linux 的shell(bash)下运行的,tsh在前台进程组中运行,此时,tsh创建的子进程也默认在前台进程组中。所以通过阻塞完成。

 

 

 

3.1.2 int builtin_cmd(char **argv)函数(5分)

函数功能:识别并解释内置命令: quit, fg, bg和jobs

参    数:char **argv

处理流程:数个并列的判断语句,其中有一个符合,则运行相应函数,并返回1(退出不返回);若均不符合,则在函数尾返回0。

要点分析:每次只是简单的函数调用,除了退出的处理不同,其他都是类似的实现。函数模块化思想及其突显。

3.1.3 void do_bgfg(char **argv) 函数(5分)

函数功能:实现内置命令bg和fg

参    数:char **argv

处理流程:

  • 先判断是否有参数传入,没有参数直接输出提示信息并返回,有参数执行后续步骤
  • 判断输入的是pid还是%jid,无论哪种都在jobs里查找,过程中出现问题则提示并返回,否则顺序执行
  • 匹配是后台运行还是前台运行。后台运行,向job所在进程组发送SIGCONT信号,更改state为bg;前台运行,向job所在的进程组发送SIGCONT信号,更改job的state为fg,然后等待当前的程序运行直到当前的job不再是前台程序。

要点分析:

  • 错误输入的处理在每一个匹配环节都要考虑
  • 区分传入pid还是%jid
  • 按照bg和fg特性,发送信号让程序持续运行。
  • fg需要将目标程序在前台运行,调用waitfg等待程序停止或终止。

3.1.4 void waitfg(pid_t pid) 函数(5分)

函数功能:等待一个前台作业结束

参    数:pid_t pid

处理流程:调用fgpid函数,判断pid有没有被改变,即所给进程是否结束。

  • 最简单的方式是pause();缺点:浪费资源,引入竞争
  • 其次是sleep(),优势是不浪费资源,但以秒为单位,效率低
  • 采用sigsuspend函数,同时不阻塞任何信号。比前两种方法都实用。

要点分析:

  • pause可能引入竞争,直接被排除。
  • sleep效率低,在调试过程中,有无法解决的问题时,用sleep虽低效,但最为保险。
  • sigsuspend函数虽然性能及其优异,但对于我们来说不是很熟悉,有些bug调试不出来。
  • 跳出循环的方式是fgpid,而fgpid发生改变是在shell捕获并处理信号之后,所以对于信号的阻塞一定要考虑清楚。

3.1.5 void sigchld_handler(int sig) 函数(10分)

函数功能: 父进程中接收到SIGCHLD信号的处理函数

参    数:int sig

处理流程:

  • 保存errno,防止过程中的改变影响进程上下文的运行。
  • 使用while循环回收所有的子进程,防止僵死
  • 对waitpid返回的情况进行匹配
  • 正常退出:删除子任务
  • 暂时停止:输出提示信息
  • 未被捕获的信号终止:输出提示信息并删除(kill就不能被捕获)
  • 恢复errno

要点分析:

  • while使用的waitpid是立即返回,没有停止或终止则返回0,主要是实时回收,不会等待某个进程执行完。
  • 在删除子任务时要阻塞所有信号,防止修改全局变量是接受信号对进程的影响。
  • 防止调用修改errno,开始保存,结尾恢复。
  • 对不同的停止做不同处理。

3.2 程序实现(tsh.c的全部内容)(10分)

重点检查代码风格:

  • 用较好的代码注释说明——5
  • 检查每个系统调用的返回值——5
/*
 * 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 */

/*
 * 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 *</
  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值