(3)DIY Shell之——创建子进程执行外部命令

Linux TOMORROW 6个月前 (06-23) 926次浏览 0个评论 扫描二维码

 

前面文章已经说到了,shell在执行外部命令的时候,shell的进程本身会被外部命令的可执行程序所取代,从而导致调用外部命令之后的程序都无法被执行。那么我们可以通过创建一个子进程,在子进程执行外部命令的方法来解决这个问题。原来的父进程就继续执行shell的其他的代码,比如等待命令执行完成、等待用户输入其他命令。具体的实现思路如下。

父进程与子进程

首先,要明确进程的概念,进程是系统进行资源分配和调度的一个独立单位。每一个进程都是由其他进程创建产生的,创建新进程的原进程叫父进程;新的进程叫子进程或者孙进程。这些是属于操作系统的内容,计算机基础不好或者没有操作系统的背景知识的话可能难以理解。可以通过下面的的实际例程来帮助理解。

fork()创建子进程

在实际代码中,Linux 系统提供了fork()API 函数,通过调用该函数来创建新的子进程。下面来从一个例程中理解。

#include <unistd.h> 
#include <sys/types.h>
#include <stdio.h>  

int main()
{
	int pid;//process identification,进程的唯一标识号
	
	pid=fork();
	
	if(0==pid)
	{
		printf("I am child process!\n");
	}
	else
	{
		printf("I am parent process!\n");
	}
	
	return 0;
}

编译上面的代码,然后执行,执行结果如下:

(3)DIY Shell 之——创建子进程执行外部命令

 

如果对进程的概念不清楚的话,肯定会疑惑:为什么两个 printf 语句都被执行了?

  • 其实 Fork 会被执行两次,先在父进程里执行创建子进程并返回子进程的 pid,然后在子进程中执行并返回 0。

 

  • 根据返回值的不同就可以判断出是父进程还是子进程,从而执行不同的代码,虽然他们用的是相同的代码段,但却可以执行不同的代码。

 

  • 子进程会继承父进程在执行 fork 之前的所有的东西,包括变量的定义、变量的赋值和其他的东西。再举个例子就好理解了。
#include <unistd.h> 
#include <sys/types.h>
#include <stdio.h>  

int main()
{
	int pid;//process identification,进程的唯一标识号
	int var=66;
	
	pid=fork();
	
	if(0==pid)
	{
		printf("I am child process! pid=%d var=%d\n",pid,var);
	}
	else
	{
		printf("I am parent process! pid=%d var=%d\n",pid,var);
	}
	
	return 0;
}

上面的例程只是多定义了一个变量 var 并将 var 和 pid 的值输出,结果如下:(3)DIY Shell 之——创建子进程执行外部命令

 

  • 父进程执行 fork 之前定义了一个变量 var,并且变量 var 赋值为 66,那么子进程中也会有一个变量 var,在 fork 之后子进程的 var 变量的值也是 66

 

  • 但是如果子进程中对 var 的值进行修改,父进程中的变量 var 的值是不会变;相反,父进程在 fork 之后对 var 的值修改,也不会影响子进程中的 var 的值的。因为,这是两个进程中的 var,虽然名字一样,但是它们所对应的内存空间是不同的。

 

  • 而 pid 的值的不同是因为fork()函数会在父进程和子进程中分别执行,至于怎么实现执行两次那是更深一层的知识了,这里不做讨论。从 pid 的值可以看到,在父进程中 fork 返回的是一个非 0 的整数,而在子进程中返回的是一个 0,那么就可通过判断fork()函数的返回值来判断当前的进程是父进程还是子进程了。

 

  • 也就是说,父进程和子进程的内存空间完全是独立的,子进程只是把父进程的内存空间里的内容复制了一份,所以子进程内存空间的初始状态是和父进程执行 fork 的时候是完全一样的,这就是继承。

 

  • 所以在 fork 之后,父进程干的所有事都跟子进程无关,子进程干的所有事也和父进程无关。

 

  • 下面用一个流程图来帮助理解:(3)DIY Shell 之——创建子进程执行外部命令

 

在子进程中执行外部命令

上面说了那么多,其实还是为了实现可以循环执行外部命令的一个shell。这时候就很明了了 , 只需要把 exec 族函数的调用放到子进程要执行的代码里面就可以了。子进程执行外部命令,父进程等待外部命令执行完之后,接着等待下一个命令的输入。

例如下面的代码,下面的代码是stupidShell源码的简化版,如需完整的代码请到 GitHub 下载:stupidShell 代码仓

pid=fork();

if(0==pid)//execute command in child process
{
	if(0!=execvp(cmd[0],cmd))//execvp 如果找不到对应的可执行文件就会返回非 0 的错误代码
		printf("No such command!\n");
	exit(EXIT_SUCCESS);//结束当前进程
}
else//parent process 
{
	//其他代码,例如等待命令执行完,然后重新执行新的命令
}

现在我们的stupidShell已经实现了重复执行内建命令和外部命令了,shell的基本命令都可以执行了,完整代码参考stupidShell 代码仓。但是,如果要执行含有管道和重定向的一些复杂命令,例如:

ls > a.txt
ls | gerp a > b.txt

就再需要加入更多的东西了。

下一章:DIY Shell 之——管道与重定向(4)

 


TOMORROW 星辰 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:(3)DIY Shell 之——创建子进程执行外部命令
喜欢 (0)
TOMORROW
关于作者:
TOMORROW星辰第一作者。如有疑问或者发现错误,请留言作者。
畅快的水蜜桃发表我的评论  如需接收评论回复通知,请填写正确的 个人信息
取消评论
表情 加粗 斜体 签到