深入理解计算机系统

Shell原理以及自己编写一个简单的Shell

2020-01-13  本文已影响0人  MachinePlay

程序和进程
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;
}

getpid

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;
}

实现一个简单的myshell

在mac上甚至可以运行任何软件


image.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或加锁)

进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。

线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。

进程和线程都有ID/寄存器组、状态和优先权、信息块,创建后都可更改自己的属性,都可与父进程共享资源、都不能直接访问其他无关进程或线程的资源

控制线程的函数与控制进程的函数类似,但另有一套。线程模型是 在进程模型建立很久之后才被引入到UNIX系统中的,然而这两种模型 之间存在复杂的交互

上一篇 下一篇

猜你喜欢

热点阅读