从此启程/范存威

从此启程/范存威

  • 二、前提

    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;

    基本数据类型Redis源码漂流记(二)-搭建Redis调试环境-LMLPHP

    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源码漂流记(二)-搭建Redis调试环境-LMLPHP选择配置Redis源码漂流记(二)-搭建Redis调试环境-LMLPHPRedis源码漂流记(二)-搭建Redis调试环境-LMLPHP

    五、下载和编译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)
    # --------------------

    更新前:Redis源码漂流记(二)-搭建Redis调试环境-LMLPHP更新后Redis源码漂流记(二)-搭建Redis调试环境-LMLPHP

    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.cmain函数会调用这里)。

    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.cprocessInputBufferprocessCommandAndResetClient 处打断点

    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,在 processCommandcall等处打断点

    处理命令的总入口。

    int processCommand(client *c) 

    经过了moduleCallCommandFilters、检查是否是quitlookupCommandauthRequiredACLCheckAllPermcluster_enabledserver.maxmemorywriteCommandsDeniedByDiskErrorrejectCommandblockClient等一系列安全检查逻辑后,来到了执行命令的地方

      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服务端整个程序流程图如下:Redis源码漂流记(二)-搭建Redis调试环境-LMLPHP

    七、环境问题解决

    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 

    八、引用资料

    九、转载请注明出处

    05-07 23:56