🌟 各位看官好,我是maomi_9526!
🌍 种一棵树最好是十年前,其次是现在!
🚀 今天来学习C语言的相关知识。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦
目录
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)
{
//chile
execvp(g_argv[0],g_argv);
exit(1);
}
//father
int 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)
{
//chile
execvp(g_argv[0],g_argv);
exit(1);
}
//father
int 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;
}