標籤:
接前文postgresql編譯安裝與調試(一),繼續說說postgresql的編譯安裝與調試。
上一篇已經詳細說明了如何在Linux系統上編譯安裝postgresql,這次我們在此基礎上簡單講講如何在linux系統上調試和追蹤代碼。
我記得之前看過一篇關於posgresql的文章,postgresql最早只有20萬左右的代碼量,而如今已經過100萬行了,如此巨大的代碼量,在沒有綱領的前提下簡直是盲人摸象。
為方便調試工作,在進入具體的調試之前,我們先來好好瞭解下postgresql的代碼體繫結構。
首先進入postgresql的第一級目錄
config檔案夾主要放的是一些設定檔;contrib檔案夾裡放的是一些第三方的外掛程式、擴充程式等,常用的有pg_standby、postgres_fdw這些;doc檔案夾不用說放的是一些協助文檔和manuals;最主要的是src目錄,這裡放置的是postgresql的原始碼,也是我們調試和跟蹤的主要檔案目錄了。然後INSTALL文檔裡較為詳細的寫了如何編譯安裝postgresql;configure和Makefile這些是程式編譯時間要用的檔案了。
進去src目錄,
首先那幾個Makefile檔案什麼的就不用多介紹了,主要看看這幾個檔案夾。
bin/ 放置了postgresql的unix命令,比如psql、initdb這些的原始碼;
backend/ postgresql後端程式的原始碼;
include/ 標頭檔;
interfaces/ 前端相關的庫的代碼(包括pgsql的C語言庫libpq);
makefiles/ 平台相關的make的設定檔案;
pl/ 預存程序語言的代碼;
port/ 平台移植相關的代碼;
template/ 平台相關的設定檔案;
test/ postgresql內建的各種測試指令碼;
timezone/ 時區相關的代碼檔案;
tools/ 各種開發工具和文檔;
tutorial/ 各種相關教程。
可以看出比較核心的是backend、bin、interfaces這三個目錄,其中backend對應後端(伺服器端),剩下兩個對應前端(用戶端)。
對於我們的調試工作,大部分關注點集中在後端,即backend目錄,在該目錄下細分了好多目錄:
access/ 各種儲存存取方法(在各個子目錄下) common(共同函數)、gin (Generalized Inverted Index通用逆向索引) 、gist (Generalized Search Tree通用索引)、
hash (雜湊索引)、heap (heap的存取方法)、index (通用索引函數)、 nbtree (Btree函數)、transam (交易處理)、 bootstrap/ 資料庫的初始化處理(initdb的時候)
catalog/ 系統目錄
commands/ SELECT/INSERT/UPDATE/DELETE以為的SQL文的處理
executor/ 執行器(訪問的執行)
foreign/ FDW(Foreign Data Wrapper)處理
lib/ 共同函數
libpq/ 前端/後端通訊處理
main/ postgres的主函數
nodes/ 構文樹節點相關的處理函數
optimizer/ 最佳化器
parser/ SQL構文解析器
port/ 平台相關的代碼
postmaster/ postmaster的主函數 (常駐postgres)
replication/ streaming replication
regex/ 正則處理
rewrite/ 規則及視圖相關的重寫處理
snowball/ 全文檢索索引相關(語幹處理)
storage/ 共用記憶體、磁碟上的儲存、緩衝等全部一次/二次記錄管理(以下的目錄)buffer/(緩衝管理)、 file/(檔案)、freespace/(Fee Space Map管理) ipc/(處理序間通訊)、
large_object /(大對象的訪問函數)、 lmgr/(鎖管理)、page/(頁面訪問相關函數)、 smgr/(儲存管理器)
tcop/ postgres (資料庫引擎的進程)的主要部分
tsearch/ 全文檢索索引
utils/ 各種模組(以下目錄) adt/(嵌入的資料類型)、cache/(緩衝管理)、 error/(錯誤處理)、fmgr/(函數管理)、hash/(hash函數)、 init/(資料庫初始化、postgres的初期處理)、
mb/(多位元組文文書處理)、misc/(其他)、mmgr/(記憶體的管理函數)、 resowner/(查詢處理中的資料(buffer pin及表鎖)的管理)、sort/(排序處理)、time/(事務的 MVCC 管理)
首先我們要有gdb這個工具,如果沒有,可以用yum命令自動的去安裝它。
調試postgresql,我們先以最簡單的SQL文作為例子示範如何調試跟蹤代碼。例如:
select 1;
首先,我們利用postgres使用者進入postgresql:
既然要用gdb跟蹤偵錯工具,我們首先要知道postgresql後端進程的pid,然後才能attach上進行調試(對gdb命令不熟悉的可以先自行百度下)。
要擷取postgresql的pid,我們有兩個辦法。
方法1.使用ps命令查看
[[email protected] ~]# ps -ef | grep postgres
我們可以看到
那個[local] idle 提示的那個就是我們要的,可知進程pid為16581;
方法2.直接在進入postgresql後運行下面的查詢語句:
select pg_backend_pid();
也可以得到進程的pid,方便快捷。
得到進程的pid後,我們就可以進入gdb調試了。
另開一個視窗,我們輸入如下命令:
[[email protected] ~]# gdb postgres 16581
進入了gdb命令列介面。
在這個狀態下,可以接受gdb命令,這裡,我們使用b命令在ExecResult處打上斷點:
這個時候我們再回到postgresql的視窗,執行SQL文:
我們可以看到因為postgres進程已經暫停,SQL會卡在那裡動不了,這也是我們的目的,不然怎麼一步一步(似魔鬼的步伐)的調試呢?
我們再回到gdb這邊,運行c命令,程式就會繼續執行下去,然後再斷點處(ExecResult)停止。
作為一個好奇寶寶,我們當然會很好奇執行路徑上走過了哪些檔案調用了哪些函數(廢話,不然幹嘛要調試)?
好的,我們執行gdb的bt命令:
這一大串就是我們夢寐以求的函數調用的堆棧了。這樣從程式開始到ExecResult為止的函數調用都有了。既然說是“堆棧”,我們自然是要反著看的,比如,我們可以看到最早調用的是main函數,它在(at)main.c檔案裡,在main函數的第228行,調用了PostmasterMain函數,依次類推即可知道函數的調用路徑。
知道了函數的調用路徑,我們可以一步一步地看看這條語句是怎麼走的了。以postgresql9.5.4為例(限於篇幅和時間限制,只粗略的講講):
#13 main.c 內:
line99: 函數MemoryContextInit()啟動必須的子系統error和memory管理系統;
line110:函數set_pglocale_pgservice()擷取並設定環境變數;
line146~148: 函數init_locale初始化環境變數;
line219~228:根據輸入參數確定程式走向,這裡進入了PostmasterMain(),跳轉至postmaster.c檔案。
#12#11#10#9 postmaster.c 內:
該檔案中定義了後端的常駐進程"postmaster"所使用的主要函數介面和資料結構定義。postmaster接受前端的請求,建立新的backend進程。
line561~623:讀取上下文資訊和設定檔,完成初始化;
line630~812:讀取psql命令列的命令參數;
line930~1000:建立socket通訊;
line1100~1159:建立shared memory和semaphores以及堆棧和pipe,初始化子系統(stats collection、autovacuum);
line1296:進入ServerLoop()函數,跳轉至line1604;
line1604:ServerLoop()函數入口。該函數迴圈監聽連接埠上的串連請求;
line1673~1699:判斷是否有"合法"的串連請求,fork一個子進程去處理它,進入BackendStartup()函數,跳轉至line3857;
line3857:BackendStartup()函數入口。該函數負責開啟一個新的backend進程;
line3858~3914:做一些初始化準備(資料結構,開啟和關閉一些必要的進程等等);
line3917:進入BackendRun()函數,跳轉至line4179;
line4179:BackendRun()函數入口,該函數運行backend進程,主要幹兩件事:1.建立參數列表並初始化2.調用PostgresMain()函數;
line4243:調用PostgresMain()函數,進入postgres.c檔案.
#8#7 postgres.c 檔案內:
該檔案定義了postgres後端的主要模組,相當於後端的main,並且負責後端進程的調度。
line3572:PostgresMain()函數入口。根據輸入的dbname,username和輸入參數建立一個會話;
line3573~3801:初始化工作。開設初始化環境和預設參數,設定訊號處理函數和其他參數,建立記憶體上下文,設定share buffer等等等等;
line3825:進入POSTGRES的主處理迴圈,這個if語句主要用於判斷輸入處理是否有異常等;
line3933:進入處理迴圈中。該迴圈監聽新的查詢請求並判斷請求的類別;
line4045:判斷查詢請求為simple query,調用exec_simple_query()函數,跳轉至line884;
line884:exec_simple_query()函數入口。該函數做一些初始化工作,建立一個transaction command,做簡單的文法規則判斷,分析重寫,並為該查詢建立查詢計劃,並返回查詢結果;
line1104:進入函數PortalRun(),進入pquery.c檔案.
#6#5 pquery.c 檔案內:
該檔案定義了postgres後端查詢語句的代碼。
line706:PortalRun()函數入口。該函數負責運行一個或一組查詢;
line786:進入PortalRunSelect()函數,跳轉至line888;
line888:PortalRunSelect()函數入口。該函數只能執行簡單的SELECT查詢操作;
line942:進入ExecutorRun()函數,進入execMain.c檔案.
#4#3#2 execMain.c 檔案內:
該檔案給出了執行的四個介面函數,分別是ExecutorStart() ExecutorRun() ExecutorFinish() ExecutorEnd()。
line279:ExecutorRun()函數入口。該函數時執行模組的主要部分,它接受一個查詢描述符並真正的執行一個查詢語句;
line285:進入standard_ExecutorRun()函數。跳轉至line289;
line289:standard_ExecutorRun()函數入口。它執行"標準"的查詢;
line337:進入ExecutePlan()函數,跳轉至line1517;
line1517:ExecutePlan()函數入口。還記得前面exec_simple_query()說的查詢計劃嗎?這裡用上了,執行該查詢計劃。
line1541:進入查詢計劃執行的主迴圈;
line1549:進入ExecProcNode()函數,進入execProcnode.c檔案.
#1 execProcnode.c 檔案內:
該檔案內提供了執行查詢計劃的調度函數,功能分別是:
ExecInitNode():初始化查詢計劃的節點以及其子查詢計劃;
ExecProcNode():通過執行查詢計劃獲得元組;
ExecEndNode():關閉一個查詢節點和它的子查詢計劃。
line367:ExecProcNode()函數入口;
line385:進入ExecResult()函數,跳轉至檔案nodeResult.c.
#0 nodeResult.c 檔案內:
該檔案主要為每個查詢計劃的節點提供支援。
line67:ExecResult()函數入口,該函數返回查詢計劃獲得的元組。
這一段從#13到#0的函數調用簡單分析就到這裡,完全是自己的理解,如果有什麼不對的地方,歡迎大家批評指正,共同進步。接下來還有函數調用的返回,這裡就不細說了,留給自己和大家一起好好琢磨琢磨吧~
不得不說,postgresql的源碼寫的很優雅,注釋也很到位,看起來很少有雲山霧罩的感覺,真乃吾輩楷模。說起閱讀源碼,想推薦一本書,叫《代碼閱讀方法與實踐》,書不太好找,我還是在托師弟在學校的圖書館才找到的。
另外,今天這種代碼閱讀方法仍然有些原始和低效,決定再看看使用Emacs的Tag或者Eclipse來調試一些更難一些的例子,這個例子畢竟比較簡單。這些就留給postgresql編譯安裝與調試(三)來完成吧,感覺這個系列要出好多的樣子呢,哈哈~
postgresql編譯安裝與調試(二)