tracee源码初探TCP处理流程

Posted 小虎牙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tracee源码初探TCP处理流程相关的知识,希望对你有一定的参考价值。

handleEvents(ctx), processNetCaptureEvents(若开启Capture.Net)协程一直常驻,并等待netCapChannel消息通知. 当有事件传过来时, 程序先看该事件是否需要处理,也就是说tracee是上报所有事件的,然后过滤来处理事件。在tracee.go中的initBPF函数里t.bpfModule.InitPerfBuf( "net_cap_events", ...)函数,初始化了网络事件的PerfBuf, 然后tracee.bpf.c是挂载到内核的函数SEC("cgroup_skb/ingress")SEC("cgroup_skb/egress"),当有网络事件时,把事件放到net_cap_events map中并提交到用户空间。具体过程是:
挂载事件触发函数cgroup_skb_generic-->CGROUP_SKB_HANDLE(proto)->函数定义展开为cgroup_skb_handle_proto(ctx, neteventctx, nethdrs)-->CGROUP_SKB_HANDLE_FUNCTION(proto)【这一步映射是猜的】 如果是IP报文则解析IP报文属性,当解析出事TCP报文时调用CGROUP_SKB_HANDLE_FUNCTION(proto_tcp)-->cgroup_skb_capture-->cgroup_skb_capture_event(ctx, neteventctx, NET_PACKET_CAP_BASE)-->cgroup_skb_submit(&net_cap_events,ctx,neteventctx,event_type,nc->capture_length);
这里需要注意的一点是cgroup_skb_generic函数中用到的ingress和egress方向的已存储map:cgrpctxmap_in和cgrpctxmap_eg是由SEC("kprobe/__cgroup_bpf_run_filter_skb")挂载的函数实现存储起来的,cgroup_skb_generic只通过bpf_map_lookup_elem查找处理完之后再用bpf_map_delete_elem把对应的map数据删除。

当输入传送到用户空间的handleEvents函数来处理,会调用processEvents触发processor来处理(例如processWriteEvent等)然后是deriveEvents来处理派生函数NetPacketTCP对应的派生函数是derive.NetPacketTCP()解析TCP的各个字段,最后handleEvents调用sinkEvents把事件放入到t.config.ChanEvents中,然后tracee一直运行的Run()函数取出ChanEvents中的事件进行事件输出给用户可视端。

而如果config.Capture.Net配置开启还有processNetCapEvent来处理,刚好事件的ID是NET_PACKET_CAP_BASE, 最后写入pcap文件。

redis源码学习之工作流程初探

背景

redis是当下比较流行的KV数据库之一,是抵御高并发的一把利器,本着知其然还要知其所以然的目的,我决定花一点时间来研究其源码,希望最后能向自己解释清楚“redis为什么这么快”这个疑惑,这一篇主要介绍redis运行环境搭建和redis工作流程初探,后期会陆续献上其他有意思的章节。

环境准备

我自己的电脑是win10系统,所以我会准备一套适合windows系统的环境来供自己学习,这样方便调试分析。

下载redis源码

下载Visual Studio

Visual Studio打开redis源码

按照下图方式打开下载的redis源码

redis源码学习之工作流程初探


redis源码学习之工作流程初探


redis源码学习之工作流程初探


c程序的入口是main方法,redis main方法的位置在redis.c文件中,下面我们通过main方法来逐步了解redis的工作流程。


启动过程分析

跟着main方法顺序看下去,大概有以下几个关键步骤(略过了sentinel相关逻辑):
1.设置随机数种子、获取当前时间等;
2.初始化服务配置信息,设置默认值(initServerConfig);
3.解析配置文件(loadServerConfig);
4.初始化server对象(initServer);
 4.1创建eventLoop对象;
 4.2创建serverSocket,监听端口;
 4.3添加定时事件到eventLoop对象中;
 4.4将serverSocket文件描述符添加到监视集中,这里借助IO多路复用框架的能力(windows平台使用IOCP,其他平台使用select、epoll、evport等);
5.从磁盘加载数据到内存中(loadDataFromDisk);
6.执行事件循环逻辑(aeMain),这是redis真正挥洒汗水的地方,下一节会单独讲述这块内容。

调用关系图

redis源码学习之工作流程初探

事件循环分析

我们都知道redis是单线程执行客户端命令的,那究竟是怎样一种设计才能支持高并发的读写呢。

工作模型

1.server启动,创建serverSocket监听端口,将serverSocket对应的FD(文件描述符)简称为FD-Server添加到IO多路复用框架的监视集当中,注册AE_READABLE事件(可读),关联的事件处理器是acceptTcpHandler;
2.client连接server;
3.事件循环开始轮询IO多路复用框架接口aeApiPoll,会得到就绪的FD,执行对应的事件处理器;
4.由第3步事件循环触发FD-Server AE_READABLE事件对应的事件处理器acceptTcpHandler;
 4.1调用accept获得clientSocket对应的FD简称为FD-Client;
 4.2将FD-Client添加到IO多路复用框架的监视集当中,注册AE_READABLE事件(可读),关联的事件处理器是readQueryFromClient;
5.client发送redis命令;
6.由第3步事件循环触发FD-Clien AE_READABLE事件对应的事件处理器readQueryFromClient;
 6.1解析客户端发来的redis命令,找到命令对应的redisCommandProc(命令对应的处理函数);
 6.2执行redisCommandProc;
 6.3prepareClientToWrite准备回写响应信息,为FD-Client注册AE_WRITEABLE事件(可写),关联的事件处理器是sendReplyToClient;
7.执行redis中的定时任务;
8.由第3步事件循环触发FD-Clien AE_WRITEABLE事件对应的事件处理器sendReplyToClient,发送响应内容给client;

redis源码学习之工作流程初探


代码分析

server启动,创建serverSocket并注册AE_READABLE事件,设置事件处理器为acceptTcpHandler

void initServer() { //省略部分代码
//初始化eventLoop对象,eventLoop对象里面存储了所有的事件 server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
//创建serverSocket,监听端口 if (server.port != 0 && listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR) exit(1); //添加定时任务到eventLoop中 if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) { }


//将serverSocket对应的文件描述符添加到监视集中,关联的事件处理器是acceptTcpHandler for (j = 0; j < server.ipfd_count; j++) { if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) }}

acceptTcpHandler当有连接过来的时候被触发,调用accept得到client socket对应的FD,并将FD添加到监视集中,关联的事件处理器是readQueryFromClient

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { int cport, cfd; //调用accept获得clientSocket对应的FD cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);  //将clientSocket对应的FD添加到监视集中  acceptCommonHandler(cfd,0);}
static void acceptCommonHandler(int fd, int flags) { redisClient *c; //调用createClient添加 if ((c = createClient(fd)) == NULL) { } }
redisClient *createClient(int fd) { redisClient *c = zmalloc(sizeof(redisClient));
if (fd != -1) { anetNonBlock(NULL,fd); anetEnableTcpNoDelay(NULL,fd); if (server.tcpkeepalive) anetKeepAlive(NULL,fd,server.tcpkeepalive);
//将fd添加到监视集中,关联的事件处理器是readQueryFromClient if (aeCreateFileEvent(server.el,fd,AE_READABLE, readQueryFromClient, c) == AE_ERR) { } }}

aeMain就是跑一个循环,一直去调用aeProcessEvents

void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { if (eventLoop->beforesleep != NULL) eventLoop->beforesleep(eventLoop); aeProcessEvents(eventLoop, AE_ALL_EVENTS); }}

aeProcessEvents会调用aeApiPoll方法来获得就绪的文件描述符,然后执行文件描述符关联的的事件处理器

int aeProcessEvents(aeEventLoop *eventLoop, int flags){ int processed = 0, numevents;
#ifdef _WIN32 if (ServiceStopIssued() == TRUE) aeStop(eventLoop);#endif
/* Nothing to do? return ASAP */ if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
/* Note that we want call select() even if there are no * file events to process as long as we want to process time * events, in order to sleep until the next time event is ready * to fire. */ if (eventLoop->maxfd != -1 || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { int j; aeTimeEvent *shortest = NULL; struct timeval tv, *tvp;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) shortest = aeSearchNearestTimer(eventLoop); if (shortest) { long now_sec, now_ms;
/* Calculate the time missing for the nearest * timer to fire. */ aeGetTime(&now_sec, &now_ms); tvp = &tv; tvp->tv_sec = shortest->when_sec - now_sec; if (shortest->when_ms < now_ms) { tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000; tvp->tv_sec --; } else { tvp->tv_usec = (shortest->when_ms - now_ms)*1000; } if (tvp->tv_sec < 0) tvp->tv_sec = 0; if (tvp->tv_usec < 0) tvp->tv_usec = 0; } else { /* If we have to check for events but need to return * ASAP because of AE_DONT_WAIT we need to set the timeout * to zero */ if (flags & AE_DONT_WAIT) { tv.tv_sec = tv.tv_usec = 0; tvp = &tv; } else { /* Otherwise we can block */ tvp = NULL; /* wait forever */ } }
numevents = aeApiPoll(eventLoop, tvp); for (j = 0; j < numevents; j++) { aeFileEvent *fe; int mask = eventLoop->fired[j].mask; int fd = eventLoop->fired[j].fd; int rfired = 0;
fe = &eventLoop->events[eventLoop->fired[j].fd];
/* note the fe->mask & mask & ... code: maybe an already processed * event removed an element that fired and we still didn't * processed, so we check if the event is still valid. */ if (fe->mask & mask & AE_READABLE) { rfired = 1; fe->rfileProc(eventLoop,fd,fe->clientData,mask); } if (fe->mask & mask & AE_WRITABLE) { if (!rfired || fe->wfileProc != fe->rfileProc) fe->wfileProc(eventLoop,fd,fe->clientData,mask); } processed++; } } /* Check time events */ if (flags & AE_TIME_EVENTS) processed += processTimeEvents(eventLoop);//处理延迟任务
return processed; /* return the number of processed file/time events */}

动画演示

做了一个动画帮助理解工作过程(redis启动之后使用命令行telnet到6379端口,然后执行keys *命令,最终拿到结果)

redis源码学习之工作流程初探

网络模块

IO多路复用

这部分内容网络上精彩的内容太多,这里把我认为比较经典的一些内容贴出来供大家品读(建议从上往下顺序阅读)

The C10K problem

http://www.kegel.com/c10k.html


socket阻塞非阻塞等头疼问题解释

https://www.cnblogs.com/junneyang/p/6126635.html


LINUX – IO MULTIPLEXING – SELECT VS POLL VS EPOLL
https://devarea.com/linux-io-multiplexing-select-vs-poll-vs-epoll/#.XvB2n8gzaks


poll vs select vs event-based

https://daniel.haxx.se/docs/poll-vs-select.html


redis事件驱动

https://redis.io/topics/internals-eventlib


踩刀诗人 发起了一个读者讨论 欢迎拍砖



EOF


踩刀诗人

聊聊技术,唠唠段子,偶然做菜写诗

                                                                    

听说有人没点在看?

                                                                                                   

以上是关于tracee源码初探TCP处理流程的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码剖析1:初探Spring IOC核心流程

源码角度了解Skywalking之服务端OAP对Trace的处理

4.kafka消费者源码初探

CANoe中使用CAPL刷写流程详解(Trace图解)(CAN总线)

Day 18(06/13) 文件处理函数

RABBITMQ初探——消息分发