Linux编程入门(18)-进程(六)实现 shell

什么是 shell

Linux 系统中,shell 是一个特殊的应用程序,它可以管理进程和运行程序。Linux 中有很多种 shell,每种都有自己的风格和优势。常用的 shell 都有三个主要的功能:

  • 运行程序

例如,ls、cd、date 等都是一些普通程序,用 C 语言编写,并被编译成机器语言。shell 将它们载入内存并运行。也可以认为 shell 是一个程序启动器。

  • 管理输入和输出

shell 不仅可以运行程序,还可以使用符号 <、> 和 | 将输入、输出重定向。告诉 shell 将进程的输入和输出连接到一个文件,或者是其他的进程。

  • 可编程

shell 也是带有变量和流程控制的编程语言。

本篇文章主要讲解 shell 是如何运行一个程序的。对前面学习的进程相关的内容做一个总结。

下面我们分析一下,shell 运行程序的流程和原理。

shell 如何运行程序

shell 首先会打印提示符 $ 或 #,输入命令后,shell 就会运行这个命令,然后shell 再次打印提示符,如此反复。其背后到底发生了什么呢?

在这里插入图片描述

一个 shell 的主循环执行下面的 4 步:

(1)用户输入命令。

(2)shell 建立一个新进程来运行这个程序。

(3)shell 将程序从磁盘载入。

(4)程序在它的进程中运行,直到结束。

整个过程的伪代码如下:

while(! end_of_input)
{
	get command /* 获取命令 */
	execute command  /* 执行命令 */
	wait for command to finish /* 等待命令执行结束 */
}

考虑如下指令:

$ ls
cutecom.log  Downloads         minicom.log  Public  Templates  work
Desktop      examples.desktop  Music        smb     test
Documents    learn             Pictures     snap    Videos
$ ps
  PID TTY          TIME CMD
 2504 pts/0    00:00:00 bash
 2799 pts/0    00:00:00 ps
$ 

可以用下图来展示一下事件发生的次序。其中,时间从左向右消逝。

在这里插入图片描述

shell 由标识为 sh 的方块代表。shell 读入用户输入的字符串 “ls”。shell 建立一个新进程,然后在这个进程中运行 ls 程序,并等待其运行结束。

要实现一个 shell,需要用到前面学过的:

  • 运行一个程序
  • 建立一个进程
  • 等待进程退出

实现 shell 分析

1. 运行一个程序

运行一个程序可以调用 execvp() 函数来完成。详细介绍可参考

Linux编程入门(17)-进程(五)运行全新的程序

其函数原型如下:

#include <unistd.h>

int execvp(const char *file, char *const argv[]);

execvp() 载入由 file 指定的程序到当前进程,然后试图运行它。将字符串列表 argv 传递给要运行的程序。

execvp() 在环境变量 PATH 所指定的路径中查找 file 文件。

2. 创建新进程

创建一个进程可以用 fork() 函数,以及进程退出的相关内容,可参考:

Linux编程入门(15)-进程(三)编程

系统函数 fork() 的原型为:

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

pid_t fork(void);

使用 fork() 创建一个新进程,在新的进程中调用 execvp() 函数,可以执行任何用户指明的程序。

如此,shell 运行程序时,不会影响其本身,并且可以运行多条命令。

进程退出,可以调用 exit() 函数,将状态信息传递给父进程。参数 status 表示进程的终止状态。

#include <stdlib.h>

void exit(int status);

3. 等待子进程退出

进程可以调用 wait() 函数来等待其子进程退出。详细内容可参考:

Linux编程入门(16)-进程(四)等待子进程

wait() 会暂停调用它的进程直到子进程结束,然后取得子进程结束时传递给 exit() 的值。

函数原型为:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);

父进程要获取子进程的退出状态,需要将一个整型变量的地址传递给 wait() 函数。

4. 综合分析

shell 实现运行一个程序的功能,其流程可以概括为:

  • shell 调用 fork 创建新进程
  • 用 exec 在新进程中运行用户指定的程序
  • shell 通过 wait 等待新进程结束。
  • 通过 wait 可以取得进程退出的状态信息。

在这里插入图片描述

shell 实现代码

让我们综合运用前边学习的进程知识,来实现一个简易版的 shell。

具体的代码如下,代码中含有注释:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>

#define MAXARGS        20
#define ARGLEN         100

char *makestring(char *buf);
void execute(char *argv[]);

/* 主函数 */
int main()
{
	/* 字符串数组 */
	char *arglist[MAXARGS + 1];
	int numargs;
	char argbuf[ARGLEN]; /* 接收用户输入 */

	numargs = 0;
	while(numargs < MAXARGS)
	{
		/* 打印提示符 */
		printf("Arg[%d]: ", numargs);
		/* 读取用户输入 */
		if(fgets(argbuf, ARGLEN, stdin) && (*argbuf != '\n'))
		{
			/* 填充字符串数组 */
    	arglist[numargs++] = makestring(argbuf);
		}
		else
		{
			if(numargs > 0)
			{
				/* 字符串结尾添加 NULL */
				arglist[numargs] = NULL;
				/* 执行程序 */
				execute(arglist);
				numargs = 0;
			}
		}
	}
	return 0;
}

/* 执行程序,程序参数为字符串列表 */
void execute(char *argv[])
{
	pid_t pid;
	int exitstatus;
	
	/* 创建新进程 */
	pid = fork();
	switch(pid)
	{
		/* 出错了 */
		case -1:
		{
			perror("fork failed");
			exit(1);
		}
		case 0:
		{
			/* 子进程,运行新程序 */
			execvp(argv[0], argv);
			perror("execv failed");
			exit(1);
		}
		default:
		{
			/* 父进程等待子进程退出 */
			while(wait(&exitstatus) != pid);
			/* 打印运行结果 */
			printf("child exited with status: %d, %d\n", (exitstatus >> 8), (exitstatus & 0xff));
		}
	}
}

/* 创建字符串 */
char *makestring(char *buf)
{
	char *p = NULL;	

	buf[strlen(buf) - 1] = '\0';
	p = malloc(strlen(buf) + 1);
	if(p == NULL)
	{
		fprintf(stderr, "no memory\n");
		exit(1);
	}
	strcpy(p, buf);
	return p;
}

编译、运行。 并测试其运行情况:

在这里插入图片描述

这个 shell 程序可以接受程序名称、参数列表、运行程序、报告结果;然后再重新接收和运行其他程序。

ok,我们之前学过的内容,得到了综合运行。并且还学习了一下 shell 执行程序的原理。

总结

在此总结一下进程相关的基础内容:

  • 进程是运行一个程序所需的内存空间和其他资源的集合。
  • Linux 通过将可执行代码载入进程并执行它,来运行一个程序。
  • 每个运行中的程序在自己的进程中。
  • 进程都有唯一的进程 ID、所有者、大小等属性。
  • 系统函数 fork 可以创建一个新进程。
  • 一个程序可以通过 exec 函数在当前进程中执行一个新程序
  • 一个程序通过调用 wait 来等待子进程结束。
  • 调用进程能将一个字符串列表传递给新程序。
  • 新程序能通过 exit 回传一个 8 位长的值。
  • shell 通过调用 fork、exec、wait 来运行程序。

Linux 中一个重要的知识点 “进程”,先介绍到这。后面进行其他方面的学习。加油~


关注公众号【一起学嵌入式】,获取更多精彩内容
在这里插入图片描述

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zsky_01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值