SPIN LOCK死锁问题

背景

板卡升级版本后,初始化疑似未执行完,SSH无法连接,可以PING通。

问题定位

通过查看控制台的打印,可以看到,初始化运行到了57一半,就卡住了。
两次控制台输入i(这个命令是我们内部实现的,功能和vxworks的类似,打印一些任务状态),只有PID为1785的程序执行时间在增长,怀疑任务陷入死循环。控制台查看其堆栈:

     [<0x2b55ad04>] (pthread_spin_lock + 0x4)

该任务在持续获取一把自旋锁。通过在正常版本查看,优先级为16的实时任务为logTask,负责向控制台输出打印。PID为1782的应该为主线程,负责执行初始化,查看其堆栈如下:

    tt 1782
    [<0x2b55ad4c>] (pthread_spin_trylock + 0x1c)
    value = 2 = 0x2

至此,怀疑logTask任务以及主线程任务,由于SPIN LOCK,互相锁死。查看BSP代码,用到的自旋锁变量为g_LogMsgBuf_sl,查看其内容:

    g_LogMsgBuf_sl
    g_LogMsgBuf_sl = 0x837078:  value = 1 = 0x1

可以看到,其值为1,说明已经有人拿到这把锁。由于两个任务优先级不同,主线程为非实时优先级,logTask为实时优先级,怀疑主线程在拿到锁后,被logTask打断,而logTask又无法获取该锁,造成死锁。
通过在控制台使用chrt命令,将logTask任务的调度策略修改为OTHER:

[2016/07/25 10:41:04]: ->system "chrt -o -p 0 1785"
[2016/07/25 10:41:14]: pid 1785's current scheduling policy: SCHED_FIFO
[2016/07/25 10:41:14]: pid 1785's current scheduling priority: 16
[2016/07/25 10:41:14]: pid 1785's new scheduling policy: SCHED_OTHER
[2016/07/25 10:41:14]: pid 1785's new scheduling priority: 0
[2016/07/25 10:41:14]: value = 0 = 0x0

之后,控制台打印恢复,开始打印后面的初始化信息。至此,原因已基本分析清楚,需要从代码层面,进行理论分析。
问题分析
首先分析logMsg的实现,伪代码如下:

    void logMsg(fmt,…)
    {
        if(pthread_spin_trylock() == 成功)
        {
            放入缓冲区
            pthread_spin_unlock()
            sem_post()/*通知logTask有新的打印需要输出*/
        }
        else
        {
            丢弃这条打印
        }
    }

在该函数内,只是尝试获取锁,并没有死等该资源,这是因为这个接口几乎所有任务都会调用,不能因为一条打印就阻塞任务的执行,如果获取不到锁则直接丢弃打印。也可以从这段代码看出,这把锁主要是用来做缓冲区互斥的。下面分析logTask任务的实现:

    void logTask()
    {
        while(1)
        {
            sem_wait()
            while(1)
            {
                pthread_spin_lock()
                if(有数据)
                    取到数据
                    pthread_spin_unlock()
                    输出打印
                else
                    pthread_spin_unlock()
                    break
            }
        }
    }

logTask阻塞在sem上,等待logMsg通知后,将缓冲区的所有数据全部输出,当没有数据可输出时,再次返回阻塞在sem上。
分析了一种可能导致死锁的情况:
(1) 有数据输出,logTask取到数据,解锁spin,然后发生任务切换
(2) 主线程拿到CPU,并且需要输出打印,成功获取到spin lock,并且正在将数据放入缓冲区
(3) 此时logTask执行完printf,由于优先级高于主线程,因此抢占CPU,尝试获取spin lock,但是由于spin lock在主线程,因此logTask会处于一直尝试获取spin lock中,而这造成了主线程无法获取到CPU,所以无法进一步释放spin lock,最终导致死锁
解决方案
 调整主线程任务优先级,必须保证所有任务优先级都比logTask高,因为整个系统中只有logTask使用的是spin_lock,其他任务在logMsg中使用的是trylock,高优先级trylock时,并不会造成死锁。
 spin lock是一个比较危险的锁,在网上查了一些资料,除非对性能极其在意,均不建议使用spin lock。BSP修改代码,将spin lock修改为mutex。spin lock与mutex效率对比见后。

遗留问题

spin lock与mutex效率对比

在SMC对比了二者时间。测试程序循环执行lock与unlock,不考虑锁冲突的情况,在板卡上运行10000次,结果如下:
spin lock: 0 s, 516027 ns
mutex: 0 s, 1596182 ns
从效率来说,spin lock比mutex大概快3倍。从网上查阅了一些资料,spin lock的实现无需切换至内核态,在用户态通过汇编直接实现,实际上是对一个变量进行原子加减。而mutex需要切换至内核态。

低优先级抢占高优先级?

在问题分析时,实际上是有一个问题没有解释清楚的,当时分析的可能的死锁情况的第一步:
(1) 有数据输出,logTask取到数据,解锁spin,然后发生任务切换
这块是说不通的,在高优先级解锁spin后,低优先级是如何获取到CPU,抢到spin,导致在高优先级拿不到spin?在logTask中,唯一可能引起阻塞的操作就是最外层的sem_wait,否则在内层循环中,logTask应该持续占用CPU才对,低优先级的主线程又是如何抢到CPU的?
针对这个问题,编写测试代码如下:

void func()
{
    int i;
    frwk_set_self_priority(16, SCHED_FIFO);

    while(1)
    {
        if(0 != pthread_spin_trylock(&sp))
        {
            printf("pthread_spin_trylock failed\n");
        }

        pthread_spin_unlock(&sp);
    }
}

int main()
{
    int i;
    pthread_t tid;

    pthread_spin_init(&sp, PTHREAD_PROCESS_PRIVATE);
    pthread_create(&tid, NULL, func, NULL);
    sleep(1);
    pthread_spin_lock(&sp);

    return 0;
}

在上述代码中已屏蔽掉不重要的函数。在main函数中,创建一个优先级为16的实时任务,该任务不停在获取、释放spin lock。而在main函数创建线程后,将会尝试去获取spin lock。一旦主线程获取到这把锁,则高优先级任务中trylock就会失败,并产生打印。在实际测试中,发现低优先级的主线程确实会在比较长的一段时间(大概几十秒)后,拿到这把锁。拿到锁后,屏幕会大量出现pthread_spin_trylock failed的打印,但是主线程依然没有退出。
目前,我们能得到的结论是,高优先级任务占据了极大比例的CPU,但是当前的任务调度并没有保证完全的占用,低优先级任务依然有极低的几率,占用了极低的一部分CPU(pthread_spin_lock效率极高,但是进程退出就没有执行完成)。

你可能感兴趣的:(Linux,C语言)