標籤:style blog http color 使用 os strong io
上節中已經對後台作業進行了簡單處理,基本上要實現的功能已經完了,下面回過頭來,對代碼進行一個調整,把寫得不好的地方梳理一下,
給代碼加入適當的注釋,這種習慣其實是比較好了,由於在開發的時候時間都比較緊,都只是想辦法去儘快實現,而肯定會有一些代碼是寫得不太好的,所以有時間的話最好是從頭至尾將整個代碼進行梳理,也許在梳理的過程中會發現許多不足的地方,好了,下面開始:而這個訊號安裝函數是在init.c中實現的:接下來進行shell迴圈:它的實現是在parse.c中:如注釋所示,可以挪至init.c中:接下來,擷取命令:然後解析命令:接下來的這句,是為了測試,在發布時可以注釋掉了:最後執行命令:這個方法裡面的代碼有點亂,下面將其實現抽取到另外一個檔案中,使得該函數要看起來清爽一些:其實現execute.c:
#include "execute.h"#include "def.h"#include "externs.h"#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <linux/limits.h>#include <fcntl.h>void forkexec(int i){ pid_t pid; pid = fork(); if(pid == -1) { /* 建立進程失敗了 */ ERR_EXIT("fork"); } if(pid > 0) { /* 父進程 */ if (backgnd == 1) printf("%d\n", pid); lastpid = pid; } else if(pid == 0) { /* 子進程 */ /* 表示將第一條簡單命令的infd重新導向至/dev/null,其中cmd[i].infd == 0隻有可能是第一條簡單命令 */ /* 當第一條命令試圖從標準輸入擷取資料的時候立既返回EOF */ if(cmd[i].infd == 0 && backgnd == 1){ //屏蔽後台作業,因為沒有實現作業控制 cmd[i].infd = open("/dev/null", O_RDONLY); } /* 將第一個簡單命令進程作為進程組組長 */ if(i == 0){ setpgid(0, 0); } if(cmd[i].infd != 0){ //說明該命令的輸入是指向管道的讀端 close(0); dup(cmd[i].infd); } if(cmd[i].outfd != 1){ //說明該命令的輸出指向的是管道的寫端 close(1); dup(cmd[i].outfd); } /* 關閉3以上的所有檔案描述符 */ /*int i; for(i=3; i<OPEN_MAX; ++i){ close(i); }*/ /*前台作業能夠接收SIGINT,SIGQUIT訊號,這兩個訊號就要恢複成預設操作*/ if(backgnd == 0){//非後台作業 signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); } /* 開始替換進程 */ execvp(cmd[i].args[0], cmd[i].args); /* 如果執行到這句,則證明替換失敗了 */ exit(EXIT_FAILURE); }}int execute_disk_command(void){ /* ls | grep init | wc -w */ if(cmd_count == 0) { return 0; } if(infile[0] != ‘\0‘){ cmd[0].infd = open(infile, O_RDONLY); } if(outfile[0] != ‘\0‘){ if(append)//說明是以追加的方式 cmd[cmd_count-1].outfd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, 0666); else cmd[cmd_count-1].outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666); } /* 因為後台作業不會調用wait等待子進程退出,為避免殭屍進程,可以忽略SIGCHLD訊號 */ if(backgnd == 1){ signal(SIGCHLD, SIG_IGN); }else{ signal(SIGCHLD, SIG_DFL); } int i; /* 管道描述符 */ int fds[2]; int fd; for(i=0; i<cmd_count; ++i){ /* 如果不是最後一條命令,則需要建立管道 */ if(i < cmd_count-1){ pipe(fds); /* 第一條命令的輸出不再是標準輸出,而是管道的寫端 */ cmd[i].outfd = fds[1]; /* 第二條命令的輸入不再是標準輸入,而是管道的讀端 */ cmd[i+1].infd = fds[0]; } /* 建立一個進程,並且替換成系統命令 */ forkexec(i); if((fd = cmd[i].infd) != 0) close(fd); if((fd = cmd[i].outfd) != 1) close(fd); } if(backgnd == 0){//如果是非後台作業 while(wait(NULL) != lastpid) ; }}
將其forkexec函數也抽取到execute.c檔案中,下面來進行編譯一下:
另外在編譯成,需要修改一下Makefile:
修改這麼多後下面編譯一下:好了,對於上面的實現都解釋的外部命令,那對於系統的內部命令還需要相容,下面主要是來實現內部命令的解析:首先要判斷是否是內部命令,如果是,則執行內部命令:另外需要將builtin.h包含在parse.c檔案中:下面來編譯一下:說明忘了將builtin.o檔案加入到Makefile中了,修改並再編譯:一大堆錯,不用著急,一個個解決,首先builtin.c檔案中也需要用到check函數,而之前是定義在parse.c中,這時應該將其定義在parse.h中,讓builtin.c來包含它既可,另外還需包含一些系統標頭檔:在builtin.c中去包含parse.h檔案:下面再來make並執行:思考一個問題:系統有大量的內部命令,那是不是每解析一個內部命令,我們都要在builtin.c中加一個判斷語句,這樣會造成builtin函數會越來越龐大,所以這種實現方式還是不太靈活,下面改用數組來避免這種情況的發生:最後builtin.c的代碼如下:
#include "builtin.h"#include "parse.h"#include "externs.h"#include <stdlib.h>#include <stdio.h>typedef void (*CMD_HANDLER)(void);typedef struct builtin_cmd{ char *name; CMD_HANDLER handler;} BUILTIN_CMD;void do_exit(void);void do_cd(void);void do_type(void);BUILTIN_CMD builtins[] = { {"exit", do_exit}, {"cd", do_cd}, {"type", do_type}, {NULL, NULL}};/* * 內部命令解析 * 返回1表示為內部命令,0表示不是內部命令 */int builtin(void){ /* if (check("exit")) do_exit(); else if (check("cd")) do_cd(); else return 0; return 1; */ int i = 0; int found = 0; while (builtins[i].name != NULL) { if (check(builtins[i].name)) { builtins[i].handler(); found = 1; break; } i++; } return found;}void do_exit(void){ printf("exit\n"); exit(EXIT_SUCCESS);}void do_cd(void){ printf("do_cd ... \n");}void do_type(void){ printf("do_type ... \n");}
編譯運行:
好了,關於內部命令的具體實現這裡就不多說了,主要是還是實現其原理,這樣自己的一個小型的shell程式就已經完成了,跟系統的shell程式還相差很多,但是通過這個程式足以可以將之前學的知識給串接起來, 達到一個很好的練習的目的,關於小型shell程式最終就實現到這,東西還是比較多,需好好消化。【說明】:由於linux系統命令太多太多,下面只是簡單實現幾個,做一個範例,重點是知道其實現原理。 下面列一下該程式中使用到的各個檔案的作用:main.c----主調程式def:h----定義常量,結構體externs.h----定義extern的變數init.h/init.c----做一些初始化操作parse.h/parse.c----做命令的解析execute.h/execute.c----做外部命令的執行builtin.h/builtin.c----做內部命令的執行【只實現其原理】