本节主要介绍一个进程控制的实例,功能就是在前台或者后台接收命令并执行命令,还能处理由若干个命令组成的命令行,该程序命名为samllsh。
基本逻辑就是
while(EOF not typed) { 从用户终端取得命令行 执行命令 }
setp1:取得命令行内容,用uerin函数实现,处理步骤首先显示提示符,提示符的具体内容由用户通过参数传递给函数,然后每次从键盘读取一个字符,存入inpbuf中,结束时userin返回字符个数或者EOF(文件结尾),换行符也要存入inpbuf
代码如下:
#include "smallsh.h" /* 程序缓冲区和指针 */ static char inpbuf[MAXBUF],tokbuf[2*MAXBUF], *ptr = inpbuf,*tok = tokbuf; /* userin()函数 */ int userin(chat* p) { int c,count; ptr = inpbuf; tok = tokbuf; /* 显示提示 */ printf("%s ",p); for (count = 0;;) { if ((c=getchar())==EOF) return(EOF); if (count < MAXBUF) inpbuf[count++] = c; if (c ==' ' && count < MAXBUF) { inpbuf[count] = ' '; return(count); } /* 如果行过长重新输入 */ if (c == ' ') { printf("smallsh:input line too long "); count=0; printf("%s ",p); } } }
其中的头文件samllsh.h内容为
#include <stdio.h> #define EOL 1 /* 行结束 */ #define ARG 2 #define AMPERSAND 3 #define SEMICOLON 4 #define MAXARG 512 /* 命令行参数个数的最大值 */ #define MAXBUF 512 /* 输入行的最大长度 */ #define FOREGROUND 0 #define BACKGROUND 1
step2:从userin构造的命令行缓冲区中分析出命令名和参数,采用gettok函数,其中的tptr是一个字符型指针,调用后该指针指向实际的解析出的内容,调用方法是toktype = gettok(&tptr)
int gettok(char* output) { int type; outptr = tok; /* 首先去除空白字符 */ for (;*ptr==''||*ptr==' ';ptr++) { *tok++ = *ptr; } switch(*ptr++) { case ' ': type=EOL; break; case '&': type=AMPERSAND; break; case ';': type=SEMICOLON; break; default: type=ARG; while (inarg(*ptr)) { *tok++ = *ptr++; } } *tok++ = ' '; return (type); }
step3:采用函数inarg用于确定一个字符是否可以作为参数的组成符,检查字符是否是smallsh的特殊字符,代码如下
static char special[]={‘‘,’ ’,’*’,’;’,’ ’,’ ’}; int inarg(char c) { char *wrk; for (wrk = special;*wrk != ’ ’;wrk++) { if (c == *wrk) return(0); } return(1); }
step4:程序procline使用函数gettok()分析命令行,处理过程中构造一张参数表,当遇到换行符或者分号时,就调用runcommand来执行被分析的命令行,假设已经用userin读入了一个输入行
#include "smallsh.h" void procline() { char * arg[MAXARG+1]; int toktype; int narg; int type; for(narg = 0;;) { switch(toktype = gettok(&arg[narg])) { case ARG: if (narg<MAXARG) narg++; break: case EOL: case SEMICOLON: case AMPERSAND: type = (toktype==AMPERSAND)?BACKGROUND:FOREGROUND; if (narg != 0) { arg[narg] = NULL; runcommand(arg,type); } if (toktype==EOL) return; narg=0; break; } } }
step5:函数runcommand,实现启动命令进程,入参为一个整型参数where,如果where被设置为BACKGROUND,那么将忽略wait()的调用,并且runcommand只显示进程标识符就返回。wait()返回的是第一个结束的子进程的进程标识符,而不是最后一个被启动的子进程的进程标识符。
execvp()表明按当前环境变量Path中的目录来搜索命令中表明的程序文件
#include "smallsh.h" int runcommand(char** cline,int where) { int pid,exitstat,ret; if((pid = fork()) < 0) { perror("fork fail"); return(-1); } if (!pid) { /* 子进程代码 */ execvp(*cline,cline); perror(*cline); exit(127); } /* 父进程代码 */ /* 后台进程代码 */ if (where==BACKGROUND) { printf("[process id %d] ",pid); return(0); } /* 前台进程代码 */ while ((ret = wait(&exitstat))!= pid && ret != -1) ; return (ret==-1?-1:exitstat); }
step6:最后就是main函数了
#include "smallsh.h" char *prompt = "command>"; void main() { while (userin(prompt)!=EOF) { procline(); } }