🌟?各位看官好,我是maomi_9526!
🌍?種一棵樹最好是十年前,其次是現在!
🚀?今天來學習C語言的相關知識。
👍?如果覺得這篇文章有幫助,歡迎您一鍵三連,分享給更多人哦
目錄
1. 進程程序替換
2.exec函數
2.1 execl?
2.2 execlp?
2.3 execle?
2.4 execv?
2.5 execvp?
2.6 execvpe?
?2.7execve
2.8命名理解
3.進程替換
3.1進程替換原理
4. 自主Shell命令行解釋器
4.1獲取當前環境信息
4.2輸出命令行提示符
4.3獲取命令行輸入
4.4執行命令行
4.4.1執行內建命令?
4.4.2執行外部命令
4.5更新環境變量
3. Shell 實現完整代碼
1. 進程程序替換
-
fork() 系統調用創建一個子進程,父子進程開始執行相同的程序代碼。若子進程要執行一個不同的程序,可以使用
exec
系列函數來實現程序的替換。 -
這些
exec
函數會加載一個全新的程序(包括代碼和數據)到子進程的地址空間中,并從新程序的入口點開始執行,原有的程序代碼被替換掉。exec
函數系列中最常用的是execve
,其他的execl
,execlp
,execv
,execvp
,execle
等只是execve
的不同封裝。
2.exec函數
頭文件:#include<unistd.h>
返回值:當失敗時返回-1
2.1 execl?
int execl(const char *path, const char *arg, ...);
execl("/usr/bin/ls","ls","-l",NULL);
2.2 execlp?
int execlp(const char *file, const char *arg, ...);
execlp("ls","ls","-l",NULL);
2.3 execle?
int execle(const char *path, const char *arg, ..., char * const envp[]);
extern char**environ;//聲明全局環境變量
execle("/usr/bin/ls","1s","-l","-a",NULL,environ};
2.4 execv?
int execv(const char *path, char *const argv[]);
char*argv[]={"1s","-l","-a",NULL};execv("/usr/bin/ls",argv);
2.5 execvp?
int execvp(const char *file, char *const argv[]);
char*argv[]={"1s","-l","-a",NULL};execvp("ls",argv);
2.6 execvpe?
int execvpe(const char *file, char *const argv[],char *const envp[]);
char*argv[]={"ls","-a","-l",NULL};
execvpe("ls",argv,environ);
?2.7execve
系統調用函數execve
上面的exec系列函數本質上都不是系統級別的調用,都是對execve的語言級別的封裝
int execve(const char *filename, char *const argv[],?char *const envp[]);
?
2.8命名理解
- l(list) : 表示參數采用列表
- v(vector) : 參數用數組
- p(path) : 有p自動搜索環境變量PATH
- e(env) : 表示自己維護環境變量
函數級別 | 函數名 | 列表 | 傳參是否帶路徑 | 是否使用當前環境變量 |
語言級別 | execl | 列表 | 是 | 是 |
execlp | 列表 | 否 | 是 | |
execle | 列表 | 是 | 否 | |
execv | 數組 | 是 | 是 | |
execvp | 數組 | 否 | 是 | |
execvpe | 數組 | 否 | 否 | |
系統級別 | execve | 數組 | 否 | 否 |
3.進程替換
3.1進程替換原理
當進程執行了代碼替換操作后,原先加載的代碼會被新的代碼所替換。
此時,原有的代碼不再存在于進程的地址空間中,執行流轉向新的代碼。具體來說,在進程替換時,原代碼的內存空間被新的代碼段覆蓋,新的代碼開始運行。此過程的本質是將進程的代碼區域替換為新的內容,從而導致原有代碼失效并不可再訪問。
所以原來代碼我的進程執行完畢并不會出現。?
4. 自主Shell命令行解釋器
-
通過實現一個自定義的 shell,可以處理命令行輸入,并根據輸入執行對應的命令。Shell 需要有以下功能:
4.1獲取當前環境信息
getenv()
是一個 C 標準庫函數,用于從環境變量中獲取指定名稱的值。環境變量是系統級的變量,它們存儲了操作系統和程序運行時需要的配置信息,比如系統路徑、用戶設置等。getenv()
函數通過讀取這些環境變量,允許程序動態地獲取環境設置。
頭文件:#include<stdlib.h>
函數:char *getenv(const char *name);
返回值:
-
成功:如果找到了指定名稱的環境變量,
getenv()
會返回該變量的值(一個指向字符數組的指針,代表該環境變量的值)。 -
失敗:如果未找到指定的環境變量,
getenv()
返回NULL
。
代碼實現:
//獲取當前環境信息
const char* GETPWD()
{char *pwd=getenv("PWD");return pwd==NULL?"None":pwd;
}//獲取用戶信息
const char*GETUSER()
{char*user=getenv("USER");return user==NULL?"None":user;
}//獲取系統信息
const char*GETHOSTNAME()
{char*hostname=getenv("HOSTNAME");return hostname==NULL?"None":hostname;
}
4.2輸出命令行提示符
snprintf
是 C 語言標準庫中的一個函數,屬于 stdio.h
頭文件。它的作用是將格式化的數據輸出到一個字符數組中,并且保證不會發生緩沖區溢出。snprintf
函數是對 sprintf
的一種改進,主要是增加了一個最大字符數的限制,避免了 sprintf
在沒有足夠空間時造成內存溢出的風險。?
頭文件:#include<stdio.h>
int snprintf(char *str, size_t size, const char *format, ...);
返回值:
-
成功:返回寫入字節數(當被寫入內容超過寫入大小,發生截斷)
-
失敗:返回負數?
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]#"
void MakeCMDPrompt(char cmdprompt[],size_t size)//制作命令行提示符
{snprintf(cmdprompt,size,FORMAT,GETUSER(),GETHOSTNAME(),GETPWD());
}
void PrintCMDPrompt()//打印命令行提示符
{char prompt[COMMAND_SIZE];MakeCMDPrompt(prompt,sizeof(prompt));printf("%s",prompt);
}
4.3獲取命令行輸入
fgets
是 C 語言標準庫中的一個函數,屬于 stdio.h
頭文件。它的作用是從指定的文件流中讀取一行字符串,并將讀取的內容存儲到一個字符數組中。與 gets
不同,fgets
可以避免緩沖區溢出的問題,因為它會限制讀取的字符數。?
頭文件:#include<stdio.h>
char *fgets(char *s, int size, FILE *stream);
返回值:
- 成功?:返回寫入的s的位置
- 失敗:返回NULL
?代碼實現:
//接受命令行
bool MakeCMDLine(char*out,size_t size)
{char*line=fgets(out,size,stdin);if(line==NULL) return false;//返回值為空,寫入失敗out[strlen(out)-1]=0;//去除輸入的換行符if(strlen(out)==0) return false;return true;
}
4.2解析命令行
將用戶輸入的命令解析成可執行的命令和參數。
strtok
是 C 語言標準庫中的一個函數,屬于 string.h
頭文件。它用于將一個字符串分割成一系列子字符串(tokens),根據指定的分隔符。該函數通常用于處理由空格、逗號、換行符等字符分隔的文本數據。
char *strtok(char *str, const char *delim);
str:待分割的字符串。如果是第一次調用
strtok
,該參數應為需要分割的字符串;如果是后續調用,應該傳遞NULL
,以繼續分割上一次傳入的字符串。delim:分隔符字符串,定義了用于分割字符串的字符集合。可以是單個字符,也可以是多個字符,
strtok
會將字符串中的任何一個分隔符都視為分隔點。
//分割字符串
bool CMDLinePrase(char *line)
{
#define ADC " "g_argc=0;//每次初始化為0,確保每個命令都是從首位開始g_argv[g_argc++]=strtok(line,ADC);while(g_argv[g_argc++]=strtok(nullptr,ADC));g_argc--;return true;
}
4.4執行命令行
4.4.1執行內建命令?
通過父進程本身來進行執行:(cd命令)
頭文件:#include<unistd.h>
?int chdir(const char *path);
bool CheckBuiltIn()
{std::string cmd=g_argv[0];if(cmd=="cd"){if(g_argc==1){chdir(GETHOME());return true;}else{std::string pwd=g_argv[1];chdir(pwd.c_str());}return true;}return false;
}
4.4.2執行外部命令
?通過子進程來進行執行:
//子程序進行進程替換執行命令
int Execute()
{int id=fork();if(id==0){//chileexecvp(g_argv[0],g_argv);exit(1);}//fatherint idd=waitpid(id,NULL,0);//阻塞等待(void)idd;//使用避免報錯return 0;
}
4.5更新環境變量
getcwd
是 unistd.h
頭文件中的一個函數,用于獲取當前工作目錄。?
?#include<unistd.h>
char *getcwd(char *buf, size_t size);
-
buf
:一個字符數組的指針,用來存儲獲取的當前工作目錄的路徑。你需要在調用getcwd
之前分配足夠的內存空間來存儲路徑。 -
size
:buf
指針指向的字符數組的大小。它指定了buf
能夠存儲的最大字符數。
char g_env[1024];
char g_cwd[1024];void ChangEnv()
{const char*cwd=getcwd(g_cwd,sizeof(g_cwd));if(cwd!=nullptr){snprintf(g_env,sizeof(g_env),"PWD=%s",g_cwd);putenv(g_env);}
}
3. Shell 實現完整代碼
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]#"
#define MAXARGC 128
char g_env[1024];
char g_cwd[1024];
char* g_argv[MAXARGC];
int g_argc=0;
const char* GETPWD()
{char *pwd=getenv("PWD");return pwd==NULL?"None":pwd;
}
const char*GETUSER()
{char*user=getenv("USER");return user==NULL?"None":user;
}
const char*GETHOSTNAME()
{char*hostname=getenv("HOSTNAME");return hostname==NULL?"None":hostname;
}
const char*GETHOME()
{char*home=getenv("HOME");return home==NULL?"None":home;
}
void ChangEnv()
{const char*cwd=getcwd(g_cwd,sizeof(g_cwd));if(cwd!=nullptr){snprintf(g_env,sizeof(g_env),"PWD=%s",g_cwd);putenv(g_env);}
}
bool CheckBuiltIn()
{std::string cmd=g_argv[0];if(cmd=="cd"){if(g_argc==1){chdir(GETHOME());return true;}else{std::string pwd=g_argv[1];chdir(pwd.c_str());}ChangEnv();return true;}return false;
}
std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir=pwd;auto pose=dir.rfind(SLASH);if(pose==std::string::npos) return "BUG?";return dir.substr(pose+1);
}
void MakeCMDPrompt(char cmdprompt[],size_t size)
{//snprintf(cmdprompt,size,FORMAT,GETUSER(),GETHOSTNAME(),GETPWD());snprintf(cmdprompt,size,FORMAT,GETUSER(),GETHOSTNAME(),DirName(GETPWD()).c_str());
}
void PrintCMDPrompt()
{char prompt[COMMAND_SIZE];MakeCMDPrompt(prompt,sizeof(prompt));printf("%s",prompt);
}
bool MakeCMDLine(char*out,size_t size)
{char*line=fgets(out,size,stdin);if(line==NULL) return false;out[strlen(out)-1]=0;if(strlen(out)==0) return false;return true;
}
bool CMDLinePrase(char *line)
{
#define ADC " "g_argc=0;g_argv[g_argc++]=strtok(line,ADC);while(g_argv[g_argc++]=strtok(nullptr,ADC));g_argc--;return g_argc==0?false:true;
}
void PrintCMDLinePrase()
{for(int i=0;g_argv[i];i++){printf("argv[%d]->%s\n",i,g_argv[i]);}printf("argc :%d\n",g_argc);
}
void Print()
{char cmdline[COMMAND_SIZE];if( MakeCMDLine(cmdline,sizeof(cmdline))){printf("%s",cmdline);}
}
int Execute()
{int id=fork();if(id==0){//chileexecvp(g_argv[0],g_argv);exit(1);}//fatherint idd=waitpid(id,NULL,0);//阻塞等待(void)idd;//使用避免報錯return 0;
}
int main()
{while(true){PrintCMDPrompt();char cmdline[COMMAND_SIZE];if(! MakeCMDLine(cmdline,sizeof(cmdline))){continue;}if(!CMDLinePrase(cmdline)){continue;}if(CheckBuiltIn()){continue;}Execute();}return 0;
}