终止情况只有三种:
为何status和exit()的参数不一致呢?
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
} else if (pid == 0) { // child
printf("child is run, pid is : %d\n", getpid());
sleep(5);//子进程等待5秒
exit(257);
} else {
int status = 0;
pid_t ret = waitpid(-1, &status, 0); // 阻塞式等待,等待5S
printf("this is test for wait\n");
if (WIFEXITED(status) && ret == pid) {//fork函数运行后,父进程得到的pid就是子进程的pid
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
} else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
typedef void (*handler_t)(); // 函数指针类型
std::vector<handler_t> handlers; // 函数指针数组
void fun_one() { printf("这是⼀个临时任务1\n"); }
void fun_two() { printf("这是⼀个临时任务2\n"); }
void Load() {
handlers.push_back(fun_one);
handlers.push_back(fun_two);
}
void handler() {
if (handlers.empty())
Load();
for (auto iter : handlers)
iter();
}
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
} else if (pid == 0) { // child
printf("child is run, pid is : %d\n", getpid());
sleep(5);
exit(1);
} else {
int status = 0;
pid_t ret = 0;
do {
ret = waitpid(-1, &status, WNOHANG); // ⾮阻塞式等待,WNOHANG表示若子进程没结束,就返回0,若正常结束,则返回该⼦进程的ID。
if (ret == 0) {
printf("child is running\n");
}
handler();
} while (ret == 0);
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n",
WEXITSTATUS(status));
} else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
运行此代码后输出如下:
当进程调⽤⼀种exec函数时,该进程的 ⽤⼾空间代码和数据完全被新程序替换
,从新程序的启动例程开始执⾏。调⽤exec并不创建新进程,所以调⽤exec前后该进程的 id并未改变
。
直接替换当前进程的代码数据
,所以 原来位于exec后的printf("我的程序运行完毕了\n")直接被覆盖了,自然无法打印
参数详解:
那么如何使用exec函数也不改变原来的代码呢?这个时候子进程就有用啦:
但强制写路径./other
,也是可以的。other的代码如下所示:
运行结果为:
命令行参数和环境变量全被父进程中execvpe的参数替代了
。那么如何新增环境变量给子进程呢?
可以使用putenv函数:
putenv会把这个环境变量导入到当前进程
myshell.cc函数:
#include<iostream>
#include<cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//自己的shell
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "
//1.命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;
char cwd[1024];
char cwdenv[1024];
// last exit code 退出码
int lastcode = 0;
// 2. 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;
const char* GetUserName()//从环境变量中拿到User
{
const char* name = getenv("USER");
return name== NULL?"None":name;
}
const char* GetHostName()//从环境变量中拿到hostname
{
const char* hostname = getenv("HOSTNAME");
return hostname== NULL?"None":hostname;
}
const char* GetPwd()//从环境变量中拿到路径
{
// const char* pwd = getenv("PWD");
const char *pwd = getcwd(cwd, sizeof(cwd));//不再从环境变量中拿,而是直接获取当前进程的地址,避免因环境变量未更新导致的地址不变
if(pwd != NULL)
{
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);//格式要是name=value的格式,即PWD=。。。
putenv(cwdenv);//修改原来的环境变量,如果不存在就新建一个
}
return pwd== NULL?"None":pwd;
}
const char *GetHome()//得到home的地址
{
const char *home = getenv("HOME");
return home == NULL ? "" : home;
}
void InitEnv()
{
extern char **environ;//environ会自动获取父进程的环境变量
memset(g_env, 0, sizeof(g_env));//把g_env全部清空
g_envs = 0;
//本来要从配置文件来,自己的shell从父进程中获取环境变量
//1. 获取环境变量
for(int i = 0; environ[i]; i++)
{
// 1.1 申请空间
g_env[i] = (char*)malloc(strlen(environ[i])+1);//需要申请空间,因为每一个元素都是复制父进程的
strcpy(g_env[i], environ[i]);
g_envs++;
}
//g_env[g_envs++] = (char*)"HAHA=for_test"; //for_test
// g_env[g_envs] = NULL;
//2. 导成环境变量
for(int i = 0; g_env[i]; i++)
{
putenv(g_env[i]);
}
environ = g_env;
}
bool Cd()//处理cd命令----内键命令
{
// cd argc = 1
if(g_argc == 1)//只有一个cd 表示返回家目录
{
std::string home = GetHome();
if(home.empty()) return true;
chdir(home.c_str());//chdir函数,改变当前的工作目录到家目录
}
else
{
std::string where = g_argv[1];//cd后有路径或选项
// cd - / cd ~
if(where == "-")
{
// Todu
}
else if(where == "~")
{
// Todu
}
else
{
chdir(where.c_str());//这就是cd后有路径的选项
}
}
return true;
}
void Echo()
{
if(g_argc == 2)
{
// echo "hello world"
// echo $?
// echo $PATH
std::string opt = g_argv[1];
if(opt == "$?")//echo $?--------打印退出码
{
std::cout << lastcode << std::endl;
lastcode = 0;//打印一次之后要把退出码清0
}
else if(opt[0] == '$')//打印环境变量
{
std::string env_name = opt.substr(1);//去掉$后的内容,即环境变量名
const char *env_value = getenv(env_name.c_str());//getenv获取环境变量的具体内容
if(env_value)
std::cout << env_value << std::endl;
}
else
{
std::cout << opt << std::endl;//单纯打印字符串
}
}
}
std::string DirName(const char *pwd)//对路径进行修改
{
#define SLASH "/"//定义分隔符
std::string dir = pwd;
if(dir == SLASH) return SLASH;
auto pos = dir.rfind(SLASH);
if(pos == std::string::npos) return "BUG?";
return dir.substr(pos+1);
}
void MakeCommandLine(char cmd_prompt[],int size)
{
snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
}
void PrintCommandPrompt()//bin
{
char prompt[COMMAND_SIZE];
MakeCommandLine(prompt,sizeof(prompt));
printf("%s",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;//去掉/0,避免打印空行
if(strlen(out)==0) return false;
return true;
}
bool CommandParse(char* commandline)
{
//3.命令行分析 "ls -a -l"-> "ls" "-a" "-l"
#define SEP " "//CONST类型,需要使用双引号
g_argc=0;
g_argv[g_argc++]=strtok(commandline,SEP);//strtok函数用于切割字符串,SEP为分割符,切割完的返回值是切割下来的字符串
while(g_argv[g_argc++]=strtok(NULL,SEP));//接着第一次切需要把第一个参数设置为nullptr
g_argc--;//记得要--,因为是后置++
return true;
}
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()//判断是哪一种内键命令,不是返回false
{
std::string cmd = g_argv[0];
if(cmd == "cd")//执行cd命令
{
Cd();
return true;
}
else if(cmd == "echo")//执行echo命令
{
Echo();
return true;
}
else if(cmd == "export")
{
}
else if(cmd == "alias")
{
// std::string nickname = g_argv[1];
// alias_list.insert(k, v);
}
return false;
}
int Execute()
{
pid_t id = fork();
if(id == 0)
{
//child
execvp(g_argv[0], g_argv);//所有的命令都由子进程执行,但cd不可,需要父进程执行,因为先cd后子进程确实跳转了,但父进程的pwd并没有被改变,. 一次执行命令时会生成新的子进程会沿用父进程的pwd,所以pwd并没有改变
exit(1);
}
int status = 0;//定义状态
// father
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
lastcode = WEXITSTATUS(status);//使用WEXITSTATUS(状态)获得退出码
}
return 0;
}
int main()
{
while(true)
{
//1.输出命令行提示符
PrintCommandPrompt();
//2.获取用户输入的命令
char commandline[COMMAND_SIZE];
if(!GetCommandLine(commandline,sizeof(commandline)))
{
continue;
}
//3.命令行分析 "ls -a -l"-> "ls" "-a" "-l"
if(!CommandParse(commandline))
continue;
// PrintArgv();
//4.检测内键命令
if(CheckAndExecBuiltin())
continue;
// 5.执行命令
Execute();
}
return 0;
}
命令行参数表
和 环境变量表