写一个自己的shell

shell

什么是shell?
shell就像是在linux中的一个媒婆,我们给shell命令,shell来告诉系统我们需要做什么,为我们牵线,实现调用操作系统提供给我们的接口,shell提供了你与操作系统之间通讯的方式。
linux内核(kernel),我们无法直接触碰到,而是通过shell,shell是操作系统最外面的一层。shell管理你与操作系统之间的交互:等待你输入,向操作系统解释你的输入,并且处理各种各样的操作系统的输出结果,解释给你看。
下图中红圈的东西即shell展现在我们面前的形式。
shell

自己写简单shell的准备工作

fork()函数创建子进程

fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。
fork()举例请移步我的关于进程的博客

wait()函数

函数原型:

	pid_t wait(int* status);
	pid_t waitpid(pid_t pid, int* status, int options);

wait():等待任意一个子进程退出,若没有子进程退出,则一直阻塞等待
waitpid():可以等待指定子进程,也可以等待任意一个子进程退出,可以设置为非阻塞等待
阻塞:为了完成一个功能发起一个函数调用,如果没有完成这功能则一直挂起等待功能完成才返回
非阻塞:为了完成一个功能发起一个函数调用,如果现在不具备完成的条件,则立即返回不等待
选项:

  1. WNOHANG 如果没有子进程退出,则立刻报错返回,如果有则回收资源
  2. WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会

进程退出状态码获取:在wait的参数中存储了子进程的退出原因以及退出码,而参数中只用了低16位两个字节用于存储这些信息
在这低16位中,高8位存储的是退出码,程序运行完毕退出才会有,低7位存储的是异常状况,第8位是core dump标志
这里写图片描述
如何获取
WIFEXITED和WEXITSTATUS一般配合使用

WIFEXTED(status) 这个宏用来获取是否正常退出,正常退出获得true
WEXITSTATUS(status) 该宏只可在WIFEXITED为true时使用,获取正常退出的状态码

WIFSIGNALED和WTERMSIG

WIFSIGNALED(status) 这个宏用来获取是否异常退出,异常退出获得true
WTERMSIG(status) 该宏只可在WIFSIGNALED为true时使用,获取异常退出的状态码

子进程替换代码——exec类函数

exec函数族作用是程序替换,如果替换成功,那么源代码的exec后的代码都不会运行,除非出错,也就是说如果在exec后写printf,不会有任何输出

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* envp[]);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execve(const char* path, char* const argv[], char *const envp[]);

这六个函数名字和参数都相近,用起来也是差不多,其中’l’(list)表示使用参数列表,‘v’(vector)表示参数使用数组,‘p’(path)表示能够自动搜索环境变量PATH,‘e’(env)表示需要自己维护环境变量:

  1. 参数为const char* path的,需要告诉操作系统程序文件的全路径
    参数为const char* file的,不需要告诉路径,只需要文件名即可,会自动到PATH中的路径下查找
  2. execle和execve不从父进程继承环境变量,由用户自己组织环境变量
    其他函数(不以e结尾的函数)从父进程继承环境变量
  3. execl的三个函数更符合我们平时的使用习惯,以ls -l为例,使用时输入int execlp("ls", "ls", "-l");
    execv的三个函数则用指针数组的形式

梳理shell的执行过程

  1. 获取终端输入
  2. 解析输入
  3. 创建一个子进程,子进程进行程序替换
  4. 父进程等待子进程运行完毕,收尸

代码

感谢孙雨墨大佬指出的bug,代码已经修改,向大佬学习

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int argc = 0;
char* argv[32] = {NULL};

int div_commond(char* buf)
{
	if (buf == NULL)
		return -1;
	argc = 0;
	char* front = buf;
	char* back = buf;
	//不断分割命令成指针数组
	while (1)
	{
		while (*front == ' ')
			front++;
		if (*front == '\0')
			break;
		back = front;
		while (*back != ' ' && *back != '\0')
			back++;
		if (*back == '\0')
		{
			argv[argc++] = front;
			break;
		}
		*back = '\0';
		argv[argc++] = front;
		front = back + 1;
	}
	argv[argc++] = NULL;
	if (argc == 1)
		return -1;
	return 0;
}

int exec_cmd()
{
	int pid = fork();
	if (pid < 0)
	{
		perror("fork failed");
		return -1;
	}
	else if (pid == 0)
	{
		//替换进程
		execvp(argv[0], argv);
		exit(0);
	}
	else
	{
		//wait用法,等待子进程退出,退出后一个状态码会存入statu
		//用WIFEXITED这个宏来获取退出值是否是正常退出
		int status;
		wait(&status);
		if (WIFEXITED(status) == 1)
			printf("%s\n", strerror(WEXITSTATUS(status)));
	}
	return 0;
}
	
int main()
{
	char buf[1024];
	while (1)
	{
		memset(buf, 0x00, 1024);
		printf("------shell>>>>>>");
		//读取到\n为止,删除缓冲区数据
		scanf("%[^\n]%*c", buf);
		if (div_commond(buf) != 0)
		{
			char black_hall[100];
			fgets(black_hall, 100, stdin);
			printf("wrong commond\n");
			continue;
		}
		exec_cmd();
	}
	return 0;
}

执行效果

shell演示

发布了89 篇原创文章 · 获赞 96 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/Boring_Wednesday/article/details/81952551