非阻塞调用waitpid
这样父进程就不会阻塞,此时循环使用我们可以让父进程执行其他任务而不是阻塞等待
进程程序替换
进程=PCB+加载到内存中的代码和数据
替换就是完全替换当前进程的代码段、数据段、堆和栈,保存当前的PCB
代码指的是二进制代码不是源码!!!
cpp
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
pathname是要执行的可执行文件的完整路径 /bin/ls
file程序名,不带路径在环境变量PATH查找
l就是list,以可变参数的形式传递"ls","-l","NULL"
p就是会从环境变量查找,只要程序名即可
e就是环境变量数组
v就是vector以指针数组的形式传递
自定义shell的编写
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <unordered_map>
#include <string>
#define COMMAD_SIZE 1024
#define FORMAT "[%s@%s %s]# "
// 下面是shell定义的全局数据
// 1.命令行参数
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;
// 2.环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;
// 3.别名映射表
std::unordered_map<std::string, std::string> alias_list; // la对于ls -a
// for test
char cwd[1024];
char cwdenv[1024];
char oldpwd[1024];
// last exit code
int lastcode = 0; // 最新子进程退出码
const char *GetUserName()
{
const char *name = getenv("USER");
return name == NULL ? "None" : name;
}
const char *GetHostName() // 变化不大,可以不用系统调用。其实最好也用系统调用
{
const char *hostname = getenv("HOSTNAME");
return hostname == NULL ? "None" : hostname;
}
// 切换路径时,环境变量表也要切换。因此我们要更新环境变量。
// 注意我们自定义shell的环境变量表继承自shell
// 路径先变,环境变量才变,调用系统调用
const char *GetPwd()
{
const char *pwd = getcwd(cwd, sizeof(cwd));
if (pwd)
{
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
putenv(cwdenv);
}
return pwd == NULL ? "None" : pwd;
}
const char *GetOldPwd()
{
return getenv("OLDPWD");
}
const char *GetHome()
{
return getenv("HOME");
}
void InitEnv()
{
extern char **environ; // 头文件#include <unistd.h>定义了这个,这里是声明
memset(g_env, 0, sizeof(g_env));
// 本来从父进程继承,现在直接从配置文件(操作系统)来
// 1.获取环境变量
for (int i = 0; environ[i]; i++)
{
// 申请空间
// 别用sizeof,sizeof得到的是指针大小
g_env[i] = (char *)malloc(strlen(environ[i]) + 1);
strcpy(g_env[i], environ[i]);
g_envs++;
}
g_env[g_envs] = NULL;
// 2.导入环境变量
// 增量修改
for (int i = 0; g_env[i]; i++)
{
putenv(g_env[i]);
}
// environ=g_env;完全重置
}
// command 内建命令
bool Cd()
{
memset(oldpwd, 0, sizeof(oldpwd));
snprintf(oldpwd, sizeof(oldpwd), "OLDPWD=%s", GetPwd());
putenv(oldpwd);
if (g_argc == 1)
{
std::string home = GetHome();
if (home.empty())
return true;
chdir(home.c_str());
}
else
{
std::string where = g_argv[1];
if (where == "-")
chdir(GetOldPwd());
else if (where == "~")
chdir(GetHome());
else
chdir(where.c_str());
}
return true;
// cd argc = 1
if (g_argc == 1)
{
std::string home = GetHome();
if (home.empty())
return true;
chdir(home.c_str());
}
else
{
std::string where = g_argv[1];
// cd - / cd ~
if (where == "-")
{
// Todu
}
else if (where == "~")
{
// Todu
}
else
{
chdir(where.c_str());
}
}
return true;
}
void Echo()
{
if (g_argc == 2)
{
std::string opt = g_argv[1];
if (opt == "$?")
{
std::cout << lastcode << std::endl;
lastcode = 0; // echo执行完,退出码应该是0
}
else if (opt[0] == '$')
{
std::string env_name = opt.substr(1);
const char *env_value = getenv(env_name.c_str());
if (env_value)
std::cout << env_value << std::endl;
}
else
{
std::cout << opt << std::endl;
}
}
}
// 防止自定义bash路径名太长
/*std::string DirName(const char *pwd)
{
#define SLASH "/"
std::string dir = pwd;
if (dir == SLASH)
return SLASH; // 只有根目录直接返回
auto pos = dir.rfind(SLASH);
return dir.substr(pos + 1);
}*/
// 命令行提示符
void MakeCommandLine(char cmd_prompt[], int size)
{
snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}
// 打印命令行提示符
void PrintCommandPrompt()
{
char prompt[COMMAD_SIZE];
MakeCommandLine(prompt, sizeof(prompt));
std::cout << prompt;
fflush(stdout);
}
bool GetCommandLine(char *out, int size)
{
char *c = fgets(out, size, stdin);
if (c == NULL)
return false;
out[strlen(out) - 1] = 0; // 清理\n
if (strlen(out) == 0)
return false; // 只输入了/n
return true;
}
// 3.命令行分析 "ls -a -l" 解析为 "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
g_argc = 0;
g_argv[g_argc++] = strtok(commandline, SEP);
while(g_argv[g_argc++] = strtok(nullptr, SEP));
g_argc--;
return g_argc > 0 ? true : false;
}
void PrintArgv()
{
for (int i = 0; g_argv[i]; i++)
{
printf("argv[%d]->%s\n", i, g_argv[i]);
}
printf("argc: %d\n", g_argc);
}
bool CheckAndExecBuiltin()
{
std::string cmd = g_argv[0];
if (cmd == "cd")
{
Cd();
return true;
}
else if (cmd == "echo")
{
Echo();
return true;
}
else if (cmd == "export")
return true;
else if (cmd == "alias")
return true;
return false;
}
int Execute()
{
pid_t id = fork();
if (id == 0)
{
// child
execvp(g_argv[0], g_argv);
exit(1); // 一旦替换exit不会执行
}
// father
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0) // 等待成功
lastcode = WEXITSTATUS(status); // 获取子进程退出码
return 0;
}
int main()
{
InitEnv();
while (true) // 死循环
{
// 1.输出命令行提示符
PrintCommandPrompt();
// 2.获取用户输入的命令
char commandline[COMMAD_SIZE];
if (!GetCommandLine(commandline, COMMAD_SIZE))
continue;
// 3.命令行分析,并将分析后的命令行导入全面变量表中
if (!CommandParse(commandline))
continue;
// 检测是否是内建命令,若是直接调用然后continue,若不是则执行下面的
if (CheckAndExecBuiltin())
continue;
;
Execute();
}
return 0;
}