Redis原始碼漂流記(二)-搭建Redis除錯環境
一、目標
搭建Redis除錯環境 簡要理解Redis命令運轉流程
二、前提
1、有一些c知識簡單基礎(變數命名、常用資料型別、指標等)
可以參考這篇簡單入門C語言入門教程 , 或者B站搜尋C語言相關教材(播放量最高的幾個均可)。
/*引入標頭檔案,類似java和go中的import包,C#中的using名稱空間*/
#include<stdio.h>
int main(void) /*一個簡單的C程式*/
{
int number; /*定義個名字叫做number的變數*/
number=2022; /*給number賦一個值*/
printf("This year is %d\n",number); /*呼叫printf()函式*/
int intsize = sizeof(int);
/*輸出:int sizeof is 4 bytes*/
printf("int sizeof is %d bytes\n",intsize);
return 0;
}
/*Redis State of an event based program */
typedef struct aeEventLoop {
/* highest file descriptor currently registered */
int maxfd;
/* max number of file descriptors tracked */
int setsize;
long long timeEventNextId;
aeFileEvent *events; /* Registered events */
aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead;
int stop;
/* This is used for polling API specific data */
void *apidata;
aeBeforeSleepProc *beforesleep;
aeBeforeSleepProc *aftersleep;
int flags;
} aeEventLoop;
基本資料型別
2、瞭解Redis的基本使用
如 set/get等即可。
3、 本地搭建過Ubautu虛擬機器或者直接有伺服器。
考慮到redis一般安裝到linux環境中,所以採取Ubantu進行除錯。
“windows下需要下載redis-windows版本的原始碼,IDE採用使用Clion搭建redis debug環境或Using GCC with MinGW
”
三、搭建IDE環境
1、安裝vscode
2、安裝vscode c++擴充套件
C/C++:提供C/C++支援 Code Runner:提供編譯後程式的執行環境 C/C++ Snippets:提供一些常用的C/C++片段,如for(;;){},安裝後寫程式碼 方便(tip.如果想要新增自己寫的程式碼段可以點左下角齒輪->使用者程式碼片段) EPITECH C/C++ Headers :為C/C++檔案新增頭部(包括作者、建立和修改日期等),併為.h標頭檔案新增防重複的巨集 Include Autocomplete: 標頭檔案自動補全
3、安裝 gcc/gdb
先嚐試下面命令安裝
sudo apt-get update
sudo apt-get install build-essential gdb
如果不行就嘗試下面的。
sudo apt-get install aptitude
sudo aptitude install gcc g++
如果存在依賴性報錯安裝失敗,對建議的方案,第一個no,第二個yes. 檢測是否安裝成功
gcc -v
g++ -v
gdb -v
make -v
四、檢測c檔案執行和除錯
建立一個目錄,用於存放演示檔案
mkdir MyCode/src
cd MyCode/src
建立hello.c檔案
#include <stdio.h>
int main()
{
puts("HelloC");
return 0;
}
執行:按CodeRunner快捷鍵【Ctrl+Alt+N】執行程式碼:
[Running] cd "/home/fcw/MyCode/src/main/" && gcc hello.c -o hello && "/home/fcw/MyCode/src/main/"hello
HelloC
除錯: Run-->Start Debugging或 F5除錯
選擇環境 選擇配置
五、下載和編譯Redis
1.下載redis原始碼
// 建立redis目錄
mkdir MyCode/redis
cd MyCode/redis
// 下載redis
wget http://download.redis.io/releases/redis-6.2.7.tar.gz
// 解壓
tar xzf redis-6.2.7.tar.gz
cd redis-6.2.7/
2、編譯Redis
編輯Makefile (這裡也可以開啟vsCode編輯複製 )
vim src/Makefile
更新 makefile 下面對應的編譯項內容,修改該項的主要目的是為了防止編譯優化.
“O0 -->> O1 -->> O2 -->> O3 (少優化->多優化), -O0表⽰沒有優化,-O1為預設值,-O3優化級別最⾼
”
# --------------------
# OPTIMIZATION?=-O2
OPTIMIZATION?=-O0
# REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) $(OPTIMIZATION)
# --------------------
更新前: 更新後
編譯redis
make clean; make
Makefile和Make簡要說明:
“需要執行/除錯多檔案時,Makefile可以設定你想要的編譯規則,你想要編譯哪些檔案,哪些檔案不需要編譯等等都可以體現在Makefile中,而且支援多執行緒併發操作,可以減少編譯的時間。
”
“make是用來執行Makefile的,make根據Makefile中寫的內容進行編譯和連結,make更像是一個批處理的工具,可以批處理原始檔,只要執行一條make命令,就可以實現自動編譯。當我們編譯整個專案工程的時候,make只會編譯我們修改過的檔案,沒有修改過的就不用重新編譯,使用make+Makefile極大的提高了我們的工作效率。
”
“cmake可以生成Makefile檔案,支援生成不同平臺的Makefile。cmake根據一個叫CMakeLists.txt(手寫)的檔案生成Makefile。
”
3、配置launch.json
launch.json
隨便選中一個c檔案。點除錯(F5),會提示新增配置。
{
"configurations": [
{
"name": "(gdb) 啟動",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/src/redis-server",
"args": [ "${workspaceFolder}/redis.conf"],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "為 gdb 啟用整齊列印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "將反彙編風格設定為 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
六、除錯Redis原始碼-初探
通過Readme.md
可以看到相關檔案的簡介,例server.c
檔案
server.c
---
This is the entry point of the Redis server, where the `main()` function
is defined. The following are the most important steps in order to startup
the Redis server.
* `initServerConfig()` sets up the default values of the `server` structure.
* `initServer()` allocates the data structures needed to operate, setup the listening socket, and so forth.
* `aeMain()` starts the event loop which listens for new connections.
There are two special functions called periodically by the event loop:
1. `serverCron()` is called periodically (according to `server.hz` frequency), and performs tasks that must be performed from time to time, like checking for timed out clients.
2. `beforeSleep()` is called every time the event loop fired, Redis served a few requests, and is returning back into the event loop.
Inside server.c you can find code that handles other vital things of the Redis server:
* `call()` is used in order to call a given command in the context of a given client.
* `activeExpireCycle()` handles eviction of keys with a time to live set via the `EXPIRE` command.
* `performEvictions()` is called when a new write command should be performed but Redis is out of memory according to the `maxmemory` directive.
* The global variable `redisCommandTable` defines all the Redis commands, specifying the name of the command, the function implementing the command, the number of arguments required, and other properties of each command.
找到 server.c
, main
這是總入口
int main(int argc, char **argv)
aeMain(server.el);//這裡面是一個事件迴圈監聽
aeDeleteEventLoop(server.el);
這裡接收tcp或socket連線,然後將event及handler放入事件池/bus中。
/* Create an event handler for accepting new connections in TCP or TLS domain sockets.
* This works atomically for all socket fds */
int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler) {
int j;
for (j = 0; j < sfd->count; j++) {
if (aeCreateFileEvent(server.el, sfd->fd[j], AE_READABLE, accept_handler,NULL) == AE_ERR) {
/* Rollback */
for (j = j-1; j >= 0; j--) aeDeleteFileEvent(server.el, sfd->fd[j], AE_READABLE);
return C_ERR;
}
}
return C_OK;
}
另起一個終端,執行redis-cli
,會連結到redis-server
,從而除錯redis相關原始碼。
cd MyCode/redis/redis-6.2.7/
./src/redis-cli
找到ae.c
,這裡是一個while迴圈監控命令,用於監聽新函式的事件迴圈處理(server.c
的main
函式會呼叫這裡)。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
AE_CALL_BEFORE_SLEEP|
AE_CALL_AFTER_SLEEP);
}
}
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
找到connection.c
static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
找到connhelper.c
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
connIncrRefs(conn);
if (handler) handler(conn);
connDecrRefs(conn);
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
if (!connHasRefs(conn)) connClose(conn);
return 0;
}
return 1;
找到networking.c
在 processInputBuffer
和processCommandAndResetClient
處打斷點
int processCommandAndResetClient(client *c) {
int deadclient = 0;
client *old_client = server.current_client;
server.current_client = c;
if (processCommand(c) == C_OK) {
commandProcessed(c);
}
找到server.c
,在 processCommand
和call
等處打斷點
處理命令的總入口。
int processCommand(client *c)
經過了moduleCallCommandFilters
、檢查是否是quit
、lookupCommand
、authRequired
、ACLCheckAllPerm
、cluster_enabled
、server.maxmemory
、writeCommandsDeniedByDiskError
、rejectCommand
、blockClient
等一系列安全檢查邏輯後,來到了執行命令的地方
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
...
...
/* Exec the command */
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand &&
c->cmd->proc != resetCommand)
{
queueMultiCommand(c);
addReply(c,shared.queued);
} else {
call(c,CMD_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys))
handleClientsBlockedOnKeys();
}
void call(client *c, int flags)
...
server.in_nested_call++;
c->cmd->proc(c);
server.in_nested_call--;
找到t_string.c
setCommand
處理命令的堆疊資訊如下:
此後將結果寫回到客戶端
int writeToClient(client *c, int handler_installed) {
/* Update total number of writes on server */
atomicIncr(server.stat_total_writes_processed, 1);
返回結果的堆疊資訊如下:
從上面的除錯以及堆疊資訊可以看出,處理結果和將結果寫回到客戶端是在兩個事件中處理的。
總結redis服務端整個程式流程圖如下:
七、環境問題解決
gcc : 依賴: gcc-7(>= 7.3.0-12~) 但是它將不會被安裝
sudo apt-get install gcc
出現如下錯誤:
正在讀取軟體包列表… 完成
正在分析軟體包的依賴關係樹
正在讀取狀態資訊… 完成
有一些軟體包無法被安裝。如果您用的是 unstable 發行版,這也許是
因為系統無法達到您要求的狀態造成的。該版本中可能會有一些您需要的軟體
包尚未被建立或是它們已被從新到(Incoming)目錄移出。
下列資訊可能會對解決問題有所幫助:
下列軟體包有未滿足的依賴關係:
gcc : 依賴: gcc-7(>= 7.3.0-12~) 但是它將不會被安裝
E: 無法修正錯誤,因為您要求某些軟體包保持現狀,就是它們破壞了軟體包間的依賴關係。
使用aptitude包依賴管理工具代替apt來處理,aptitude軟體包管理工具在解決依賴性問題上更有優勢,具體使用方法如下:
sudo apt-get install aptitude
sudo aptitude install gcc g++
終端中輸入後會提示aptitude給出的解決方案,可以選擇no,
會繼續提供下一個解決方案,但前面的方案會是忽略掉依賴衝突,所以想要徹底解決的話可以跳過前面的幾種方案,然後再yes解決。(個人第一次No,第二次Yes)
https://blog.csdn.net/zhutingting0428/article/details/51120949
linux ubuntu gcc編譯 fatal error: bits/libc-header-start.h 錯誤解決
apt-get install gcc-multilib
“其實主要是gcc安裝環境沒有安裝完善Multilib,顧名思義,就是多重的。 用它完全可以替代原來單一的lib。 這樣就既能產生32位的程式碼,又能生成64位的。比如:64bit機器,同時可以產生32和64兩種格式,
”
八、引用資料
gcc優化選項-O1-O2-O3-Os優先順序 淺析Makefile、make、cmake Debug Redis in VsCode with Gdb 用 gdb 除錯 redis Ubuntu下配置VS Code C++ 環境 ubuntu18.04+VScode使用記錄(持續更新 在vscode中配置C/C++環境 how-to-install-and-configure-redis-on-ubuntu-20-04 【Redis原始碼】Redis命令set學習(一)
“這篇文章整理了一個從服務端和客戶端兩個維度流程圖片
”
九、轉載請註明出處
https://www.cnblogs.com/fancunwei ”威行雲棧“公眾號(微訊號:fundeway)