鸿蒙内核源码分析(信号消费篇) | 用户栈到内核栈的两次切换 | 百篇博客分析HarmonyOS源码 | v49.03
百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony >
信号消费
本篇为信号消费篇,读之前建议先阅读信号生产篇,信号部分姊妹篇如下:
本篇有相当的难度,涉及用户栈和内核栈的两次切换,寄存器改值,将围绕下图来说明.
解读
- 为本篇理解方便,把图做简化标签说明:
- user:用户空间
- kernel:内核空间
- source(...):源函数
- sighandle(...):信号处理函数,
- syscall(...):系统调用,参数为系统调用号,如sigreturn,N(表任意)
- user.source():表示在用户空间运行的源函数
- 系列篇已多次说过,用户态的任务有两个运行栈,一个是用户栈,一个是内核栈.栈空间分别来自用户空间和内核空间.两种空间是有严格的地址划分的,通过虚拟地址的大小就能判断出是用户空间还是内核空间.系统调用本质上是软中断,它使CPU执行指令的场地由用户栈变成内核栈.怎么变的并不复杂,就是改变(sp和cpsr寄存器的值).sp指向哪个栈就代表在哪个栈运行, 当cpu在用户栈运行时是不能访问内核空间的,但内核态任务可以访问整个空间,而且内核态任务没有用户栈.
- 理解了上面的说明,再来说下正常系统调用流程是这样的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()继续运行,就必须保存用户栈现场各寄存器的值.这些值保存在内核栈中,恢复也是从内核栈恢复.
- 信号消费的过程的上图可简化表示为: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中间插入了信号处理函数的调用. 这正是本篇要通过代码来说清楚的核心问题.
- 顺着这个思路可以推到以下几点,实际也是这么做的:
- kernel.syscall(N) 中必须要再次保存user.source()的上下文
sig_switch_context
,为何已经保存了一次还要再保存一次? - 因为第一次是保存在内核栈中,而内核栈这部分数据会因回到用户态user.sighandle()运行而被恢复现场出栈了.保存现场/恢复现场是成双出队的好基友,注意有些文章说会把整个内核栈清空,这是不对的.
- 第二次保存在任务结构体中,任务来源于任务池,是内核全局变量,常驻内存的.两次保存的都是user.source()运行时现场信息,再回顾下相关的结构体.关键是
sig_switch_context
typedef struct { // ... sig_cb sig;//信号控制块,用于异步通信 } LosTaskCB; typedef struct {//信号控制块(描述符) sigset_t sigFlag; //不屏蔽的信号集 sigset_t sigPendFlag; //信号阻塞标签集,记录那些信号来过,任务依然阻塞的集合.即:这些信号不能唤醒任务 sigset_t sigprocmask; /* Signals that are blocked */ //任务屏蔽了哪些信号 sq_queue_t sigactionq; //信号捕捉队列 LOS_DL_LIST waitList; //等待链表,上面挂的是等待信号到来的任务, 请查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 理解 sigset_t sigwaitmask; /* Waiting for pending signals */ //任务在等待哪些信号的到来 siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任务解锁时的信号信息 sig_switch_context context; //信号切换上下文, 用于保存切换现场, 比如发生系统调用时的返回,涉及同一个任务的两个栈进行切换 } sig_cb;
- 还必须要改变原有PC/R0/R1寄存器的值.想要执行user.sighandle(),PC寄存器就必须指向它,而R0,R1就是它的参数.
- 信号处理完成后须回到内核态,怎么再次陷入内核态? 答案是:
__NR_sigreturn
,这也是个系统调用.回来后还原sig_switch_context
,即还原user.source()被打断时SP/PC等寄存器的值,使其跳回到用户栈从user.source()的被打断处继续执行.
- kernel.syscall(N) 中必须要再次保存user.source()的上下文
- 有了这三个推论,再理解下面的代码就是吹灰之力了,涉及三个关键函数
OsArmA32SyscallHandle
,OsSaveSignalContext
,OsRestorSignalContext
本篇一一解读,彻底挖透.先看信号上下文结构体sig_switch_context
.
sig_switch_context
//任务中断上下文 #define TASK_IRQ_CONTEXT \ unsigned int R0; \ unsigned int R1; \ unsigned int R2; \ unsigned int R3; \ unsigned int R12; \ unsigned int USP; \ unsigned int ULR; \ unsigned int CPSR; \ unsigned int PC; typedef struct {//信号切换上下文 TASK_IRQ_CONTEXT unsigned int R7; //存放系统调用的ID unsigned int count; //记录是否保存了信号上下文 } sig_switch_context;
- 保存user.source()现场的结构体,
USP
,ULR
代表用户栈指针和返回地址. CPSR
寄存器用于设置CPU的工作模式,CPU有7种工作模式,具体可前往翻看
v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > 谈论的用户态(usr
普通用户)和内核态(sys
超级用户)对应的只是其中的两种.二者都共用相同的寄存器.还原它就是告诉CPU内核已切到普通用户模式运行.- 其他寄存器没有保存的原因是系统调用不会用到它们,所以不需要保存.
R7
是在系统调用发生时用于记录系统调用号,在信号处理过程中,R0将获得信号编号,作为user.sighandle()的第一个参数.count
记录是否保存了信号上下文
OsArmA32SyscallHandle 系统调用总入口
/* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */ /****************************************************************** 由汇编调用,见于 los_hw_exc.s / BLX OsArmA32SyscallHandle SYSCALL是产生系统调用时触发的信号,R7寄存器存放具体的系统调用ID,也叫系统调用号 regs:参数就是所有寄存器 注意:本函数在用户态和内核态下都可能被调用到 //MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 ******************************************************************/ LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } //用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://如何是两个参数的系统调用,这里传三个参数也没有问题,因被调用函数不会去取用R2值 case ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp); break; case ARG_NUM_4: case ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); break; default: //7个参数的情况 ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0保存系统调用返回值 OsSaveSignalContext(regs);//如果有信号要处理,将改写pc,r0,r1寄存器,改变返回正常用户态路径,而先去执行信号处理程序. /* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with the SYS_context_switch system call. */ return regs;//返回寄存器的值 }
解读
- 这是系统调用的总入口,所有的系统调用都要跑这里要统一处理.通过系统号(保存在R7),找到注册函数并回调.完成系统调用过程.
- 关于系统调用可查看 v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | harmony > 本篇不详细说系统调用过程,只说跟信号相关的部分.
OsArmA32SyscallHandle
总体理解起来是被信号的保存和还原两个函数给包夹了.注意要在运行过程中去理解调用两个函数的过程,对于同一个任务来说,一定是先执行OsSaveSignalContext
,第二次进入OsArmA32SyscallHandle
后再执行OsRestorSignalContext
.- 看
OsSaveSignalContext
,由它负责保存user.source() 的上下文,其中改变了sp,r0/r1寄存器值,切到信号处理函数user.sighandle()运行. - 在函数的开头,碰到系统调用号
__NR_sigreturn
,直接恢复信号上下文就退出了,因为这是要切回user.source()继续运行的操作.//用户进程信号处理函数完成后的系统调用 svc 119 #__NR_sigreturn if (cmd == __NR_sigreturn) { OsRestorSignalContext(regs);//恢复信号上下文,回到用户栈运行. return regs; }
OsSaveSignalContext 保存信号上下文
有了上面的铺垫,就不难理解这个函数的作用.
/********************************************** 产生系统调用时,也就是软中断时,保存用户栈寄存器现场信息 改写PC寄存器的值 **********************************************/ void OsSaveSignalContext(unsigned int *sp) { UINTPTR sigHandler; UINT32 intSave; LosTaskCB *task = NULL; LosProcessCB *process = NULL; sig_cb *sigcb = NULL; unsigned long cpsr; OS_RETURN_IF_VOID(sp == NULL); cpsr = OS_SYSCALL_GET_CPSR(sp);//获取系统调用时的 CPSR值 OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必须工作在CPU的用户模式下,注意CPSR_USER_MODE(cpu层面)和OS_USER_MODE(系统层面)是两码事. SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分发/信号处理篇 task = OsCurrTaskGet(); process = OsCurrProcessGet(); sigcb = &task->sig;//获取任务的信号控制块 //1.未保存任务上下文任务 //2.任何的信号标签集不为空或者进程有信号要处理 if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) { sigHandler = OsGetSigHandler();//获取信号处理函数 if (sigHandler == 0) {//信号没有注册 sigcb->sigFlag = 0; process->sigShare = 0; SCHEDULER_UNLOCK(intSave); PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n", task->processID); return; } /* One pthread do the share signal */ sigcb->sigFlag |= process->sigShare;//扩展任务的信号标签集 unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1; OsProcessExitCodeSignalSet(process, signo);//设置进程退出信号 sigcb->context.CPSR = cpsr; //保存状态寄存器 sigcb->context.PC = sp[REG_PC]; //获取被打断现场寄存器的值 sigcb->context.USP = sp[REG_SP];//用户栈顶位置,以便能从内核栈切回用户栈 sigcb->context.ULR = sp[REG_LR];//用户栈返回地址 sigcb->context.R0 = sp[REG_R0]; //系统调用的返回值 sigcb->context.R1 = sp[REG_R1]; sigcb->context.R2 = sp[REG_R2]; sigcb->context.R3 = sp[REG_R3]; sigcb->context.R7 = sp[REG_R7];//为何参数不用传R7,是因为系统调用发生时 R7始终保存的是系统调用号. sigcb->context.R12 = sp[REG_R12];//详见 https://my.oschina.net/weharmony/blog/4967613 sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2 /* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */ sigcb->sigFlag ^= 1ULL << (signo - 1); sigcb->context.count++; //代表已保存 } SCHEDULER_UNLOCK(intSave); }
解读
- 先是判断执行条件,确实是有信号需要处理,有处理函数.自定义处理函数是由用户进程安装进来的,所有进程旗下的任务都共用,参数就是信号
signo
,注意可不是系统调用号,有区别的.信号编号长这样.
系统调用号长这样,是不是看到一些很熟悉的函数.#define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(ctrl + c) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞
#define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_time 13 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_break 17
- 最后是最最最关键的代码,改变pc寄存器的值,此值一变,在
_osExceptSwiHdl
中恢复上下文后,cpu跳到用户空间的代码段 user.sighandle(R0,R1) 开始执行,即执行信号处理函数.sp[REG_PC] = sigHandler;//指定信号执行函数,注意此处改变保存任务上下文中PC寄存器的值,恢复上下文时将执行这个函数. sp[REG_R0] = signo; //参数1,信号ID sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //参数2
OsRestorSignalContext 恢复信号上下文
/**************************************************** 恢复信号上下文,由系统调用之__NR_sigreturn产生,这是一个内部产生的系统调用. 为什么要恢复呢? 因为系统调用的执行由任务内核态完成,使用的栈也是内核栈,CPU相关寄存器记录的都是内核栈的内容, 而系统调用完成后,需返回任务的用户栈执行,这时需将CPU各寄存器回到用户态现场 所以函数的功能就变成了还原寄存器的值 ****************************************************/ void OsRestorSignalContext(unsigned int *sp) { LosTaskCB *task = NULL; /* Do not adjust this statement */ LosProcessCB *process = NULL; sig_cb *sigcb = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); task = OsCurrTaskGet(); sigcb = &task->sig;//获取当前任务信号控制块 if (sigcb->context.count != 1) {//必须之前保存过,才能被恢复 SCHEDULER_UNLOCK(intSave); PRINT_ERR("sig error count : %d\n", sigcb->context.count); return; } process = OsCurrProcessGet();//获取当前进程 sp[REG_PC] = sigcb->context.PC;//指令寄存器 OS_SYSCALL_SET_CPSR(sp, sigcb->context.CPSR);//重置程序状态寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置 sp[REG_R0] = sigcb->context.R0; sp[REG_R1] = sigcb->context.R1; sp[REG_R2] = sigcb->context.R2; sp[REG_R3] = sigcb->context.R3; sp[REG_R7] = sigcb->context.R7; sp[REG_R12] = sigcb->context.R12; sigcb->context.count--; //信号上下文的数量回到减少 process->sigShare = 0; //回到用户态,信号共享清0 OsProcessExitCodeSignalClear(process);//清空进程退出码 SCHEDULER_UNLOCK(intSave); }
解读
-
在信号处理函数完成之后,内核会触发一个
__NR_sigreturn
的系统调用,又陷入内核态,回到了OsArmA32SyscallHandle
. -
恢复的过程很简单,把之前保存的信号上下文恢复到内核栈sp开始位置,数据在栈中的保存顺序可查看 用栈方式篇 ,最重要的看这几句.
sp[REG_PC] = sigcb->context.PC;//指令寄存器 sp[REG_SP] = sigcb->context.USP;//用户栈堆栈指针, USP指的是 用户态的堆栈,即将回到用户栈继续运行 sp[REG_LR] = sigcb->context.ULR;//返回用户栈代码执行位置
注意这里还不是真正的切换上下文,只是改变内核栈中现有的数据.这些数据将还原给寄存器.
USP
和ULR
指向的是用户栈的位置.一旦PC
,USP
,ULR
从栈中弹出赋给寄存器.才真正完成了内核栈到用户栈的切换.回到了user.source()继续运行. -
真正的切换汇编代码如下,都已添加注释,在保存和恢复上下文中夹着
OsArmA32SyscallHandle
@ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理,注意此时已在内核栈运行 @保存任务上下文(TaskContext) 开始... 一定要对照TaskContext来理解 SUB SP, SP, #(4 * 16) @先申请16个栈空间单元用于处理本次软中断 STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA从左到右执行,先放R0 .. R12 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 => TaskContext.regPSR ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 从右向左 保存 => TaskContext.LR和SP SUB SP, SP, #4 @ => TaskContext.resved PUSH_FPU_REGS R1 @保存中断模式(用户模式) @保存任务上下文(TaskContext) 结束 MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用,参数为R0,指向TaskContext的开始位置*/ CPSID I @执行后续指令前必须先关中断 @恢复任务上下文(TaskContext) 开始 POP_FPU_REGS R1 @弹出FPU值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 @恢复任务上下文(TaskContext) 结束
具体也可看这两篇:
鸿蒙源码百篇博客 往期回顾
-
在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P
-
写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容.
-
v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | harmony >
-
v38.xx (寄存器篇) | arm所有寄存器一网打尽,不再神秘 < csdn | 51cto | harmony >
-
v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | harmony >
-
v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | harmony >
-
v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | harmony >
参与贡献
-
Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request
喜欢请「点赞+关注+收藏」
-
各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处.

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
百度C++工程师的那些极限优化(内存篇)
导读:在百度看似简简单单的界面后面,是遍布全国的各个数据中心里,运转着的海量C++服务。如何提升性能,降低延时和成本就成了百度C++工程师的必修功课。伴随着优化的深入攻坚,诞生并积累下来一系列的性能优化理论和方案,其中不乏一些冷门但精巧实用的经验和技巧。本文从内存访问角度,收集总结了一些具有通用意义的典型案例,分享出来和大家学习交流。 1 背景 在百度看似简简单单的界面后面,是遍布全国的各个数据中心里,运转着的海量C++服务。对C++的重度应用是百度的一把双刃剑,学习成本陡峭,指针类错误定位难、扩散性广另很多开发者望而却步。然而在另一方面,语言层引入的额外开销低,对底层能力可操作性强,又能够为追求极致性能提供优异的实践环境。 因此,对百度的C++工程师来说,掌握底层特性并加以利用来指导应用的性能优化,就成了一门必要而且必须的技能。久而久之,百度工程师就将这种追求极致的性能优化,逐渐沉淀成了习惯,甚至形成了对技术的信仰。下面我们就来盘点和分享一些,在性能优化的征途上,百度C++工程师积累下来的理论和实践,以及那些为了追求极致,所发掘的『奇技淫巧』。 2 重新认识性能优化 作为程序员,大家...
- 下一篇
Jet 4.5 发布,分布式批处理和流处理引擎
Jet 是一个开源的、内存中的、分布式批处理和流处理引擎。你可以用它来处理大量的实时事件或大批量的静态数据集。为了给人一种规模感,Jet 的一个节点已经被证明可以每秒聚集1000万个事件,延迟低于10毫秒。 新功能: [sql] 动态 ClassDefinition 消除了在 IMap 中使用新的记录类型时重新启动集群的需要; 增强: [jdbc] 添加了一个选项,用于配置 JDBC Sink 的批处理大小; [sql] 优化了 SQL 聚合的内存占用; [sql] 现在你可以在 SQL 生成器函数中使用表达式了; [核心]从 IMap/ICache 中提取数据可能会由于同时创建过多的分区迭代器而导致 OOME; [核心] 删除了因工作被取消而发生的错误的误导性记录; [pipeline-api] 大大提高了滑动窗口中 pickAny 聚合操作的性能; 修复: [core]Jet 与 Java Logging Framework 的集成导致它无意中从关机 hook 中关闭了 System.out,这将破坏其他关机 hook; [core] 修复了 DAG.toDotString(),以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Linux系统CentOS6、CentOS7手动修改IP地址
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS6,CentOS7官方镜像安装Oracle11G
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- 设置Eclipse缩进为4个空格,增强代码规范