二、前提
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进行调试。
三、搭建IDE环境
1、安装vscode
2、安装vscode c++扩展
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
vim src/Makefile
# --------------------
# OPTIMIZATION?=-O2
OPTIMIZATION?=-O0
# REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) $(OPTIMIZATION)
# --------------------
更新前:更新后
make clean; make
Makefile和Make简要说明:
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服务端整个程序流程图如下:
七、环境问题解决
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
apt-get install gcc-multilib