linux命令解释器的设计本科毕业论文(编辑修改稿)内容摘要:

unning, done, stopped running 的含义: runningProgs 0 stopped 的含义: runninoProgs= stoppedProgs done 的含义: runningProgs=0 管道 管道是 Linux 支持的最初 Unix IPC 形式之一,具有这一些特点:管道是半双工的,管道当中的内容是从一个方向向另一个方向进行;所以当两方开始通信时,要把两个管道创建起来;然而它仅仅可以用于父进程和子进程或者两个兄弟进程之间(具有亲缘关系的进程);独自组成一个相互没有关联的文件系统:其实管道可以看做是一个文件,但是 与其它普通文件不同的是它并不属于一种文件系统,而是自己有自己的特点,独自构成一种文件系统,而且只是在内存中。 数据的读出与写入:一个进程只能向一个管道中的一头写入数据,管道另一头的进程会读出这个数据内容,这些写入的数据每次都会放在管道缓冲区的最后,但是读数据时必须要从从缓冲区的头部开始读数据。 在输入命令时要用“ |”将两个命令分开,系统就会自动从左边的命令的输出当做右边的命令的输入。 当我们一直使用管道命令时,第二个命令的输出同样会作为第三个命令的输入,以此类推,管道方便了我们一直输入相同的命令的,可以直接调用 相应的文件来查看命令。 管道相关函数简介 编写程序时,管道的两端,用 fd[0]以及 fd[1]来表示,管道的两端只能有固定的作用,不能混用。 就是说管道的一端只能用于读的作用,用 fd[0]表示,将其称为读端;同样,管道的另一端则只能用于写的作用,由 fd[1]来表示,将其称为写端。 但是如果当我们从管道的 fd[1]进行读取数据时,或者向管道 fd[0]用于输入数据的作用,那么就会发生错误,管道是严格的按照相应的规则进行的。 一般文件的大多数的 I/O函数都可以当做用于管道的函数,如 close、 read、 write等等头文 件: include函数原型: int pipe(int fd[2])函数传入值fd[2]:管道文件的描述符,然后就可以判断返回值:成功返回 0,失败返回 1。 重定向 重定向就是当执行 shell 命令行时一般会打开三个标准文件,即标准输入文件 (stdin),一般对应终端的输入键盘;标准输出文件 (stdout)和标准错误输出文件 (stderr),这两个文件都对应着终端的输出屏幕。 进程将从标准输入文件中读取输入数据,将正确的数据输出到标准输出文件,将不正确的信息输出到标准 错误文件中。 一个程序命令后可能还跟有元字符“ ”或“ ”,它们是重定向符,而在重定向符号后面还跟着一个文件名。 在“ ”的情况下,程序的输入被重定向到一个指定的文件中。 在“ ”的情况下,程序的输出被重定向到一个指定的文件中。 如果输入文件不存在,则认为是出现了错误。 4 Shell 的实现 在这次设计中,首先是建立了循环数组和链表数组,都用于 history 命令,用数组来保存以前曾经输入的命令字符。 对链表的操作必须首先把作业用链表存储起来。 首 先定义链表的节点: typedef struct NODE{ pid_t pid。 //进程号 char cmd[100]。 //命令名 char state[10]。 //作业状态 struct NODE *link。 //下一节点指针 } NODE。 NODE *head.*end 定义 head 指针来指向链表的表头,用 end指针来指向链表的表尾。 程序结构 这四种不同类型的命令的执行是不一样的,但是每种命令的程序 都有着相同的操作步骤:初始化的环境,显示命令提示符,获取并判定用户输入的命令,解析命令,最后就是寻找命令 这几个步骤,如下图所示: 初始化环境 在刚开始写程序的时候,需要对几个环境变量进行初始化的工作。 比如将你 所需要的查找的路径放入数组 envpath[]中,对 history 和 jobs 的头指针和尾指针等进行相应的初始化。 初始化的工作在程序中主要是由函数 init_environ来进行的。 void init_environ() { int fd,n,i。 char buf[80]。 if((fd = open(myshell_profile,O_RDONLY,660)) == 1){ printf(init environ variable error\n)。 exit(1)。 } while (n = line(fd,buf)){ getenviron(n,buf)。 } = 0。 = 0。 head = end = NULL。 } 在上面的程序中我们看到函数打开一个名字是 myshell_profile的自己编写的 文件,这是一个刚开始的配置文件,用户自己把配置的路径写到这个文件中。 然后调用了另外两个函数 line (fd, buf]和 getenviron(n, buf)。 line(fd,buf)的作用是读取行的信息到 buf 中。 getenviron(n, buf)的主要作用是读取line(fd,buf)读取的命令,然后用冒号分隔开 buf 中的信息,将命令各自放在envpath[]中,等待后面查找命令时在 envpath[]中寻找命令。 这样命令前期初始化工作就已经完成了。 然后初始化 history 命令中链表的头指针和尾指针,将 和 的值置为 0,同样将 jobs 命令中的头指针和尾指针分别指为空, head=end=NULL。 到现在为止,我们所做的程序的前期准备工作已经大概做完了。 然后,就会进行 while 循环,与普通的 shell 命令解释器是一样的,当命令之行结束,或者将这个命令放在后台执行,用户就可以重新输入新的命令行, shell 可以重头开始重复原来的工作。 解析命令 解析指令时,对输入到数组 input 的命令内容完成解析,然后得到该命令和对应的参数。 shell 中的命令分成 4种:重定向命令,普通命 令 (外部命令 ),管道命令和内部命令。 但是由于管道和重定向命令比较复杂,所以对管道和重定向命令需要另外来处理。 for(i = 0,j = 0,k = 0。 i=input_len。 i++){ if(input[i] == 39。 39。 ||input[i] == 39。 39。 ||input[i] == 39。 |39。 ){ if(input[i] == 39。 |39。 ){ pipel(input,input_len)。 add_history(input)。 free(input)。 }else{ redirect(input,input_len)。 add_history(input)。 free(input)。 } is_pr = 1。 break。 } } 该程序中用 for 循环将带有字符“ ”,“ ”和“ |”符号的管道和重定向命令进行另外的完成。 然后定义一个 is_pr,这是管道和重定向的命令标志,置为 1。 然后分别调用 redirect(input, input_len)和 pipel(input, input_len)两个函数来处理这两类命令。 对于普通的命令,当检测到 is_pr 的值是 0的时候,就进行下面的程序,下 面的代码就是主要普通命令和内部命令进行处理。 for(i = 0,j = 0,k = 0。 i=input_len。 i++){ if(input[i] == 39。 39。 ||input[i] == 39。 \039。 ){ if(j == 0) continue。 else{ buf[j++] = 39。 \039。 arg[k] = (char *) malloc(sizeof(char)*j)。 strcpy(arg[k++],buf)。 j = 0。 } }else{ if(input[i] == 39。 amp。 39。 amp。 amp。 input[i+1] == 39。 \039。 ){ is_bg = 1。 continue。 } buf[j++] = input[i]。 上面程序的 for 循环的作用就是对 input 数组中的命令进行分析。 当用户输入空格时,就会区分命令和参数,如:“ ls l”。 这个例子的作用就是将这条命令的 ls 首先存储到 arg[0],而参数 l 则存储在 arg[1]。 但是对于 input 数组的解析是要符合以下的规则的:以空格分段,分别放在 arg[i]中。 当这个 for循环运行过后,数组 argv[0]中的内容 就非常重要了,可以对数组 arg[0]的内容来分辨输入的到底是内部命令还是外部命令。 判断是内部命令的话,就会执行相对应的操作。 查找外部程序 当我们检测到的命令不是内部命令,也不是重定向命令和管道命令,就可以判断是外部命令了。 对于外部命令的处理就是查找该命令的执行文件。 if(is_pr == 0){ arg[k] = (char *)malloc(sizeof(char))。 arg[k] = NULL。 if(is_founded(arg[0]) == 0){ printf(This mand is not founded!\n)。 for(i = 0。 i=k。 i++) free(arg[i])。 continue。 } } is_founded 函数就是用来来判断输入的外部命令的文件是不是已经存在。 函数如下: int is_founded(char *cmd) { int k = 0。 while(envpath[k]!=NULL){ strcpy(buf,envpath[k])。 strcat(buf,cmd)。 if(access(buf,F_OK) == 0) return 1。 k++。 } return 0。 } 当我们对程序初始化的时候,我们已经将命令以及其对应的路径已经存储到数组 envpath[i]中,所以下面的工作就比较好做了,就直接在相对应的路径下查找和判断输入命令到底有没有存在。 如果找到返回 l,没有则返回 0。 当我们判断的时后就用到了 access 系统调用函数。 格式 include int access(const char *pathname,int mode)。 参 数说明 pathndme 是文件名称。 我们要判断 mode 的属性,可以取它们之间的组合或者是直接取值,文件可以读用 R_OK 表示,文件可以写用 W_OK 表示,文件可以执行用 X_OK 表示,文件存在则用 F_OK 表示,当函数的返回值为 0 时,测显示测试成功,否则返回值为1。 当输入的命令被查找到时,就把相应的命令和路径放到数组 buf 中。 然后就直接执行命令。 执行命令 当我们查找到命令文件时,就可以继续执行该命令内容了。 这时,首先要新建一个子进程,在该子进程中执行那个命令。 fork 创建一个新的子进程的时 候,该进程会把其对应的父进程的堆栈和数据都继承下来,以及用户的 ID、环境变量等,还有工作的目录等等。 其实 Linux 会使用一个称作 COW 的技术,该技术是当中间有一个进程如果想要修改所复制的空间时,才会真正的做复制动作。 子进程的数据发生变化时,父进程的数据是不会由于子进程当中的数据变化而改变自身的数据的。 当一个子进程创建完成后,父进程中的 pid号就是其子进程的真正的 ID号,但是创建失败时,就会返回值为 1。 这一段程序如下: If((pid = fork()) == 0) Execv(buf,arg)。 Else If(is_bg== 0) Waitpid(pid,amp。 status,0)。 这几行代码运行后就可以执行外部命令。 这里用到了两个系统凋用函数execv 和 waitpid。 函数 waitpid 格式 : pid_t waitpid(pid_t pid,int *status,int options)。 当一个命令是前台执行的,那么其父进程则必须执行函数 waitpid,而且必须等待子进程,只有当子进程结束后,父进程才可以执行。 后台执行恰恰不需要等待子进程的完成就可以继续执行,就不需要 waitpid。 这就是前后台命 令的差别。 waitpid 所等待的子进程由它的第个参数 pid 决定。 因为 pid 就是他父进程中子进程的 id 号。 只有当其对应的子进程执行结束后,父进程才可以继续运行。 程序写到这,那么一个比较简便的 shell 命令解释器基本写完了,但是它只局限于内部命令和外部命令,为了完善它的功能,还需要增加很多东西和代码。 但是基本的 shell 的思想大致就是这样的。 管道 函数 intpipel 就是实现 shell 管道程序的函。
阅读剩余 0%
本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。