深入理解计算机系统项目之 Shell Lab
博客中的文章均为meelo原创,请务必以链接形式注明本文地址
Shell Lab是CMU计算机系统入门课程的一个实验。在这个实验里你需要实现一个shell,shell是用户与计算机的交互界面。普通意义上的shell就是可以接受用户输入命令的程序。它之所以被称作shell是因为它隐藏了操作系统低层的细节。完成Shell Lab你会对shell有更加深入的认识,并熟悉Linux的多进程编程方法。
编程实现是一种绝佳的学习方式,然而就像这个实验一样,很多很好的课程作业都隐藏在互联网当中。大多数人难以通过这种方式来学习,这篇文章的目的接就是介绍给你这个绝佳地学习Linux编程的方式,让这个学习的过程变得稍微简单一点。

项目实现的shell
Shell介绍
Shell会打印出提示符,等待来自stdlin的输入,根据输入执行特定地操作,这样就产生了一种错觉,似乎输入的文字(命令行)控制了程序的执行。
命令行是一串ASCII字符由空格分隔。字符串的第一个单词是一个可执行程序,或者是shell的内置命令。命令行的其余部分是命令的参数。如果第一个单词是内置命令,shell会立即在当前进程中执行。否则,shell会新建一个子进程,然后再子进程中执行程序。新建的子进程又叫做作业。通常,作业可以由Unix管道连接的多个子进程组成。
如果命令行以&符号结尾,那么作业将在后台运行,这意味着在打印提示符并等待下一个命令之前,shell不会等待作业终止。 否则,作业在前台运行,这意味着shell在作业终止前不会执行下一条命令行。 因此,在任何时候,最多可以在一个作业中运行在前台。 但是,任意数量的作业可以在后台运行。例如,键入命令行:
sh> jobs
会让shell运行内置命令jobs。键入命令行
sh> /bin/ls -l -d
会导致shell在前台运行ls程序。根据约定,shell会执行程序的main函数
int main(int argc, char *argv[])
argc和argv会接收到下面的值:
argc == 3,
argv[0] == ‘‘/bin/ls’’,
argv[1]== ‘‘-l’’,
argv[2]== ‘‘-d’’.
下面以&结尾的命令行会在后台执行ls程序
sh> /bin/ls -l -d &
Unix shell支持作业控制的概念,允许用户在前台和后台之间来回移动作业,并更改进程的状态(运行,停止或终止)。在作业运行时,键入ctrl-c会将SIGINT信号传递到前台作业中的每个进程。SIGINT的默认动作是终止进程。类似地,键入ctrl-z会导致SIGTSTP信号传递给所有前台进程。 SIGTSTP的默认操作是停止进程,直到它被SIGCONT信号唤醒为止。Unix shell还提供支持作业控制的各种内置命令。例如:
jobs:列出运行和停止的后台作业。
bg <job>:将停止的后台作业更改为正在运行的后台作业。
fg <job>:将停止或运行的后台作业更改为在前台运行。
kill <job>:终止作业。
实验的流程
实验和配套的教材《深入理解计算机系统》是紧密相关的,在网络上还可以找到CMU使用这本教材的教学视频。我没有阅读教材,只是把对应的视频看了一遍。
实验提供了初始文件,包括很多辅助函数,这样你就只需要实现shell最为核心的部分。
在做实验之前,需要阅读实验说明,对实验的整体有一个初步的认识。也就是说你需要了解你需要实现什么功能,大体会需要什么样的函数。
你需要编写的文件是tsh.c,因此需要把这个文件里的程序阅读一遍,了解提供了哪些辅助函数。
此外实验还提供了测试用例以及标准的shell实现,这样你就可以对比你的实现结果是否与标准的结果一致。这是一个绝佳的调试方法,也是攻破这个实验的一条路径,先解决第1个测试用例,然后第2个……这样你就不用担心无从下手了。
测试函数调用了myint、myspin和mysplit程序,因此你也需要阅读一遍。
难点
编程需要遵循良好的编程规范,其中一个就是检查函数的返回值,通常系统函数会使用返回值0或-1表示执行错误。虽然大多数情况下都不会出现问题,但是一旦出错检查返回值能够让你快速发现错误的源头。在csapp.h头文件里,很多系统函数有一个头文件大写的函数,与原有的系统函数拥有同样的参数,但是合理地检查了返回值。
在fork新的进程时,有可能发生竞争条件。子进程很快结束了运行,发送SIGCHLD给主进程,进而回收子进程同时从作业列表中删除该作业。但是此时,主进程还没来得及将作业加入作业列表。解决方案是在主进程将作业加入作业列表之前屏蔽该信号,完成后再恢复该信号。需要注意的是子进程会继承屏蔽的信号,因此在子进程也需要恢复。
另一个难点是SIGCHLD的信号处理函数,如果你没有正确处理,有可能会无法通过最后一个测试用例。
问题之一:有可能多个子进程结束,主进程却只接收到一次信号。主进程无法知道有多少个子进程结束了。
解决方案:将waitpid置于while循环中,并传入参数WNOHANG。参数WNOHANG表示,如果没有需要回收的进程了,会返回0,如果回收了子进程会返回子进程的pid,通过判断返回值就可以结束循环。
问题之二:waitpid默认只有当正常结束才会返回,如果是被其它进程kill或停止是不会返回的,这样shell就无从知晓子进程是否结束了。
解决方案:传入WUNTRACED参数给waitpid。这样子进程正常结束、被kill或者是停止都会返回。我们就需要一种方式判断子进程到底是由何种方式结束的,这个信息可以在waitpid的status参数中得到。status是一个整数,不同的值表示不同的返回状态,有一系列的宏可以判断是否status是某种状态。比如WIFEXITED(status)可以判断是否正常结束,WIFSIGNALED(status)可以判断是否被终止,WIFSTOPPED是否被停止。所有的信息都可以在man页面找到。
实验的说明里提到,前台进程与后台进程的唯一区别是shell会等待前台进程,因此前台进程只有一个。waitfg实现了这一等待的功能。最显而易见的选择是用waitpid等待前台进程的结束。那么你需要像SIGCHLD信号处理函数那样考虑各种复杂的进程结束条件,因此这不是最佳选择。最佳选择是使用sleep函数,只要前台进程仍然是需要等待的进程,主进程就sleep。那么sleep多长时间呢,sleep(0)是最佳的选择,0表示进程会让其它进程来执行,如果没有其它的进程在执行会继续执行。这样总会有进程再执行,而不会出现CPU空转的情况。
从实验说开去
从实验中我们明确区分了两类命令:内置命令和可执行程序。内置命令直接执行,不需要进行作业管理,可执行程序需要创建一个可执行程序来执行。那么对于一个真实的shell来说,有哪些内置命令。下面列出来bash的部分内置命令。shell内置命令大致可以分为4类,通过type命令可以显示命令的类型,type自己就是一个内置命令:
A.2.1 bash内置命令
.:执行当前进程环境中的程序。同source。
. file:dot命令从文件file中读取命令并执行。
: 空操作,返回退出状态0。
alias:显示和创建已有命令的别名。
bg:把作业放到后台。
bind:显示当前关键字与函数的绑定情况,或将关键字与readline函数或宏进行绑定。
break:从最内层循环跳出。
builtin [sh-builtin [args]]:运行一个内置Shell命令,并传送参数,返回退出状态0。当一个函数与一个内置命令同名时,该命令将很有用。
cd [arg]:改变目录,如果不带参数,则回到主目录,带参数则切换到参数所指的目录。
command comand [arg]:即使有同名函数,仍然执行该命令。也就是说,跳过函数查找。
declare [var]:显示所有变量,或用可选属性声明变量。
dirs:显示当前记录的目录(pushd的结果)。
disown:从作业表中删除一个活动作业。
echo [args]:显示args并换行。
enable:启用或禁用Shell内置的命令。
eval [args]:把args读入Shell,并执行产生的命令。
exec command:运行命令,替换掉当前Shell。
exit [n]:以状态n退出Shell。
export [var]:使变量可被子Shell识别。
fc:历史的修改命令,用于编辑历史命令。
fg:把后台作业放到前台。
getopts:解析并处理命令行选项。
hash:控制用于加速命令查找的内部哈希表。
help [command]:显示关于内置命令的有用信息。如果指定了一个命令,则将显示该命令的详细信息。
history:显示带行号的命令历史列表。
jobs:显示放到后台的作业。
kill [-signal process]:向由PID号或作业号指定的进程发送信号。输入kill-l查看信号列表。
let:用来计算算术表达式的值,并把算术运算的结果赋给变量。
local:用在函数中,把变量的作用域限制在函数内部。
logout:退出登录Shell。
popd:从目录栈中删除项。
pushd:向目录栈中增加项。
pwd:打印出当前的工作目录。
read [var]:从标准输入读取一行,保存到变量var中。
readonly [var]:将变量var设为只读,不允许重置该变量。
return [n]:从函数中退出,n是指定给return命令的退出状态值。
set:设置选项和位置参量。
shift [n]:将位置参量左移n次。
stop pid:暂停第pid号进程的运行。
suspend:终止当前Shell的运行(对登录Shell无效)。
test:检查文件类型,并计算条件表达式。
times:显示由当前Shell启动的进程运行所累计用户时间和系统时间。
trap [arg] [n]:当Shell收到信号n(n为0、1、2或15)时,执行arg。
type [command]:显示命令的类型,例如:pwd是Shell的一个内置命令。
typeset:同declare。设置变量并赋予其属性。
ulimit:显示或设置进程可用资源的最大限额。
umask [八进制数字]:用户文件关于属主、属组和其他用户的创建模式掩码。
unalias:取消所有的命令别名设置。
unset [name]:取消指定变量的值或函数的定义。
wait [pid#n]:等待pid号为n的后台进程结束,并报告它的结束状态。
meelo
处理作业:bg fg jobs disown kill wait stop
文件系统:cd pwd dirs pushd popd
变量相关:let local readonly printf var declare
命令相关:history type alias help unalias hash
函数相关:return shift
用实现的shell执行程序,必须给出程序的完整路径,比如需要执行ls需要输入/bin/ls。那么bash是如何确定该执行那个程序的呢?下面给出的两篇文章解释得非常清楚。shell会以一定的顺序搜索命令,如果找到了命令就执行,没找到会返回错误信息。
shell搜索变量的顺序
- ALIASES
- Shell函数
- 内置命令
- HASH表
- PATH变量
https://www.cyberciti.biz/tips/how-linux-or-unix-understand-which-program-to-run-part-i.html
https://www.cyberciti.biz/tips/an-example-how-shell-understand-which-program-to-run-part-ii.html
深入理解计算机系统项目之 Shell Lab的更多相关文章
- CS:APP3e 深入理解计算机系统_3e C Programming Lab实验
queue.h: /* * Code for basic C skills diagnostic. * Developed for courses 15-213/18-213/15-513 by R. ...
- 《深入理解计算机系统》实验一 —Data Lab
本文是CSAPP第二章的配套实验,通过使用有限的运算符来实现正数,负数,浮点数的位级表示.通过完成这13个函数,可以使我们更好的理解计算机中数据的编码方式. 准备工作 首先去官网Lab Assig ...
- 《深入理解计算机系统》(CSAPP)读书笔记 —— 第一章 计算机系统漫游
本章通过跟踪hello程序的生命周期来开始对计算机系统进行学习.一个源程序从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止.我们将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念 ...
- 深入理解计算机系统(1.1)------Hello World 是如何运行的
上一篇序章我谈了谈 程序员为啥要懂底层计算机结构 ,有人赞同也有人反对,但是这并不影响 LZ 对深入理解计算机系统研究的热情.这篇博客以案例驱动的模式,通过跟踪一个简单 Hello World 程序的 ...
- 深入理解计算机系统_3e 第八章家庭作业 CS:APP3e chapter 8 homework
8.9 关于并行的定义我之前写过一篇文章,参考: 并发与并行的区别 The differences between Concurrency and Parallel +---------------- ...
- 深入理解计算机系统_3e 第九章家庭作业 CS:APP3e chapter 9 homework
9.11 A. 00001001 111100 B. +----------------------------+ | Parameter Value | +--------------------- ...
- CSAPP shell Lab 详细解答
Shell Lab的任务为实现一个带有作业控制的简单Shell,需要对异常控制流特别是信号有比较好的理解才能完成.需要详细阅读CS:APP第八章异常控制流并理解所有例程. Slides下载:https ...
- 【DIY】【CSAPP-LAB】深入理解计算机系统--datalab笔记
title: 前言 <深入理解计算机系统>一书是入门计算机系统的极好选择,从其第三版的豆瓣评分9.8分可见一斑.该书的起源是卡耐基梅龙大学 计算机系统入门课(Introduction to ...
- 《深入理解计算机系统V2》学习指导
<深入理解计算机系统V2>学习指导 目录 图书简况 学习指导 第一章 计算机系统漫游 第二章 信息的表示和处理 第三章 程序的机器级表示 第四章 处理器体系结构 第五章 优化程序性能 第六 ...
随机推荐
- Fox
Portal --> broken qwq Description 有n只狐狸在一起聚餐,每只狐狸都有一个年龄.按照狐狸们的习惯,坐在一起的两只狐狸的年龄之和需要是质数.现在这些狐狸们在一些圆桌 ...
- python基础----内置函数----匿名函数(lambda)
Python3版本所有的内置函数: 1. abs() 获取绝对值 >>> abs(-) >>> abs() >>> abs() >>& ...
- SAS8.1安装步骤(附图)
安装前应当把系统时间更改到一九九几年. 1.在解压后的文件夹里找到 setup .exe 双击 开始安装 2.单击SAS System Setup 3.点击Next 4.选择 complete 并单击 ...
- Flash平台的分析与RIA的趋势
10月3号,Flash Player 11 和 AIR 3.0正式提供下载,一片安静.最近这两年来,关于Flash的新闻一向是以负面为主,先是 Silverlight 的挑战,然后是 iphone和i ...
- C++中添加配置文件读写方法
比如有一个工程,一些变量有可能需要不时的修改,这时候可以通过从配置文件中读取该数值,需要修改时只需要修改配位文件即可. 比如有一个这样的变量m_nTest; 我么可以写两个函数ReadConfig() ...
- HAOI2017游记
HACF的最终成绩已经出炉,但是事情还没有结束. 好多想说的,不知道从何说起,就按照时间顺序说吧. 考前 考前大概一周半就开始复习了,一些比较重要的算法,比如KDT,单纯性,线性基等等没有再继续学,所 ...
- ASP.NET MVC4+EasyUI+EntityFrameWork5权限管理系统——数据库的设计(一)
快一年没写博客了,这段时间感觉好迷茫,写点博客,记录一下自己的成长过程,希望对大家也有帮助 先上图 一个用户可以有多个角色,一个用户可以属于多个部门,这些都可以控制到权限,有时特殊要求,同样的部门和角 ...
- C++类四个默认函数&深复制&浅复制
学习C++语言的同学都知道,C++中类是有默认的几个函数的,主要是有四个函数: 四个函数 默认构造函数:A(void),无参构造函数 拷贝(复制)构造函数:A(const A&a).用一个对象 ...
- windows上使用wordpress搭建博客
环境windows8.1 需要的工具:wampserver.wordpress 首先先下载好wordpress源码和wampserver安装包 安装完成后开启wampserver 开启的状态是绿色的: ...
- 动态内容的缓存技术:CSI vs SSI vs ESI
CDN 中动态内容是不太好解决的,通常需要很麻烦的技术和方法来实现这些功能,比如我设计过一种动态缓存的方法,基于 session 栏接,然后根据热点来做动态缓存时间的控制.目前开放的实现 Cache ...