11/11/2020 记DMA冲突引发的血案

环境

硬件使用Pixhawk fmuv2版本,软件为基于rtthread的飞控。

问题现象

电机控制线程使用UART6-DMA模式下从F4向F1发送数据时,开启日志记录,或使用和文件系统相关的命令时,UART6串口停止发送数据。在不开启SD卡相关记录和命令时,电机控制线程正常。

犯罪现场

将PWM使用的IO抽象为一个设备时(F4为主控芯片控制电机转速,是经过UART6串口发送给F1,再通过F1控制电机。所以将PWM抽象为一个设备,PWM的读写又操作了串口设备。后续有详细过程

  • 信号量初始为1

    rt_sem_init(&iomcu_tx_pack_sem, "txpack", 1, RT_IPC_FLAG_FIFO);
  • 对应的发送函数:

static rt_err_t send(uint8_t* buff, uint32_t size)
{rt_size_t bytes;if (rt_sem_take(&iomcu_tx_pack_sem, MSEC_TO_TICKS(20)) != RT_EOK){return -RT_EIO;}else{bytes = rt_device_write(serial_dev, 0, (const void *)buff, size);if(bytes != size)return -RT_ERROR;}return RT_EOK;
}
  • 发送完成回调
rt_err_t iomcu_serial_tx_done(rt_device_t dev, void * buffer)
{rt_sem_release(&iomcu_tx_pack_sem);return RT_EOK;
}

具体现象

在记日志前,发送一次时取信号量,完成后释放信号量,每次发送都有回调。但是开启记日志后,发送后不会进入回调,也就不会释放信号量,导致下一次发送获取不到信号量,无法发送(其实看到这里就应该想到时SD卡的读取和UART6的收发底层配置冲突,但是CUBE软件误导了我,后面会说)。

排查过程

  1. 通过keil断点调试,发现发送回调是在logger.c中的logger_start()函数后就收不到了。具体是在open(file_name, O_WRONLY | O_APPEND | O_CREAT);这个操作后。

    uint8_t logger_start(char* file_name, uint32_t log_period)
    {uint8_t res = 0;if(!mnt_init_complete()){Console.e(TAG, "err, file system is not init properly\n");return 1;}if(_logger_info.status == LOGGER_BUSY){Console.print("logger is busy, please first stop log\n");return 2;}if(logger_create_header(log_period>0 ? log_period : LOGGER_DEFAULT_PERIOD))return 3;/* create log file */int size;logger_fp = open(file_name, O_WRONLY | O_APPEND | O_CREAT);//具体就是在这一句if(logger_fp >= RT_EOK){//fres = f_write(&logger_fp, log_header_t, sizeof(LOG_HeaderDef)-sizeof(LOG_ElementInfoDef*), &bw);size = write(logger_fp, log_header_t, sizeof(LOG_HeaderDef)-sizeof(LOG_ElementInfoDef*));if(size  == sizeof(LOG_HeaderDef)-sizeof(LOG_ElementInfoDef*)){//fres = f_write(&logger_fp, log_header_t->element_info, log_header_t->element_num*sizeof(LOG_ElementInfoDef), &bw);size = write(logger_fp, log_header_t->element_info, log_header_t->element_num*sizeof(LOG_ElementInfoDef));if(size!= log_header_t->element_num*sizeof(LOG_ElementInfoDef)){Console.e(TAG, "log header write fail:%d\n", size);res = 4;goto error;}rt_tick_t tick = log_period>0 ? log_period : LOGGER_DEFAULT_PERIOD;_logger_info.status = LOGGER_BUSY;_logger_info.last_record_time = 0;_logger_info.log_period = tick;/* start logger timer */rt_timer_control(&_timer_logger, RT_TIMER_CTRL_SET_TIME, &tick);rt_timer_start(&_timer_logger);Console.print("log file create successful, start to log... tick=%d\n", tick);}else{Console.e(TAG, "log header write fail\n");res = 4;}}else{Console.e(TAG, "log file create fail:%d\n", logger_fp);res = 4;}
    
  2. 继续进入open()函数查找问题

    • 猜想一:在进行文件系统的操作时,进行了临界区的上锁,关闭硬件中断后,无法响应发送完成中断。
    • 猜想二:文件系统优先级较高,一直占用CPU资源。使得UART6的DMA写入第一次未完成,就进行了第二次写入。第一次写入前取了信号量,但是由于被第二次打断了,没有完成发送完成中断的回调函数释放信号量。

​ 对于猜想二,我直接将文件系统的优先级调至比电机驱动线程低,但问题仍未解决。

​ 对于猜想一:文件系统是使用互斥量上锁,防止其他进程中间打断的,并不影响硬件中断的响应。

void dfs_lock(void)
{rt_err_t result = -RT_EBUSY;while (result == -RT_EBUSY){result = rt_mutex_take(&fslock, RT_WAITING_FOREVER);}if (result != RT_EOK){RT_ASSERT(0);}
}

​ 而在信号量获取或者释放过程中,的确进行了中断关闭,但是事件极短,在关闭期间丢掉了发送完成中断的几率很小。

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{register rt_base_t temp;struct rt_thread *thread;/* this function must not be used in interrupt even if time = 0 */RT_DEBUG_IN_THREAD_CONTEXT;/* parameter check */RT_ASSERT(mutex != RT_NULL);RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);/* get current thread */thread = rt_thread_self();/* disable interrupt */temp = rt_hw_interrupt_disable(); //在这里关闭了中断,后面开启。RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));RT_DEBUG_LOG(RT_DEBUG_IPC,("mutex_take: current thread %s, mutex value: %d, hold: %d\n",thread->name, mutex->value, mutex->hold));/* reset thread error */thread->error = RT_EOK;if (mutex->owner == thread){/* it's the same thread */mutex->hold ++;}else{__again:/* The value of mutex is 1 in initial status. Therefore, if the* value is great than 0, it indicates the mutex is avaible.*/if (mutex->value > 0){/* mutex is available */mutex->value --;/* set mutex owner and original priority */mutex->owner             = thread;mutex->original_priority = thread->current_priority;mutex->hold ++;}else{/* no waiting, return with timeout */if (time == 0){/* set error as timeout */thread->error = -RT_ETIMEOUT;/* enable interrupt */rt_hw_interrupt_enable(temp);return -RT_ETIMEOUT;}else{/* mutex is unavailable, push to suspend list */RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",thread->name));/* change the owner thread priority of mutex */if (thread->current_priority < mutex->owner->current_priority){/* change the owner thread priority */rt_thread_control(mutex->owner,RT_THREAD_CTRL_CHANGE_PRIORITY,&thread->current_priority);}/* suspend current thread */rt_ipc_list_suspend(&(mutex->parent.suspend_thread),thread,mutex->parent.parent.flag);/* has waiting time, start thread timer */if (time > 0){RT_DEBUG_LOG(RT_DEBUG_IPC,("mutex_take: start the timer of thread:%s\n",thread->name));/* reset the timeout of thread timer and start it */rt_timer_control(&(thread->thread_timer),RT_TIMER_CTRL_SET_TIME,&time);rt_timer_start(&(thread->thread_timer));}/* enable interrupt */rt_hw_interrupt_enable(temp);/* do schedule */rt_schedule();if (thread->error != RT_EOK){/* interrupt by signal, try it again */if (thread->error == -RT_EINTR) goto __again;/* return error */return thread->error;}else{/* the mutex is taken successfully. *//* disable interrupt */temp = rt_hw_interrupt_disable();}}}}

更换思路

​ 因为文件系统的实现极其复杂,并且是经过验证的,在这里出错的机率不大。所以放弃从上层向下,反过来从底层向上找。

  1. DMA发送完成中断处理函数的入口

    void UART6_DMA_TX_IRQHandler(void)
    {/* enter interrupt */rt_interrupt_enter();HAL_DMA_IRQHandler(&uart_obj[UART6_INDEX].dma_tx.handle);/* leave interrupt */rt_interrupt_leave();
    }
    

    dma_config.h文件中

    #define UART6_DMA_TX_IRQHandler DMA2_Stream6_IRQHandler

    在startup_stm32f427xx.s文件中加入中断向量表

    DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6

  2. 进入HAL_DMA_IRQHandler()中断请求函数中查看

    void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
    {uint32_t tmpisr;__IO uint32_t count = 0U;uint32_t timeout = SystemCoreClock / 9600U;/* calculate DMA base and stream number */DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;tmpisr = regs->ISR;/* Transfer Error Interrupt management ***************************************/if ((tmpisr & (DMA_FLAG_TEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TE) != RESET){/* Disable the transfer error interrupt */hdma->Instance->CR  &= ~(DMA_IT_TE);/* Clear the transfer error flag */regs->IFCR = DMA_FLAG_TEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_TE;}}/* FIFO Error Interrupt management ******************************************/if ((tmpisr & (DMA_FLAG_FEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_FE) != RESET) //开启日志记录后,会进入FIFO错误处理{/* Clear the FIFO error flag */regs->IFCR = DMA_FLAG_FEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_FE;}}/* Direct Mode Error Interrupt management ***********************************/....../* Half Transfer Complete Interrupt management ******************************/....../* Transfer Complete Interrupt management ***********************************/if ((tmpisr & (DMA_FLAG_TCIF0_4 << hdma->StreamIndex)) != RESET){   //开启日志记录后,发送完成标志位会置位,进入第一个if,但是后面判断中断使能时为disable,不会继续进行。            if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TC) != RESET){/* Clear the transfer complete flag */regs->IFCR = DMA_FLAG_TCIF0_4 << hdma->StreamIndex;if(HAL_DMA_STATE_ABORT == hdma->State){/* Disable all the transfer interrupts */hdma->Instance->CR  &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);hdma->Instance->FCR &= ~(DMA_IT_FE);if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL)){hdma->Instance->CR  &= ~(DMA_IT_HT);}/* Clear all interrupt flags at correct offset within the register */regs->IFCR = 0x3FU << hdma->StreamIndex;/* Process Unlocked */__HAL_UNLOCK(hdma);/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;if(hdma->XferAbortCallback != NULL){hdma->XferAbortCallback(hdma);}return;}if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET){/* Current memory buffer used is Memory 0 */if((hdma->Instance->CR & DMA_SxCR_CT) == RESET){if(hdma->XferM1CpltCallback != NULL){/* Transfer complete Callback for memory1 */hdma->XferM1CpltCallback(hdma);}}/* Current memory buffer used is Memory 1 */else{if(hdma->XferCpltCallback != NULL){/* Transfer complete Callback for memory0 */hdma->XferCpltCallback(hdma);}}}/* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */......}}
    

    这里是当时最让人费解的地方:开启记录日志后,发送函数无法进入,但是FIFO错误标志位会置位,发送完成标志位也会置位,但是发送完成的回调不会调用,因为并没有开启发送完成使能。

    先说FIFO错误标志位,在配置DMA的时候,就没有开启FIFO功能,怎么会出现错误标志位呢?

    再看发送完成标志位置位,但是却没有使能?

    再往上找,在哪里开启了发送完成使能呢?

    HAL_StatusTypeDef HAL_USART_Transmit_DMA(USART_HandleTypeDef *husart, uint8_t *pTxData, uint16_t Size)
    {....../* Enable the USART transmit DMA stream */tmp = (uint32_t *)&pTxData;HAL_DMA_Start_IT(husart->hdmatx, *(uint32_t *)tmp, (uint32_t)&husart->Instance->DR, Size);......
    }
    
    HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
    {......if(HAL_DMA_STATE_READY == hdma->State){....../* Enable Common interrupts*/hdma->Instance->CR  |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME;hdma->Instance->FCR |= DMA_IT_FE;......}......
    }
    

    也就是说在每次调用串口DMA发送函数时,配置完成后就就开启写入,开启写入时开启中断使能。**那么就更奇怪了!**只要使用发送函数,就会开启中断使能来响应发送完成函数啊,怎么会中断标志位置位,但是使能却关闭了呢?

解决

​ 正在我百思不得其解的时候,白同学提醒了我,是不是还是硬件配置导致的冲突?!在之前之所以没有到最底层去查看UART6和SDIO的配置,就是因为我依赖了CUBE,觉得CUBE配置对了就不会错。

​ CUBE配置图如下,没有任何冲突。CUBE的检查机制也保证了不会有任何冲突,不然无法生成代码。

​ 但是我还是去到底层查看了一下

SDIO

#define SDIO_BUS_CONFIG                                  \{                                                    \.Instance = SDIO,                                \.dma_rx.dma_rcc = RCC_AHB1ENR_DMA2EN,            \.dma_tx.dma_rcc = RCC_AHB1ENR_DMA2EN,            \.dma_rx.Instance = DMA2_Stream3,                 \.dma_rx.channel = DMA_CHANNEL_4,                 \.dma_rx.dma_irq = DMA2_Stream3_IRQn,             \.dma_tx.Instance = DMA2_Stream6,                 \.dma_tx.channel = DMA_CHANNEL_4,                 \.dma_tx.dma_irq = DMA2_Stream6_IRQn,             \}

UART6

#define UART6_DMA_TX_IRQHandler         DMA2_Stream6_IRQHandler
#define UART6_TX_DMA_RCC                RCC_AHB1ENR_DMA2EN
#define UART6_TX_DMA_INSTANCE           DMA2_Stream6
#define UART6_TX_DMA_CHANNEL            DMA_CHANNEL_5
#define UART6_TX_DMA_IRQ                DMA2_Stream6_IRQn

果然!SDIO和UART6的发送共用的DMA2的Stream6!

那一切就合理了,在使用SDIO前,UART6可以正常的发送和响应中断,直到SDIO开启的一瞬间,也就是之前文件系统中的open()的一瞬间,DMA2_Stream6这个通道被SDIO抢走了。由于SDIO正在使用这个通道,DMA2_Stream6的中断位还会不断的被置位,但是由于UART6并没有发送,也就没有中断使能,所以无法响应这个中断。

PS:SDIO没有使用DMA2_Stream6中断,而是使用了SDIO中断

番外

在调试过程中,为了测试是否是SD操作过程中丢失了某一次UART6的回调,导致信号量一直位0无法发送,结果将信号量调大,调至100。发现,打开日志记录后,信号量卡在92不变,发送和回调都不能进入。同时电机线程一直处于挂起状态,不再调用。后来在查找原因时发现了,这一现象的原因,涉及到串口发送的机制(数据队列)。

下面为F4主控芯片的电机线程发送PWM值到F1IO芯片,进而控制电机转速的全过程封装层数较多

  1. 电机驱动是一个单独的线程,每1MS进入一次。

    void MotorTask_entry(void* parameter)
    {rt_err_t res;rt_uint32_t recv_set = 0;rt_uint32_t wait_set = FT_MOTORS_EVENT_LOOP;/* create event */res = rt_event_init(&EventMotorTask, "EFTM", RT_IPC_FLAG_FIFO);/* register timer event */rt_timer_init(&TimerMotorTask, "TFTM",MotorTask_TimeUpdate,RT_NULL,FT_MOTORS_MSEC_TO_TICKS(FT_MOTORS_TIME_MS),RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);rt_timer_start(&TimerMotorTask);while(1){res = rt_event_recv(&EventMotorTask, wait_set, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &recv_set);if(res == RT_EOK){if(recv_set & FT_MOTORS_EVENT_LOOP){MotorTask_Output();}}}}
    

    判断订阅的电机节点是否更新,更新则进行输出。

    void MotorTask_Output()
    {//V2硬件电机PWM的值通过串口发送到IO芯片,所以耗时较大,大约4000微秒
    #ifndef HIL_SIMULATIONfloat motors[8];//static uint32_t start_us = 0;//start_us = time_nowUs();static uint32_t NavNow = 0;if(itc_poll(motors_node_t)) {MotorTask_SwitchMotorsOutput();itc_copy(ITC_ID(MOTOR_THROTTLE), motors_node_t, motors);rt_device_write(pMotorDevice, MOTOR_CH_ALL, motors, motorNum);
    /*if (time_nowMs() - NavNow > 500) {NavNow = time_nowMs();char val[20] = {0};sprintf(val, "%d\n", time_nowUs() - start_us);rt_kprintf("%s",val);}
    */}
    #endif
    }
    
  2. PWM输出被抽象为一个设备叫做"motor",调用设备在初始画的时候注册的写函数

    static rt_size_t _motor_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
    {struct rt_device_motor *motor = (struct rt_device_motor *)dev;static float throttle_dc[8];float throttle;Motor_Chan_Info *chan_info = (Motor_Chan_Info*)dev->user_data;if(size > chan_info->max_output_chan_num){Console.e(TAG, "unsupport motor num:%ld\n", size);return 0;}/* set motor throttle */for(uint8_t i = 0 ; i < size ; i++){if(pos & (1<<i)){throttle = *((float*)buffer+i);constrain(&throttle, 0.0f, 1.0f);throttle_dc[i] = MOTOR_MIN_DC + throttle * (MOTOR_MAX_DC - MOTOR_MIN_DC);}}motor->ops->pwm_write(dev, (uint8_t)pos, throttle_dc);return size;
    }
    
  3. "motor"设备的写函数调用了send_package进行数据打包和发送。过程如下:

    • 在堆中动态申请了一片内存来存放数据
    • 申请了一片内存存放打包好的待发送
    • 数据打包
    • 调用"uart6"设备的写函数
    • 释放内存
    uint8_t send_package(uint8_t cmd, uint8_t* data, uint16_t len)
    {Package_Def pack;SendPackage_Def send_pack;if(!make_package(data , cmd , len , &pack)){return 0;}package2sendpack(pack , &send_pack);send(send_pack.send_buff, send_pack.buff_size);free_pack(&pack);free_sendpack(&send_pack);return 1;
    }
    

    动态申请内存主要是为了多个线程调用这个公用函数时,可以使数据不被覆盖。

    static rt_err_t send(uint8_t* buff, uint32_t size)
    {rt_size_t bytes;if (rt_sem_take(&iomcu_tx_pack_sem, MSEC_TO_TICKS(20)) != RT_EOK){return -RT_EIO;}else{bytes = rt_device_write(serial_dev, 0, (const void *)buff, size);if(bytes != size)return -RT_ERROR;}return RT_EOK;
    }
    

    使用信号量保证上次发送完成,调用"uart6"设备的写函数。

  4. 引入了数据队列,初始化数据队列长度为8。所以在发送时,先将数据排在队尾,判断队列是否满,如果满则将线程挂起,否则发送一次数据。

这里也就是为什么之前将信号量设为100的时候,开启SD卡后变为92。因为等待时间设的RT_WAITING_FOREVER,队列的出列是在发送完成的回调函数完成的,所以队满后电机线程会一直被挂起等待信号量。队列大小为8,等于说日志开启后,又发送了八次,但是每次都不能回调,导致队列满,线程挂起,无法发送,所以信号量定格在8。

rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{rt_base_t level;rt_err_t result;struct rt_serial_tx_dma *tx_dma;tx_dma = (struct rt_serial_tx_dma*)(serial->serial_tx);result = rt_data_queue_push(&(tx_dma->data_queue), data, length, RT_WAITING_FOREVER);if (result == RT_EOK){level = rt_hw_interrupt_disable();if (tx_dma->activated != RT_TRUE){tx_dma->activated = RT_TRUE;rt_hw_interrupt_enable(level);/* make a DMA transfer */serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);}else{rt_hw_interrupt_enable(level);}return length;}else{rt_set_errno(result);return 0;}
}
  1. 调用底层的HAL库DMA发送函数

    static rt_size_t stm32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction)
    {struct stm32_uart *uart;RT_ASSERT(serial != RT_NULL);RT_ASSERT(buf != RT_NULL);uart = rt_container_of(serial, struct stm32_uart, serial);if (size == 0){return 0;}if (RT_SERIAL_DMA_TX == direction){if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK){return size;}else{return 0;}}return 0;
    }
    
    1. 调用中断回调函数(嵌套层数较多

      中断服务入口函数DMA2_Stream6_IRQHandler -> 用户重写弱函数UART6_DMA_TX_IRQHandler -> HAL库提供中断处理函数HAL_DMA_IRQHandler-> 调用了用户配置的回调函数 HAL_UART_TxCpltCallback -> 判断DMA是否发送完成(网友说会出现DMA发送完成中断产生,但是最后一个自己数据未发送完成的情况)_dma_tx_complete -> 终极回调函数 rt_hw_serial_isr

      终极回调函数中关注两件事1、将发送完成的数据弹出数据队列 2、调用设备的完成回调(释放信号量)

      void rt_hw_serial_isr(struct rt_serial_device *serial, int event)
      {......rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);/* invoke callback */if (serial->parent.tx_complete != RT_NULL){serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);}}

记DMA冲突引发的血案相关推荐

  1. ”一个馒头引发的血案“|记Mybatis之BindingException异常的产生及解决过程

    一. 业务场景 前几天壹哥带学生做一个项目,需要更新数据库中的车辆信息表,具体需求是要根据指定车辆的设备id(编号和设备ID均非主键)来更新车辆信息.壹哥要求学生们用Mybatis进行实现,所以就在对 ...

  2. Windows10记一次去掉桌面图标箭头引发的血案:该文件没有与之关联的应用来执行操作,请安装一个程序,若已安装程序,请在默认程序控制面板中创建关联。

    前两天我的Windows10系统升级,这次升级试一次大型升级,从1803版本升级到1903版本,本以为升级系统能给我带来不一样的体验,可是没有--升级之后我桌面图标左下方又出现了小箭头,对于我这种强迫 ...

  3. 第三方账号登陆的过程及由此引发的血案

    72agency · 2014/03/19 10:40 0x00 前言 第三方账号登陆也就是当你没有A网站的注册账号时,你可以使用该与A网站合作的第三方账号登陆A,在大多数情况下你会立即拥有与你第三方 ...

  4. 波涛汹涌的黄金甲,一碗中药引发的血案!

    严重声明:网路转载 主要情节: 父王(周润发)说母后(巩利)身体虚寒,需要每天定时服用亲自配置的中药,已服用了几十年.而父王早就知道了母后和太子元祥(刘烨)之间的苟且之事,远征回宫后在其中药中加入一味 ...

  5. mysql backlog_一次优化引发的血案

    前些天一个Nginx+PHP项目上线后遭遇了性能问题,于是打算练练手,因为代码并不是我亲自写的,所以决定从系统层面入手看看能否做一些粗线条的优化. 首先,我发现服务的Backlog设置过小,可以通过s ...

  6. 一个普通ERROR 1135 (HY000)错误引发的血案:

    一个普通ERROR 1135 (HY000)错误引发的血案: 今天接到测试人员反应,测试环境前端应用程序无连接mysql数据库,登录mysql服务器,查看错误日志,发现有如下报错: 点击(此处)折叠或 ...

  7. 一次 Druid 连接池泄露引发的血案!

    最近某个应用程序老是卡,需要重启才能解决问题,导致被各种投诉,排查问题是 Druid 连接池泄露引发的血案.. 异常日志如下: ERROR - com.alibaba.druid.pool.GetCo ...

  8. 线上 CPU100% 异常案例:一个正则表达式引发的血案

    前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%.通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息. 我们可以看到所 ...

  9. 一场由过滤器Filter引发的血案

    一场由过滤器Filter引发的血案 事件起因 本来应该是下图的登录界面 变成了这样 What's the fuck????? 抓狂 原因 解决方法: 在过滤器中给资源文件开个绿色通道

最新文章

  1. 具有不同字体的列表框
  2. 记一次Oracle数据故障排除过程
  3. Algorithm Course Review(7.1)
  4. SpringMVC拦截器HandlerInterceptor原理及使用
  5. docker编译Linux内核,c – 如何编译在docker中使用内核函数的C代...
  6. 程序员 rs编码_为什么声明性编码使您成为更好的程序员
  7. 苹果要悄悄对这个产品动手了?你们最期盼的NFC功能也要来?
  8. 论文笔记_S2D.31_2015-CVPR_对单张图像进行统一的深度和语义预测
  9. 【大数据分析】Spark SQL查询:使用SQL命令
  10. 上下文无关文法的组成
  11. 一文盘点目前免费的云服务器
  12. 《Gpu Gems》《Gpu Pro》《Gpu Zen》系列读书笔记
  13. 【Memcached】分布式内存对象缓存系统
  14. linux网卡ens160显示不出来,修改Centos7的网卡名称ens160、eno192改为eth0、eth1
  15. 手写vue3源码——reactive, effect ,scheduler, stop 等
  16. print spooler打印服务启动后,自动停止的解决方法。
  17. HTML5 table表格合并单元格和合并边框
  18. 如何将小车标注在百度地图上并且设置车头方向
  19. 【欧拉计划第 5 题】最小公倍数 Smallest multiple
  20. C语言图形编程(绘图函数部分),C语言图形编程(三、绘图函数-02)12

热门文章

  1. 「 C++ 宏 」“DDX动态数据交换与DDV动态数据验证”浅谈
  2. Cache中的比较器个数和位数
  3. IP 地址分类(A、B、C、D、E类)
  4. Font Icon 实践
  5. 【团队协作工具】Teambition 石墨 CSDN
  6. 情人节程序员表白代码合集
  7. iOS Provisioning Profile(配置文件)与Code Signing详解
  8. Http请求方法的区别
  9. 函数连续性的概念及用处
  10. yolov5 pt->onnx->om yolov5模型转onnx转om模型转换