国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
GNU bash實(shí)現(xiàn)機(jī)制與源代碼簡(jiǎn)析
作者:林健

摘要

本文是本人學(xué)習(xí)shell實(shí)現(xiàn)機(jī)理,分析GNU bash源代碼時(shí)總結(jié)的筆記性文檔。通過(guò)分析bash源代碼,闡述了其主要功能模塊的組織和實(shí)現(xiàn)方式,同時(shí)對(duì)幾個(gè)特定的工作流程進(jìn)行了說(shuō)明。

第 1 章 概述

1.1. bash

GNU bash是各類(lèi)UNIX系統(tǒng),特別是Linux下經(jīng)典的shell。作為一個(gè)命令行解釋器,它提供了強(qiáng)大的可編程功能,為用戶(hù)提供了操作系統(tǒng)功能的良好接口。作為一個(gè)經(jīng)典的開(kāi)源項(xiàng)目,它的源代碼結(jié)構(gòu)較為清晰,可靠性、性能和易用性經(jīng)歷了考驗(yàn)。

本文分析的bash版本為3.2.0(1),源代碼為configure之后的版本,因?yàn)椴糠衷创a是在configure過(guò)程中由輔助工具生成的。builtins目錄下的*.c文件是make之后的版本(需要注釋掉builtins/Makefile中刪除*.c文件的語(yǔ)句),因?yàn)樯蛇@些源代碼的輔助工具需要在make過(guò)程中生成。

1.2. 環(huán)境與工具

項(xiàng)目configure和make環(huán)境為Ubuntu 7.10(Linux 2.6.22),Intel Pentium III。

源代碼分析工具為Windows下的Source Insight 3.5。Source Insight提供的交叉參考功能和調(diào)用鏈分析功能有助于理清復(fù)雜的函數(shù)調(diào)用和依賴(lài)關(guān)系。

第 2 章 程序結(jié)構(gòu)分析

2.1. 系統(tǒng)架構(gòu)

通常而言,shell的功能是從終端或其它輸入取得命令行,將其解析為一系列操作指令,調(diào)用系統(tǒng)內(nèi)核或相應(yīng)的外部程序執(zhí)行,然后將執(zhí)行結(jié)果返回給終端或其它輸出。因此,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的shell是一項(xiàng)容易的工作。但bash的功能不僅限于此,它支持用管道和重定向協(xié)同執(zhí)行命令,提供了強(qiáng)大的腳本編程能力,具備作業(yè)管理功能。一般的Linux發(fā)行版中,bash的可執(zhí)行文件往往是/bin中最大的幾個(gè)實(shí)用程序之一,客觀反映了它的復(fù)雜性。

依據(jù)bash源代碼的文件組織及函數(shù)調(diào)用關(guān)系,可分析出它的基本架構(gòu)。

圖 2.1. bash基本架構(gòu)圖


bash使用GNU Readline庫(kù)處理用戶(hù)命令輸入,Readline提供類(lèi)似于vi或emacs的行編輯功能。

bash運(yùn)行時(shí)的調(diào)度中心是其主控循環(huán)。主控循環(huán)的功能較為簡(jiǎn)單,它循環(huán)讀取用戶(hù)(或腳本)輸入,傳遞給語(yǔ)法分析器,同時(shí)處理下層遞歸返回的錯(cuò)誤。

語(yǔ)法分析器對(duì)文本形式的輸入首先進(jìn)行通配符、別名、算術(shù)和變量展開(kāi)等工作,然后通過(guò)命令生成器得到規(guī)范的命令結(jié)構(gòu),并由專(zhuān)門(mén)的重定向處理機(jī)制填寫(xiě)重定向語(yǔ)義,交由命令執(zhí)行器執(zhí)行。命令執(zhí)行器依據(jù)命令種類(lèi)不同,執(zhí)行內(nèi)部命令函數(shù)、外部程序或文件系統(tǒng)調(diào)用。在命令執(zhí)行過(guò)程中,執(zhí)行器要對(duì)系統(tǒng)信號(hào)進(jìn)行捕獲和處理。

在支持作業(yè)管理的操作系統(tǒng)中,命令執(zhí)行器將進(jìn)程信息加入作業(yè)控制機(jī)制,并允許用戶(hù)使用內(nèi)部命令或鍵盤(pán)信號(hào)來(lái)啟停作業(yè)。如果在不支持作業(yè)管理的操作系統(tǒng)中編譯bash,會(huì)使用另一套接口相同的機(jī)制對(duì)進(jìn)程信息進(jìn)行簡(jiǎn)單的維護(hù)。

2.2. 主要數(shù)據(jù)結(jié)構(gòu)

2.2.1. WORD_DESC與WORD_LIST

/* A structure which represents a word. */typedef struct word_desc {  char *word;		/* Zero terminated string. */  int flags;		/* Flags associated with this word. */} WORD_DESC;/* A linked list of words. */typedef struct word_list {  struct word_list *next;  WORD_DESC *word;} WORD_LIST;

命令等各種結(jié)構(gòu)都可能引用字符串或字符串?dāng)?shù)組。bash對(duì)于有必要做進(jìn)一步語(yǔ)義處理的字符串使用WORD_DESC、WORD_LIST結(jié)構(gòu)進(jìn)行封裝。

WORD_DESC結(jié)構(gòu)是對(duì)字符指針的一層封裝,可稱(chēng)為字符串描述符。它在字符指針的基礎(chǔ)上附加了一個(gè)flags標(biāo)志位集合。其標(biāo)志位包含W_HASDOLLAR、W_QUOTED、W_DQUOTE、W_GLOBEXP等,分別表示該字符串中包含了“$”、引號(hào)、雙引號(hào)、通配符等,目的是便于在變量代入、通配展開(kāi)等過(guò)程中處理相應(yīng)字符串。

WORD_LIST結(jié)構(gòu)是WORD_DESC對(duì)象的鏈表。

2.2.2. COMMAND

/* What a command looks like. */typedef struct command {  enum command_type type;	/* FOR CASE WHILE IF CONNECTION or SIMPLE. */  int flags;			/* Flags controlling execution environment. */  int line;			/* line number the command starts on */  REDIRECT *redirects;		/* Special redirects for FOR CASE, etc. */  union {    struct for_com *For;    struct case_com *Case;    struct while_com *While;    struct if_com *If;    struct connection *Connection;    struct simple_com *Simple;    struct function_def *Function_def;    struct group_com *Group;#if defined (SELECT_COMMAND)    struct select_com *Select;#endif#if defined (DPAREN_ARITHMETIC)    struct arith_com *Arith;#endif#if defined (COND_COMMAND)    struct cond_com *Cond;#endif#if defined (ARITH_FOR_COMMAND)    struct arith_for_com *ArithFor;#endif    struct subshell_com *Subshell;  } value;} COMMAND;

COMMAND結(jié)構(gòu)描述一條bash命令,這里的“命令”概念指語(yǔ)法分析器通過(guò)定界符、管道或控制語(yǔ)句分析出的相對(duì)獨(dú)立的執(zhí)行單元,它可以是內(nèi)部或外部命令、函數(shù)、控制結(jié)構(gòu)、算術(shù)表達(dá)式等。

枚舉參數(shù)type表示了一個(gè)命令對(duì)象代表的命令屬于上述何種類(lèi)型。整型變量flags記錄了一系列有關(guān)運(yùn)行環(huán)境的標(biāo)志位,包括:

/* Possible values for command->flags. */#define CMD_WANT_SUBSHELL  0x01	/* User wants a subshell: ( command ) */#define CMD_FORCE_SUBSHELL 0x02	/* Shell needs to force a subshell. */#define CMD_INVERT_RETURN  0x04	/* Invert the exit value. */#define CMD_IGNORE_RETURN  0x08	/* Ignore the exit value.  For set -e. */#define CMD_NO_FUNCTIONS   0x10 /* Ignore functions during command lookup. */#define CMD_INHIBIT_EXPANSION 0x20 /* Do not expand the command words. */#define CMD_NO_FORK	   0x40	/* Don't fork; just call execve */#define CMD_TIME_PIPELINE  0x80 /* Time a pipeline */#define CMD_TIME_POSIX	   0x100 /* time -p; use POSIX.2 time output spec. */#define CMD_AMPERSAND	   0x200 /* command & */#define CMD_STDIN_REDIR	   0x400 /* async command needs implicit 

整型變量line表示該命令在一個(gè)腳本中所處的行數(shù)。指針redirects指向說(shuō)明本命令重定向信息的REDIRECT結(jié)構(gòu)對(duì)象。最后,針對(duì)不同的命令類(lèi)型,COMMAND結(jié)構(gòu)包含不同命令類(lèi)型具體內(nèi)部結(jié)構(gòu)的聯(lián)合:

對(duì)于內(nèi)部或外部命令,使用聯(lián)合中的simple_com結(jié)構(gòu),這個(gè)結(jié)構(gòu)主要記錄了命令名和命令行參數(shù)。它們存儲(chǔ)于WORD_LIST鏈表結(jié)構(gòu),表元結(jié)點(diǎn)是WORD_DESC結(jié)構(gòu)。

對(duì)于分支、循環(huán)等控制結(jié)構(gòu),它們的內(nèi)部結(jié)構(gòu)中主要包含指向相關(guān)控制流對(duì)應(yīng)命令的指針。例如while_com結(jié)構(gòu)包含了測(cè)試條件和循環(huán)體對(duì)應(yīng)命令的指針;if_com結(jié)構(gòu)包含了測(cè)試條件、真值執(zhí)行體和假值執(zhí)行體對(duì)應(yīng)命令的指針。

對(duì)于函數(shù)定義,function_def結(jié)構(gòu)包含了指向函數(shù)名的WORD_DESC結(jié)構(gòu)指針、函數(shù)對(duì)應(yīng)命令的指針以及指定函數(shù)所在文件名的指針。

2.2.3. REDIRECT與REDIRECTEE

/* Structure describing a redirection.  If REDIRECTOR is negative, the parser   (or translator in redir.c) encountered an out-of-range file descriptor. */typedef struct redirect {  struct redirect *next;	/* Next element, or NULL. */  int redirector;		/* Descriptor to be redirected. */  int flags;			/* Flag value for `open'. */  enum r_instruction  instruction; /* What to do with the information. */  REDIRECTEE redirectee;	/* File descriptor or filename */  char *here_doc_eof;		/* The word that appeared in <<="" pre="">

REDIRECT結(jié)構(gòu)是對(duì)命令輸入輸出重定向的定義。一條命令可以設(shè)置多個(gè)(輸入、輸出、錯(cuò)誤)重定向,因此REDIRECT結(jié)構(gòu)本身包含指向下一個(gè)REDIRECT對(duì)象的next指針,以便構(gòu)成一條命令的重定向鏈表。

整型參數(shù)redirector為重定向源的文件描述符,標(biāo)志位集合flags定義目標(biāo)文件打開(kāi)方式。instruction枚舉定義了重定向的具體類(lèi)型,它們包括:

/* Instructions describing what kind of thing to do for a redirection. */enum r_instruction {  r_output_direction, r_input_direction, r_inputa_direction,  r_appending_to, r_reading_until, r_reading_string,  r_duplicating_input, r_duplicating_output, r_deblank_reading_until,  r_close_this, r_err_and_out, r_input_output, r_output_force,  r_duplicating_input_word, r_duplicating_output_word,  r_move_input, r_move_output, r_move_input_word, r_move_output_word};

重定向的目標(biāo)記錄在REDIRECTEE聯(lián)合中,它可以是一個(gè)文件名或文件描述符。定義如下:

/* What a redirection descriptor looks like.  If the redirection instruction   is ri_duplicating_input or ri_duplicating_output, use DEST, otherwise   use the file in FILENAME.  Out-of-range descriptors are identified by a   negative DEST. */typedef union {  int dest;			/* Place to redirect REDIRECTOR to, or ... */  WORD_DESC *filename;		/* filename to redirect to. */} REDIRECTEE;

此外,對(duì)于Here Document類(lèi)型的重定向,REDIRECT結(jié)構(gòu)中的here_doc_eof指針指向Here Document。

2.2.4. VAR_CONTEXT與SHELL_VAR

bash本身的shell變量以及其中運(yùn)行的函數(shù)的局部變量上下文存儲(chǔ)在VAR_CONTEXT結(jié)構(gòu)中。

/* A variable context. */typedef struct var_context {  char *name;		/* empty or NULL means global context */  int scope;		/* 0 means global context */  int flags;  struct var_context *up;	/* previous function calls */  struct var_context *down;	/* down towards global context */  HASH_TABLE *table;		/* variables at this scope */} VAR_CONTEXT;

VAR_CONTEXT的字符指針name如果為空則表示它存儲(chǔ)的是bash全局上下文,否則表示某一個(gè)函數(shù)的局部上下文,name指向函數(shù)的名稱(chēng)。整型變量scope是本上下文在棧中的層數(shù),0表示全局上下文,每深入一層函數(shù)調(diào)用scope遞增1,這樣可以體現(xiàn)出該上下文的作用域。標(biāo)志位集合flags記錄該上下文是否為局部的、是否屬于函數(shù)、是否屬于內(nèi)部命令,或者是不是臨時(shí)建立的等信息。up和down指針指向函數(shù)調(diào)用棧中上一個(gè)和下一個(gè)局部上下文。哈希表table的內(nèi)容是該上下文中的變量名值對(duì)。

bash中的變量不強(qiáng)調(diào)類(lèi)型,可以認(rèn)為都是字符串。其存儲(chǔ)結(jié)構(gòu)如下:

typedef struct variable {  char *name;			/* Symbol that the user types. */  char *value;			/* Value that is returned. */  char *exportstr;		/* String for the environment. */  sh_var_value_func_t *dynamic_value;	/* Function called to return a `dynamic'				   value for a variable, like $SECONDS				   or $RANDOM. */  sh_var_assign_func_t *assign_func; /* Function called when this `special				   variable' is assigned a value in				   bind_variable. */  int attributes;		/* export, readonly, array, invisible... */  int context;			/* Which context this variable belongs to. */} SHELL_VAR;

字符指針name和value分別指向上下文變量的名和值字符串。對(duì)于導(dǎo)出(export)環(huán)境變量,exportstr指向一個(gè)形如“名=值”的字符串。對(duì)于返回一個(gè)動(dòng)態(tài)變化值的變量(如RANDOM),函數(shù)指針dynamic_value指向生成該值的函數(shù)。對(duì)于特定的變量,在被賦值的時(shí)候可以設(shè)置一個(gè)回調(diào)函數(shù),其指針是assign_func。整型變量attributes記錄該上下文變量的可訪問(wèn)性,比如是否為導(dǎo)出的、只讀的或隱藏的等。整型變量context記錄該上下文變量屬于可訪問(wèn)的作用域內(nèi)局部變量棧的哪一層。

第 3 章 主要文件分析

3.1. 根目錄

  • shell.c/shell.h

    shell.c是main()函數(shù)的所在,它定義了shell啟動(dòng)和運(yùn)行過(guò)程中的一些狀態(tài)量,依據(jù)不同的啟動(dòng)參數(shù)、環(huán)境變量等來(lái)初始化shell的工作狀態(tài)(包括受限模式等),之后進(jìn)入eval.c中的交互循環(huán)函數(shù)reader_loop()解析命令直到退出。

    初始化函數(shù)shell_initialize()調(diào)用了variables.c中的initialize_shell_variables()、set.c中的initialize_shell_options()等一系列子模塊初始化函數(shù)。如果要新增功能模塊,可以將它們的初始化調(diào)用放在這里。

    run_startup_files()函數(shù)執(zhí)行~/.profile、~/.bash_profile、~/.bash_login等配置文件,同時(shí)判斷了bash是否是由sshd或rshd啟動(dòng)的。對(duì)于login shell,不執(zhí)行~/.bashrc;對(duì)于non-login交互式shell,或通過(guò)sshd、rshd啟動(dòng)的shell,執(zhí)行~/.bashrc。

    run_one_command()函數(shù)處理了-c參數(shù)運(yùn)行一條命令的模式。

    open_shell_script()函數(shù)處理運(yùn)行腳本文件的模式。

    退出函數(shù)exit_shell()處理了掛起作業(yè)、保存歷史等善后工作。

  • eval.c

    讀取并解釋執(zhí)行shell命令。主循環(huán)為reader_loop()函數(shù),它調(diào)用read_command(),read_command()調(diào)用parse_command(),parse_command()調(diào)用語(yǔ)法分析器y.tab.c中的yyparse()。得到命令后,reader_loop()調(diào)用execute_cmd.c中的execute_command()執(zhí)行命令。

    注:token查找優(yōu)先順序:別名>關(guān)鍵字>函數(shù)>內(nèi)部命令>腳本或可執(zhí)行程序。

  • execute_cmd.c/execute_cmd.h

    執(zhí)行命令(COMMAND結(jié)構(gòu))。外部調(diào)用接口是execute_command(),內(nèi)部通過(guò)execute_command_internal()執(zhí)行命令。execute_command_internal()包含可選的管道重定向以及后臺(tái)運(yùn)行的參數(shù)。

    針對(duì)不同類(lèi)型的命令(控制結(jié)構(gòu)、函數(shù)、算術(shù)等),execute_command_internal()調(diào)用不同的函數(shù)來(lái)完成相應(yīng)功能。其中execute_builtin()執(zhí)行內(nèi)部命令;execute_disk_command()執(zhí)行外部文件。execute_disk_command()通過(guò)調(diào)用jobs.c或nojobs.c中的make_child()來(lái)fork新進(jìn)程執(zhí)行。

    本文件中維護(hù)了一個(gè)文件描述符的位圖。

  • make_cmd.c/make_cmd.h

    構(gòu)造各類(lèi)命令、重定向等語(yǔ)法結(jié)構(gòu)實(shí)例所需的函數(shù)。由語(yǔ)法分析器、redir.c等調(diào)用。

    其中make_redirection()填寫(xiě)命令結(jié)構(gòu)的重定向參數(shù)。

  • copy_command.c

    用來(lái)遞歸復(fù)制各種COMMAND結(jié)構(gòu)的一系列函數(shù)。

    作者注釋稱(chēng):This is needed primarily for making function definitions, but I'm not sure that anyone else will need it.

  • dispose_command.c/dispose_command.h

    清理COMMAND結(jié)構(gòu)占用的資源,dispose_redirects()清理重定向語(yǔ)句。

  • print_cmd.c

    將命令結(jié)構(gòu)轉(zhuǎn)化為可打印的字符串。在execute_cmd.c的execute_command_internal()中有調(diào)用。

  • redir.c/redir.h

    實(shí)現(xiàn)輸入輸出重定向。在執(zhí)行之前,命令結(jié)構(gòu)的redirects參數(shù)已由make_cmd.c填好,外部主要由execute_cmd.c調(diào)用以執(zhí)行重定向操作。

    外部調(diào)用接口是do_redirections(),它解析命令結(jié)構(gòu)的重定向參數(shù),內(nèi)部交由do_redirection_internal()執(zhí)行。接著依據(jù)不同的重定向類(lèi)型,redir_open()分別使用常規(guī)的open()、redir_special_open()、noclobber_open()等函數(shù)打開(kāi)重定向的文件描述符。如果要添加新的重定向方式(如重定向到FTP),可考慮在這里添加代碼。

    重定向原理參見(jiàn)參考文獻(xiàn)《Unix/Linux編程實(shí)踐教程》10.3節(jié)。

  • paser.y

    yacc語(yǔ)法定義文件。

  • y.tab.c/y.tab.h

    yacc生成的語(yǔ)法分析器。

    解析token,調(diào)用make_cmd.c中的函數(shù),生成命令結(jié)構(gòu),便于execute_cmd.c中的函數(shù)執(zhí)行。

    其中包括調(diào)用make_redirection()填寫(xiě)命令結(jié)構(gòu)的重定向參數(shù)。

  • alias.c/alias.h

    別名操作相關(guān)函數(shù),包括增、刪、查、改等。內(nèi)部命令alias的實(shí)現(xiàn)是調(diào)用本文件中的函數(shù)。

  • array.c/array.h/arrayfunc.c/arrayfunc.c

    字符串?dāng)?shù)組定義及相關(guān)函數(shù),實(shí)現(xiàn)了數(shù)組的一些高級(jí)操作。bash程序中一些字符串?dāng)?shù)組使用了這里定義的ARRAY結(jié)構(gòu)。

  • bashansi.h

    針對(duì)不同的編譯器處理一些系統(tǒng)頭文件的包含關(guān)系。

  • bashhist.c/bashhist.h

    命令歷史記錄功能相關(guān)的函數(shù),包括歷史記錄的啟、停、增、查等。

  • bashintl.h

    引入locate.h、gettext.h等國(guó)際化支持。

  • bashjmp.h

    對(duì)setjmp.h的一層封裝,定義了longjmp()的幾種狀態(tài)參數(shù)。

  • bashline.c/bashline.h

    與readline庫(kù)的接口,解決命令自動(dòng)補(bǔ)全、類(lèi)emacs與vi行編輯功能等。

  • bashtypes.h

    定義了word類(lèi)型。

  • bracecomp.c/braces.c

    使用大括號(hào)通配文件名功能的函數(shù)。

  • builtins.h

    定義內(nèi)部命令的基本結(jié)構(gòu)struct builtin。

  • command.h

    各類(lèi)命令(控制結(jié)構(gòu)、函數(shù)、算術(shù)、重定向等)的結(jié)構(gòu)定義。

  • config.h/config-top.h/config-bot.h

    config.h由configure生成,決定有哪些特性要被編譯進(jìn)bash。如果要新增功能,可以加一個(gè)“開(kāi)關(guān)”宏定義。

  • conftypes.h

    定義主機(jī)體系結(jié)構(gòu)和操作系統(tǒng)類(lèi)型的名稱(chēng)。

  • error.c/error.h

    錯(cuò)誤處理與報(bào)告函數(shù)。

  • expr.c

    處理算術(shù)表達(dá)式。外部調(diào)用接口是evalexp()。

  • externs.h

    聲明一些源文件中的函數(shù),它們?cè)谧约旱念^文件中沒(méi)有聲明。

  • findcmd.c/findcmd.h

    通過(guò)名字查找命令。主要是從PATH變量位置查找外部可執(zhí)行程序。

  • flags.c/flags.h

    存儲(chǔ)和處理各個(gè)運(yùn)行狀態(tài)標(biāo)志,如Standard Sh Flags與Non-Standard Flags。

  • general.c/general.h

    很多文件可能公用的一些基礎(chǔ)的、不便分類(lèi)的函數(shù)。

  • hashcmd.c/hashcmd.h

    管理哈希表,主要用于將命令名字映射到完整路徑。

  • hashlib.c/hashlib.h

    哈希表的數(shù)據(jù)結(jié)構(gòu)。

  • input.c/input.h

    處理輸入流緩沖。

  • jobs.c/jobs.h

    作業(yè)控制。主要入口是make_child(),用來(lái)創(chuàng)建進(jìn)程并執(zhí)行。

    jobs、fg、bg、kill等命令的內(nèi)部實(shí)現(xiàn)都在這里。

    作業(yè)管理詳細(xì)流程暫未分析。

  • nojobs.c

    在未實(shí)現(xiàn)作業(yè)控制的操作系統(tǒng)中代替jobs.c編譯。

  • list.c

    鏈表數(shù)據(jù)結(jié)構(gòu)。

  • locale.c

    與國(guó)際化相關(guān)的函數(shù),包括對(duì)“LC_”系列環(huán)境變量的操作。

  • lsignames.h/signames.h

    定義了各種信號(hào)的名稱(chēng)字符串。signames.h是由編譯時(shí)輔助工具mksignames生成的,mksignames的源代碼在support子目錄。

  • mailcheck.c/mailcheck.h

    檢查賬戶(hù)郵箱的函數(shù)。

  • mksyntax.c

    用來(lái)生成編譯時(shí)輔助工具mksyntax。mksyntax與用來(lái)構(gòu)建詞法分析文件syntax.c。

  • syntax.c/syntax.h

    syntax.c是由mksyntax生成的詞法分析文件,syntax.h定義了詞法分析工作中需要的宏和標(biāo)志位等。

  • parser.h

    parse.y和bashhist.c所需的定界符棧結(jié)構(gòu)(struct dstack)的定義。

  • patchlevel.h

    記錄bash的修正版本號(hào)。

  • pathexp.c/pathexp.h

    與通配(globbing)庫(kù)的接口。

  • pathnames.h

    記錄一些操作系統(tǒng)配置文件的路徑。

  • pcomplete.c/pcomplete.h/pcomplib.c

    可編程的命令補(bǔ)全功能。

  • quit.h

    定義通用的異常退出宏,是對(duì)SIGINT信號(hào)的響應(yīng)。

  • sig.c/sig.h/siglist.c/siglist.h

    信號(hào)處理相關(guān)函數(shù)。

  • stringlib.c

    字符串處理相關(guān)函數(shù)。包括從字符串-整數(shù)鍵值對(duì)結(jié)構(gòu)(ALIST)中查找數(shù)據(jù)項(xiàng)等函數(shù)。

  • subst.c/subst.h

    負(fù)責(zé)參數(shù)、命令、算術(shù)、路徑擴(kuò)展、引號(hào)等的代入、展開(kāi)工作。

  • test.c/test.h

    GNU test program,各類(lèi)條件比較條令,在shell腳本中常用。

  • trap.c

    操作trap命令所需的一些對(duì)象的函數(shù)。

  • unwind_prot.c/unwind_prot.h

    通用的函數(shù)執(zhí)行保護(hù)和退出處理機(jī)制。

  • variables.c/variables.h

    處理shell變量。用不同的哈希表分別存儲(chǔ)不同生命周期的shell變量與函數(shù)。

    變量列表是由當(dāng)前環(huán)境來(lái)初始化的。bash啟動(dòng)時(shí)環(huán)境由main()的char **env參數(shù)傳入。

    對(duì)于函數(shù),使用棧來(lái)保存和切換局部變量的上下文。

  • version.c/version.h

    顯示bash版本號(hào)。

  • xmalloc.c/xmalloc.h

    安全版本的malloc封裝。

3.2. 其它目錄

  • builtins

    該目錄下是內(nèi)部命令的源代碼。

    每個(gè)內(nèi)部命令是一個(gè)def文件,Makefile中DEFSRC聲明了所有內(nèi)部命令的def文件。

    由mkbuiltins.c生成編譯時(shí)輔助工具mkbuiltins,mkbuiltins處理*.def文件,生成命令的*.c源程序以及builtins.c、builtext.h。builtins.c和builtext.h相當(dāng)于各個(gè)內(nèi)部命令的索引。

    所有文件最后編譯得到libbuiltins.a。

    此例試驗(yàn)加入一個(gè)了內(nèi)部命令。

  • cross-build

    此目錄下的文件是用于為其它系統(tǒng)交叉編譯而緩存的configure結(jié)果。

  • CWRU

    雜項(xiàng)文件,可能是來(lái)自CWRU,暫未分析。

  • doc

    Te文檔。

  • examples

    腳本編程示例(可用于對(duì)擴(kuò)展后的bash的驗(yàn)證)。

  • include/lib

    bash所需頭文件、庫(kù)文件(源代碼)。

  • po

    用于國(guó)際化的語(yǔ)言定義文件。

  • support

    編譯過(guò)程所需的支持工具及其源代碼。

  • tests

    make tests所用測(cè)試腳本,可用于對(duì)擴(kuò)展后的bash的驗(yàn)證。

第 4 章 主要流程分析

4.1. 命令解析與執(zhí)行

命令解析與執(zhí)行的外部視圖見(jiàn)參考文獻(xiàn)中《詳解Bash命令行處理》一文。

bash啟動(dòng)并初始化完成后,進(jìn)入eval.c中的交互循環(huán)函數(shù)reader_loop()開(kāi)始解析命令。reader_loop()不斷循環(huán)讀取和執(zhí)行命令,直到遇到EOF。

reader_loop()中讀取命令調(diào)用的是read_command()函數(shù),read_command()調(diào)用parse_command(),parse_command()調(diào)用語(yǔ)法分析器y.tab.c中的yyparse(),最終取到命令。read_command()將讀到的命令存入了全局變量global_command。其中:

read_command()的額外工作是執(zhí)行“shell空閑一段時(shí)間后自動(dòng)登出”功能(環(huán)境變量TMOUT)。

parse_command()的額外工作是執(zhí)行PROMPT_COMMAND指定的命令,調(diào)用處理here document的函數(shù)。

yyparse()由yacc通過(guò)parse.y生成。它分析出命令語(yǔ)法后,調(diào)用make_cmd.c中的各種函數(shù)生成不同的COMMAND結(jié)構(gòu)對(duì)象,用以執(zhí)行。

讀到命令后,reader_loop()調(diào)用execute_cmd.c中的execute_command()執(zhí)行命令。針對(duì)不同類(lèi)型的命令(控制結(jié)構(gòu)、函數(shù)、算術(shù)、重定向等),execute_command_internal()調(diào)用不同的函數(shù)來(lái)完成相應(yīng)功能。其中execute_builtin()執(zhí)行內(nèi)部命令;execute_disk_command()執(zhí)行外部文件。execute_disk_command()通過(guò)調(diào)用jobs.c或nojobs.c中的make_child()來(lái)fork新進(jìn)程執(zhí)行。

make_child()同步了輸入流緩沖區(qū),然后fork新進(jìn)程。對(duì)于jobs.c版的make_child(),對(duì)作業(yè)做一些初始化工作,再將待執(zhí)行的命令通過(guò)add_process()函數(shù)加入啟動(dòng)進(jìn)程鏈表。

從make_child()返回后,execute_disk_command()判斷pid,如果是子進(jìn)程,就調(diào)用shell_execve()函數(shù)在該函數(shù)中執(zhí)行(exec)目標(biāo)命令,同時(shí)做一些錯(cuò)誤處理。

源程序?qū)xecute_disk_command()的注釋如下:

/* Execute a simple command that is hopefully defined in a disk file   somewhere.   1) fork ()   2) connect pipes   3) look up the command   4) do redirections   5) execve ()   6) If the execve failed, see if the file has executable mode set.   If so, and it isn't a directory, then execute its contents as   a shell script.   Note that the filename hashing stuff has to take place up here,   in the parent.  This is probably why the Bourne style shells   don't handle it, since that would require them to go through   this gnarly hair, for no good reason.   NOTE: callers expect this to fork or exit(). */

4.2. 重定向的實(shí)現(xiàn)

COMMAND結(jié)構(gòu)有一個(gè)REDIRECT類(lèi)型的指針(redirects),指向了本命令的重定向信息。

REDIRECT結(jié)構(gòu)記錄了重定向的源描述符和目標(biāo):redirectee(類(lèi)型為REDIRECTEE),REDIRECTEE是一個(gè)聯(lián)合類(lèi)型,它可以是目標(biāo)描述符或目標(biāo)文件名。REDIRECT本身包含指向下一個(gè)REDIRECT對(duì)象的指針,因此對(duì)于一個(gè)COMMAND對(duì)象,可以有一系列重定向信息構(gòu)成的鏈表。

語(yǔ)法分析器在遇到重定向語(yǔ)法時(shí),調(diào)用make_cmd.c中的make_redirection()函數(shù)填寫(xiě)COMMAND結(jié)構(gòu)的REDIRECT參數(shù),并設(shè)置表示重定向方式的標(biāo)志位。

execute_cmd.c中的函數(shù)執(zhí)行命令時(shí),調(diào)用redir.c中的do_redirections()實(shí)現(xiàn)重定向。對(duì)于重定向信息鏈表中的每個(gè)REDIRECT對(duì)象,分別交由do_redirection_internal()處理。

do_redirection_internal()針對(duì)重定向方式的標(biāo)志位,做一些特定的設(shè)置,然后調(diào)用redir_open()。

redir_open()對(duì)于不同的重定向目標(biāo),調(diào)用不同的函數(shù)完成文件描述符的打開(kāi)操作。例如軟驅(qū)和網(wǎng)絡(luò)設(shè)備文件調(diào)用redir_special_open(),對(duì)于noclobber mode(禁止覆蓋變量模式)調(diào)用redir_special_open(),一般情況下調(diào)用常規(guī)的open(),打開(kāi)系統(tǒng)最小未用的文件描述符,實(shí)現(xiàn)重定向。

4.3. 內(nèi)部命令(built-in)的構(gòu)建

源代碼目錄(記為$(srcdir))下的builtins目錄存儲(chǔ)的是各個(gè)內(nèi)部命令的源代碼預(yù)定義文件(*.def)。在make的過(guò)程中,由mkbuiltins工具將它們預(yù)編譯為源程序(*.c),進(jìn)而編譯為目標(biāo)文件(*.o)。mkbuiltins工具是由同一目錄下的mkbuiltins.c編譯生成的,它在處理*.def文件的同時(shí),還會(huì)生成builtins.c和builtext.h兩個(gè)文件,用做bash主程序調(diào)用內(nèi)部命令的接口以及各個(gè)內(nèi)部命令的索引。

要添加一條新內(nèi)部命令,只需參考原有命令的存在形式即可,步驟如下:

1、新建預(yù)定義文件:$(srcdir)/builtins/[命令名].def??蓮?fù)制已有命令的預(yù)定義文件,修改其中的$PRODUCES、$BUILTIN、$FUNCTION、$SHORT_DOC等定義,使之與命令名相符。

2、在預(yù)定義文件中建立命令處理函數(shù),原型參考已有命令的處理函數(shù),函數(shù)名與$FUNCTION的定義一致。參數(shù)為WORD_LIST *list,該結(jié)構(gòu)的定義在$(srcdir)/command.h中。處理參數(shù)的具體方法同樣可參考已有的命令(如echo)的處理函數(shù)。

3、修改$(srcdir)/builtins/Makefile.in,參照已有的命令,分別在DEFSRC、OFILES添加對(duì)[命令名].def、[命令名].o的定義;添加[命令名].o對(duì)[命令名].def以及其它頭文件的依賴(lài)關(guān)系。

4、回到$(srcdir)下,對(duì)源代碼進(jìn)行configure、make,如果一切順利的話,此時(shí)生成的bash程序?qū)绿砑拥膬?nèi)部命令。

例 4.1. 新建一條“l(fā)injian”命令

本例中添加的命令處理函數(shù)為:

int linjian_builtin (list)     WORD_LIST *list;{  printf ("This is a built-in for test by Lin Jian.\n");  if (list)    printf("Parameter: %s\n", list->word->word);  return (EXECUTION_SUCCESS);}

編譯后試驗(yàn)結(jié)果如下:

#在原版bash下工作:lj@lj-laptop:~/bash-3.2$ ps  PID TTY          TIME CMD 6212 pts/2    00:00:00 bash 9893 pts/2    00:00:00 pslj@lj-laptop:~/bash-3.2$ linjian-bash: linjian: command not found #進(jìn)入修改后的bash:lj@lj-laptop:~/bash-3.2$ ./bashlj@lj-laptop:~/bash-3.2$ ps  PID TTY          TIME CMD 6212 pts/2    00:00:00 bash 9904 pts/2    00:00:00 bash 9922 pts/2    00:00:00 pslj@lj-laptop:~/bash-3.2$ linjian hello!This is a built-in for test by Lin Jian.Parameter: hello!lj@lj-laptop:~/bash-3.2$ type linjianlinjian is a shell builtin

4.4. 環(huán)境變量與上下文

Linux中每個(gè)進(jìn)程都有自己的環(huán)境(main函數(shù)的char *env[]參數(shù)指向),環(huán)境是由一組變量組成的,這些變量中存有進(jìn)程可能需要引用的上下文信息。bash將環(huán)境變量的復(fù)本保存在variables.c中名為shell_variables的全局VAR_CONTEXT結(jié)構(gòu)中。要導(dǎo)出給子進(jìn)程的變量由全局字符串指針char **export_env記錄,形式是“名=值”字符串?dāng)?shù)組,也就是鍵入export命令看到的內(nèi)容。

bash啟動(dòng)后,調(diào)用variables.c中的initialize_shell_variables()函數(shù),傳入來(lái)自main函數(shù)的env參數(shù),將env中的環(huán)境變量存入shell_variables。對(duì)于PATH、IFS、PS1之類(lèi)bash本身要使用的環(huán)境變量,如果env中尚無(wú),則在此時(shí)建立。另外一些有關(guān)bash版本、命令歷史、郵件檢查等內(nèi)部輔助功能的環(huán)境變量也在這里建立。

execute_cmd.c中調(diào)用各類(lèi)命令的函數(shù)在執(zhí)行命令之前,首先調(diào)用variables.c中的maybe_make_export_env()函數(shù),構(gòu)建導(dǎo)出給子進(jìn)程的環(huán)境,即export_env。shell_execve()執(zhí)行外部命令時(shí)使用的是exec族中的execve()函數(shù),因此可以將export_env傳遞給bash啟動(dòng)的子進(jìn)程。

凡需要增改環(huán)境變量的地方,調(diào)用variables.c中的bind_variable()函數(shù)實(shí)現(xiàn)。例如在cd命令執(zhí)行后需要重設(shè)PWD。

4.5. 由sshd啟動(dòng)bash的過(guò)程

bash啟動(dòng)時(shí),shell.c中的run_startup_files()通過(guò)查找SSH_CLIENT、SSH2_CLIENT環(huán)境變量是否存在,來(lái)判斷自己是不是由sshd啟動(dòng)的,記錄在變量run_by_ssh中。此外可以通過(guò)檢查stdin的文件描述符是否被重定向?yàn)榫W(wǎng)絡(luò)文件或套接字來(lái)判斷bash是不是由rshd啟動(dòng)的。如果bash啟動(dòng)自sshd或rshd,并且是頂層shell(非子shell),則執(zhí)行~/.bashrc腳本。

防止~/.bashrc被多次執(zhí)行的方法是只在bash是頂層shell時(shí)加載之。作者曾考慮在initialize_shell_variables()過(guò)程中設(shè)置SSH_CLIENT、SSH2_CLIENT環(huán)境變量為非導(dǎo)出的,來(lái)避免子shell知道自己的父shell是由sshd啟動(dòng)的,從而不執(zhí)行~/.bashrc。但他最終放棄了這個(gè)方法。

除此以外,bash對(duì)ssh沒(méi)有其它特殊處理。

4.6. 子shell

shell啟動(dòng)的shell子進(jìn)程稱(chēng)為子shell。直接以文件名運(yùn)行可執(zhí)行文件時(shí),bash并不知道它調(diào)用的一個(gè)可執(zhí)行是二進(jìn)制文件還是腳本,只是在exec過(guò)程中交給系統(tǒng)內(nèi)核處理。對(duì)于shell腳本,通常以“#![shell可執(zhí)行文件名]”開(kāi)頭,“#!”是一種magic number。當(dāng)內(nèi)核通過(guò)magic number斷定執(zhí)行的是腳本時(shí),就會(huì)調(diào)用一個(gè)新的指定的shell的實(shí)例來(lái)解釋執(zhí)行腳本,這個(gè)實(shí)例就是子shell。父子shell是兩個(gè)進(jìn)程,所以各自的變量是獨(dú)立的。除非父shell將自己的變量導(dǎo)出到環(huán)境中,否則子shell無(wú)法獲得父shell中定義的變量。

bash通過(guò)變量SHLVL記錄自己是進(jìn)程調(diào)用棧中哪一層的shell,即bash被嵌套的深度。bash啟動(dòng)時(shí),調(diào)用variables.c中的initialize_shell_level()設(shè)置SHLVL。系統(tǒng)login之后啟動(dòng)的bash的SHLVL為1,每層shell啟動(dòng)的子shell的SHLVL在其環(huán)境中讀到的SHLVL基礎(chǔ)上加1。

使用source命令(“.”命令)執(zhí)行腳本時(shí),不開(kāi)啟子shell。bash內(nèi)部的實(shí)現(xiàn)是將腳本文件內(nèi)容讀入一個(gè)緩沖區(qū),然后執(zhí)行語(yǔ)法分析,因此效果與直接從鍵盤(pán)輸入腳本內(nèi)容相同。

第 5 章 雜記

5.1. bash編程風(fēng)格

bash的C語(yǔ)言函數(shù)聲明使用了“__P”宏,定義遵循K&R規(guī)范,因此可以使用舊的非ANSI的編譯器編譯。bash源代碼充分考慮了不同的CPU體系結(jié)構(gòu)、不同的操作系統(tǒng)和不同的編譯器的差異,使用宏和條件編譯處理這類(lèi)問(wèn)題,增強(qiáng)可移植性的同時(shí)不增加代碼復(fù)雜性。對(duì)于某些可選擇編譯的特性,bash通過(guò)定義宏作為開(kāi)關(guān),這些宏可以在configure時(shí)由用戶(hù)參數(shù)決定取值,不需要編譯者顯式修改源代碼。

bash的代碼縮進(jìn)風(fēng)格并不是很統(tǒng)一,可能是有來(lái)自不同貢獻(xiàn)者的代碼。多數(shù)代碼與常見(jiàn)的Java風(fēng)格差異較大,有些地方不借助Source Insight這樣的工具很容易找不到頭緒。bash并不十分避諱goto的使用,一些使用goto的地方的可讀性還是比較好的。

由于多數(shù)函數(shù)名稱(chēng)都是完整的動(dòng)賓短語(yǔ),所以并非每個(gè)函數(shù)都有注釋?zhuān)ㄟ^(guò)名稱(chēng)可以了解其大致功能。對(duì)于某些復(fù)雜的流程,函數(shù)內(nèi)部有一些注釋?zhuān)簧僮⑨尵哂杏懻摵徒ㄗh的性質(zhì),對(duì)未來(lái)的貢獻(xiàn)者有啟發(fā)性。和大多數(shù)GNU程序一樣,bash中也有不少風(fēng)趣的注釋?zhuān)w現(xiàn)出西方特色的幽默。

附錄 A. 學(xué)習(xí)備注(Q&A)

  • Q:__P是什么?

    A:ANSI C之前的舊編譯器不支持函數(shù)原型定義。使用“__P”宏為ANSI和非ANSI的編譯器提供一種可移植的方案?!癬_P”的實(shí)現(xiàn)通常如下:

      # if defined(__STDC__) || defined(__GNUC__)  #   definec__P(x) x  # else  #   define __P(x) ()  # endif
  • Q:IFS是什么?

    shell環(huán)境變量IFS(Internal Field Separator)中可能存儲(chǔ)的值是空格、TAB、換行符等,在bash中默認(rèn)是0x0a。它用來(lái)在語(yǔ)法分析、變量代入等過(guò)程中界定命令。

  • Q:Here Document是什么?

    一個(gè)here document就是一段帶有特殊目的的代碼段。它使用I/O重定向的形式將一個(gè)命令序列傳遞到一個(gè)交互程序或者命令中,比如ftp、cat或者ex文本編輯器。例如:

    COMMAND <<="" pre="">

附錄 B. 參考文獻(xiàn)

  1. Mendel Cooper.Advanced Bash-Scripting Guide,2006.http://personal.riverusers.com/~thegrendel/abs-guide.pdf

  2. Bruce Molay.Unix/Linux編程實(shí)踐教程,北京:清華大學(xué)出版社,2004.

  3. Eric S. Raymond.Unix編程藝術(shù),北京:電子工業(yè)出版社,2006.

  4. home_king.詳解Bash命令行處理,2005.http://www.linuxsir.org/main/?q=node/134

  5. Chet Ramey.Bash FAQ version 3.36,2006.ftp://ftp.cwru.edu/pub/bash/FAQ

附錄 C. 作者信息

林健,北京理工大學(xué)計(jì)算機(jī)科學(xué)技術(shù)學(xué)院學(xué)生。轉(zhuǎn)載本文請(qǐng)注明出處。如發(fā)現(xiàn)文中不妥或錯(cuò)誤之處,望不吝賜教。

作者網(wǎng)站:http://www.linjian.cn

作者Blog:http://blog.linjian.cn

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶(hù)發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
shell 基礎(chǔ) $(cd `dirname $0`;pwd)
shell特殊字符匯總
linux shell 學(xué)習(xí)筆記(四)
學(xué)習(xí) shell腳本之前的基礎(chǔ)知識(shí)
Bash 編程
bash shell編程快速入門(mén)教程
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服