Linux模拟命令解释器shell的设计
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
LINUX操作系统
大作业报告
姓名XXX
学号
姓名XXX
学号
姓名XXX
学号
指导教师XX
学期2011 至2012 学年第二学期
完成时间2012 年01 月03 日
Linux 操作系统设计性实验报告
学生姓名
XXX
XXX XXX
学号 教学班 B01
实验项目 模拟命令解释器Myshell 的设计
实验地点 H527 实验最后完成时间 2012年01月05日
必修√选修
指导教师
XX
同组人
指导教师评语及成绩:
成绩: 指导教师签名: 日期:
打印稿记录
等级
迟交: 抄袭:
一、实验综述
㈠实验目的及要求
1.实验目的:
Linux/UNIX 系统中,shell 是用户和系统内核沟通的中介,它为用户使用操作系统的服务提供了一个命令界面。
用户在命令提示符($或是#)下输入的每一个命令都由shell 解释,然后传给内核执行。
本实验要求用C 语言编写一个模拟shell 命令解释器,达到如下目的: ● 用C 语言编写清晰易读、设计优良的程序;
● 在编写系统应用程序时熟练使用man 手册或其他查询命令、系统调用和库函数的工具。
● 学习使用文件操作、进程控制、信号、管道相关程序设计方法和系统调用; ● 锻炼团队成员之间的交流和合作完成项目能力。
2.实验要求:
设计并实现一个Linux 下的的交互式shell 命令解释器:MyShell ;以模拟unix/linux 系统下的完善的bash shell 的功能。
不需要具备非常完备的shell 功能。
主要有如下功能: ● 初始化环境,主要是默认搜索路径、history 循环数组、作业列表
●打印命令提示符;
●接受、识别和分析命令提示符后输入的命令行,并创建子程序,在子程序中将其转换为
相对的系统调用以调用内核功能实现任务;
●能实现内部命令,包括:exit(退出shell)、history、cd、jobs、bg、fg。
●能够执行外部命令,并到搜索路径中去查找外部命令对应的执行文件;命令均可带参数;
●支持&、|、<、>四个特殊字符及其所代表的功能;
●支持前、后台作业控制,包括fg/bg/jobs以及ctrl+c/ctrl+z(挂起、中止和继续运行)。
●最后用make来建立工程;
●不断测试
●写报告,提供清晰、详细的解决方案和设计文档。
●结束
实验过程中,要求学生2~3人一组,要求各小组内部对该实验任务加以分工,各小组之间相互独立完成实验。
教师根据各小组所编写的程序代码以及实现功能加以评分,如能完成实验要求的基本功能,则予以通过和合格成绩;如能在实现的简洁度和清晰度上做出优化,则加分;如能实现更完善功能,则给以优良分数。
3.实验要求说明:
●读取test_profile文件中的默认搜索路径列表,作为环境。
●打印命令提示符:
模仿bash shell的命令提示符,要求打印出形式为“当前用户名@当前目录名>”形式的命令提示符;涉及到的函数有:
getlogin()
get_current_dir_name()或getcwd(),注意get_current_dir_name()返回值类型。
●接受、识别和分析命令提示符后输入的命令行,并将其转换为相对的系统调用以调用内
核功能实现任务:
能提取命令字符串;
能识别&、|、<、>四个特殊字符,并调用相应的功能。
(追加功能:实现追加重定向>>的功能)
命令按enter键才能确认;空命令不产生任何操作,重新打印命令提示符。
命令解释器能够处理命令行前后无用的重复空格。
●能实现内部命令:
exit:结束所有子进程并退出命令解释器。
cd:涉及函数chdir();
jobs:作业号按进入作业队列顺序编为1、2、……
history:显示历史命令记录,此处定义最多10条
fg %<作业号>
bg %<作业号>
●实现外部命令
若外部命令或其他可执行文件(比如ls)在文件test_profile中所指定的路径中,必须能找到;
追加功能:(a)能执行诸如“/tmp/aaa”之类的绝对路径,(b)能执行当前路径下的可执行文件。
●作业控制,ctrl+c/ctrl+z;fg/bg/jobs
ctrl+c/ctrl+z将中止/暂停前台进程,但不会对命令解释器产生影响。
注意SIGTSTP、SIGSTOP、SIGCONT、SIGINT
●程序结构和其他注意事项:
见“综合实验说明.ppt”。
●程序框架:
见“程序框架.doc”
●注意点:
这个大程序中,如果某些功能是重复使用的,需要为其编写一个函数模块,以方便反复调用;
注意在头文件中定义一些全局变量,来方便函数间的数据共享。
同组同学可以在制定了程序的总体框架后,按功能模块分工。
㈡实验仪器、设备或软件
Fedora Core 7系统或Ubuntu linux系统环境、Vmware虚拟机、gcc编译器。
㈢实验结果要求
●要求实验结果如下:
[root@localhost 2010_test]# gcc shellx.c
[root@localhost 2010_test]# ./a.out
jiangjian@/tmp/2010_test> ls
a.out makefile readme.txt ysh2.c ysh3.c ysh.c ysh.h ysh_profile
jiangjian@/tmp/2010_test> cd /tmp
jiangjian@/tmp> cd /tmp/2010_test
jiangjian@/tmp/2010_test> exit
Bye bye!
jiangjian@/tmp/2010_test> cp dead1 /bin
jiangjian@/tmp/2010_test> cp dead2 /bin
jiangjian@/tmp/2010_test>dead2
[2] stopped ./dead2 //此处按了ctrl+z
jiangjian@/tmp/2010_test> jobs
1 2026 stopped ./dead2
jiangjian@/tmp/2010_test> dead1&
1 2026 stopped ./dead2
2 2029 running dead1&
jiangjian@/tmp/2010_test> fg
[1] running dead1&
jiangjian@/tmp/2010_test> bg
jiangjian@/tmp/2010_test> jobs
1 203
2 running ./dead2
2 2035 running dead1&
jiangjian@/tmp/2010_test> ls>aaa
jiangjian@/tmp/2010_test> ls
aaa a.out dead1 dead1.c dead2 makefile readme.txt ysh2.c ysh3.c ysh.c ysh.h ysh_profile
jiangjian@/tmp/2010_test> ls -l|more
\x{603B}\x{7528}\x{91CF} 140
-rw------- 1 root root 88 10\x{6708} 23 01:43 aaa
-rwxr-xr-x 1 root root 22331 10\x{6708} 23 01:35 a.out
-rwxr-xr-x 1 root root 11420 10\x{6708} 23 01:38 dead1
-rw-r--r-- 1 root root 46 10\x{6708} 23 01:38 dead1.c
-rwxr-xr-x 1 root root 11420 10\x{6708} 23 01:38 dead2
-rwxr-xr-x 1 root root 28 10\x{6708} 23 01:34 makefile
jiangjian@/tmp/2010_test> exit
Bye bye!
二、实验过程(试验步骤、关键源程序代码)
1、MyShell程序框架
typedef struct ENV_HISTROY //历史命令节点
{
int start;
int end;
char his_cmd[HISNUM][100];
}ENV_HISTORY;
typedef struct NODE //存储作业信息的结构体
{
pid_t pid;
char cmd[100];
char state[10];
struct NODE *next;
} NODE;
char *envpath[10],buf[BUFSIZE],*input=NULL,*input_temp;//用户输入的命令字
符
int sig_flag=0,flag=0,N;
pid_t cur_pid=9999;
ENV_HISTORY envhis;
NODE *head,*end;
char *username,*path,*CMD,*arg[20];//用户输入参数
void add_history(char *);
/****************************初始化环境变量*****************************/ void init_environ()
/****************************读取一行命令*****************************/
int getline(char* arg[])
/****************************改变工作路径*****************************/
void cd_cmd(char *route)
/****************************打印提示符*****************************/
void printCMD()
/****************************添加history命令****************************/
void add_history(char *inputcmd) //环形队列
/****************************显示history命令 *****************************/
void dis_history()
/*********************查找命令的函数************************/
int is_founded(char *arg)
/****************************执行外部命令***************************/
void outfun(int i,int j)
/**************清空内存******************************/
void clear()
/*********************增加作业节点****************************/
void add_node(char *input_cmd,const char * state)
/*********************删除节点*************************/
/*********************显示jobs*************************/
void jobs_cmd()
/*************为了防止僵死进程,父进程在创建新进程前执行的函数*************/
void wait_child(int SIGNO)
/*****************线程函数,监听是否有作业要加入job队列********************/
void* thread_add(void* m)
/*********************输出重定向(包括追加)*************************/
void redirect(int j,int i)
/*********************输入重定向*************************/
void redirect1(int j) // 输入重定向
/*********************管道函数*************************/
/*********************fg/bg命令*************************/
void fg(int i)
void bg(int i)
/*********************检测用户输入的是何种命令*************************/
int test(int i)
void mypipe(int j)
/*********************后台执行作业函数*************************/
void background()
void fg(int i) //将后台命令调至前台行 jobs 查看有多少后台运行的命令void bg(int i) //放到后台运行
int test(int i)//检测是何种命令
/*********************主函数*************************/
main()
{
signal(SIGCHLD,wait_child);
setbuf(stdin,NULL); // 把缓冲区与流相联,切断
init_environ();
int i,j,k;
for(i=0;i<20;i++)
arg[i]=(char*)malloc(20);
CMD=(char *)malloc(100);
username=(char *)malloc(20);
path=(char *)malloc(50);
input=(char *)malloc(100);
input_temp=(char *)malloc(100);
pthread_t t; //POSIX线程为了使用里面的函数编译是需要使用选项 - lpthread 来链接线程库pthread_create(&t,NULL,thread_add,NULL);
while(1)
{
signal(SIGTSTP,ctr_z);
signal(SIGINT,ctr_c);
printCMD();
j=getline(arg);
N=j;
if(j>0&&test(j))
{
for(i=0;i<20;i++)
arg[i]=(char*)malloc(20);
continue;
}
if(j==0)
{
clear();
continue;
}
if(!strcmp(arg[0],"cd"))
{
cd_cmd(arg[1]);
clear();
continue;
}
if(!strcmp(arg[0],"history")) {
dis_history();
clear();
continue;
}
if(!strcmp(arg[0],"jobs"))
{
jobs_cmd();
clear();
continue;
}
if(j>2&&!strcmp(arg[j-2],">")) {
redirect(j,1);
clear();
continue;
}
if(j>2&&!strcmp(arg[j-2],">>")) {
redirect(j,2);
clear();
continue;
}
if(j>2&&!strcmp(arg[j-2],"<")) {
redirect1(j);
clear();
continue;
}
if((k=is_founded(arg[0]))>=0)
{
outfun(k,j);
clear();
continue;
}
if(!strcmp(arg[0],"exit"))
{
printf("Thank you for using!\n");
_exit(1);
}
sleep(0.5);
}
pthread_join(t,NULL);//等待子进程的终止
}
2、MyShell程序设计步骤简要说明、代码
1.程序开始,我首先做的是模拟shell命令解释器,在屏幕上打印如:[root@localhost root]#
的格式的命令提示符,本程序打印的是:[root@xiao/root/0094011]>样式。
实现这个功能,用到函数(以下一些函数不是整个函数的代码,只显示关键字段)
void printCMD()
{
path=(char*)get_current_dir_name(); //强制转换为char* 也可以使用
getcwd()
username=getlogin();
CMD[0]='[';
strcat(CMD,username);
strcat(CMD,"@xiao");
strcat(CMD,path);
strcat(CMD,"]>");
printf("%s ",CMD);
fflush(stdout);
memset(CMD,0,100);
memset(username,0,20);
memset(path,0,50);
}
在主函数中,利用while(1){}循环,睡眠0.5秒打印提示符
2.初始化环境:要求利用老师给的test_profile文件,读取里面的环境变量路径,每个路径以:结尾,设计函如下:
void init_environ()
{
char temp[20],ch;
int i=0,j=0;
FILE *fp;/*打开保存查找路径的test_profile文件*/
if((fp=fopen("/root/test_profile","r"))==NULL)
{
printf("cannot open the file");
exit(0);
}
fseek(fp,5,SEEK_CUR);
while((ch=fgetc(fp))!=EOF)
{
if(ch==':') //PATH=/bin:/usr/local/bin:/usr/bin
{
temp[j]='\0';
envpath[i]=(char *)malloc(20);
strcpy(envpath[i],temp);//envpath[]是Shell查找可执行文
件的顺序
i++;
j=0;
}
else
temp[j++]=ch;
}
envhis.start=0;/*初始化history循环数组*/
envhis.end=0;
head=(NODE *)malloc(sizeof(NODE));/*初始化jobs链表指针*/
head->next=NULL;
}
3有了以上工作,现在的任务是能够解析用户输入的命令行数据,把各个参数保存起来(本例保存在arg数组中),程序如下:
int getline(char* arg[])
{
char ch;
char temp[20];
int i=0,j=0;
ch=getchar();
if(ch=='\n')
return 0;
while(ch!='\n')
{
if(ch!=' ')
temp[j++]=ch;
else
{
temp[j]='\0';//数组尾部加上结束标志
if(strlen(temp)>0)
{
strcpy(arg[i],temp);
strcat(input,temp);
i++;
j=0;
}
}
ch=getchar();
}
temp[j]='\0';
if(strlen(temp)>0)
{
strcpy(arg[i],temp);
strcat(input,temp);
i++;
j=0;
}
add_history(input); //加入将输入命令加入历史命令
strcpy(input_temp,input);
memset(input,0,100);
return i;
}
程序返回值是:用户输入参数的个数
4 现在成功获取用户命令,我接下工作是显示历史记录(前提是写好了增加历史记录)
这两个比较简单,用到函数add_history(char *)和dis_history();
void add_history(char *inputcmd) //环形队列
{
envhis.end=(envhis.end+1)%HISNUM;/*end前移一位*/
if (envhis.end==envhis.start)
{/*end和start指向同一数组*/
envhis.start=(envhis.start+1)%HISNUM;/*start前移一位*/ }
// printf("%s\n",inputcmd);
strcpy(envhis.his_cmd[envhis.end],inputcmd);/*将命令复制到end指向的数组中*/
}
void dis_history()
{
int i=envhis.start,j=envhis.end;
while(i!=j)
{
i=(i+1)%HISNUM;/*start前移一位*/
printf("%s\n",envhis.his_cmd[i]);
}
}
5 执行外部命令思想:当用户输入命令后,根据arg[0]的内容,在环境变量路径下面,遍历文件,查找是否存在名为arg[0]的可执行文件名。
如果存在,父进程创建一个子进程,子进程利用execl()函数族调用外部命令。
其中遍历文件夹函数是网上找的:
int is_founded(char *arg)
{
DIR* pDir=NULL;
int i;
struct dirent* ent = NULL;
if(arg[0]=='.'&&arg[1]=='/')
return 0;
for(i=0;i<6;i++)
{
pDir = opendir(envpath[i]);
if (NULL==pDir)
continue;
while(NULL!=(ent=readdir(pDir)))
{
if(!strcmp(arg,ent->d_name))
return i;
}
closedir(pDir);
pDir=NULL;
ent=NULL;
}
return -1;
}
执行外部命令函数:
void outfun(int i,int j)
{
int status,exit_status;
pid_t child;
char *cmd=(char *)malloc(50);
strcat(cmd,envpath[i]);
strcat(cmd,"/");
strcat(cmd,arg[0]);
child=fork();
if(child==-1)
{
printf("执行出错……\n");
return;
}
else if(child==0)
{
if(i==0)
{
char *path=(char *)malloc(30);
path=(char *)get_current_dir_name();
char * temp=arg[0]+2;
char * cc=(char *)malloc(50);
strcat(cc,path);
strcat(cc,"/");
strcat(cc,temp);
execl(cc,temp,NULL);
}
if(j==1)
execl(cmd,arg[0],NULL);
if(j==2)
execl(cmd,arg[0],arg[1],NULL);
if(j==3)
execl(cmd,arg[0],arg[1],arg[2],NULL);
if(j==4)
execl(cmd,arg[0],arg[1],arg[2],arg[3],NULL);
if(j==5)
execl(cmd,arg[0],arg[1],arg[2],arg[3],arg[4],NULL);
if(j==6)
execl(cmd,arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],NULL);
exit(0);
}
else
{
cur_pid=child;
waitpid(cur_pid,0,WUNTRACED);
}
}
7接下来现实ctr+c和ctr+z,我的思想是:ctr+z ,在主函数中为ctr+z设计捕捉信号函数,当主函数捕获到ctr+z发出的信号时,主函数转向去执行事先写好的信号处理函数ctr_z(),在该函数中,主进程发送kill(cur_pid,SIGSTOP);信号(cur_pid就是当前进程号,要它暂停的那个)。
Ctr+c类似,就是在ctr_c函数中,主进程向子进程发送SIGKILL信号:kill(cur_pid,SIGKILL); 具体函数如下:
void ctr_z()
{
if(kill(cur_pid,0)==0)
{
flag=1;
printf("1 %d stopped %s\n",cur_pid,arg[0]);
kill(cur_pid,SIGSTOP);
}
}
void ctr_c()
{
pid_t child;
child=fork();
if(child==-1)
{
printf("执行出错……\n");
return;
}
else if(child==0)
{
exit(0);
}
else
{
if(kill(cur_pid,0)==0)
{
printf("1 %d killed %s\n",cur_pid,arg[0]);
kill(cur_pid,SIGKILL);
}
waitpid(child,0,0);
}
}
8 实现输出重定向思想:利用dup2(int,int)函数,来交换文件描述符,来达到重定向输出,具体就是:对于重定向,先以f=open(fp,O_WRONL Y|O_CREAT,S_IRUSR|S_IWUSR)方式打开一个目的文件,再用dup2(f,1);把标准输出文件描述符1转到刚才打开文件的文件描述符,然后关闭close(f)。
对于追加重定向,只要把打开文件的方式改为:
f=open(fp,O_WRONL Y|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR),即以追加的方式打开。
主要代码如下:
void redirect(int j,int i)
{
pid_t child;
if(child==-1)
{
printf("执行出错……\n");
return;
}
else if(child==0)
{
int filedes;
if(i==1) // >输出重定向
{
if((filedes=open(arg[j-1],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))<0) fprintf(stderr,"%s ","open file error");
}
if(i==2) // >>追加方式输出重定向
{
if((filedes=open(arg[j-1],O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR))<0) fprintf(stderr,"%s ","open file error");
}
dup2(filedes,1);//输出重定向管道重定向
close(filedes);
if(j==3)
execlp(arg[0],arg[0],(char *)0);
if(j==4)
execlp(arg[0],arg[0],arg[1],(char *)0);
if(j==5)
execlp(arg[0],arg[0],arg[1],arg[2],(char *)0);
if(j==6)
execlp(arg[0],arg[0],arg[1],arg[2],arg[3],(char *)0);
exit(0);
}
else
{
waitpid(child,0,0);
}
}
9 对于输入重定向,和输出重定向类似,利用dup来转置文件描述符,以只读方式打开要读取的文件,然后关闭标准输入文件描述符0,再用dup(int)将刚才打开的文件描述符转到标准输入,这样,标准输入就从刚才打开的文件中读取。
关键代码如下:
void redirect1(int j) // 输入重定向
{
pid_t child;
if(child==-1)
{
printf("执行出错……\n");
return;
}
else if(child==0)
{
int filedes;
if((filedes=open(arg[j-1],O_RDONLY))<0)
fprintf(stderr,"%s ","open file error");
close(0);
dup(filedes);
close(filedes);
if(j==3)
execlp(arg[0],arg[0],(char *)0);
if(j==4)
execlp(arg[0],arg[0],arg[1],(char *)0);
if(j==5)
execlp(arg[0],arg[0],arg[1],arg[2],(char *)0);
exit(0);
}
else
{
waitpid(child,0,0);
}
}
10 对于管道,原理和重定向类似,也是利用dup2,先建立一个管道,然后创建一个子进程1,关闭子进程1的读端口fp[0],把标准输出端口1转置到它的写端口fp[1]。
在父进程中先关闭自己的写端口fp[1],再创建一个子进程2,然后把标准输入端口0转置到子进程2的读端口fp[0]。
这样就能实现子进程进程1的输出作为子进程2的输入。
关键代码如下:
void mypipe(int j)
{
arg[j]=NULL;
int s1=is_founded(arg[0]),wt;
int s2=is_founded(arg[j+1]);
char *cmd1=(char *)malloc(50);
strcat(cmd1,envpath[s1]);
strcat(cmd1,"/");
strcat(cmd1,arg[0]);
char *cmd2=(char *)malloc(50);
strcat(cmd2,envpath[s2]);
strcat(cmd2,"/");
strcat(cmd2,arg[j+1]);
pid_t pid;
int i, fp[2];
pipe(fp);
pid=fork();
if(pid==0)
{
close(fp[0]); //关闭读端口
dup2(fp[1], 1);
execv(cmd1, arg);
exit(0);
}
else if(pid > 0)
{
close(fp[1]);
pid = fork();
if(pid==0)
{
dup2(fp[0], 0);
execl(cmd2, arg[j+1], NULL);
exit(0);
}
else if(pid > 0)
{
waitpid(pid,&wt,WUNTRACED);
}
waitpid(pid,&wt,WUNTRACED);
}
}
11 实现后台执行命令的思想:和前台执行命令不同之处在于,前台执行时,父进程要用waitpid 等待子进程的退出,而后台执行,主进程无需等待子进程的退出。
13 将后台进程调到前台执行函数void fg(int i):其中i表示作业号。
思想就是主进程向对应的后台作业(子进程)发送SIGCONT信号,让子进程继续执行,对于在后台已经运行的作业对SIGCONT信号不响应。
调到前台的同时,主进程要等待子进程的退出。
关键语句:
void fg(int i) //将后台命令调至前台行 jobs 查看有多少后台运行的命令
{
if(i==1)
{
strcpy(input_temp,head->next->cmd);
printf("%s\n",head->next->cmd);
kill(head->next->pid,SIGCONT);//调前台
cur_pid=head->next->pid;
flag=2;
}
else
{
NODE *r=head;
int j=arg[1][0]-'0';
while(j)
{
r=r->next;
j--;
}
strcpy(input_temp,r->cmd);
printf("%s\n",r->cmd);
kill(r->pid,SIGCONT);
cur_pid=r->pid;
flag=2;
}
}
14将前台进程调到后台执行函数void bg(int i):与void fg(int i)类似,不同之处在于主进程不等待子进程的退出。
发送信号kill(r->pid,SIGCONT) 即可。
void bg(int i) //放到后台运行
{
if(i==1)
{
strcpy(input_temp,head->next->cmd);
printf("%s&\n",head->next->cmd);
kill(head->next->pid,SIGCONT);
cur_pid=head->next->pid;
flag=2;
return;
}
else
{
NODE *r=head;
int j=arg[1][0]-'0';
while(j)
{
r=r->next;
j--;
}
strcpy(input_temp,r->cmd);
printf("%s&\n",r->cmd);
kill(r->pid,SIGCONT);
cur_pid=r->pid;
flag=2;
return;
}
}
15 增加jobs队列节点线程函数thread_add(void*)。
思想:要增加节点有如下情况1、后台执行一个命令;2、用户按了ctr+z,使当前作业暂停,利用thread_add()函数去修改该作业的状态。
关键代码如下:
void* thread_add(void* m)
{
int k;
while(1)
{
if(flag==1)
{
add_node(input_temp,"Stopped");
flag=0;
}
if(flag==2)
{
add_node(input_temp,"Running");
flag=0;
}
sleep(1);
}
}
16 最后一个信号函数是为了解决僵死进程和删除jobs队列中已经退出的作业(子进程)。
该函数是利用了Linux下面的一条性质:当每个子进程状态发生改变的时候,都会想父进程发送SIGCILD信号,那么父进程捕捉该信号,在该信号函数中,父进程利用waitpid()函数等待子进程,一旦子进程死亡,父进程就可以为其“收尸”。
还一个功能是,当子进程死亡的时候,该信号函数还做一下工作:在jobs队列里,找到相应的作业,删除他的节点。
函数如下:void wait_child(int SIGNO)
{
pid_t pid;
NODE *r,*s;
int stat;
while ((pid=waitpid(-1,&stat,WNOHANG))>0)
{
s=head;
r=head->next;
while(r!=NULL)
{
if(r->pid==pid)
{
s->next=r->next;
break;
}
s=r;
r=r->next;
}
}
return;
}
三、实验结果
1. MyShell程序运行结果:
[root@localhost root]# cd 1194011
[root@localhost 1194011]# gcc MyShell.c -lpthread -o MyShell
[root@localhost 1194011]# ./MyShell
[root@xiao/root/0082596]> ls
dead MyShell MyShell.c sleep
[root@ xiao /root/1194011]> cd /tmp
[root@xiao/tmp]> cd /root/1194011
[root@xiao/root/1194011]> exit
Thank you for using!
[root@localhost 1194011]# ./MyShell
[root@xiao/root/0082596]> ./dead
hello
hello
1 1626 stopped ./dead //这里按了ctr+z
[root@xiao/root/1194011]> jobs
1 1626 Stopped ./dead
[root@xiao/root/1194011]> ./sleep
1 1627 stopped ./sleep //这里按了ctr+z
[root@xiao/root/1194011]> jobs
1 1627 Stopped ./sleep
2 1626 Stopped ./dead //现在两个作业处于暂停状态
[root@xiao/root/1194011]> ./sleep & //后台运行sleep
[root@xiao/root/1194011]> jobs
1 1630 Running ./sleep& //sleep处于运行状态
2 1627 Stopped ./sleep
3 1626 Stopped ./dead
[root@xiao/root/1194011]> xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao
//sleep睡眠15秒后,打印15个xiao退出,查看jobs队列,sleep&作业退出了
[root@xiao/root/1194011]> jobs
1 1627 Stopped ./sleep
2 1626 Stopped ./dead
[root@xiao/root/1194011]> exit
Thank you for using!
[root@localhost 1194011]# ./MyShell
[root@xiao/root/0082596]> ./sleep1
1 1738 stopped ./sleep1
[root@xiao/root/1194011]> ./sleep2 &//注意:sleep2不能以换行打印xiao,否则就算是后台运行也会霸占终端,因为换行符会强制刷出输出缓冲区
[root@xiao/root/1194011]> jobs
1 1739 Running ./sleep2&
2 1738 Stopped ./sleep1
[root@xiao/root/1194011]> fg 2
./sleep1
[root@xiao/root/1194011]> bg 2
./sleep1
[root@xiao/root/1194011]> jobs
1 1739 Running ./sleep2&
2 1738 Running ./sleep1
[root@xiao/root/1194011]> xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao
[root@xiao/root/1194011]> xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao xiao
睡眠15秒后,sleep1和sleep2各自打印15个xiao后退出运行
[root@xiao/root/1194011]> jobs //再用jobs查看,无作业
[root@xiao/root/1194011]> ./dead
hello
hello
hello
1 2087 killed ./dead //这里按了ctr+C,将dead进程杀死
[root@xiao/root/1194011]> jobs
[root@xiao/root/1194011]>
[root@xiao/root/1194011]> ls >xiao //输出重定向
[root@xiao/root/1194011]> ls
dead xiao MyShell MyShell.c sleep1 sleep2
[root@xiao/root/1194011]> cat xiao
dead
xiao
MyShell
MyShell.c
sleep1
sleep2
[root@xiao/root/1194011]> ls >> xiao 追加重定向
[root@xiao/root/1194011]> cat xiao
dead
xiao
MyShell
MyShell.c
sleep1
sleep2
dead
xiao
MyShell
MyShell.c
sleep1
sleep2
[root@xiao/root/1194011]> ps > x1iao
[root@xiao/root/1194011]> cat < xiao1 //输入重定向
PID TTY TIME CMD
1677 pts/0 00:00:00 bash
1703 pts/0 00:00:00 MyShell
1820 pts/0 00:00:00 ps
[root@xiao/root/1194011]> cat MyShell.c | more //管道
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include<string.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
//#include "test_shell.h"
………………
………………-这里的内容是分页显示的
--mor--
[root@Xiao/root/1194011]> history //显示命令历史记录cat<xiao
ps>xiao1
cat<xiao1
clear
catMyShell.c|more
clear
catMyShell.c|more
clear
history
[root@xiao/root/1194011]> exit
Thank you for using!
[root@localhost 0094011]#
四、总结
实验所完成了的功能、性能;完成的经验、体会、存在的问题等!
本次实验实现了初始化环境,包括默认搜索路径、history循环数组、作业列表;打印命令提示符;接受、识别和分析命令提示符后输入的命令行,并创建子程序,在子程序中将其转换为相对的系统调用以调用内核功能实现任务;能实现内部命令,包括:exit、history、cd、jobs。
能够执行外部命令,并到搜索路径中去查找外部命令对应的执行文件;命令均可带参数;支持&、|、<、>、>>四个特殊字符及其所代表的功能;支持ctrl+z,ctr+c。
内部命令bg、fg。
基本完成实验要求的内容。
未能完成的:执行绝对路径下的可执行文件,不能实现用Tab补全命令,以及用↑↓切换历史命令。
XXX的个人总结:
通过这次实验设计,让我熟悉了很多上课时没弄懂的知识点,我通过上网百度,反复看老师上课讲的PPT,还有就是查看man手册解决了一系列的问题。
发现在很大程度上培养独立思考的能力和解决问题的能力。
编写过程中,遇到的段错误,使我对指针和动态申请内存有了进一步了解。
这类段错误源于内存空间越界或是释放了不属于自己的内存空间,这也是一些细节性的问题。
从整个程序来看,Linux的核心技术就是管道,线程和进程这三大技术了。
XXX的个人总结:
本次作业,我个人认为是对我学习的一个考察,也是对我学习的一个很好的提高,就感觉对以前了解不够的知识重新有了一个新的认识,比如这次用的比较多的进程,线程的知识,就是靠这段时间的复习才能熟练掌握。
然后就是感觉真的是要有压力才有动力,因为这次作业,然后复习着上课的那些知识点,看着那些课件,去网上找资料。
通过这次作业我就是觉得我们还是应该适当的给自己施压,这样有利于我们的“成长”。
XXX的个人总结:
通过本次模拟命令解释器Myshell的编写,我们收获很多,虽然在实验过程中遇到了很多问题,常常为了解决一个问题而停滞不前,但经过不断的思考、尝试问题都逐一的被解决了。
我感觉,书本上学到的知识,老师上课讲的知识还是很有限的,想要把LINUX学好,真正理解并学好LINUX管道,线程,进程和信号等技术,还需要自己多去查资料,多了解这些方面的知识。
通过本次试验,我还感觉到:学习LINUX,不能只通过看书本上的知识,还要多上机编写程序,才会有深刻的体会,同时自己对应LINUX各种机制才会有更好的理解,才能更好的运用编程,同时自己的编程能力也会有较大的提高。
五、分工
●XXX
●初始化环境,主要是默认搜索路径、history循环数组、作业列表
●打印命令提示符;
●接受、识别和分析命令提示符后输入的命令行,并创建子程序,在子程序中将其转换为
相对的系统调用以调用内核功能实现任务;
●XXX
●能实现内部命令,包括:exit(退出shell)、history、cd、jobs、bg、fg。
●能够执行外部命令,并到搜索路径中去查找外部命令对应的执行文件;命令均可带参数;
●XX
●支持&、|、<、>、>>个特殊字符及其所代表的功能;
●支持前、后台作业控制,包括fg/bg/jobs以及ctrl+c/ctrl+z(挂起、中止和继续运行)。
●并且负责整体思路设计,以及各个部分的整合。