Shell原理以及自己编写一个简单的Shell
程序和进程
1.程序
程序(program)是一个存储在磁盘上某个目录中的可执行文件。 内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程 序
2.进程和进程ID
程序的执行实例被称为进程(process)。本书的每一页几乎都会使 用这一术语。某些操作系统用任务(task)表示正在被执行的程序。 UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程 ID(process ID)。进程 ID总是一个非负整数。
我们写一个简单的程序获取进程pid
#include <unistd.h>
#include <stdio.h>
#include <iostream>
#include <memory>
int main() {
long pid = 0;
printf("this process show the pid\n");
printf("pid : %ld\n",static_cast<long>(getpid()));
return 0;
}
![](https://img.haomeiwen.com/i4064394/0fe6cf2a790b697d.png)
3.进程控制
有3个用于进程控制的主要函数:fork、exec和waitpid。(exec函数 有7种变体,但经常把它们统称为exec函数。)
事实上我们在shell执行程序时就是shell fork出一个子进程,exec执行
我们可以写一个简单的shell从标准输入读取命令,然后执行这些命令。
它类似于shell 程序的基本实施部分。
#include "../all.h"
#include <stdio.h>
#include "sys/wait.h"
#include <iostream>
int main() {
char buf[MAXLINE];
pid_t pid;
int status;
printf("myshell$ ");
while(fgets(buf, MAXLINE, stdin) != NULL) {
if(buf[strlen(buf) - 1] == '\n') {
buf[strlen(buf)-1] = 0; //replace the newline with null
}
if((pid = fork()) < 0 ) {
printf("fork error\n");
exit(-1);
} else if (pid == 0) { //child
execlp(buf,buf,(char*)0);
printf("couldn't excute %s\n", buf);
exit(127);//子进程退出,返回127
}
/*parent*/
if((pid = waitpid(pid, &status, 0)) < 0) {
printf("wait pid error\n");
}
printf("myshell$ ");
}
return 0;
}
![](https://img.haomeiwen.com/i4064394/5365064150b08e42.png)
在mac上甚至可以运行任何软件
![](https://img.haomeiwen.com/i4064394/5b6dbfebc10e9755.png)
在这个30行的程序中,有很多功能需要考虑。
•用标准I/O函数fgets从标准输入一次读取一行。当键入文件结束符 (通常是Ctrl+D)作为行的第一个字符时,fgets 返回一个 null 指针,于 是循环停止,进程也就终止。
•因为fgets返回的每一行都以换行符终止,后随一个null字节,因此 用标准C函数strlen计算此字符串的长度,然后用一个null字节替换换行 符。这样做是因为execlp函数要求的参数是以null结束的而不是以换行符 结束的。
•调用fork创建一个新进程。新进程是调用进程的一个副本,我们称 调用进程为父进程,新创建的进程为子进程。fork对父进程返回新的子 进程的进程ID(一个非负整数),对子进程则返回0。因为fork 创建一 个新进程,所以说它被调用一次(由父进程),但返回两次(分别在父 进程中和在子进程中)。
•在子进程中,调用 execlp 以执行从标准输入读入的命令。这就用新的程序文件替换了子进程原先执行的程序文件。fork和跟随其后的 exec两者的组合就是某些操作系统所称的产生(spawn)一个新进程。在
UNIX系统中,这两部分分离成两个独立的函数。
•子进程调用 execlp 执行新程序文件,而父进程希望等待子进程终 止,这是通过调用waitpid实现的,其参数指定要等待的进程(即pid参数 是子进程ID)。waitpid函数返回子进程的终止状态(status 变量)。在 我们这个简单的程序中,没有使用该值。
如果需要,可以用此值准确地判定子进程是如何终止的。
当然这个小shell并不能执行复杂的指令,只能执行 ls而不能执行ls -a这种组合命令。
该程序的最主要限制是不能向所执行的命令传递参数。例如不能 指定要列出目录项的目录名,只能对工作目录执行ls命令。为了传递参 数,先要分析输入行,然后用某种约定把参数分开(可能使用空格或制 表符),再将分隔后的各个参数传递给execlp函数。尽管如此,此程序 仍可用来说明UNIX系统的进程控制功能。
4.线程和线程id
通常,一个进程只有一个控制线程(thread)—某一时刻执行的一 组机器指令。对于某些问题,如果有多个控制线程分别作用于它的不同 部分,那么解决起来就容易得多。另外,多个控制线程也可以充分利用 多处理器系统的并行能力。
一个进程内的所有线程共享同一地址空间(堆)、文件描述符、栈以及与 进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享 数据时需要采取同步措施以避免不一致性。(volatile或加锁)
- 1、首先是定义
进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
-
2、一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
-
3、线程是一种轻量级的进程,与进程相比,线程给操作系统带来侧创建、维护、和管理的负担要轻,意味着线程的代价或开销比较小。
-
4、线程没有地址空间,线程包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权,线程文本包含在他的进程 的文本片段中,进程拥有的所有资源都属于线程。所有的线程共享进程的内存和资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段, 寄存器的内容,栈段又叫运行时段,用来存放所有局部变量和临时变量。
-
5、父和子进程使用进程间通信机制,同一进程的线程通过读取和写入数据到进程变量来通信。
-
6、进程内的任何线程都被看做是同位体,且处于相同的级别。不管是哪个线程创建了哪一个线程,进程内的任何线程都可以销毁、挂起、恢复和更改其它线程的优先权。线程也要对进程施加控制,进程中任何线程都可以通过销毁主线程来销毁进程,销毁主线程将导致该进程的销毁,对主线程的修改可能影响所有的线程。
-
7、子进程不对任何其他子进程施加控制,进程的线程可以对同一进程的其它线程施加控制。子进程不能对父进程施加控制,进程中所有线程都可以对主线程施加控制。
-
相同点:
进程和线程都有ID/寄存器组、状态和优先权、信息块,创建后都可更改自己的属性,都可与父进程共享资源、都不能直接访问其他无关进程或线程的资源
控制线程的函数与控制进程的函数类似,但另有一套。线程模型是 在进程模型建立很久之后才被引入到UNIX系统中的,然而这两种模型 之间存在复杂的交互