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)