Shell主要逻辑源码级分析 (2)——SHELL做业控制

版权声明:本文由李航原创文章,转载请注明出处: 
文章原文连接:https://www.qcloud.com/community/article/110shell

来源:腾云阁 https://www.qcloud.com/community数组

 

做业控制自己也是基于进程控制的,二者关系密切,因此SHELL进程控制与做业控制的机制都在本章描述。bash

一. 主要相关文件

jobs.c
jobs.h
nojobs.c

备注:其中nojobs.c用于在早期的一些不支持做业控制的操做系统中代替jobs.c编译,其函数接口集是jobs.c的子集,并且现今的主要操做系统都是支持做业控制的,所以并未专门注释nojobs.c文件,而详细注释了jobs.c文件。若是须要查看nojobs.c中部分函数的功能,则直接查找对应的jobs.c的版本便可。数据结构

二. 重要数据结构

进程:app

typedef struct process {

struct process *next;     /* 指向管道中的下一个进程*/

pid_t pid;         /* 进程id */

WAIT status;           /* wait等待该进程所返回的结果*/

int running;            /* 是否处于运行状态,共有PS_DONE、PS_RUNNING、

PS_STOPPED、PS_RECYCLED*/

char *command;    /* 该进程所正在执行的命令*/

} PROCESS;

该结构描述了一个shell里定义的进程。其中command成员记录了该进程所execve执行的命令的名称。
做业:异步

typedef struct job {

  char *wd;    /* 工做目录*/

  PROCESS *pipe;   /* 构成当前做业的进程组的管道链表,是个循环链表*/

  pid_t pgrp;         /*该管道进程组的PID*/

  JOB_STATE state; /* 当前做业的状态*/

  int flags;      /* 做业标记,共有J_NOHUP、J_FOREGROUND、J_NOTIFIED、

J_JOBCONTROL、J_STATSAVED、J_ASYNC6个标记 */

#if defined (JOB_CONTROL)

  COMMAND *deferred; /* 本做业完成时要执行的工做*/

  sh_vptrfunc_t *j_cleanup; /* 做业被标志为死亡时要调用的清理函数*/

  PTR_T cleanarg;     /* 要传递给j_cleanup 成员的参数*/

#endif /* JOB_CONTROL */

} JOB;

该结构定义了一个做业结构体。能够看到,一个做业下面能够有数个进程组成,该进程组存储在pipe成员为入口的循环链表中.这些进程也构成了一个做业的主体,即做业要作的各个步骤的工做。而该做业的返回状态能够认为等同于管道中最后一个进程的返回状态。全局做业数组:JOB **jobs存储当前shell下全部的做业结构的数组。数组下标即该做业的做业号。
全局做业状态:async

struct jobstats {

  long c_childmax;/*容许的最大子进程数*/

  /*如下3个成员为子进程的统计信息*/
  int c_living;            /*正在运行或暂停(即非死亡)的进程数*/
  int c_reaped;          /* 死亡但还位于joblist中的进程数 */
  int c_injobs;            /*jobs list中的子进程总数 */
  /* 子进程总数 */
  int c_totforked;      /* 当前shellfork产生的子进程总数 */
  int c_totreaped;     /*当前shell已经移除掉的子进程总数*/
  /* 如下5个值都是job的下标或者计数器 */
  int j_jobslots;   /* jobs数组的总容量*/
  int j_lastj;        /* 最后一个分配的做业的做业号 */
  int j_firstj;              /*第一个分配的做业的做业号*/
  int j_njobs;             /* 做业数组中的非空做业的总数 */
  int j_ndead;            /* 做业数组中全部死亡做业的总数 */
  int j_current;   /* 当前做业的做业号 */
    int j_previous; /* 前一做业的做业号*/
  JOB *j_lastmade;   /* stop_pipeline函数分配的最后一个做业的做业号 */
  JOB *j_lastasync;   /* stop_pipeline 函数分配的最后一个异步做业的做业号*/

};

保存当前shell做业管理状态的结构体。记录了全局做业数组jobs的相关信息。
后台进程链表:函数

struct pidstat {

 struct pidstat *next;/*指向下一个进程的指针*/
 pid_t pid;/*该进程的PID*/
 int status;/*该进程的退出状态*/

};

维护一个后台进程的信息的节点结构。ui

struct bgpids {

  struct pidstat *list;/*链表头节点*/
  struct pidstat *end;/*链表尾节点*/
  int npid;/*后台运行的进程总数*/

};

struct bgpids bgpids = { 0, 0, 0 };

维护全部后台运行的进程的状态的链表。shell中用bgpids结构维护全部在后台运行的进程的信息,并提供了bgp_allocbgp_addbgp_deletebgp_clearbgp_searchbgp_prune等一组接口函数来对其进行操做。spa

三. 做业控制机制

附:与做业控制相关的shell命令

命令 含义
bg 启动被终止的后台做业
fg 将后台做业调到前台来
jobs 列出全部正在运行的做业
kill 向指定做业发送kill信号
stop 挂起一个后台做业
stty tostop 当一个后台做业向终端发送输出时就挂起它
wait[n] 等待一个指定的做业并返回它的退出状态,这里n是一个PID或做业号
∧Z(Ctrl-Z) 终止(挂起)做业。屏幕上将出现提示符
jobs命令的参数 含义
%n 做业号n
%string 以string开头的做业名
%?string 做业名包含string
%% 当前做业
%+ 当前做业
%- 当前做业前的一个做业
-r 列出全部运行的做业
-s 列出全部挂起的做业

在很老的版本的shell中,是不支持做业控制的,只容许同时存在一个做业。做业控制的意思就是:容许在一个终端启动多个做业,是BSD于1980年加入的
目前ksh,bash,csh都支持做业控制。

首先介绍两个概念,前台做业和后台做业。通常运行的做业都是前台做业,即在前台运行,占用终端。前台做业一旦运行起来,除非收到信号被挂起或者终止或者运行结束,都会一直将终端挂起,致使没法启动其余做业。后台做业则是在后台运行,不阻塞终端和键盘的使用。

1.前台做业和后台做业的关系:

本质上来讲没特别大的区别,主要在因而否阻塞键盘输入。他们共享键盘、显示器、CPU时间片等资源。所以若是不对后台做业的输出进行重定向,则其在后台运行的输出结果会不时的在终端打印出来。所以习惯上来讲,后台做业的输出都会进行重定向。因为前台做业会阻塞键盘输入,所以会长时间执行的做业通常放在后台执行。能够先将前台做业挂起,而后用BG命令将其转入后台执行,也能够用fg命令将后台做业转到前台执行,所以先后台做业之间是能够相互转化的。

2.做业启动流程图:

下图是start_job函数的执行流程图,即shell下启动一个做业的流程。

改变做业状态shell中提供了fg和bg命令来改变做业的状态,fg命令将后台做业提到前台运行,bg将前台做业压到后台执行。fg和bg命令的内部实现也是调用了start_job函数。打印做业信息

jobs命令能够打印当前shell下的做业信息,源码中与jobs命令相关的函数接口有:

static char * printable_job_status (j, p, format)    int j; 
PROCESS *p; 
int format;

功能:根据做业j以及其管道p的状态成员的信息,构造可读的字符串并返回。

static int print_job (job, format, state, job_index)     JOB *job;  
 int format, state, job_index;

功能:按照format格式,,调用pretty_print_job往stdout打印出jobindex指定的处于state状态的做业。

static void print_pipeline (p, job_index, format, stream)     PROCESS *p; 
int job_index, format;
FILE *stream;

功能:遍历p所在管道中的每个进程,根据format规定的格式打印出进程的信息。若是job_index参数的值大于0,则它表示该管道所在做业的做业号。
如下是源代码中关于format格式的描述。

JLIST_NORMAL)   [1]+ Running    emacs

JLIST_LONG  )   [1]+ 2378 Running      emacs   -1    )   [1]+ 2378             emacs

JLIST_NORMAL)   [1]+ Stopped    ls | more

JLIST_LONG  )   [1]+ 2369 Stopped      ls  2367           | 

more  JLIST_PID_ONLY)    只打印领头进程的进程ID

JLIST_CHANGED_ONLY)      只打印状态发生了改变而且还未通知过用户的做业。
static void pretty_print_job (job_index, format, stream) ;
int job_index,format;  
FILE *stream;

功能:向stream文件中按照format格式打印job_index号做业的信息,其中的管道信息调用print_pipeline打印。

void describe_pid (pid)     pid_t pid;

功能:打印可读的,以pid为领头进程的做业的做业号和进程ID信息。

void list_one_job (job, format, ignore, job_index)     JOB *job;    
int format, ignore, job_index;

功能:调用pretty_print_job (job_index, format, stdout);打印一个做业的相关信息。

void list_stopped_jobs (format)     int format;

功能:打印全部处于中止状态的做业的信息。

void list_running_jobs (format)     int format;

功能:打印全部处于运行状态的做业的信息。

void list_all_jobs (format)     int format;

功能:打印全部做业的信息,若是format非0,则打印长格式信息,即详细信息,不然只打印短信息。

3.建立子进程:

建立子进程本质上也是调用fork库函数,可是shell中对其进行了几层封装以处理不一样的状况。make_child函数封装了fork函数来建立子进程。make_child不只用在做业控制中,在执行shell命令须要建立子进程时也是调用该函数进行。
如下是make_child函数的注释:

pid_t make_child (command, async_p)     char *command;     
int async_p;

功能:执行fork库函数产生子进程,处理相应的错误,返回子进程的进程号。
核心代码注释:

/*调用making_children函数设置already_making_children的值而且调用start_pipeline初始化当前管道变量the_pipeline的值*/

making_children ();

/*执行fork操做,而且若是是EAGAIN错误则容许重试3次*/

while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX)

    {

      /* 若是一直不能建立子进程,则现场时清楚一些死亡的老进程 */

      waitchld (-1, 0);

      sys_error ("fork: retry");

      if (sleep (forksleep) != 0)

      break;

      forksleep <<= 1;/*将forksleep的值翻倍,*/

}

  if (pid < 0)

    {/*建立失败的状况*/

      sys_error ("fork");/*打印错误信息*/

      /*终止当前管道已构建的全部进程*/

      terminate_current_pipeline ();

      /*删除掉当前管道进程组占用的内存*/

      if (the_pipeline)

      kill_current_pipeline ();

      last_command_exit_value = EX_NOEXEC;/*设置退出码*/

      throw_to_top_level ();      /* 跳转到上层。*/

    }

if (pid == 0)

    {/*在子进程中*/

              mypid = getpid ();/*获取其实际进程ID*/

   #if defined (BUFFERED_INPUT)

      /*合理的关闭default_buffered_input*/

      unset_bash_input (0);

   #endif /* */

     /* 恢复top-level 信号的掩码 */

      sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);

               if (job_control)

                { /*若是job_control 非0*/

                if (pipeline_pgrp == 0) /* 说明这是领头进程 */

              pipeline_pgrp = mypid;/**/
                            /* 检查是不是shell进程组自己 */

          if (pipeline_pgrp == shell_pgrp)

          ignore_tty_job_signals ();/*忽略SIGTSTP/SIGTTIN/SIGTTOU信号*/

        else

        default_tty_job_signals ();/*设置SIGTSTP/SIGTTIN/SIGTTOU信号的处理函数为默认处理*/

if (setpgid (mypid, pipeline_pgrp) < 0)/*设置mypid进程属于pipeline_pgrp 进程组*/

       sys_error (_("child setpgid (%ld to %ld)"), (long)mypid, (long)

pipeline_pgrp); /*若是当前进程是领头进程,则在建立完其余进程前,调用pipe_read (pgrp_pipe)阻塞它*/

       if (pipeline_pgrp == mypid)

       pipe_read (pgrp_pipe);

       else                     /* job_control为0时的工做,*/

      {

        if (pipeline_pgrp == 0)

          pipeline_pgrp = shell_pgrp;/*设置进程组ID*/

        default_tty_job_signals ();/*设置信号的默认处理方式*/

      }

}
else/*在父进程中*/

    {

      if (first_pid == NO_PID)/*说明是父进程的第一个子进程*/

      first_pid = pid;

      else if (pid_wrap == -1 && pid < first_pid)

      pid_wrap = 0;/*说明进程号已经用完到最大值从新从小开始计算了*/

      else if (pid_wrap == 0 && pid >= first_pid)

      pid_wrap = 1;

      if (job_control)

      {

        if (pipeline_pgrp == 0)

          {/*设置组进程ID*/

            pipeline_pgrp = pid;

            }

        setpgid (pid, pipeline_pgrp);

      }

      else

      {

        if (pipeline_pgrp == 0)

          pipeline_pgrp = shell_pgrp;

      }

add_process (command, pid);/*将当前进程添加到当前管道进程组中*/

return (pid);/*返回子进程的ID号*/

make_child函数执行流程图:

4.wait调用

shell中对常常与fork搭配使用的wait库函数也进行了封装。shell自己也提供了wait命令。
相关的函数和宏定义有

# define WAITPID(pid, statusp, options)

封装waitpid或者wait3函数等待子进程结束。

static int waitchld (wpid, block)     pid_t wpid;     int block;

功能:消除死亡的或者终止的子进程,内部调用宏pid = WAITPID (-1, &status, waitpid_flags);实现,该宏封装了waitpif库函数,第一个参数为-1,表示等待任何一个子进程的结束便可(至关于wait库函数)。对全部终止的子进程,调用可能存在的SIGCHLD信号处理函数。由于子进程退出,必然会发出SIGCHLD的信号。

int wait_for_single_pid (pid)     pid_t pid;

功能:等待进程号为pid的子进程执行完毕。若是pid标示的子进程不是shell的子进程,则打印出错信息。若是执行失败,返回-1.若是pid子进程不存在与做业数组jobs中,则返回127.不然调用wait_for(pid)等待其执行返回并返回wait_for的调用结果。

void wait_for_background_pids ()

功能:等待当前shell下的全部后台进程执行完毕。

int wait_for (pid)     pid_t pid;

功能:等到pid号的子进程执行完毕,返回其执行状态。若是pid号金成没有找到则返回127。函数体重调用waitchld函数等待子进程退出,若是waitchld返回-1,则表示没有须要等待执行退出的子进程。

int wait_for_job (job)     int job;

功能:等待job号做业的结束,本质上经过调用wait_for等待该做业中最后一个进程的结束实现,返回wait_for的调用结果,出错返回-1。

5.封装和调用层次见下图:


kill调用相似于wait,shell也提供了kill命令用来向进程或做业发送信号,kill命令自己也是封装的kill库函数。

shell源码中相关的库函数有

int killpg (pgrp, sig)     pid_t pgrp;     int sig;
{

  return (kill (-pgrp, sig));

}

封装了kill库函数,向-pgrp值的进程发送sig信号。

void kill_current_pipeline ()
功能:结束并丢弃掉当前管道进程组。

int kill_pid (pid, sig, group) pid_t pid; int sig, group;
功能:向pid号进程发送sig信号,若是group值非0,则向pid所在的进程组都发送sig信号。内部调用kill库函数实现。其中killpg函数被shell中不少其余函数调用用于向进程或做业发送信号。kill_pid内部调用killpg或者kill库函数,kill_pid是kill命令主要调用的功能函数。

6. 信号控制机制

主要相关文件:

sig.h

sig.c

siglist.h

siglist.c

signames.h

support/signames.c

cwru/misc/sigs.c

cwru/misc/sigstat.c

trap.h

trap.c

其中,siglist.h、siglist.c、signames.h、support/signames.c四个文件主要封装和实现了sys_siglist以及signal_names数组。sig.h、sig.c定义了shell中信号处理和初始化的一些操做接口函数. cwru/misc/sigs.c和cwru/misc/sigstat.c提供了打印当前信号的处理方式和状态的一些借口。trap.h和trap.c文件则提供了shell内置命令trap操做相关的一些接口和数据结构。

主要数据结构:

struct termsig {

     int signum;/*信号的值*/

     SigHandler *orig_handler;/*若是该值非空,则是对该信号的处理函数*/

     int orig_flags;/*标记位,做用尚不明*/

};

描述一个能致使shell终止的信号。SIGHUP,SIGINT都是这类型号。

static struct termsig terminating_signals[] = {

#ifdef SIGHUP

{  SIGHUP, NULL_HANDLER, 0 },

#endif
#ifdef SIGINT

{  SIGINT, NULL_HANDLER, 0 },

#endif
#ifdef SIGILL

{  SIGILL, NULL_HANDLER, 0 },

#endif
#ifdef SIGTRAP

{  SIGTRAP, NULL_HANDLER, 0 },

#endif
#ifdef SIGIOT

{  SIGIOT, NULL_HANDLER, 0 },

#endif
#ifdef SIGDANGER

{  SIGDANGER, NULL_HANDLER, 0 },

#endif
#ifdef SIGEMT

{  SIGEMT, NULL_HANDLER, 0 },

#endif
#ifdef SIGFPE

{  SIGFPE, NULL_HANDLER, 0 },

#endif
#ifdef SIGBUS

{  SIGBUS, NULL_HANDLER, 0 },

#endif
#ifdef SIGSEGV

{  SIGSEGV, NULL_HANDLER, 0 },

#endif
#ifdef SIGSYS

{  SIGSYS, NULL_HANDLER, 0 },

#endif
#ifdef SIGPIPE

{  SIGPIPE, NULL_HANDLER, 0 },

#endif
#ifdef SIGALRM

{  SIGALRM, NULL_HANDLER, 0 },

#endif
#ifdef SIGTERM

{  SIGTERM, NULL_HANDLER, 0 },

#endif
#ifdef SIGXCPU

{  SIGXCPU, NULL_HANDLER, 0 },

#endif
#ifdef SIGXFSZ

{  SIGXFSZ, NULL_HANDLER, 0 },

#endif
#ifdef SIGVTALRM

{  SIGVTALRM, NULL_HANDLER, 0 },

#endif
#if 0
#ifdef SIGPROF

{  SIGPROF, NULL_HANDLER, 0 },

#endif
#endif
#ifdef SIGLOST

{  SIGLOST, NULL_HANDLER, 0 },

#endif
#ifdef SIGUSR1

{  SIGUSR1, NULL_HANDLER, 0 },

#endif
#ifdef SIGUSR2

{  SIGUSR2, NULL_HANDLER, 0 },

#endif

};

维护了全部能够致使shell退出执行的信号的termsig数组,这样shell就能够不用对这一类信

号中的每个都设置处理函数,能够对它们采用一样的处理机制。

char *sys_siglist[NSIG];

保存Linux下64个信号的名称的列表。下标对应信号值。名称是相似这样的值
sys_siglist[SIGINT] = _("Interrupt");char *signal_names[NSIG + 4]定义了存储全部64个Linux信号名字的数组。名字是相似这样的值。signal_names[SIGINT] = "SIGINT";

static int sigmodes[BASH_NSIG];
保存全部系统信号和3个shell自定义信号的状态标记的数组。可能的标记有

#define SIG_INHERITED   0x0   /* 继承自父进程的值 */

#define SIG_TRAPPED     0x1  /*当前被trapped状态*/

#define SIG_HARD_IGNORE 0x2  /* 信号在shell下被忽略*/

#define SIG_SPECIAL     0x4     /* 特殊处理的信号 */

#define SIG_NO_TRAP     0x8  /*该信号不能被trap */

#define SIG_INPROGRESS     0x10          /* 当前正在处理该信号 */

#define SIG_CHANGED         0x20          /* 在trap过程当中改变了trap的值 */

#define SIG_IGNORED 0x40          /* 当前信号被忽略*/

char *trap_list[BASH_NSIG];

保存每一种信号被trap以后须要执行的工做的字符串值的数组。

7.信号控制的接口

其中,发送信号的机制已经在前面进程控制里有分析,即便用封装了kil库函数的kill命令实现。

以下一些接口是与信号处理相关的函数,即决定收到信号后应该作何种操做。

static void initialize_shell_signals ()
功能:初始化shell的信号控制。

void initialize_signals (reinit) int reinit;
功能:调用 initialize_shell_signals ()和 initialize_job_signals ()函数初始化shell的信号控制,若是reinit参数为0,则还要调用initialize_siglist ();函数初始化sys_siglist数组。

void initialize_terminating_signals ()
功能:初始化终止信号数组terminating_signals,统一的为其中的信号赋予处理函数
termsig_sighandler。实际功能调用sigaction函数实现。

sighandler termsig_sighandler (sig) int sig;
功能:先判断是否在处理sig信号前已经收到两次sig信号,若是是,则将terminate_immediately置为1表示须要当即终止,而后调用termsig_handler处理sig信号。

void termsig_handler (sig) int sig;
功能:处理值为sig的终止信号。

核心代码注释:

/*若是是SIGINT信号,则执行该信号的trap函数*/

  if (sig == SIGINT && signal_is_trapped (SIGINT))

run_interrupt_trap ();

/*历史保存工做*/

  if (interactive_shell && sig != SIGABRT)

    maybe_save_shell_history ();

/*若是是SIGHUP信号,则调用hangup_all_jobs 挂起全部的做业*/

  if (sig == SIGHUP && (interactive || (subshell_environment & (SUBSHELL_COMSUB|

SUBSHELL_PROCSUB))))

    hangup_all_jobs ();

  end_job_control ();



/*最后处理退出工做,对sig信号执行其默认的处理方式*/

  run_exit_trap ();

  set_signal_handler (sig, SIG_DFL);

  kill (getpid (), sig);

sigprocmask (operation, newset, oldset) int operation, *newset, *oldset;
功能:在newset参数的信号集上执行operation操做,将执行的结果保存在oldset中。

SigHandler * set_signal_handler (sig, handler) int sig; SigHandler *handler;
功能:将sig信号的处理方式设置为handler,返回handler的指针。

void sigstat(sig) int sig;
功能:打印出sig号信号的状态,分别有被阻塞,被忽略,系统默认处理方式和被trap4种状态。

7.TRAP操做

特别的,shell中提供了内部命令trap来控制信号。Trap命令的用法简介以下:
trap捕捉到信号以后,能够有三种反应方式:

(1)执行一段程序来处理这一信号

(2)接受信号的默认操做

(3)忽视这一信号

trap对上面三种方式提供了三种基本形式:

第一种形式的trap命令在shell接收到signal list清单中数值相同的信号时,将执行双引号中的命令串。trap "commands" signal-list为了恢复信号的默认操做,使用第二种形式的trap命令:trap signal-list第三种形式的trap命令容许忽视信号trap " " signal-list

shell中trap.c与trap.h文件虽然不是trap命令的直接内部实现,可是提供了许多函数
接口和数据结构供trap命令使用。其中

char *trap_list[BASH_NSIG];
保存每一种信号被trap以后须要执行的工做的字符串值的数组。该值就是上面trap命令用法中双引号内要执行的命令的值或者DEFAULT_SIG(标志按照系统默认处理该信号)、
IGNORE_SIG(标志忽略该信号)、IMPOSSIBLE_TRAP_HANDLER(处理方式还未设置时的值)

相关的函数接口有:

static void change_signal (sig, value) int sig; char *value;
功能:将trap_list[sig]的值赋为value,并根据value是否为IGNORE_SIG设置sigmodes[sig]标记。在判断当前是否在trap调用中,即sigmodes[sig] & SIG_INPROGRESS是否为真,若是为真,则须要标记sigmodes[sig] |= SIG_CHANGED;来标记其值的变化。

static void get_original_signal (sig) int sig;
功能:对每个信号调用GET_ORIGINAL_SIGNAL宏将其的trap处理方式设置为系统默认的处理方式。

static int _run_trap_internal (sig, tag) int sig; char *tag;

功能:当shell中提供了对sig信号的trap处理而且该信号未被设置忽略标记时,运行sig信号的trap处理操做。

核心代码注释:

/*只有知足以下if条件的信号才执行trap操做*/

  if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0) &&
    (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER) &&

      ((sigmodes[sig] & SIG_INPROGRESS) == 0))

     {

         /*如下工做为在执行trap操纵前保存一些程序当前的状态值*/

       old_trap = trap_list[sig];        /*保存sig信号要作的trap工做*/

         sigmodes[sig] |= SIG_INPROGRESS;/*标记当前开始trap操做了*/

         sigmodes[sig] &= ~SIG_CHANGED;             /* 确保SIG_CHANGED位不成立*/

         trap_command =  savestring (old_trap); /*为了执行相应的操做,为相应的命令申请内存地址保存在trap_command中*/

         running_trap = sig + 1;/*设置running_trap的值*/

         trap_saved_exit_value = last_command_exit_value;/*保存执行该trap操做前最后一次命令执行的返回值*/

         ps = save_pipestatus_array ();/*保存shell变量PIPESTATUS的值*/

       token_state = save_token_state ();/*保存当前语法分析的状态*/

         save_subst_varlist = subst_assign_varlist;/*保存subst_assign_varlist的值*/

         subst_assign_varlist = 0;

       /*若是当前正运行于一个函数中,则以下代码将能处理捕获返回值并跳转的状况*/

         save_return_catch_flag = return_catch_flag;

         if (return_catch_flag)

       {

           COPY_PROCENV (return_catch, save_return_catch);

           function_code = setjmp (return_catch);

       }

         /*正确设置parse_and_execute的值,而且调用该函数解析并执行trap操做*/

         flags = SEVAL_NONINT|SEVAL_NOHIST;

      if (sig != DEBUG_TRAP && sig != RETURN_TRAP && sig != ERROR_TRAP)

      flags |= SEVAL_RESETLINE;

      if (function_code == 0)

      parse_and_execute (trap_command, tag, flags);

         /*执行完毕后,如下操做恢复以前保存的一些程序状态值*/

         restore_token_state (token_state);

         free (token_state);

         subst_assign_varlist = save_subst_varlist;

         trap_exit_value = last_command_exit_value;

         last_command_exit_value = trap_saved_exit_value;

         running_trap = 0;

         sigmodes[sig] &= ~SIG_INPROGRESS;

         /*处理函数返回值须要跳转的状况*/

         if (save_return_catch_flag)

      {

        return_catch_flag = save_return_catch_flag;

        return_catch_value = trap_exit_value;

        COPY_PROCENV (save_return_catch, return_catch);

        if (function_code)

          longjmp (return_catch, 1);

      }

        return trap_exit_value;/*返回trap操做的结果值*/

}

附:部分重要源码文件简介

文件(或文件夹) 说明
shell.h shell.c对应的头文件,共171行
shell.c main()函数的所在,定义了shell启动和运行过程当中的一些状态量,依据不一样的启动参数、环境变量等来初始化shell的工做状态,共1856行
Eval.c 读取并解释执行shell命令。共281行
Command.h 定义了表示命令的数据结构,声明了建立命令的函数,共387行
Copy_cmd.c 定义了复制命令的一系列函数,代码共450行。
Execute_cmd.c 定义了执行一个命令须要的相关函数。提供给外部的调用接口函数是execute_command(),该函数调用execute_command_internal()执行命令。针对不一样类型的命令(控制结构、函数、算术等),execute_command_internal()调用对应的内部函数来完成相应功能。其中execute_builtin()执行内部命令;execute_disk_command()执行外部文件。execute_disk_command()经过调用jobs.c或nojobs.c中的make_child()来执行新进程的fork操做。代码共5187行。
Variables.h 本文件定义了描述shell变量要用到的一些数据结构,代码共390行。
Variables.c 本文件处理了操做shell中的各类变量的函数集,bash中的变量不强调类型,能够认为都是字符串。代码共4793行。
Make_cmd.c 构造各种命令、复位向等语法结构实例所需的函数。由yacc语法分析器、redir.c等调用,代码共888行。
dispose_cmd.c 释放一个命令结构所占用的内存,代码共342行。
jobs.h 声明和定义了jobs.c以及做业控制须要的数据结构和函数,代码共250行。
jobs.c 本文件是做业控制的主要实现文件,主要入口是make_child(),该函数封装了fork库函数用来建立进程并执行。jobs、fg、bg、kill等命令的内部实现都在这里。代码共4276行。
sig.h 声明了sig.c的一些控制信号处理的函数,定义了一些相关的宏。代码共137行。
sig.c 定义了shell中信号处理和初始化的一些操做接口函数,代码共677行
siglist.h 封装了sys_siglist可能的几种定义的文件,代码共44行。
siglist.c 由于有些系统没有_sys_siglist变量来保存信号信息列表,所以提供该文件构造一个sys_siglist数组。代码共229行。
signames.h 本文件内容由mksignames.c编译生成的二进制文件执行生成,用于定义保存了全部信号名称的数组signal_names以及定义初始化该数组内容的宏 initialize_signames(),代码共78行。
support/signames.c 定义了signal_names数组,定义了初始化该数组的函数initialize_signames。代码共401行。
cwru/misc/sigs.c 打印出当前shell对于每个信号的处理方式,代码共45行。
cwru/misc/sigstat.c 打印出全部信号的状态,好比是否被阻塞,是否被忽略,是否被trap,是否采用默认处理方式等等,代码共226行。
trap.h 定义了trap机制中用到的一些数据结构,声明了trap.c文件的函数,代码共105行。
trap.c 操做trap命令所需的一些对象的函数。非trap命令的实现,只是提供了一些trap命令须要用到的接口,trap命令的实如今trap.def文件中。代码共1121行。
相关文章
相关标签/搜索