🌟 各位看官好,我是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;
}