Linux:进程控制
Linux进程控制
进程创建
回顾前面所讲的fork函数,在 Linux 中 fork 函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。子进程中fork返回0,父进程中fork返回子进程的pid,出错返回-1。
#include <unistd.h>
pid_t fork();
进程调用 fork ,当控制转移到内核中的 fork 代码后,内核做:
- 分配新的内存块和内核数据结构给子进程。
- 将父进程部分数据结构内容拷贝至子进程。
- 添加子进程到系统进程列表当中。
- fork 返回,开始调度器调度。

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
通常,父子代码共享,父子再不写入时,数据也是共享的。当任意一方试图写入,便以写时拷贝的方式各自一份副本。如下图所示,具体可看往期博客Linux进程。

写时拷贝(Copy-On-Write,COW),是一种延时申请技术。父进程调用fork()创建子进程后,子进程与父进程共享代码和数据,而不是在物理内存中直接开辟空间拷贝数据给子进程,这样就减少了创建子进程的时间,减少了空间浪费,待任意进程要写入数据时再进行写时拷贝,提高了整机内存使用率。
fork常规用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因:
- 系统中有太多进程。
- 实际用户的进程数超过了限制。
进程终止
进程终止的本质是释放系统资源,即释放进程申请的相关内核数据结构和对应的数据和代码。
进程退出场景:
- 代码运行完毕,结果正确。
- 代码运行完毕,结果不正确。
- 代码异常终止。
main函数的返回值,通常表明程序的执行情况。代码运行完毕,结果正确,返回0;代码运行完毕,结果不正确,返回非0,如1,2,3……不同值表明不同的出错原因。
echo $? 可以查看最近进程的退出码。进程退出码会写到进程的task_struct中,以供父进程回收时读取。

由于 echo $? 查看的是最近进程的退出码,所以此时再执行一次该命令,查看的就是 echo $? 的退出码,即0。

库函数 strerror 可以根据错误码返回对应的错误描述。

使用如下代码,查看所有错误码对应的信息,不知道有多少错误码,先假定有200个。

运行代码后可以看到,一共有133个错误码。
fopen函数打开文件失败后不仅会返回空,还会把全局错误码变量 errno 设置为对应值以表明错误信息。

用命令打开不存在的文件,错误码也是相同的。

如果代码异常终止,错误码就会无意义。

exit 可以结束进程,在任何地方调用就在此结束。exit(n) 进程退出后返回退出码n。

进程在函数fun()中遇到exit结束,直接退出。

_exit 是系统调用,终止调用它的进程。

exit 是库函数,使用时会回收资源,刷新缓冲区,再调用系统级别的 _exit 。运行如下代码,可以看到main()并没有被打印出来。


return是一种更常见的退出进程方法。执行 return n 等同于执行 exit(n),因为调用 main 的运行时函数会将 main 的返回值当做 exit 的参数。
进程等待
子进程先于父进程退出,父进程还未对子进程回收,子进程就变成僵尸进程。进程变成僵尸进程后,命令 kill -9 也无法杀掉进程。父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
wait(int* status) 是系统调用,成功返回被等待进程pid,失败返回-1。参数是输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

准备下面代码,父进程创建子进程后休眠15秒,子进程运行5秒后退出,成为僵尸进程。父进程休眠结束后调用wait,回收子进程。

运行代码,while :; do ps axj | head -1 && ps axj | grep myproc; sleep 1;done 观察进程。

等待子进程时,如果子进程还未退出,父进程就会阻塞在wait调用出,类似使用scanf时,键盘不输入就会一直阻塞在scanf处。如果有多个子进程,父进程会阻塞在wait处,等待任意一个子进程退出,一次只回收一个僵尸子进程。
pid_t waitpid(pid_t pid, int *status, int options) waitpid的返回值与wait一样,如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在,一般是没有子进程,或pid错误没有对应子进程。参数options用于阻塞控制,默认为0,表示阻塞等待。参数pid为要等待的子进程的pid,pid为-1则等待所有子进程,此时就等价于wait。
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息,否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单地当作整型来看待,应该当作位图来看待。status的32个bit位中,高16位不使用,全为0。低16位中,8 ~ 15位记录退出状态,即退出码;第7位是core dump标志,代码异常退出时保存对应信号编号,为0时正常,为1时异常,kill -l 查看所有信号,这些信号其实是宏;0 ~ 6位是终止信号,正常退出时全为0,代码异常退出时非0,此时退出码也无意义。

退出码 exit code 可通过 (status >> 8) & 0xFF 求出,bash也是这样拿到退出码的。(0xFF即二进制11111111)

定义一个全局变量exitcode=0,子进程退出时把它修改为对应退出码,但这样会发生写时拷贝,父进程无法拿到exitcode,所以需要waitpid这样的系统调用。
查看status0 ~ 6位表示的退出信号 exit signal 通过 status & 0x7F 。(0x7F即二进制01111111)

可以看到exit signal为0代码正常退出,但exit code为10运行结果不正确。
把子进程的部分改为while(1),另开一个机器用 kill -9 杀掉子进程,可以看到退出信号对应为9。

子进程部分添加访问空指针和除0错误,代码异常退出,可以看到退出信号为11和8。

WIFEXITED(status) 返回真,子进程正常退出,返回假,子进程被信号杀死,异常退出。底层宏实现如下。
#define WIFEXITED(status) ((status & 0x7F) == 0)
WEXITSTATUS(status) 当 WIFEXITED(status) 为真,即status的0 ~ 6位全为0,进程正常退出时,才会返回退出码,否则返回的退出码无意义。
#define WEXITSTATUS(status) (((status) >> 8) & 0xFF)
子进程部分有除0错误,用这两个宏后,得到退出信号为0,说明异常退出,此时退出码为0无意义。

所以,在获取退出码时,要先用宏 WIFEXITED(status) 判断是否为正常退出。

waitpid的参数options为0时,如果子进程还在运行,父进程就会卡在waitpid调用处,阻塞等待。当optinos为 WNOHANG 时,waitpid为非阻塞轮询模式,父进程调用waitpid后不再等待,如果返回的是0,则说明子进程还在运行。
用下面代码观察非阻塞轮询模式。

非阻塞轮询过程中,父进程也能执行别的任务。如下代码所示。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef void(*fun_t)();
#define N 5
fun_t handlers[N+1];
//任务
void DownLoad()
{
printf("下载任务......\n");
}
void Flush()
{
printf("刷新任务......\n");
}
void Log()
{
printf("记录日志任务......\n");
}
void registerHandler(fun_t h[],fun_t f)
{
int i=0;
for(i=0;i<N;i++)
{
if(h[i]==NULL)
break;
}
if(i==N)
return;
h[i]=f;
h[i+1]=NULL;
}
int main()
{
registerHandler(handlers,DownLoad);
registerHandler(handlers,Flush);
registerHandler(handlers,Log);
pid_t id = fork();
if(id == 0)
{
//子进程
int cnt=3;
while(cnt--)
{
printf("我是子进程,pid = %d ppid = %d\n",getpid(),getppid());
sleep(1);
}
exit(10);
}
//父进程
while(1)
{
int status = 0;
pid_t rid = waitpid(id,&status,WNOHANG);
if(rid>0)
{
if(WIFEXITED(status))
printf("wait success, rid = %d\nstatus = %d exit code = %d exit signal = %d\n",rid,status,WEXITSTATUS(status),WIFEXITED(status));
else
printf("子进程退出异常\n");
break;
}
else if(rid == 0)
{
printf("本轮调用结束,子进程没有退出。\n");
for(int i=0;handlers[i];i++)
{
handlers[i]();
}
sleep(1);
}
else
{
printf("wait failed %d -> %s\n",errno,strerror(errno));
break;
}
}
return 0;
}

可以看到,子进程退出前,父进程不必阻塞等待,而是可以去执行一系列其他任务。
进程程序替换
用fork创建子进程后执行的是和父进程相同的程序,子进程往往要调用⼀种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变。

代码演示。

执行到execl,发生程序替换,原始代码的后半部分就不存在了。所以第二个printf没有执行。
改成错误路径和指令,execl执行失败,两个printf正常执行。

man execl ,函数出现错误时返回-1并设置errno,成功时没有返回值,因为execl函数后面的代码已经被替换了,此时返回值无意义。

exec系列的函数不用做返回值判断,如果有返回值则失败,此时直接调用 exit(1) 。

man execl 查看exec系列函数。

int execl(const char* path,const char* arg,...);
函数名的 l 即 list 。参数path传路径+程序名。后面的可变参数列表按照命令行使用时传。execl的最后一个参数必须传入NULL。如命令行 ls -al ,调用execl函数 execl("/usr/bin/ls","ls","-al",NULL) 。
创建子进程,让子进程去执行另一个程序。

execl为什么没有影响父进程呢?进程具有独立性,数据和代码都发生了写时拷贝。
execl除了能把代码替换为命令,也能替换为我们自己写的程序。这些程序可以是C++,Java,Python等。

在myproc.c子进程内调用getpid()打印出进程 PID 为 3122;执行execl完成程序替换后,code.cc中再次调用getpid(),输出的 PID 仍为 3122,两次 PID 完全一致。说明调用exec系列函数完成程序替换时,不会生成新进程,进程 PID 自始至终保持不变。

int execlp(const char *file, const char *arg, ...);
函数名中的 p 即 PATH。第一个参数只需要传入要执行的文件名即可,execlp会自动在环境变量PATH中查找。

int execv(const char *path, char *const argv[]);
函数名中的 v 即 vector 。第一个参数path传路径和文件名,可变参数列表改为传字符指针数组,数组的最后一个元素必须为NULL。

int execvp(const char *file, char *const argv[]);
函数名中的 v 和 p 同理。参数file只需要传文件名。argv[]为命令行参数表。

int execvpe(const char *file, char *const argv[], char *const envp[]);
函数名中的 v、p同理,e指环境变量。参数file、argv[]同理,envp[]为环境变量表,最后一个元素必须为NULL。

子进程调用 execvpe 加载运行 code,传入自定义的命令行参数表和环境变量表,code 启动后它的命令行参数、环境变量就被传入的这两张表完整替换。
子进程调用 execvp ,code的命令行参数表也会被替换,但环境变量表不会被替换,如果要替换,则需要修改全局指针environ = env.

可以看到直接调用 execvpe 会覆盖环境变量表,使用 putenv 能增量式地在环境变量表中添加环境变量。

创建全局数组env,输入要加入的环境变量,调用getenv把env中的环境变量加入到环境变量表中,最后把全局指针environ传入execvpe,子进程执行code时就能看到这些新加入的环境变量。

父进程调用 fork() 创建子进程,刚 fork 完成时,子进程共享父进程的内存,包含父进程的命令行参数、环境变量表。
子进程中通过 putenv() 追加自定义环境变量,修改环境变量表时触发写时拷贝,只会复制生成一份子进程独有的环境变量表,父进程的原始环境变量表完全不会被改动。
进程全局指针 environ 指向进程自身的环境变量表,子进程修改后,environ 就指向子进程这份新增了自定义环境变量的环境变量表。
子进程调用 execvpe("./code", argv, environ),将当前进程完整环境变量表传给新程序;执行 execvpe 后子进程镜像被 code 替换,code 的 main 就能读取到原有系统环境 + 我们追加的 MYVAL/MYVAL1/MYVAL2 全部环境变量。
我们程序本身,即main,也是父进程 bash 通过 fork+exec 传递命令行参数与环境变量启动的。
exec系列的6个函数是语言层面的函数,底层调用系统级别的execve实现。

自主Shell命令行解释器
shell执行命令的流程是这样的。shell收到用户输入的字符串ls,创建子进程,子进程运行命令ls,父进程阻塞等待,直到子进程退出。之后shell恢复运行,回到初始状态,等待读取新命令。(bash是shell的一种)

所以写一个shell,需要循环以下过程:
- 获取命令行。
- 解析命令行。
- 创建一个子进程。(fork)
- 替换子进程。(正常使用命令行解释器时不带路径,故选用execvp)
- 父进程等待子进程退出。(wait)
创建文件 myshell.cc ,实现我们自己的shell。把myshell当作bash,自然myshell要有自己的一套命令行参数和环境变量。
#define COMMAND_SIZE 1024
//命令行参数
#define ARGV_SIZE 128
char* g_argv[ARGV_SIZE];
int g_argc=0;
//环境变量
#define ENV_SIZE 128
char* g_env[ENV_SIZE];
int g_envs=0;
XShell中显示的命令行由命令行提示符和命令组成。
命令行参数从输入的命令中解析获得。环境变量初始化时,先读取父 bash 传递给 myshell 的系统原生环境表environ,逐个 malloc 复制所有环境字符串,存入自定义全局指针数组g_env。
fork 产生子进程时父子进程环境内存相互隔离(写时拷贝仅为 fork 阶段内存优化),我们手动拷贝环境到g_env脱离原生只读内存,再调用putenv注册自定义环境字符串,最后将系统全局环境指针environ指向g_env,让 myshell 拥有一套完全自主可控、独立于父进程 bash 的环境变量表。
void InitEnv()
{
extern char** environ;
memset(g_env,0,sizeof(g_env));
g_envs=0;
for(int i=0;environ[i];i++)
{
g_env[i] = (char*)malloc(strlen(environ[i])+1); //
strcpy(g_env[i],environ[i]);
g_envs++;
}
g_env[g_envs]=NULL;
for(int i=0;i<g_envs;i++)
putenv(g_env[i]);
environ=g_env;
}
命令行提示符由用户名、主机名、所处目录组成。这些分别可以通过调用 getenv ,从环境变量USER、PWD、HOME、HOSTNAME中获得。但是当前工作目录不能直接通过 getenv("PWD") 获取,如果后续使用命令 cd 更改了工作目录,环境变量PWD不会自动更新,getcwd() 直接和内核交互,读取进程在内核中保存的真实工作目录。

const char* GetUser()
{
const char* user = getenv("USER");
return user == NULL?"None":user;
}
const char* GetHome()
{
const char* home = getenv("HOME");
return home == NULL?"None":home;
}
const char* GetHostName()
{
const char* host = getenv("HOSTNAME");
return host == NULL?"None":host;
}
char cwd[1024]; //当前工作路径
char cwd_env[1024];//转成环境变量形式
const char* GetPwd()
{
memset(cwd,0,sizeof(cwd));
const char* pwd = getcwd(cwd,sizeof(cwd));
if(pwd!=NULL)
{
if(strcmp(pwd,GetHome())==0)
return pwd;
snprintf(cwd_env,sizeof(cwd_env),"PWD=%s",cwd);
putenv(cwd_env);
return pwd;
}
return "None";
}
生成命令行提示符。
const char* DirName(const char* pwd)//从工作路径中截取出所在目录
{
if(pwd==NULL)
return "None";
if(strcmp(pwd,"/")==0 || strcmp(pwd,"~")==0)
return pwd;
int i=strlen(pwd)-1;
while(pwd[i]!='/')
i--;
return &pwd[++i];
}
void MakeCommandLinePrompt(char* prompt,int size)
{
snprintf(prompt,size,FORMAT,GetUser(),GetHostName(),DirName(GetPwd()));
}
void PrintCommandLinePrompt()
{
char prompt[COMMAND_SIZE];
memset(prompt,0,sizeof(prompt));
MakeCommandLinePrompt(prompt,sizeof(prompt));
printf("%s",prompt);
fflush(stdout);
}
接下来获取命令并解析命令。在XShell输入命令后会按回车,所以要把获取的命令去掉末尾的 \n 。命令带有空格,所以用fgets从键盘获取命令。解析命令如 ls -al ,根据空格把命令分割成子字符串,放入g_argv[]中。函数strtok用于切分字符串。
//获取命令
bool GetCommand(char* command,int size)
{
char* s = fgets(command,size,stdin);
if(s==NULL)
return false;
int len = strlen(command);
command[len-1]='\0';
if(strlen(command)==0)
return false;
return true;
}
//解析命令 ls -al
#define DELIM " "
bool CommandParse(char* command)
{
g_argc=0;
g_argv[g_argc++]=strtok(command,DELIM);
while((bool)(g_argv[g_argc] = strtok(NULL,DELIM)))
g_argc++;
//printf("%d\n",g_argc);
return g_argc>0?true:false;
}
封装 Execute() 函数,在该函数中创建子进程,让子进程去执行我们输入的命令。
int Execute()
{
pid_t id = fork();
if(id<0)
exit(1);
else if(id==0)
{
//printf("子进程Execute()\n");
execvp(g_argv[0],g_argv);
exit(1);
}
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0)
{
last_exit_code = WEXITSTATUS(status);
}
return 0;
}
在XShell中,我们输入命令,命令执行完毕后又会显示出一行命令行提示符,这本质就是死循环。所以main函数如下。
int main()
{
//初始化环境变量表
InitEnv();
//PrintG_env();
while(1)
{
//输出命令行提示符
PrintCommandLinePrompt();
//获取命令
char command[COMMAND_SIZE];
if(!GetCommand(command,sizeof(command)))
continue;
//命令解析
if(!CommandParse(command))
continue;
//PrintG_argv();
//内建命令与别名
if(CheckAndExecBuilt()) //内建命令返回true,不创建子进程
continue;
//执行命令
Execute();
}
return 0;
}
运行结果如下,能正常使用外部命令。

像 cd 、echo 、export 等内建命令就不能通过在子进程中调用execvp来使用。所以需要另外实现。
cd 改变当前所处路径,需要调用 chdir() 修改进程内核层面的当前工作目录。XShell中 cd 和 cd ~ 跳转到家目录,cd - 跳转到上一次所处目录,cd .. 跳转到上级目录。
echo 目前只考虑打印字符串 echo "..." 、打印退出码 echo $? 、打印环境变量 echo $env 。
对于 export ,我们输入命令 export X=999 ,只需把g_argv[1]拷贝到g_env[g_envs]中,再调用putenv注册环境变量即可。
char oldpwd[1024];
void Cd()
{
const char* pwd=GetPwd();
//printf("pwd: %s\n",pwd);
if(g_argc==1)
{
const char* home=GetHome();
if(strcmp(home,"None")==0)
exit(1);
chdir(home);
}
else
{
if(strcmp(g_argv[1],"~")==0)
{
const char* home=GetHome();
if(strcmp(home,"None")==0)
exit(1);
chdir(home);
}
else if(strcmp(g_argv[1],"-")==0)
{
if(strlen(oldpwd)==0)
return;
printf("oldpwd: %s\n",oldpwd);
chdir(oldpwd);
}
else if(strcmp(g_argv[1],"..")==0)
{
int len=strlen(pwd);
int i=len-1;
while(pwd[i]!='/')
i--;
char dest[1024];
memset(dest,0,sizeof(dest));
strncpy(dest,pwd,i+1);
chdir(dest);
}
else
{
chdir(g_argv[1]);
}
}
strcpy(oldpwd,pwd);
}
void Echo()
{
//echo "..."
//echo $?
//echo $env
char* s = g_argv[1];
if(strcmp(s,"$?")==0)
{
std::cout<<last_exit_code<<std::endl;
last_exit_code=0;
}
else if(s[0]=='$')
{
char* env_val=getenv(&s[1]);
std::cout<<env_val<<std::endl;
}
else
{
printf("%s ",&g_argv[1][1]);
for(int i=2;i<g_argc-1;i++)
printf("%s ",g_argv[i]);
g_argv[g_argc-1][strlen(g_argv[g_argc-1])-1]='\0';
printf("%s\n",g_argv[g_argc-1]);
}
}
bool CheckAndExecBuilt()
{
char* cmd=g_argv[0];
if(strcmp(cmd,"cd")==0)
{
Cd();
return true;
}
else if(strcmp(cmd,"echo")==0)
{
Echo();
return true;
}
else if(strcmp(cmd,"export")==0)
{
g_env[g_envs]=(char*)malloc(strlen(g_argv[1])+1);
strcpy(g_env[g_envs],g_argv[1]);
g_envs++;
g_env[g_envs]=NULL;
putenv(g_env[g_envs-1]);
return true;
}
return false;
}
总的代码如下。
#Makefile
myshell:myshell.cc
g++ $^ -o $@ -std=c++11
.PHONY:clean
clean:
rm -f myshell
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "
//命令行参数
#define ARGV_SIZE 128
char* g_argv[ARGV_SIZE];
int g_argc=0;
//环境变量
#define ENV_SIZE 128
char* g_env[ENV_SIZE];
int g_envs=0;
int last_exit_code=0;//最近退出码
const char* GetUser()
{
const char* user = getenv("USER");
return user == NULL?"None":user;
}
const char* GetHome()
{
const char* home = getenv("HOME");
return home == NULL?"None":home;
}
const char* GetHostName()
{
const char* host = getenv("HOSTNAME");
return host == NULL?"None":host;
}
char cwd[1024]; //当前工作路径
char cwd_env[1024];//转成环境变量形式
const char* GetPwd()
{
memset(cwd,0,sizeof(cwd));
const char* pwd = getcwd(cwd,sizeof(cwd));
if(pwd!=NULL)
{
if(strcmp(pwd,GetHome())==0)
return pwd;
snprintf(cwd_env,sizeof(cwd_env),"PWD=%s",cwd);
putenv(cwd_env);
return pwd;
}
return "None";
}
const char* DirName(const char* pwd)
{
if(pwd==NULL)
return "None";
if(strcmp(pwd,"/")==0 || strcmp(pwd,"~")==0)
return pwd;
int i=strlen(pwd)-1;
while(pwd[i]!='/')
i--;
return &pwd[++i];
}
void MakeCommandLinePrompt(char* prompt,int size)
{
snprintf(prompt,size,FORMAT,GetUser(),GetHostName(),DirName(GetPwd()));
}
void PrintCommandLinePrompt()
{
char prompt[COMMAND_SIZE];
memset(prompt,0,sizeof(prompt));
MakeCommandLinePrompt(prompt,sizeof(prompt));
printf("%s",prompt);
fflush(stdout);
}
//获取命令
bool GetCommand(char* command,int size)
{
char* s = fgets(command,size,stdin);
if(s==NULL)
return false;
int len = strlen(command);
command[len-1]='\0';
if(strlen(command)==0)
return false;
return true;
}
//解析命令 ls -al
#define DELIM " "
bool CommandParse(char* command)
{
g_argc=0;
g_argv[g_argc++]=strtok(command,DELIM);
while((bool)(g_argv[g_argc] = strtok(NULL,DELIM)))
g_argc++;
//printf("%d\n",g_argc);
return g_argc>0?true:false;
}
char oldpwd[1024];
void Cd()
{
const char* pwd=GetPwd();
//printf("pwd: %s\n",pwd);
if(g_argc==1)
{
const char* home=GetHome();
if(strcmp(home,"None")==0)
exit(1);
chdir(home);
}
else
{
if(strcmp(g_argv[1],"~")==0)
{
const char* home=GetHome();
if(strcmp(home,"None")==0)
exit(1);
chdir(home);
}
else if(strcmp(g_argv[1],"-")==0)
{
if(strlen(oldpwd)==0)
return;
printf("oldpwd: %s\n",oldpwd);
chdir(oldpwd);
}
else if(strcmp(g_argv[1],"..")==0)
{
int len=strlen(pwd);
int i=len-1;
while(pwd[i]!='/')
i--;
char dest[1024];
memset(dest,0,sizeof(dest));
strncpy(dest,pwd,i+1);
chdir(dest);
}
else
{
chdir(g_argv[1]);
}
}
strcpy(oldpwd,pwd);
}
void Echo()
{
//echo "..."
//echo $?
//echo $env
char* s = g_argv[1];
if(strcmp(s,"$?")==0)
{
std::cout<<last_exit_code<<std::endl;
last_exit_code=0;
}
else if(s[0]=='$')
{
char* env_val=getenv(&s[1]);
std::cout<<env_val<<std::endl;
}
else
{
printf("%s ",&g_argv[1][1]);
for(int i=2;i<g_argc-1;i++)
printf("%s ",g_argv[i]);
g_argv[g_argc-1][strlen(g_argv[g_argc-1])-1]='\0';
printf("%s\n",g_argv[g_argc-1]);
}
}
bool CheckAndExecBuilt()
{
char* cmd=g_argv[0];
if(strcmp(cmd,"cd")==0)
{
Cd();
return true;
}
else if(strcmp(cmd,"echo")==0)
{
Echo();
return true;
}
else if(strcmp(cmd,"export")==0)
{
g_env[g_envs]=(char*)malloc(strlen(g_argv[1])+1);
strcpy(g_env[g_envs],g_argv[1]);
g_envs++;
g_env[g_envs]=NULL;
putenv(g_env[g_envs-1]);
return true;
}
return false;
}
void InitEnv()
{
extern char** environ;
memset(g_env,0,sizeof(g_env));
g_envs=0;
for(int i=0;environ[i];i++)
{
g_env[i] = (char*)malloc(strlen(environ[i])+1); //
strcpy(g_env[i],environ[i]);
g_envs++;
}
g_env[g_envs]=NULL;
for(int i=0;i<g_envs;i++)
putenv(g_env[i]);
environ=g_env;
}
int Execute()
{
pid_t id = fork();
if(id<0)
exit(1);
else if(id==0)
{
//printf("子进程Execute()\n");
execvp(g_argv[0],g_argv);
exit(1);
}
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0)
{
last_exit_code = WEXITSTATUS(status);
}
return 0;
}
void PrintG_env()
{
std::cout<<"g_envs = "<<g_envs<<std::endl;
for(int i=0;g_env[i];i++)
printf("g_env[%d]: %s\n",i,g_env[i]);
printf("\n");
}
void PrintG_argv()
{
std::cout<<"g_argc = "<<g_argc<<std::endl;
for(int i=0;g_argv[i];i++)
printf("g_argv[%d]: %s\n",i,g_argv[i]);
printf("\n");
}
int main()
{
//初始化环境变量表
InitEnv();
//PrintG_env();
while(1)
{
//输出命令行提示符
PrintCommandLinePrompt();
//获取命令
char command[COMMAND_SIZE];
if(!GetCommand(command,sizeof(command)))
continue;
//命令解析
if(!CommandParse(command))
continue;
//PrintG_argv();
//内建命令与别名
if(CheckAndExecBuilt()) //内建命令返回true,不创建子进程
continue;
//执行命令
Execute();
}
return 0;
}

更多推荐

所有评论(0)