編寫自己的shell
進(jìn)程程序替換
替換原理
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)
以執(zhí)行另一個(gè)程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)
例程開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。
替換函數(shù)
其實(shí)有幾種以exec開頭的函數(shù),統(tǒng)稱exec函數(shù):
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
解釋
exec是函數(shù)替換的開頭,后面跟的都是多加的功能:
l:list的簡寫,表示參數(shù)采用列表。
p:path的簡寫,就是自動(dòng)搜索并添加環(huán)境變量??梢允褂铆h(huán)境變量PATH,無需寫全路徑。
v:vector的簡寫,是可以用參數(shù)數(shù)組。
e:environment的簡寫,就是環(huán)境變量。就是帶e都要自己組裝環(huán)境變量,而且是數(shù)組形式傳入。
這些函數(shù)如果調(diào)用成功則加載新的程序從啟動(dòng)代碼開始執(zhí)行,不再返回。
如果調(diào)用出錯(cuò)則返回-1
所以exec函數(shù)只有出錯(cuò)的返回值而沒有成功的返回值。
可變參數(shù)
我們剛剛可以看到int execl(const char *path, const char *arg, …);
比如:
int func(int, ... ) {
.
.
.
}
int main() {
func(2, 2, 3);
func(3, 2, 3, 4);
}
函數(shù) func() 最后一個(gè)參數(shù)寫成省略號,即三個(gè)點(diǎn)號(…),省略號之前的那個(gè)參數(shù)是 int,代表了要傳遞的可變參數(shù)的總數(shù)。為了使用這個(gè)功能,您需要使用 stdarg.h 頭文件,該文件提供了實(shí)現(xiàn)可變參數(shù)功能的函數(shù)和宏。具體步驟如下:
定義一個(gè)函數(shù),最后一個(gè)參數(shù)為省略號,省略號前面可以設(shè)置自定義參數(shù)。
在函數(shù)定義中創(chuàng)建一個(gè) va_list 類型變量,該類型是在 stdarg.h 頭文件中定義的。
使用 int 參數(shù)和 va_start() 宏來初始化 va_list 變量為一個(gè)參數(shù)列表。宏 va_start() 是在 stdarg.h 頭文件中定義的。
使用 va_arg() 宏和 va_list 變量來訪問參數(shù)列表中的每個(gè)項(xiàng)。
使用宏 va_end() 來清理賦予 va_list 變量的內(nèi)存。
也就是說可變參數(shù)是放在傳入?yún)?shù)最后,放在中間必須在輸入結(jié)束之后再輸入一個(gè)NULL,而且可變參數(shù)和前面放的參數(shù)類型一致。
exec調(diào)用舉例:
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 帶p的,可以使用環(huán)境變量PATH,無需寫全路徑
execlp("ps", "ps", "-ef", NULL);
// 帶e的,需要自己組裝環(huán)境變量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 帶p的,可以使用環(huán)境變量PATH,無需寫全路徑
execvp("ps", argv);
// 帶e的,需要自己組裝環(huán)境變量
execve("/bin/ps", argv, envp);
exit(0);
}
事實(shí)上,只有execve是真正的系統(tǒng)調(diào)用,其它五個(gè)函數(shù)最終都調(diào)用 execve,所以execve在man手冊 第2節(jié),其它函數(shù)在
man手冊第3節(jié)。這些函數(shù)之間的關(guān)系如下圖所示。
開始寫自己的shell
用下圖的時(shí)間軸來表示事件的發(fā)生次序。其中時(shí)間從左向右。shell由標(biāo)識為sh的方塊代表,它隨著時(shí)間的流逝從左
向右移動(dòng)。shell從用戶讀入字符串"ls"。shell建立一個(gè)新的進(jìn)程,然后在那個(gè)進(jìn)程中運(yùn)行l(wèi)s程序并等待那個(gè)進(jìn)程結(jié)
束。
每當(dāng)輸入一個(gè)命令時(shí),bash就會創(chuàng)建一個(gè)子進(jìn)程來實(shí)現(xiàn)的要的命令進(jìn)程,上述就是ls,等待子進(jìn)程退出,主進(jìn)程繼續(xù)等待命令輸入和讀取命令,再創(chuàng)建子進(jìn)程等…
第一步創(chuàng)建一個(gè)界面然后讓他一直死循環(huán)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#define MAX_CMD 1024
char command[MAX_CMD];
int myshell_face()
{
memset(command, 0x00, MAX_CMD);
printf("[Tomshell$]#");
fflush(stdout);
if (scanf("%[^\n]%*c", command) == 0)
{
getchar();
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
while (1) // shell主循環(huán)
{
myshell_face();
}
return 0;
}
當(dāng)然這個(gè)界面是可以輸入命令的,但是你怎么輸入都沒用。
接下來是解析你輸入的命令了。
把剛剛輸入的命令行分析出來,比如遇到空格就會再次push_back命令行數(shù)組,當(dāng)有空格就跳過空格,知道遇到NULL為止。
char **do_parse(char *command)
{
int argc = 0;
static char *argv[32];
char *ptr = command;
while (*ptr != '\0')
{
if (!isspace(*ptr))//如果不是空格就一直讀取命令,直到遇到空格
{
argv[argc++] = ptr;
while ((!isspace(*ptr)) && (*ptr) != '\0')//#include <ctype.h> isspace檢測是否遇到空格
{
ptr++; //如果不是空格就一直讀取命令,直到遇到空格
}
}
else
{
while (isspace(*ptr))//如果命令前幾個(gè)是空格就消除空格
{
//*ptr = '\0';//這句就不用加了
ptr++;
}
}
}
argv[argc] = NULL;
return argv;
}
解析完之后返回的是命令行參數(shù)數(shù)組指針
開始創(chuàng)建子進(jìn)程并且用execvp替換子進(jìn)程。
int do_exec(char *command)//進(jìn)程替換函數(shù)=》用的就是exec
{
char **argv = {NULL};
int pid = fork(); // 一切形式的進(jìn)程都讓子進(jìn)程去辦,子進(jìn)程就是白手套。
if (pid == 0)
{
argv = do_parse(command);
if (argv[0] == NULL)
{
exit(-1);
}
execvp(argv[0], argv); // 進(jìn)程替換函數(shù),可以添加環(huán)境變量p(path),參數(shù)格式是數(shù)組v(vector)
} // 可以把exec當(dāng)作call(goto)函數(shù),exit當(dāng)作return函數(shù)。
else
{
waitpid(pid, NULL, 0);
}
return 0;
}
這樣就可以在子進(jìn)程實(shí)現(xiàn)命令行進(jìn)程了。文章來源:http://www.zghlxwxcb.cn/news/detail-435248.html
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#define MAX_CMD 1024
char command[MAX_CMD];
int myshell_face()
{
memset(command, 0x00, MAX_CMD);
printf("[Tomshell$]#");
fflush(stdout);
if (scanf("%[^\n]%*c", command) == 0)
{
getchar();
return -1;
}
return 0;
}
char **do_parse(char *command)
{
int argc = 0;
static char *argv[32];
char *ptr = command;
while (*ptr != '\0')
{
if (!isspace(*ptr))//如果不是空格就一直讀取命令,直到遇到空格
{
argv[argc++] = ptr;
while ((!isspace(*ptr)) && (*ptr) != '\0')//#include <ctype.h> isspace檢測是否遇到空格
{
ptr++; //如果不是空格就一直讀取命令,直到遇到空格
}
}
else
{
while (isspace(*ptr))//如果命令前幾個(gè)是空格就消除空格
{
//*ptr = '\0';//這句就不用加了
ptr++;
}
}
}
argv[argc] = NULL;
return argv;
}
int do_exec(char *command)//進(jìn)程替換函數(shù)=》用的就是exec
{
char **argv = {NULL};
int pid = fork(); // 一切形式的進(jìn)程都讓子進(jìn)程去辦,子進(jìn)程就是白手套。
if (pid == 0)
{
argv = do_parse(command);
if (argv[0] == NULL)
{
exit(-1);
}
execvp(argv[0], argv); // 進(jìn)程替換函數(shù),可以添加環(huán)境變量p(path),參數(shù)格式是數(shù)組v(vector)
} // 可以把exec當(dāng)作call(goto)函數(shù),exit當(dāng)作return函數(shù)。
else
{
waitpid(pid, NULL, 0);
}
return 0;
}
int main(int argc, char *argv[])
{
while (1) // shell主循環(huán)
{
if (myshell_face() < 0)
continue;
do_exec(command);
}
return 0;
}
最終成果:文章來源地址http://www.zghlxwxcb.cn/news/detail-435248.html
到了這里,關(guān)于Linux一學(xué)就會——編寫自己的shell的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!