10.进程控制(下)
一、进程程序替换(重点)
在程序替换过程中,并没有创建新的进程,只是把当前进程的代码和数据用新程序的代码和数据进行覆盖式的替换。
1)一旦程序替换成功,就去执行新代码了,后序代码不执行
2)exec系列函数,只有失败返回值-1,没有成功返回值(成功返回值无意义,已经被替换了)
因此也不用判断返回值,只要返回就是程序替换失败。
认识全部接口:
6个C语言库函数:
#include <unistd.h>int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]);int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);
1)int execl(const char *path, const char *arg, ...);
path:路径名+程序名
arg:可变参数列表
总结:我要执行谁,我想怎么执行它。
execl("/usr/bin/ls","ls","-al",NULL);
必须以NULL结尾,标明参数传递完成。
为什么没有影响父进程?
1.进程具有独立性
2.数据可以发生写时拷贝,代码也可以发生写时拷贝
原理:代码也是通过页表进行虚拟地址到物理内存映射的。
只要是进程,就能进行程序替换。
2)int execlp(const char *file, const char *arg, ...);
p->PATH
file:只告诉需要执行的文件名即可,因为execlp会自动在环境变量PATH中查找指定的命令
execlp("ls","ls","-al",NULL);
3)int execv(const char *path, char *const argv[]);
v->vector
argv:提供一个命令行参数表,指针数组。
char *const argv[] = {(char* const)"ls",(char* const)"-a",(char* const)"-l",NULL }; execv("/usr/bin/ls",argv);
4)int execvp(const char *file, char *const argv[]);
v->vector, p->PATH
组合,file表示文件名,argv表示命令行参数表。
char *const argv[] = {(char* const)"ls",(char* const)"-a",(char* const)"-l",NULL }; execvp("ls",argv);
5)int execvpe(const char *file, char *const argv[], char *const envp[]);
e->env,envp表示环境变量表
新增方式给子进程拿到环境列表:
putenv,然后传递environ全局变量给第三个参数char *const envp[]。
char *const argv[] = {(char* const)"./code",(char* const)"-a",(char* const)"-l",NULL }; putenv((char*)"MyVal=123456789"); extern char **environ; execvpe("./code",argv,environ);
6)int execle(const char *path, const char *arg, ..., char * const envp[]);
envp:环境变量表
系统调用execve
#include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]);
C语言封装了系统调用,封装时,若没有显式传的,会默认传。
二、自定义shell
易错点1:
putenv函数:
使用
putenv((char*)env.c_str())
时,env
是局部变量,函数返回后其内存会被释放,导致环境变量PWD
指向无效内存。这会导致后续使用getenv("PWD")
时出现未定义行为(如崩溃或垃圾值)。setenv函数:
开辟内存,重写一份key=value的键值对。
总结:putenv是要求参数一直都存在在内存中,setenv会生成一份拷贝,即使参数释放了,也没有关系。
setenv第一个参数填键值,第二个参数填值,第三个参数表示如果键值相同值是否修改,0表示不修改,非0表示修改
自定义shell重点:
bash内存在两张表:命令行参数表 和 环境变量表。
登录时创建bash,bash从系统的配置文件中读取环境变量。
模拟时从bash读,必须开辟空间来存储环境变量表,并维护起来,putenv只是用来建立环境变量的键值对关系的,并不能开辟内存,必须维护环境变量表。
bash通过对用户输入的一行字符串做命令行分析来完成命令行参数表的初始化。
执行命令:
若是内建命令,例如cd,echo,bash亲自执行,其中echo $?,bash会存储上一次进程退出码,cd会改变当前工作目录,以及环境变量中的PWD。
若不是内建命令,例如ls,bash通过创建子进程,子进程程序替换,bash等待子进程来完成。
具体代码如下:
#include <string> #include <unordered_map> #include <algorithm> #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h>#define MAX_LINE 1024 #define MAX_ARGC 128//命令行参数表 char *g_argv[MAX_ARGC]; int g_argc = 0;#define MAX_ENVS 200 //环境变量表,键值对 char *g_env[MAX_ENVS]; int g_envs = 0;//上一次程序的退出码 int lastexit = 0;const char* get_username() {const char* username = getenv((const char*)"USER");return username == NULL ? "None" : username; }const char* get_hostname() {const char* hostname = getenv((const char*)"HOSTNAME");return hostname == NULL ? "None" : hostname; }const char* get_pwd() {char pwd[MAX_LINE];//const char* pwd = getenv((const char*)"PWD");std::string PWD = getcwd(pwd,MAX_LINE);if(PWD.empty()){return "None";}return PWD.c_str(); }//获取家目录 const char* get_home() {const char* home = getenv((const char*)"HOME");return home == NULL ? "None" : home; } //获取最后一个路径的名称 const char* GetLastPWD() {std::string pwd = get_pwd();//根目录if(pwd.size()==1)return "/";std::string::reverse_iterator rit = pwd.rbegin();std::string ret;while(rit!=pwd.rend() && *rit!='/'){ret.push_back(*rit);++rit;}reverse(ret.begin(),ret.end());return ret.c_str(); }#define MAX_PROMPT 1024 void MakePrompt(char* prompt) { #define STYLE "[%s@%s %s]# "snprintf(prompt,MAX_PROMPT,STYLE,get_username(),get_hostname(),GetLastPWD()); }void PrintPrompt() {//1.打印提示符char prompt[MAX_PROMPT];MakePrompt(prompt);printf("%s",prompt);fflush(stdout); }bool GetInput(char commandline[]) {char* ret = fgets(commandline,MAX_LINE,stdin);if(ret==NULL)return false;commandline[strlen(commandline)-1]='\0';//获取家目录 const char* get_home() {const char* home = getenv((const char*)"HOME");return home == NULL ? "None" : home; } //获取最后一个路径的名称 const char* GetLastPWD() {std::string pwd = get_pwd();//根目录if(pwd.size()==1)return "/";std::string::reverse_iterator rit = pwd.rbegin();std::string ret;while(rit!=pwd.rend() && *rit!='/'){ret.push_back(*rit);++rit;}reverse(ret.begin(),ret.end());return ret.c_str();return true; }bool SpliceString(char commandline[]) { #define space " "g_argc = 0; g_argv[g_argc++] = strtok(commandline,space);while((bool)(g_argv[g_argc++]=strtok(NULL,space)));g_argv[g_argc] = NULL;--g_argc;return true; }void CD() {std::string env = "PWD";//修改当前路径为家目录,修改环境变量if(strcmp(g_argv[1],"~")==0){const char* home = get_home();chdir(home);setenv(env.c_str(),home,1);return;} }void ECHO() { std::string content = g_argv[1];if(content[0]=='$'){//上一次程序退出码if(content=="$?"){std::cout << lastexit << std::endl;}//打印环境变量的值else{//获得环境变量的键值std::string env_key(content.begin()+1,content.end());const char* env_value = getenv(env_key.c_str());std::cout << env_value << std::endl;}}else{std::cout << g_argv[1] << std::endl;} }bool CheckBuiltIncommand() {if(strcmp(g_argv[0],"cd")==0){ CD();lastexit = 0;return true;}else if (strcmp(g_argv[0],"echo")==0){ECHO();lastexit = 0;return true;}return false; }void Excute() {pid_t pid = fork(); if(pid==0){execvp(g_argv[0],g_argv);}int status;waitpid(pid,&status,0);lastexit = WEXITSTATUS(status); }void InitEnv() {//从全局的environ读取环境变量,即从父进程bash读取extern char **environ;g_envs=0;//遍历环境变量,添加到自身环境变量表中for(int i = 0;environ[i];i++){g_env[i] = (char*)malloc(strlen(environ[i])+1);memcpy(g_env[i],environ[i],strlen(environ[i])+1);++g_envs;}//添加const char *myenv = "MYVAL=12345678";g_env[g_envs] = (char*)malloc(strlen(myenv)+1);memcpy(g_env[g_envs],myenv,strlen(myenv)+1);++g_envs;g_env[g_envs] = NULL;for(int i = 0;g_env[i];i++){putenv(g_env[i]);} }int main() {InitEnv();char commandline[MAX_LINE];memset(commandline,0,MAX_LINE);while(true){//1.打印提示符PrintPrompt();//2.获取输入的字符串if(!GetInput(commandline) || strlen(commandline)==0)continue;//3.以空格为分割,分割字符串,存到命令行参数表中 if(!SpliceString(commandline))continue;//4.检查是否是内建命令,若是由父进程执行if(CheckBuiltIncommand())continue;//5.创建子进程,子进程程序替换执行命令Excute();}return 0; }