Linux 系统中,shell 是一个特殊的应用程序,它可以管理进程和运行程序。Linux 中有很多种 shell,每种都有自己的风格和优势。常用的 shell 都有三个主要的功能:
例如,ls、cd、date 等都是一些普通程序,用 C 语言编写,并被编译成机器语言。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,需要用到前面学过的:
1. 运行一个程序
运行一个程序可以调用 execvp()
函数来完成。详细介绍可参考
Linux编程入门(17)-进程(五)运行全新的程序
其函数原型如下:
#include
int execvp(const char *file, char *const argv[]);
execvp()
载入由 file 指定的程序到当前进程,然后试图运行它。将字符串列表 argv 传递给要运行的程序。
execvp()
在环境变量 PATH 所指定的路径中查找 file 文件。
2. 创建新进程
创建一个进程可以用 fork() 函数,以及进程退出的相关内容,可参考:
Linux编程入门(15)-进程(三)编程
系统函数 fork() 的原型为:
#include
#include
pid_t fork(void);
使用 fork() 创建一个新进程,在新的进程中调用 execvp()
函数,可以执行任何用户指明的程序。
如此,shell 运行程序时,不会影响其本身,并且可以运行多条命令。
进程退出,可以调用 exit()
函数,将状态信息传递给父进程。参数 status 表示进程的终止状态。
#include
void exit(int status);
3. 等待子进程退出
进程可以调用 wait()
函数来等待其子进程退出。详细内容可参考:
Linux编程入门(16)-进程(四)等待子进程
wait()
会暂停调用它的进程直到子进程结束,然后取得子进程结束时传递给 exit() 的值。
函数原型为:
#include
#include
pid_t wait(int *wstatus);
父进程要获取子进程的退出状态,需要将一个整型变量的地址传递给 wait()
函数。
4. 综合分析
shell 实现运行一个程序的功能,其流程可以概括为:
让我们综合运用前边学习的进程知识,来实现一个简易版的 shell。
具体的代码如下,代码中含有注释:
#include
#include
#include
#include
#include
#include
#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 中一个重要的知识点 “进程”,先介绍到这。后面进行其他方面的学习。加油~