操作系统课程设计.docx
- 文档编号:9659150
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:19
- 大小:904.82KB
操作系统课程设计.docx
《操作系统课程设计.docx》由会员分享,可在线阅读,更多相关《操作系统课程设计.docx(19页珍藏版)》请在冰豆网上搜索。
操作系统课程设计
课程设计
题目Shell编程
学生姓名汪国新学号2009112110
专业计算机科学与技术班级20091121
指导教师张老师
完成日期2012年1月8日
Shell程序设计
——编写一个简单的Shell程序
摘要:
本文简单的给出了一个Shell的模拟实现,读者可以从中了解到怎么获取当前登录系统的用户的用户名,当前的主机名和当前用户正处在的当前路径名。
读者还可以知道怎么从终端读取命令,怎么解析命令和怎么执行命令以及实现所使用的相关数据结构和算法。
另外,读者还可从文中学习到管道,重定向,后台运行的实现思想。
前言:
操作系统提供两种基本用户接口:
第一种是程序接口,以函数(系统调用)的形式向程序员提供支持,通过fork()、read()、write()等函数为上层软件提供服务,函数是用户应用程序与操作系统之间的一种接口;第二种是命令接口,当用户在控制作业运行、浏览文件信息和访问硬件资源时,使用此接口和操作系统进行交互。
Shell为用户和操作系统之间的命令交互提供最基本的接口,在使用者和操作系统之间架起一座桥梁,它的名字Shell(外壳)形象地表示它在用户与操作系统内核之间的关系。
早起Shell主要是用作命令解释器。
经过不断的扩充和发展,现在已是命令语言、命令解释器、程序设计语言的统称,被泛指为一个提供人机交互界面和接口的程序。
最简单的Shell程序是基于字符的,用户通过输入一个字符串到Shell中来与操作系统进行交互,并且操作系统的响应结果是输出一行行的字符到屏幕上,更方便的Shell程序使用图符bourneShell演变而来的。
文中设计并实现了一个简单的交互式Shell——MyShell。
MyShell简单的实现支持程序后台运行、支持重定向、支持管道、支持设置搜索路径、支持内置命令(cd:
切换目录,exit:
退出Shell,path:
设置所搜路径)等一些功能。
正文:
1.总体结构图
图1MyShell总体结构图
说明:
此Shell模拟程序就如同Shell一样提供了一个用户和机器之间进行交互的交互界面,此界面是一个字符的界面,用户从字符界面终端敲入命令来使用计算机。
对于用户键入的命令,首先就要通过命令解析程序来解释用户的这条命令是干什么的,只有计算机知道了用户的命令是具体干什么的才能去实施执行,命令解析程序也就是分析用户的命令,了解用户的命令到底是内部命令呢还是自己定义的存放在可执行命令文件中的命令。
如果命令是用户自定义的命令,那么,命令解析程序还需要分析命令是管道命令还是I/O重定向命令,另外就是命令是否后台执行。
通过命令解析程序,那么计算机已经知道了用户的命令是要干什么的了,于是就是执行命令了,如果命令是内部命令,就直接调用内部命令去执行,如果命令是用户自定义的命令,那么就创建一个子进程去执行用户的命令。
所以,此Shell模拟程序也就应该有交互界面、命令解析、运行内部命令、运行可执行命令文件、创建子进程、I/O重定向、创建管道等功能模块。
2.程序总流程图
图2程序总流程图
说明:
此Shell模拟程序开始之后第一步工作就是进行一些简单的初始化工作,在初始化时获得当前登录到系统的用户名、当前机器的主机名、当前正处在的路径的路径名。
接着第二步工作就是根据第一步中获得的信息打印命令提示符创建一个与用户交互的交互界面。
第三步工作就是读取用户命令了,如果用户的命令是exit,那么就直接退出模拟程序,如果用户的命令不是exit,那么就进入第四步——命令解析,通过解析用户命令了解了用户命令是干什么的以及怎么干之后就进入第五步具体的实施执行用户的命令了。
执行一条命令结束之后就又进入第二步打印命令提示符,开始读取用户下一条命令了。
如此循环执行。
3.数据结构
为了实现此Shell模拟程序的功能,当然很多数据结果是必须的。
在此Shell模拟程序中,主要用到的数据结构有以下几个:
(1)数据结构1
struct
{
charcmdLine[cmdLineLen];/*用于存储整个命令行*/
char*type;/*NULL表示普通命令,指向cmdLine中’|’表示管道命令*/
char*runType;/*NULL表示前台运行,指向cmdLine中’&’表示后台运行*/
intpos;/*命令行当中正在处理的字符的位置*/
}commandLine={{'\0'},NULL,NULL,0};
此数据结构中的域cmdLine用户存储接收到的用户命令。
type域用指明当前的命令是一般的命令还是管道命令,如果type为NULL就说明当前的命令是普通命令;如果type指向域cmdLine中的’|’就说明是管道命令。
Runtype域指当前的命令是前台运行还是后台运行,如果run_type为NULL就说明此命令是前台运行;如果run_type指向域cmdLine中的’&’就说明此命令是后台运行的。
Pos域用于在解析命令时指明当前正在处理的字符位置。
在整个程序中只需要使用其一个实例就可以了,于是就没有给这个数据结构一个名字。
在定义这个数据结构的同时就定义了一个这个数据的全局变量实例commandLine。
每当读取用户命令之前就将cmdLine域清空,用于存放将要读取的用户的下一条命令。
另外,还要将type、run_type、pos分别置成NULL、NULL、0,消除处理上一条命令的干扰。
(2)数据结构2
structcommand
{
charcmd[cmdLen];/*用于存放解析出来的单纯的命令*/
intargc;/*用于存放参数个数*/
char*argv[argNum];/*用于存放各个参数的首地址*/
char*inFd;/*用于指出输入重定向的位置*/
char*outFd;/*用于指出输入重定向的位置*/
structcommand*next;/*用于连接管道中的下一条命令的structcommand结构*/
};
structcommand主要用在解析命令时,cmd域用于存放解析出来的命令,单纯的命令。
argc域用于指明参数的个数。
argv用于存放各个参数的首地址。
如果是重定向命令,inFd域和outFd域就分别用来指明输入重定向和输出重定向的位置。
如果当前的命令是一条管道命令,那么next域就用于连接管道的下一条命令的structcommand结构。
当然,每当上一条命令执行完成之后在将要读取下一条用户命令之前,要将上一条命令中用到的structcommand实例所占的内存空间释放。
(3)数据结构3
struct
{/*定义一个光标结构体,用于描述当前的插入点*/
char*base;/*光标的基位置*/
char*pos;/*光标的当前位置*/
intrange;/*光标的活动域*/
intoffset;/*光标当前的偏移量*/
}cursor={NULL,NULL,0,0};
命令是一个字符一个字符的解析的,当解析到某一个字符时,当前的字符是命令,还是参数,或者还是管道指示符,再或者还是重定向指示符等,应该归类放到那里去,这是就需要知道插入点,光标结构体就是用于只是当前的插入点位置的。
base域指明当前光标的基位置。
例如,当前解析到的是参数,正在往一块存放参数的内存中存放,那么base就于指向当前正在存放参数的内存的首地址。
pos域就用于指向当前正在放入字符的内存位置。
range域就用于指示当前光标可以活动的范围,如果光标的当前偏移量超出了光标的活动范围,那么就发生了内存溢出。
offset域就用于指出当前贯标的偏移量。
光标结构体和前面的命令行的结构体一样,在整个程序中只需要使用到此结构体的一个实例即可,于是也是没有给这个结构体一个名字。
在定义此结构体的同时就定义了此结构体的一个全局变量实例cursor。
同前面一样,每当在将要读取下一条用户命令之前就复位光标,将base和pos置成NULL,range和offset置成0。
(4)数据结构4
char*userName,*hostName,*currentPath;
char*类型的全局变量userName、hostName、currentPath分别用于指向当前登录到系统的用户的用户名,当前的主机名,当前在处在的路径的路径名。
(5)数据结构5
charpath[pathLen];
char类型全局数组path用于记录当前的搜索路径。
(6)数据结构6
structcommand*head=NULL;
全局structcommand结构体变量用于指向从每条用户命令中解析出来的第一条命令的命令结构体structcommand。
当然在读取每一条用户命令之前要置成NULL。
(7)说明
对于以上数据结构定义中用到的cmdLineLen、cmdLen、argNum等都是一些宏定义。
在此程序中,给出了如下的一些宏定义:
#defineHostNameLen20
#definepathLen100
#definecmdLineLen100
#definecmdLen50
#defineargNum8
#defineargLen100
4.初始化
图3初始化流程图
说明:
初始化的函数声明为voidinit(),初始化开始后就是获取userName,使用函数getlogin(),将获取的userName的首地址存放于全局char*类型的变量userName中。
接着就是获取hostName,使用函数gethostname(hostName,HostNameLen)将获取到的hostName的首地址存放于全局char*类型的变量hostName中。
再就是获取currentPath,使用函数getcwd(currentPath,pathLen)将获取到的currentPath的首地址存放于全局char*类型的变量currentPath中。
最后就是用自定义函数voidset_path(char*newPath)设置默认搜索路径后结束voidinit()返回了。
5.打印命令提示符和读取用户命令
图4打印提示符和读取命令
说明:
读取命令函数的声明是intread_command(),函数开始执行后首先做的就是清理cursor,将光标复位,base域pos域置成NULL,range域和offset域置成0。
接着就是清理commandLine,将cmdLine域全部置成’\0’,将type域和run_type域置成NULL,将pos域置成0。
再接着就是清理head,head指向的是上次解析命令后留在内存中的structcommand结构体实例的内存的首地址,在读入用户新的命令之前应该释放这些内存空间,不然内存泄露不可避免。
再就是打印命令提示符等待用户输入命令了,用户输入命令后就将用用户输入的命令存储到commandLine的cmdLine域中。
如果用户输入的是exit,那么就退出程序;如果不是那么看看命令是否是管道命令,是否是后台运行,然后就是结束intread_command()返回了。
6.命令解析
图5命令解析流程图
说明:
进入命令解析后,首先就是实例化一个structcommand并让p指向它,接着就将p赋给head,然后就是将cursor插入p->cmd的首位置,即“cursor.base=p->cmd;cursor.pos=cursor.base;cursor.range=cmdLen;cursor.offset=0;”。
接下来就进入循环读取commandLine.cmdLine中的每一个字符,如果没有读到’\0’,即没有读到commandLine.cmdLine的末尾,就继续读;如果读到’\0’,即读到了commandLine.cmdLine的末尾,就“return1;”返回结束读命令。
在没有读到’\0’的情况下,如果读到的是普通的字符,即没有特殊意义的字符,就直接向cursor.pos位置进行写入,然后又开始读取下一个字符;如果读到的是’’(空格)、’|’、’>’、’<’等字符就要进行相应的处理。
如果读到的是’’(空格)字符,首先就是判断一下命令的格式有没有错误,如果命令的格式有错误就直接“return0;”返回结束命令解析;如果命令格式没有错误再就是判断cursor.offset是否等于0,如果cursor.offset等于0,就说明是刚刚开始读命令或者参数,命令或者参数的一个字符也都还没有读取,空格是命令或者参数前面的空格,真正的命令或者参数还没有开始,于是就将commandLine.cmdLine中当前字符右移一个,开始读取下一个字符;如果offset不等于0就用malloc开辟一块内存,增加一个参数,将cursor插入刚开辟的参数首地址,接着就是p->argc++,即是参数的个数增加一个,再就是和前面一样将commandLine.cmdLine中当前字符右移一个,开始读取下一个字符。
如果读到的是’|’就说明用户的命令是管道命令。
同样还是先经过命令格式正确与否的判断,如果命令格式错误,同样还是“return0;”返回结束命令解析;如果命令格式正确就判断该’|’字符之前的字符是否为空格,如果为空格那么之前在处理此空格的时候就新建了一个参数,但是现在又处理的不是参数,于是就应该释放之前新建的参数所占据的内存空间,并且p->argc--,即将参数的个数减少一个。
不管前面有没有空格,接下来都是应该作“q=p;p=malloc(structcommand)”操作,即将当前正在处理的命令结构structcommand的实例赋给q,然后新开辟一块内存空间新建一个structcommand的实例,将p指向这个实例,使新建的structcommand实例成为当前正在处理的命令结构。
接下来就是将cursor插入p->cmd的首位置,将commandLine.cmdLine中当前字符右移一个,开始读取下一个字符。
如果读到的是’>’就说明用户当前的命令是输出重定向命令。
同样,判断格式,格式错误就返回,格式正确就继续。
判断前面是否有空格,有的话就先进行释放参数处理,没有的话就直接进入p->outFd=malloc(pathLen),为存放输出重定向路径名开辟空间,一样的将cursor插入其首位置,commandLine.cmdLine中当前字符右移一个,开始读取下一个字符。
如果读到的是’<’就说明当前的用户命令是输入重定向命令。
同样的判断格式正确与否,判断前面是否有空格,为输入重定向路径开辟空间,将光标插入其首位置,右移commandLine.cmdLine的当前字符,开始读取下一个字符。
7.执行命令
图6执行命令流程图
说明:
执行命令开始后就首先判断命令是否是内部命令,如果是就直接调用内部命令执行然后“return1;”返回;如果不是就执行自定义的命令。
如果是自定义的path命令就将path(即命令的查找路径)设置成指定的值然后“return1;”返回。
如果是自定义的cd命令就将currentPath(即当前路径)设置成指定的值然后”return1;”返回。
如果是其它命令就在自定义的搜索路径上搜索,如果搜索到相应的命令就创建进程去执行相应的命令,执行成功后就“return1;”返回,执行失败就“return0;”返回;如果没有所搜到就“return0;”返回。
图7是否内部命令流程图
说明:
判断命令是否是内部命令的程序流程图7所示,首先就是把当前的所搜路径path保存到缓冲区pathBuf中,接着就是设置搜索路径path为“/bin:
/usr/bin”,然后就是在path所在的路径上搜索命令,如果找到,就将当前搜索路径path设置成pathBuf中的值并“return0;”返回;如果没有找到就将当前搜索路径path设置成pathBuf中的值并“return1;”返回。
图8查找命令流程图
说明:
查找命令开始后就是从当前搜索路径path中取出一条路径来,并将这条路径中所有的文件的文件名输出到文件temp中,接下来就是进入命令比较阶段了。
先是判断是否已经读取到了temp文件的结尾,如果读取到了结尾就删除temp文件,并判断当前搜索路径path中是否还有下一条路径,如果有,就提取下一条路径,同样将这下一条路径中的所有文件的文件名输出到文件tmep中;如果没有读取到文件结尾,就从temp文件中读取一个文件的文件名与当前要查找的命令进行比较,如果相等就“return1;”返回;如果不相等就又进行是否到了文件结尾的判断,读取下一个文件的文件名并进行比较,如此循环重复。
8.程序运行结果展示
(1)键入./MyShell回车运行程序
(2)键入cd命令后回车导航到/home/wangguoxin文件夹下
(3)键入ls命令后回车显示/home/wangguoxin文件夹下的类容
(4)键入path后回车设置搜索路径
(5)键入内部命令reset
(6)reset命令回车后的显示
(7)导航到根目录后显示里面的类容
(8)导航到/bin文件夹下
(9)键入ls命令回车后显示/bin里面的类容
(10)键入exit回车后推出程序
总结:
此程序简单的模拟的Shell的实现并实现了一些简单的Shell功能。
程序从最初的查找资料,参考Linux操作系统试验教程开始,经过需求分析,功能模块设计,再经过编写代码实施设计,修改设计,继续完善代码,调试运行,修改错误等直到最后的完成以及报告的写作对于一个星期的完成时间,对自己来说,确实是一个挑战。
整天就是呆在电脑前写程序,调试程序,最后就是写报告。
不过这种经历是很有意义的,可以将自己学习的理论知识用到实践上来,真正的去更深刻地理解自己学习的理论知识。
整天的编程提高了自己的编程能力,报告的书写锻炼了自己的论文写作能力,一个星期的完成时间也加强了自己的抗压能力。
另外,对于linux的Shell的理解,自己也是更加清楚了,对于进一步学习Shell和学会使用linux有了很大的帮助。
参考文献:
Linux操作系统实验教程作者:
费翔林高等教育出版社
TheLinuxCommandLineandShellScriptingBible(SecondEdition)
CLibraryReferenceGuideEditor:
EricHuss
TheStandardClibraryEditor:
P.J.Plauger
TheOfficialUbuntuBookEditor:
BenjaminMakoHill,JonoBacon
附录:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 操作系统 课程设计