0第1章绪论1.1引言1.1.1选题的背景本次课程设计是在学习完了《现代操作系统》课程之后做的,本人选择了编写命令解释器模拟shell功能问题题目来实现。1.1.2设计思路和预期目标通过对《现代操作系统》的学习,我预计将用命令分割得到命令和参数,通过fork()函数创建子进程调用exec系统函数,并自己编写无法用exec系统函数执行的cd命令函数,依次完成打开提示符,获取用户输入的指令可解析指令、可寻找命令文件、可执行基本的命令的功能。1.2课程设计目的与意义通过学习Linux下的系统调用、进程和进程间的通信,我基本了解内核的外围部分是如何合理的分配各种资源来使各类应用程序正常工作的。通过编写shell命令解释器,可以练习熟练使用系统调用和各类IO库函数。进行shell编程,对于一个嵌入式工程师来说做shell编程是一个基本工,是做好嵌入式开发的前提。1.3课程设计内容与要求可以打开提示符,获取用户输入的指令可解析指令、可寻找命令文件、可执行基本的命令的功能。1.4课程设计地点及设计环境课程设计地点:图书馆五楼软件实验室课程设计环境:RedhatLinux系统开发环境:Linux开发平台1第2章系统设计2.1系统框架设计2.1.1系统主结构图图1函数调用结构图图2进程调度结构图主函数命令切割函数Cd命令函数Exec函数父进程子进程fork()父进程waitpid()execpl()execvp()22.1.2函数流程图本程序除系统函数外还有三个重要函数main()、CD()、str_cut(char*p,char*buf[])。其中main()函数流程图如图3,CD()函数流程图如图4,str_cut(char*p,char*buf[])函数流程图如图5。图3主函数流程图开始1==1输出当前路径作为提示符输入命令字符串调用切割函数切割字符串命令不为“exit”命令不为“cd”执行Cd()结束创建子进程Execvp函数没有正常执行执行exec函数错误提示是否是是是是否否3图4命令裁剪函数流程图图5CD函数流程图开始命令字符串没有读完本字符为空格用“\0”替代标志位清零指针前移标志位为“0”标志位变“1”保存首地址指针前移指针前移末尾替换为“\0”结束是是是否否否开始标志清零参数是否不非空结束执行Chdir()并把返回值赋给标志标志不等于0错误提示是是否否42.2系统模块功能说明2.2.1主模块说明主函数完成读命令,调用各其它函数,创建子进程,在子进程调用exec执行,提示错误等功能。首先,申请一个buff的字符串数组,用来存放命令,另外申请一个pid_t型的变量来存放新生成的子进程的IP号。pid_t是Linux里的一个宏定义,通常可以当作一个整形看待,但为了便于移植程序,因为不同类型的计算机对整形长度的定义是不同的,在嵌入式里,不同的嵌入式处理器的运算位数不同,数据类型的定义也不同。然后,进入一个死循环,打印出提示符当前路径。完成这一功能依赖的是char*get_current_dir_name()这一系统函数调用。要注意的是这个函数虽然加了头文件,但在使用前要声明。或者定义#define_GNU_SOURCE。因为unsignedlong和char*都是16位把返回的char*当成了unsignedlong需要用_GNU_SOURCE来声明一下,这个宏是用来声明ISOPOSIX之类的标准,就可以区分返回值是char*和unsignedlong了。接下来,读取命令,这里我用的是fgets函数,之所以不用scanf函数来读字符串,是因为它以空格为结束标志,这样参数就无法读入了。之后是if句子是用来做退出用的,如果敲入的是“exit”那么就退出循环,结束整个程序。然后还有一个if语句是用来判断是不是cd命令,而决定是否调用cd函数。这两个if语句的位置很重要,在编程初期,我将它放在了下面创建的子进程中,出现了一些问题。比如,当cd()命令执行时地址制定错误后,提示语Changedirerror!\n会出现两次。原因是当执行“exit”“cd”时子进程中不需要执行exec函数,也就是说子进程没有被替代,它一直与父进程保持一致。所以出现了以上代码会执行两次的现象。所以这两个if语句的语句必须放在子进程外。下面就是创建了一个子进程,子进程的IP号放到pid里,其中父进程得到了子进程的IP号,子进程里得到0;这样,子进程执行if里的代码,父进程执行else里的代码;5特别要指出的是子进程里,execlp使buff里的命令执行,这个子进程的代码都被命令文件的代码替换了,子进程在exec结束时已经结束,所以成功执行时后面的错误提示代码是不被执行的。当这个命令不存在时,不会将剩余代码替换,他会继续执行下面的代码,即打印一句提升:“commandnotfound!”不让程序限于阻塞。父进程里,通过得到的子进程的IP号,来使用wait函数等待子进程的退出,接着进入下一次循环,打印提示符,等待命令的再次输入。2.2.2子模块说明1、切割函数剪切的方式取决于调用的exec函数所需要的参数形式。execl(执行文件)相关函数fork,execle,execlp,execv,execve,execvp调用方式分别为:main(){execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char*)0);}main(){execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char*)0);}main(){char*argv[]={“ls”,”-al”,”/etc/passwd”,(char*)}};execv(“/bin/ls”,argv);}main(){char*argv[]={“ls”,”-al”,”/etc/passwd”,(char*)0};char*envp[]={“PATH=/bin”,0}execve(“/bin/ls”,argv,envp);}main(){char*argv[]={“ls”,”-al”,”/etc/passwd”,0};execvp(“ls”,argv);}由上可知execvp()函数调用最简单易用,且切割方便。第一个参数为命令文件名,第二个参数为一个指针数组,这样就可以把命令和参数全部丢给执行函数。为此我们也为切割函数设置一个是原命令字符串指针buf1,一个是存储字符串指针的字符串指针buf2。切割函数将完成以下功能。6将buf1中的空格全部置换为“\0”,它有很多作用。其一,在把buf1作为execvp()第一个参数时,读取命令后会自动停止,因为命令与参数之间的空格已经变为结束符。其二,在把buf2作为第二个参数时,按指针寻址后,避免了一次读取到多个参数的现象。最后一位的’\n’换成’\0’,即buf1和buf2最后一个单元置空,以符合参数形式要求。2、CD函数命令分为两种,一种为内部命令,一种为外部命令。用户输入的命令是执行存储在文件系统下中的可执行程序,我们称之为外部命令或外部程序。外部命令的形式是一系列分隔的字符串。第一个字符串可以是可执行程序的名字,其它的是传递给这个外部程序的参数。如果第一个字符串所声名的可执行文件并不存在或者不可执行,则认为这个命令是错误的。以上其它命令就是外部命令,因而可以用exec系统文件函数完成执行。但是cd命令和exit命令是内部命令,不能用exec函数执行的,它的出现时shell特有的功能。所以在这里,cd函数也是我自己编写的。它的功能有两个,其一是判断路径的正确性,其二是执行变换路径函chdir()。7第3章源程序代码设计#includesys/types.h#includeunistd.h#includestlib.h#includestring.h#includestdio.hchar*get_current_dir_name();voidstr_cut(char*p,char*buf[]){inti=0;//i即为buf数组下标intu=0;//u是一个标志位,u=0,表示前一字符为空格或无字符//即刚开始;u=1,表示前一字符为字母while(*p)//p为原字符串指针,*p不为'\0'则继续循环{if(*p==''){*p='\0';//把空格替换为结束符p++;u=0;//标志位u清零}else{if(u==0){buf[i++]=p++;//单词的首地址赋给bufu=1;//标志位u置1}elsep++;//什么也不做,p指向下一个}}buf[i]=NULL;//buf里最后一个地址是空便于execvp使用}voidCD(charargv1[]){intres=0;8if(argv1!=NULL)res=chdir(argv1);if(res0)printf(Changedirerror!\n);}intmain(void){charbuf1[50];char*buf2[10];pid_tpid;char*p;while(1){p=get_current_dir_name();printf([%s]#,p);fgets(buf1,sizeof(buf1),stdin);buf1[strlen(buf1)-1]='\0';str_cut(buf1,buf2);//调用上面的切割函数,原字符串仍存在//buf1里,其分割后地址存在buf2里if(strncmp(buf1,exit,4)==0)break;if(strncmp(buf1,cd,2)==0){CD(buf2[1]);continue;}if((pid=fork())==0){execvp(buf2[0],buf2);printf(commandnotfound!\n);//命令不可执行,则向下走//打印无此命令exit(1);}elsewaitpid(pid,NULL,0);}9exit(0);}第4章系统的调试和运行本系统可以执行目录命令、文件命令,带参数和不带参数命令均可正确执行。并且有检错功能,伴有各类错误提示。“ls”“pwd”“mkdir”“rmdir”“cd”“mv”等不带参数的目录操作命令可以正常执行,结果如图6。“ls–l-a”“mkdir–p电路/数电”“rmdir–p电路/数电”“cd/root”等带参数甚至多参数目录命令可以正常执行,结果如图7。“cat”“more”“cp”“rm”“vi”“gcc”等命令无论有无参数也均可正常执行,结果如图8。其中vi编辑情况如图9。本程序有比较好的健壮性,不会阻塞,且有各类错误提示,如图10。内部命令“exit”执行结果如图10。10图6无参数目录命令执行图图7有参数目录命令执行图11图8文件操作命令执行图图9vi插入数据图图10错误提示及退出执行图通过各类数据的测试包括不同命令、不同参数、不同参数数量以及错误命令的测试,可以证明,本程序属于一个功能完善,健壮性强的作品。12结论本次操作系统课程设计我选择的命令解析器模拟shell的问题是通过Linux操作系统实现,使我对Linux系统有了一些了解,并在课程设计过程中得到进一步深化,特别是对Linux下各种命令及其系统文件调用方式方法。巩固了我的操作系统的理论,提高了我使用linuxC的能力。本程序有很多创新点,它是一步步完善,一步步升级的。在第一阶段,它只有输入不带参数的简单命令(不包括cd命令),使用的是scanf()读取字符,以空格结束命令。用execlp()调用系统文件函数。第二阶段增加创新点,用fgets()获取命令字符串,它以回车为结束标志,使得空空格分隔命令和参数时也可以整条命令存入字符串。用execvp()调用系统文件函数,使得可以传入多条参数,且参数数量可以不固定。并按其参数要求设计切割函数切割方式。第三阶段在发现了cd命令执行报错后,查看相关资料发现cd不属于外部命令,不能用exec函数调用,之后我自己编写了CD()函数。第四阶段在全面功能测试时我发现cd命令输入错误路径后的错误提示输出了