首页 > 代码库 > 进程和程序:编写shell——《Unix/Linux编程实践教程》读书笔记(第8章)

进程和程序:编写shell——《Unix/Linux编程实践教程》读书笔记(第8章)

1、Unix shell的功能

shell是一个管理进程和运行程序的程序。所有常用的shell都有3个主要功能:

(1)运行程序;

(2)管理输入和输出

(3)可编程

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


2、Unix的进程模型

一个程序是存储在文件中的机器指令序列,一般它是由编译器将源代码编译成二进制格式的代码。运行一个程序意味着将这些机器指令序列载入内存然后让处理器(CPU)逐条执行。在Unix术语中,一个可执行程序是一些机器指令机器数据的序列。一个进程是程序运行时的内存空间和设置。数据和程序存储在磁盘文件中,程序在进程中运行。

每个进程都有一个可以唯一标识它的数字,被称为进程ID,一般简称PID;同时也有一个父进程ID(PPID)。每个进程都与一个终端相连,都一个已运行的时间,有优先级,有niceness级别,有大小。。。

Unix系统中的内存分为系统空间和用户空间。进程存在于用户空间。


3、如何执行一个程序

shell打印提示符,用户输入指令,shell就运行这个命令,然后shell再次打印提示符——如此反复。

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

(1)用户键入a.out

(2)shell建立一个新的进程来运行这个出现

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

(4)程序在它的进程中运行知道结束

即:

while (!end_of_input)
    get command
    execute command
    wait for command to finish


一个程序如何运行另一个程序?答案是程序通过调用exec家族函数:
man 3 exec
#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
           ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
                char *const envp[]);
/*
 * The exec() family of functions replaces the current process
 * image with a new process image. The functions described
 * in this manual page are front-ends for execve(2).
 */


4、如何创建一个进程

一个进程调用fork来复制自己。进程调用fork,当控制转移到内核中的fork代码后,内核做:

(1)分配新的内存块和内核数据结构

(2)复制原来的进程到新的进程

(3)向运行进程添加新的进程

(4)将控制返回给两个进程

man 2 fork
#include <unistd.h>
pid_t fork(void);


5、父进程和子进程之间如何通信(父进程如何等待子进程的退出)

进程调用wait等待子程序的结束。系统调用wait做两件事。首先,wait暂停调用它的进程直到子进程介绍。然后,wait取得子进程结束时传给exit的值。这样wait执行两个操作:通知和通信。

wait的目的之一是通知父进程子进程结束运行了。它的第二个目的是告诉父进程子进程是如何结束的。一个进程以3中方式(成功、失败或死亡)之一结束。按照Unix惯例,成功的程序调用exit(0)或者从main函数中return 0;程序遇到问题而要退出调用exit时传给它一个非零的值。

父进程调用wait时传一个整型变量地址给函数。内核将子进程的u提出状态保存在这个变量中。如果子进程调用exit退出,那么内核把exit的返回值放到这个整型变量中;如果进程是被杀死的,那么内核将信号序号存放在这个变量中。这个整数由3部分组成——高8位记录退出值,低7位记录信号序号,第7位则用来指明发生错误并产生了内核映像(core dump)。

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

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);


6、进程死亡:exit和_exit

exit是fork的逆操作,进程通过调用exit来停止运行。fork创建一个进程,exit删除一个进程。基本上是这样。

exit刷新所以的流,调用atexit和on_exit注册的函数,执行当前系统定义的其他与exit相关的操作。然后调用_exit。系统函数_exit是一个内核操作,这个操作处理所有分配给这个进程的内存,关闭所有这个进程打开的文件,释放所有内核用来管理和维护这个进程的数据结构。

那些已经死亡但是没有给exit赋值的进程被称为僵尸进程。

系统调用_exit终止当前进程并执行所有必须的清理工作:

(1)关闭所有文件描述符和目录描述符

(2)将该进程的PID置为init进程的PID

(3)如果父进程调用wait或waitpid来等待子进程结束,则通知父进程

(4)向父进程发送SIGCHLD信号

下图摘自书本,为shell的fork()、exec()和exit()循环

/*
 * prompting shell version 02
 *
 * Solves the ‘one-shot‘ problem of version 01
 * Uses execvp(), but fork()s first so that the
 * shell waits around to perform another command
 * New problem: shell cathes signals. Run vi, press ^C.
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#define MAXARGS     20      /* cmdline args */
#define ARGLEN      100     /* token length */

int main(void)
{
    char    *arglist[MAXARGS+1];        /* an array of ptrs */
    int     numargs;                    /* index into array */
    char    argbuf[ARGLEN];             /* read stuff here */
    void    execute(char **);
    char    *makestring(char *);          /* malloc etc */
    
    numargs = 0;
    while (numargs < MAXARGS)
    {
        printf("Arg[%d]?", numargs);
        if (fgets(argbuf, ARGLEN, stdin) && *argbuf != ‘\n‘)
            arglist[numargs++] = makestring(argbuf);
        else
        {
            if (numargs > 0)                /* any args */
            {
                arglist[numargs] = NULL;    /* colse list */
                execute(arglist);           /* do it */
                numargs = 0;                /* and reset */
            }
        }
    }
    return 0;
}

void execute(char **arglist)
/*
 * use fork and execvp and wiat to do it
 */
{
    pid_t   pid, exitstatus;                /* of child */

    pid = fork();                           /* make new process */
    switch (pid)
    {
    case -1:
        perror("fork falued");
        exit(1);
    case 0:
        execvp(arglist[0], arglist);        /* do it */
        perror("execvp failed");
        exit(1);
    default:
        while (wait(&exitstatus) != pid)
            ;
        printf("child exited with status %d, %d\n",
            exitstatus >> 8, exitstatus & 0377);
        break;
    }
}

char *makestring(char *buf)
/*
 * trim off newline and create storage for the string
 */
{
    char *cp;

    buf[strlen(buf)-1] = ‘\0‘;              /* trim newline */
    cp = malloc(strlen(buf)+1);             /* get memory */
    if (cp == NULL)                         /* or die */
    {
        fprintf(stderr, "no memory\n");
        exit(1);
    }
    strcpy(cp, buf);                        /* copy chars */
    return cp;
}