BPlog通过ttySMD透传到PC遇到的问题及分析


引子:

AP和BP通过shared memory相连,BP的log通过shared memory传输到AP,AP在通过USB把数据传输到PC,PC有软件实时接收分析数据。

AP/BP间的通信机制是AP向memory1写,BP从memory1读;BP向memory2写,AP从memory2读。
当AP向memory1写入数据时,会更改写指针变量,并发中断通知BP;BP根据读指针和写指针变量,读出数据,更新读指针,最后发中断通知
AP数据已经读出。反之亦然。

BPlog输出到PC侧,实现的方法是一个透传应用程序,BP的log来自ttyN2/3传输到ttyGS0/1, USB会把ttyGS0/1的数据传输到PC。
现在出现的问题就在这里:BP的log输出时,AP经常crash掉且BP的log出一段时间后就不再输出。

1.先说crash的问题:


1.1 dead loop in tasklet

一开始没有打开softlock,所以没有crash。输出的log来自kernel/watchdog.c:
watchdog_timer_fn:
    printk(KERN_EMERG "BUG: soft lockup - CPU#%d stuck for %us! [%s:%d]\n",
            smp_processor_id(), duration, current->comm, task_pid_nr(current));
可以看出一个thread占据太多时间,导致thread: watchdog没有机会运行,最后出现该log.

下面有看看那个thread占据这么多时间那?
 1]如果有crash dump,可以使用命令:ps -l;
 2]ftrace
 3]打印带时间戳的log;
 最后终于定位到:函数smd_ch_irq_tasklet_handler
void smd_ch_irq_tasklet_handler(unsigned long data)
{
    unsigned char *ptr;
    int avail;
    struct tty_struct *tty;
    int index=data;
    struct smd_tty_info *tty_info = &driver_info->smd_tty[index];
    tty = tty_port_tty_get(&tty_info->port);
    if (!tty)
        return;

    pr_err("smd_ch_irq_tasklet_handler before loop\n");
    for (;;) {
        avail = smd_stream_read_avail(tty_info->ch);
        if (avail == 0)
            break;

        avail = tty_prepare_flip_string(tty, &ptr, avail);
        smd_stream_read(tty_info->ch,ptr,avail);
        smd_send_intr(tty_info->a2b_int_rx);
        tty_flip_buffer_push(tty);
    }

    pr_err("smd_ch_irq_tasklet_handler after loop\n");
    tty_kref_put(tty);
}

函数中有个潜在的死循环,更严重的是这个函数是个tasklet,优先级很高:除了中断处理函数,就是它了,只有死在这里,其他的
线程没有任何运行机会。

为什么会出现死循环?
    for (;;) {
        avail = smd_stream_read_avail(tty_info->ch);
        if (avail == 0)
            break;

        avail = tty_prepare_flip_string(tty, &ptr, avail);
        smd_stream_read(tty_info->ch,ptr,avail);
        smd_send_intr(tty_info->a2b_int_rx);
        tty_flip_buffer_push(tty);
    }
原因是函数tty_prepare_flip_string没有申请到tty_buffer导致 shared memory中数据读不出去,最后导致死循环。
int tty_prepare_flip_string(struct tty_struct *tty, unsigned char **chars,size_t size)
 -> nt tty_buffer_request_room(struct tty_struct *tty, size_t size)
    -> struct tty_buffer *tty_buffer_find(struct tty_struct *tty, size_t size)
        -> struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size){
            {
                struct tty_buffer *p;
                if (tty->buf.memory_used + size > 65536)
                return NULL;
               }
            
怎样解决这个问题?做法很简单,提示然后直接读出,这也遗留下大问题。
    for (;;) {
        avail = smd_stream_read_avail(tty_info->ch);
        if (avail == 0)
            break;

        avail = tty_prepare_flip_string(tty, &ptr, avail);
        if(!avail){
            pr_err("smd_ch_irq_tasklet_handler: cannot get space from tty_buffer!\n");
            break;
        }
        smd_stream_read(tty_info->ch,ptr,avail);
        smd_send_intr(tty_info->a2b_int_rx);
        tty_flip_buffer_push(tty);
    }

1.2 tasklet and workqueue

解决了上面的问题,使用那个透传应用程序抓log还有会出现AP crash的情况,还是会看到softlock的情况,还有一些thread没有即使处理
导致的crash,问题定位到时间好多消耗在smd_ch_irq_tasklet_handler:因为BP log 很多,触发的中断次数很多,且是在tasklet中,导致
其他线程得不到运行。
解决的方法是,有关的log的通道从tasklet中放到work中去实现。

到此,有关BP log会产生crash的问题就解决了。

2 有关log会中断的问题

定位问题:是BP不输出啦?应用程序出错啦,没有把数据从BPlog中读出?还是USB的输出有问题,没有把得到的数据传给PC?
已开始是在ubuntu上调试的,手动起的透传应用程序,看上去是不会停的,【这种情况下没插卡】;
如果是应用程序启动透传应用程序,只会得到一段的数据,然后停止。

BP的同事可以看出此时 shared memory已满,说有不会再输出,此时会出现log
smd_ch_irq_tasklet_handler: cannot get space from tty_buffer!
    for (;;) {
        avail = smd_stream_read_avail(tty_info->ch);
        if (avail == 0)
            break;

        avail = tty_prepare_flip_string(tty, &ptr, avail);
        if(!avail){
            pr_err("smd_ch_irq_tasklet_handler: cannot get space from tty_buffer!\n");
            break;
        }
        smd_stream_read(tty_info->ch,ptr,avail);
        smd_send_intr(tty_info->a2b_int_rx);
        tty_flip_buffer_push(tty);
    }

tty_buffer可以申请到的memory 最多是64K,超过这值代码里是直接退出,BP没有收到中断且shared memory已满,所以不会有数据输出。
问题已经很明确:怎么去改啊?

1.为什么memory used没有得到释放

从代码中可以看出,上层没有即使读出,PC侧的应用程序没有及时打开。

  及时把应用程序打开,这里说的及时很难做到啊,用户也不干啊!
 
  可以变大tty_buffer 的上限,为及时打开PC侧应用程序赢得时间。
    static struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)
    {
        struct tty_buffer *p;

        if (tty->buf.memory_used + size > 65536)
            return NULL;
    }
  当是bplog 通道是改变上限:看看代码就会感到汗颜,什么代码都不会写啊,在简单也要调试啊!
  一开始是这样:
    static struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)
    {
        struct tty_buffer *p;
           if(tty->index==2||tty->index==3){
               if(tty->buf.memory_used+size > 0x4000000)
                       return NULL;
               }
        else if (tty->buf.memory_used + size > 65536)
            return NULL;
    }
 这里的条件判断看上去有点繁琐,不够专业,于是改为:
    static struct tty_buffer *tty_buffer_alloc(struct tty_struct *tty, size_t size)
    {
        struct tty_buffer *p;
           if((tty->index==2||tty->index==3) && tty->buf.memory_used+size > 0x100000)
                   return NULL;

        else if (tty->buf.memory_used + size > 65536)
            return NULL;
    }
这下意思全变了,并没有改变tty_buffer的上限,设想memory_used > 64K 但 < 0x100000,还是走下面的分支,返回NULL。
和写代码的意图就不一致了。

2.实现流控 tty

想让数据源也就是BP改变一下策略,但他们认为是AP没有把数据读走导致的问题和他们没有关系。想想也是感觉自己理亏,虽然他们

改改也能解决问题。这次交流还是有收获的,知道了他们发送数据的条件。他们会判断一个标志位,且这个标志是AP设置的。

是不是可以使用流控?去控制BP的输出。
看tty的代码,发现已经预留了接口,直接实现就可以了。什么是高手,这就是,早就框架了,设计好啦。

何时控制输入关闭?

当有数据输入时:
tty_flip_buffer_push(struct tty_struct *tty)
    ->schedule_work(&tty->buf.work);

flush_to_ldisc(struct work_struct *work)
    ->    disc->ops->receive_buf(tty, char_buf,flag_buf, count);[n_tty_receive_buf]
当tty的receive_room快没有空间时:throttle.
n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
                  char *fp, int count)
{
    /*
     * Check the remaining room for the input canonicalization
     * mode.  We don't want to throttle the driver if we're in
     * canonical mode and don't have a newline yet!
     */
    if (tty->receive_room < TTY_THRESHOLD_THROTTLE)
        tty_throttle(tty);

}
void tty_throttle(struct tty_struct *tty)
{
    mutex_lock(&tty->termios_mutex);
    /* check TTY_THROTTLED first so it indicates our state */
    if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) &&
        tty->ops->throttle)
        tty->ops->throttle(tty);
    mutex_unlock(&tty->termios_mutex);
}


当tty 的receive_room有数据被读出,有空间时:unthrottle
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
             unsigned char __user *buf, size_t nr)
{
        /* If there is enough space in the read buffer now, let the
         * low-level driver know. We use n_tty_chars_in_buffer() to
         * check the buffer, as it now knows about canonical mode.
         * Otherwise, if the driver is throttled and the line is
         * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
         * we won't get any more characters.
         */
        if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
            n_tty_set_room(tty);
            check_unthrottle(tty);
        }
}

static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
{
    unsigned long flags;
    ssize_t n = 0;

    spin_lock_irqsave(&tty->read_lock, flags);
    if (!tty->icanon) {
        n = tty->read_cnt;
    } else if (tty->canon_data) {
        n = (tty->canon_head > tty->read_tail) ?
            tty->canon_head - tty->read_tail :
            tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail);
    }
    spin_unlock_irqrestore(&tty->read_lock, flags);
    return n;
}

static void check_unthrottle(struct tty_struct *tty)
{
    if (tty->count)
        tty_unthrottle(tty);
}

void tty_unthrottle(struct tty_struct *tty)
{
    mutex_lock(&tty->termios_mutex);
    if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) &&
        tty->ops->unthrottle)
        tty->ops->unthrottle(tty);
    mutex_unlock(&tty->termios_mutex);
}
只有实现具体平台相关的throttle/unthrottle就可以了。

现在可以随时打开关闭接收log的应用程序,到此问题都解决了。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值