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行。
相關文章
相關標籤/搜索