用fork,exec,wait实现一个自己的shell
记录一下编写linux内核课程中遇到的问题和解决过程。
一、程序出不去?
先看程序
// myshell.c
#include <stdio.h>
// used by exit
#include <stdlib.h>
// used by func fork,execlp,wait
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
int main(){
// 记录子进程结束状态
int status_c;
while (1)
{
pid_t pid_c, pid_cid;
// 创建子进程
pid_c = fork();
// 判断fork调用结果
// 出错
if (pid_c == -1)
{
printf("Error when fork called\n");
exit(-1);
}
// 子进程 处理命令行传入参数
else if (pid_c == 0)
{
// 接受命令行传入参数,存入字符串
char cmd_get[20];
// 模拟当前用户
printf("h_zeng@ubuntu:");
// 读取传入命令
scanf("%s", cmd_get);
if (strcmp(cmd_get, "exit") == 0)
{
printf("exit my shell\n");
exit(1);
}
//printf("pid when child process : %d\n", getpid());
// 处理echo命令
if (strcmp(cmd_get, "echo") == 0)
{
scanf("%s", cmd_get);
// execl执行失败,返回
if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
{
printf("Error when execl called\n");
return -1;
}
exit(0);
}
// 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
// execlp执行失败,返回
else if (execlp(cmd_get, cmd_get, NULL) == -1)
{
//printf("cmd_get : %s\n", cmd_get);
printf("Error when execlp called\n");
exit(-1);
}
exit(0);
}
// 父进程 等待子进程结束
else
{
// 等待子程序返回
pid_cid = wait(&status_c);
//printf("pid when wait : %d\n", getpid());
// wait调用失败
if(pid_cid == -1)
{
printf("Error when wait called\n");
break;
}
// wait调用成功
// 子程序非正常结束返回,结束shell
//printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
//printf("status_c : %d\n", WEXITSTATUS(status_c));
if(status_c < 0)
{
printf("Exception occurs when child process end\n");
break;
}
}
}
return 0;
}
编译运行,得到如图结果,普通的命令包括echo等都能正常完成,但是到exit的时候程序执行完了可以看到开头仍是我程序里写的模拟用户。
回到程序,分析一波,emmm,啥都没分析出来,参考网上的类似程序实现,发现他把第一个命令行传入参数的接收操作放在了fork之前。嗯,在产生子程序之前判断了读入exit,然后结束整个程序,应该可行。改代码!
二、多出来的子程序?
// myshell.c
int main(){
// 记录子进程结束状态
int status_c;
while (1)
{
pid_t pid_c, pid_cid;
// 接受命令行传入参数,存入字符串
char cmd_get[20];
// 模拟当前用户
printf("h_zeng@ubuntu:");
// 读取传入命令
scanf("%s", cmd_get);
if (strcmp(cmd_get, "exit") == 0)
{
printf("exit my shell\n");
exit(1);
}
// 创建子进程
pid_c = fork();
// 判断fork调用结果
// 出错
if (pid_c == -1)
{
printf("Error when fork called\n");
exit(-1);
}
// 子进程 处理命令行传入参数
else if (pid_c == 0)
{
//printf("pid when child process : %d\n", getpid());
// 处理echo命令
if (strcmp(cmd_get, "echo") == 0)
{
scanf("%s", cmd_get);
// execl执行失败,返回
if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
{
printf("Error when execl called\n");
return -1;
}
exit(0);
}
// 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
// execlp执行失败,返回
else if (execlp(cmd_get, cmd_get, NULL) == -1)
{
//printf("cmd_get : %s\n", cmd_get);
printf("Error when execlp called\n");
exit(-1);
}
exit(0);
}
// 父进程 等待子进程结束
else
{
// 等待子程序返回
pid_cid = wait(&status_c);
//printf("pid when wait : %d\n", getpid());
// wait调用失败
if(pid_cid == -1)
{
printf("Error when wait called\n");
break;
}
// wait调用成功
// 子程序非正常结束返回,结束shell
//printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
//printf("status_c : %d\n", WEXITSTATUS(status_c));
if(status_c < 0)
{
printf("Exception occurs when child process end\n");
break;
}
}
}
return 0;
}
再次编译运行,得到如图结果,exit能正常推出了,但是到echo的时候,正常打印出hi之后,execlp函数出现了错误,并且下面一行打印了两次模拟用户信息。
回到程序,分析一波,很容易猜到程序比预想的“分裂”了多一次,导致把第二次读入的hi当成命令传入给execlp函数,但是突然发现一个问题,execlp函数出错后,子进程返回异常,父进程接收到异常应该结束掉整个shell程序,但是实际并没有。于是再次改代码,加上一些printf打印pid等信息看看猜想正确与否,以及为什么程序没有因为异常结束。
// myshell.c
int main(){
// 记录子进程结束状态
int status_c;
while (1)
{
pid_t pid_c, pid_cid;
// 接受命令行传入参数,存入字符串
char cmd_get[20];
// 模拟当前用户
printf("h_zeng@ubuntu:");
// 读取传入命令
scanf("%s", cmd_get);
if (strcmp(cmd_get, "exit") == 0)
{
printf("exit my shell\n");
exit(1);
}
// 创建子进程
pid_c = fork();
// 判断fork调用结果
// 出错
if (pid_c == -1)
{
printf("Error when fork called\n");
exit(-1);
}
// 子进程 处理命令行传入参数
else if (pid_c == 0)
{
printf("pid when child process : %d\n", getpid());
// 处理echo命令
if (strcmp(cmd_get, "echo") == 0)
{
scanf("%s", cmd_get);
// execl执行失败,返回
if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
{
printf("Error when execl called\n");
return -1;
}
exit(0);
}
// 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
// execlp执行失败,返回
else if (execlp(cmd_get, cmd_get, NULL) == -1)
{
printf("cmd_get : %s\n", cmd_get);
printf("Error when execlp called\n");
exit(-1);
}
exit(0);
}
// 父进程 等待子进程结束
else
{
// 等待子程序返回
pid_cid = wait(&status_c);
printf("pid when wait : %d\n", getpid());
// wait调用失败
if(pid_cid == -1)
{
printf("Error when wait called\n");
break;
}
// wait调用成功
// 子程序非正常结束返回,结束shell
printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
// printf("status_c : %d\n", WEXITSTATUS(status_c));
printf("status_c : %d\n", status_c);
if(status_c < 0)
{
printf("Exception occurs when child process end\n");
break;
}
}
}
return 0;
}
编译运行,得到如图结果,可以看到确实在用户输入echo hi之后有两个子程序执行了,第一个子进程正常结束,pid被父进程的wait函数捕获,值也一致,第二个子进程接收到了错误了hi为命令行输入,抛出execlp函数错误。然后--------------!wait函数得到的子进程返回状态居然是65280!!
三、wait函数status
好了,知道第一次为什么程序结束不了了,父进程在子进程异常结束后,wait函数得到的子进程结束状态并不是想当然的 -1。 随即查阅了wait函数status参数的意义,发现要用WEXITSTATUS等宏来解析。重新编写程序。
// myshell.c
int main(){
// 记录子进程结束状态
int status_c;
while (1)
{
pid_t pid_c, pid_cid;
// 创建子进程
pid_c = fork();
// 判断fork调用结果
// 出错
if (pid_c == -1)
{
printf("Error when fork called\n");
exit(-1);
}
// 子进程 处理命令行传入参数
else if (pid_c == 0)
{
printf("pid when child process : %d\n", getpid());
// 接受命令行传入参数,存入字符串
char cmd_get[20];
// 模拟当前用户
printf("h_zeng@ubuntu:");
// 读取传入命令
scanf("%s", cmd_get);
if (strcmp(cmd_get, "exit") == 0)
{
printf("exit my shell\n");
exit(1);
}
// 处理echo命令
if (strcmp(cmd_get, "echo") == 0)
{
scanf("%s", cmd_get);
// execl执行失败,返回
if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
{
printf("Error when execl called\n");
return -1;
}
exit(0);
}
// 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
// execlp执行失败,返回
else if (execlp(cmd_get, cmd_get, NULL) == -1)
{
printf("cmd_get : %s\n", cmd_get);
printf("Error when execlp called\n");
exit(-1);
}
exit(0);
}
// 父进程 等待子进程结束
else
{
// 等待子程序返回
pid_cid = wait(&status_c);
printf("pid when wait : %d\n", getpid());
// wait调用失败
if(pid_cid == -1)
{
printf("Error when wait called\n");
break;
}
// wait调用成功
// 子程序非正常结束返回,结束shell
printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
printf("status_c : %d\n", WEXITSTATUS(status_c));
if(WEXITSTATUS(status_c) != 0 && WEXITSTATUS(status_c) != 1)
{
printf("Exception occurs when child process end\n");
break;
}
else if(WEXITSTATUS(status_c) == 1)
{
printf("the shell ended cause you typed 'exit' \n");
break;
}
}
}
return 0;
}
编译运行,得到如图结果,程序可以正常运行,WEXITSTATUS宏可以将子进程结束状态与子程序exit中整型变量对应。
注意:如果exit(-1),那么解析出来的是255,但是其他机器上我不知道,保险起见把除0/1以外的返回视为异常。
四、总结
程序整体思路是利用fork产生子进程,处理命令行输入参数的处理,然后结束子进程,父进程得到子进程返回状态,判断整体程序是否应该继续执行