精华内容
下载资源
问答
  • 在单处理机上,spin_lock()就退化成了Preemp_disable(),它就是禁止抢占,即虽然时间片用完了,但是仍然是不能切换到其它进程去的。因此每个进程有一个preemp_count这个变量,如果这个变量为0的话表示其可以被其它...
    (1) linux上的自旋锁有三种实现:
    
              1. 在单cpu,不可抢占内核中,自旋锁为空操作。
              2. 在单cpu,可抢占内核中,自旋锁实现为“禁止内核抢占”,并不实现“自旋”。
              3. 在多cpu,可抢占内核中,自旋锁实现为“禁止内核抢占” + “自旋”。

    (2) 关于抢占式内核与非抢占式内核:
              在非抢占式内核中,如果一个进程在内核态运行,其只有在以下两种情况会被切换:
              1.  其运行完成(返回用户空间)
              2.  主动让出cpu(即主动调用schedule或内核中的任务阻塞——这同样也会导致调用schedule)

              在抢占式内核中,如果一个进程在内核态运行,其只有在以下四种情况会被切换:
              1.  其运行完成(返回用户空间)
              2.  主动让出cpu(即主动调用schedule或内核中的任务阻塞——这同样也会导致调用schedule)
              3.  当从中断处理程序正在执行,且返回内核空间之前(此时可抢占标志premptcount须为0) 。
              4.  当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等。

    禁止内核抢占只是关闭“可抢占标志”,而不是禁止进程切换。显式使用schedule或进程阻塞(此也会导致调用schedule)时,还是会发生进程调度的。

    一种死锁情况:
    死锁是有可能发生的。
    死锁发生在多核的情况,下面来分析一下:
    首先,对于多核抢占与多核非抢占的情况,在使用自旋锁时,其情况基本是一致的。
    因为在多核抢占的情况下,使用自旋锁会禁止内核抢占,这样多核抢占就相当于多核非抢占的情况。

    那下面就只分析多核非抢占的情况。
    假设系统有A,B两个CPU。
    A上正在运行的a进程已获得自旋锁,并在临界区运行。
    B上正在运行的b进程企图获得自旋锁,但由于自旋锁已被占用,于是b进程在B CPU上“自旋”空转。

    这时,如果在A上的a进程因程序阻塞,而被休眠。接着A会切换运行另一进程c。
    若这个进程c也企图获取自旋锁,c进程同样会因为锁已被占用,而在A上“自旋”空转。
    这时候,A上的a进程与c进程就形成了死锁。a进程需要被c进程占用的CPU,c进程需要被a进程占用的锁。

    至于在单cpu内核上不会出现上述情况,因为单cpu上的自旋锁实际没有“自旋功能”。

    假如持有自旋锁时进程的时间片用完了的话,是该如何来处理呢?
    在单处理机上,spin_lock()就退化成了Preemp_disable(),它就是禁止抢占,即虽然时间片用完了,但是仍然是不能切换到其它进程去的。因此每个进程有一个preemp_count这个变量,如果这个变量为0的话表示其可以被其它进程所抢占,如果大于0则不能够被抢占,而在调用preemp_disable()的时候就会将该进程的preemp_count值加1。使得其不能够被抢占。而继续运行该进程,直到调用preemp_enable()将其设为可抢占,由于时间一般不长,因此不会有影响。




    1、自旋锁作用与基本使用方法?
    与其他锁一样,自旋锁也用于保护临界区,但是自旋锁主要是用于在SMP上保护临界区。在SMP上,自旋锁最多只能被一个可执行线程持有,如果一个线程尝试获得一个被争用的自旋锁,该线程将一直旋转(while循环)直到锁可用;如果锁未被争用,请求锁的执行线程将立刻争用它,并继续执行。
     
    LINUX下自旋锁的基本使用方法:
    声明锁:
    spinlock_t lock;
     
    初始化:
    lock = SPIN_LOCK_UNLOCKED;
         或者
    spin_lock_init(&lock);

     

     
    加锁有4个接口,3个会阻塞,1个不阻塞
    spin_lock(&lock);//获取自旋锁
    spin_lock_irq(&lock);//关中断,获取自旋锁,不建议使用
    spin_lock_irqsave(&lock, flags);//关中断,保存中断状态,获取自旋锁
    spin_trylock(&lock);//与spin_lock一样,但是获取不到的时候不阻塞,返回非0
     
    对应的解锁接口有3个
    spin_unlock(&lock);//spin_lock和spin_trylock都用该接口解锁
    spin_unlock_irq(&lock);//不建议使用
    spin_unlock_irqrestore(&lock, flags);
     
    另外还提供了一个获取锁状态的接口:
    spin_is_locked(&lock);//如果指定的锁被获取,返回非0,否则,返回0
     
    2、在SMP和UP上的不同表现?
    对于SMP,自旋锁将在禁止抢占后,while循环自旋直到锁可用;
    对于UP,自旋锁的行为有点不一样,具体表现为:
    • 内核不支持抢占,则spin_lock是个空函数,spin_unlock也是空函数;
    • 内核支持抢占,则spin_lock关抢占,spin_unlock开抢占。
    要分析SMP和UP的区别,还得先理解UP下内核支持抢占与否的区分。
    在不支持抢占的时候,内核调度时机为:
    • 内核代码一直要执行到完成(返回用户空间);
    • 阻塞或主动调用schedule();
    支持内核抢占的内核,则在以下情况会发生抢占:
    • 从中断处理程序回到内核空间且内核具有抢占性时;
    • 当内核代码再一次具有可抢占性时;(如时钟中断,发现进程时间片已经用完,则发生进程抢占)
    • 内核显式调用schedule();
    • 内核中的任务阻塞(同样导致schedule());
    从以上区别可以看出,若是内核不支持抢占,只要临界区的代码不阻塞,就能保证原子性,所以spin_lock/spin_unlock是空函数。
    而对于支持抢占的内核, spin_lock/spin_unlock自然只需要关闭/恢复抢占功能即可。
     
     
    3、自旋锁与上下文
    由于自旋锁不睡眠,既可以用于进程上下文,也可用于非进程上下文,这是它与信号量相比的一个优势。
    正常来讲,如果自旋锁都是在进程上下文中使用,则建议使用spin_lock/spin_unlock。
    若在中断处理例程中也要使用自旋锁,则所有争用同一个锁的地方应使用spin_lock_irqsave/spin_unlock_irqrestore。
    若没有在中断而在下半部使用自旋锁,则所有急用同一个锁的地方可以使用spin_lock_bh/spin_unlock_bh,这对函数平时用得比较少。
    注:自旋锁用在中断例程中,若是进程上下文中的内核代码使用spin_lock,则在临界区,可能发生中断 ,要么原子性被破坏(UP),要么造成死锁(SMP).
     
    4、使用spin_lock()后为什么不能睡眠?
    在UP下,正如前面所说,原子性得不到保证。
    而在SMP下,则可能发生死锁:
    假设有3个进程A,B,C,2个CPU称CPU0,CPU1.
    CPU0上A进程获取自旋锁进入睡眠,调度了B进程,B进程将自旋; 
    CPU1上调度了C进程也将自旋。
    B等着A释放锁,A等着B释放CPU,自旋后的CPU不能发生调度,CPU0和CPU1将一直自旋下去,形成了死锁。
     
     
    5、强调:锁什么?
    使用锁的时候一定要对症下药,明确保护的是数据而不是代码,这是使用锁的正确思考方式。经常看别人代码的人应该会有所体会,针对代码加锁常常使代码难以理解,特别是复杂的程序,往往加锁没做好,引起竞争条件。
    不防在使用锁的时候先思考一下自己要保护什么,然后给对应的锁取跟数据相关的名称。比如"int foo;spinlock_t foo_lock;"表示foo由foo_lock加锁;

    /* -------------------------------------------- 认为不错------------------------------------------*/

    中断处理函数中,在获取锁之前,必须先禁止本地中断。否则,持有锁的内核代码会被中断处理程序打断,接着试图去争用这个已经被持有的自旋锁。这样的结果是,中断处理函数自旋,等待该锁重新可用,但是锁的持有者在该中断处理程序执行完毕之前不可能运行,这就成为了双重请求死锁。注意,需要关闭的只是当前处理器上的中断。因为中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放


    内核提供的禁止中断同时请求锁的方法很简单,如下:

    [cpp]  view plain  copy
    1. DEFINE_SPINLOCK(mr_lock);  
    2. unsigned long flags;  
    3.   
    4. spin_lock_irqsave(&mr_lock,flags);  
    5. /*临界区*/  
    6. spin_unlock_irqrestore(&mr_lock,flags);  

    函数spin_lock_irqsave():保存中断的当前状态,禁止本地中断,然后获取指定的锁。

    函数spin_unlock_reqrestore():对指定的锁解锁,让中断恢复到加锁前的状态。所以即使中断最初是被禁止的,代码也不会错误地激活它们。


    自旋锁可分为用在单核处理器上和用在多核处理器上。


    单核处理器:
    用在单核处理器上,又可分为两种:
    1.系统不支持内核抢占
    此时自旋锁什么也不做,确实也不需要做什么,因为单核处理器只有一个线程在执行,又不支持内核抢占,因此资源不可能会被其他的线程访问到。
    2.系统支持内核抢占
    这种情况下,自旋锁加锁仅仅是禁止了内核抢占,解锁则是启用了内核抢占。
    在上述两种情况下,在获取自旋锁后可能会发生中断,若中断处理程序去访问自旋锁所保护的资源,则会发生死锁。因此,linux内核又提供了spin_lock_irq()和spin_lock_irqsave(),这两个函数会在获取自旋锁的同时(同时禁止内核抢占),禁止本地外部可屏蔽中断,从而保证自旋锁的原子操作。

    多核处理器:
    多核处理器意味着有多个线程可以同时在不同的处理器上并行执行。举个例子:
    四核处理器,若A处理器上的线程1获取了锁,B、C两个处理器恰好这个时候也要访问这个锁保护的资源,因此他俩CPU就一直自旋忙等待。D并不需要这个资源,因此它可以正常处理其他事情。

    自旋锁的几个特点:
    1. 被自旋锁保护的临界区代码执行时不能睡眠。单核处理器下,获取到锁的线程睡眠,若恰好此时CPU调度的另一个执行线程也需要获取这个锁,则会造成死锁;多核处理器下,若想获取锁的线程在同一个处理器下,同样会造成死锁,若位于另外的处理器,则会长时间占用CPU等待睡眠的线程释放锁,从而浪费CPU资源。
    2.被自旋锁保护的临界区代码执行时不能被其他中断打断。原因同上类似。
    3.被自旋锁保护的临界区代码在执行时,内核不能被抢占,亦同上类似。



    为何自旋锁会自旋? 

    假设:求锁的进程 由于请求自旋锁 而使自己在 进程运行就绪队列上 优先级变高了 普通进程就没有出头之日

    如果这个进程是实时进程,将会一直忙等待持有锁
    比如SCHED_FIFO级别的进程不受时间片限制,可以一直执行下去。只要它不阻塞或主动显示放弃cpu,普通进程就没有出头之日



    转:http://blog.sina.com.cn/s/blog_98ee3a930102wg2x.html

    2个调度器

    可以用两种方法来激活调度

    • 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU

    • 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要

    因此当前linux的调度程序由两个调度器组成:主调度器周期性调度器(两者又统称为通用调度器(generic scheduler)核心调度器(core scheduler))

    并且每个调度器包括两个内容:调度框架(其实质就是两个函数框架)及调度器类

    6种调度策略

    linux内核目前实现了6中调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能

    • SCHED_NORMAL和SCHED_BATCH调度普通的非实时进程

    • SCHED_FIFO和SCHED_RR和SCHED_DEADLINE则采用不同的调度策略调度实时进程

    • SCHED_IDLE则在系统空闲时调用idle进程.

    5个调度器类

    而依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.

    其所属进程的优先级顺序为

    stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
     
    • 1

    3个调度实体

    调度器不限于调度进程, 还可以调度更大的实体, 比如实现组调度.

    这种一般性要求调度器不直接操作进程, 而是处理可调度实体, 因此需要一个通用的数据结构描述这个调度实体,即seched_entity结构, 其实际上就代表了一个调度对象,可以为一个进程,也可以为一个进程组.

    linux中针对当前可调度的实时和非实时进程, 定义了类型为seched_entity的3个调度实体

    • sched_dl_entity 采用EDF算法调度的实时调度实体

    • sched_rt_entity 采用Roound-Robin或者FIFO算法调度的实时调度实体

    • sched_entity 采用CFS算法调度的普通非实时进程的调度实体

    1.2 主调度器与内核/用户抢占


    1.2.1 调度过程中关闭内核抢占


    我们在上一篇linux内核主调度器schedule(文章链接, CSDNGithub)中在分析主调度器的时候, 我们会发现内核在进行调度之前都会通过preempt_disable关闭内核抢占, 而在完成调度工作后, 又会重新开启内核抢占

    参见主调度器函数schedule

        do {
            preempt_disable();                                  /*  关闭内核抢占  */
            __schedule(false);                                  /*  完成调度  */
            sched_preempt_enable_no_resched();                  /*  开启内核抢占  */
    
        } while (need_resched());   /*  如果该进程被其他进程设置了TIF_NEED_RESCHED标志,则函数重新执行进行调度    */
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个很容易理解, 我们在内核完成调度器过程中, 这时候如果发生了内核抢占, 我们的调度会被中断, 而调度却还没有完成, 这样会丢失我们调度的信息.

    1.2.2 调度完成检查need_resched看是否需要重新调度

    而同样我们可以看到, 在调度完成后, 内核会去判断need_resched条件, 如果这个时候为真, 内核会重新进程一次调度.

    这个的原因, 我们在前一篇博客中, 也已经说的很明白了,

    内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度,如果设置了,就会发生调度, 这被称为用户抢占

    2 非抢占式和可抢占式内核


    为了简化问题,我使用嵌入式实时系统uC/OS作为例子

    首先要指出的是,uC/OS只有内核态,没有用户态,这和Linux不一样

    多任务系统中, 内核负责管理各个任务, 或者说为每个任务分配CPU时间, 并且负责任务之间的通讯.

    内核提供的基本服务是任务切换. 调度(Scheduler),英文还有一词叫dispatcher, 也是调度的意思.

    这是内核的主要职责之一, 就是要决定该轮到哪个任务运行了. 多数实时内核是基于优先级调度法的, 每个任务根据其重要程度的不同被赋予一定的优先级. 基于优先级的调度法指,CPU总是让处在就绪态的优先级最高的任务先运行. 然而, 究竟何时让高优先级任务掌握CPU的使用权, 有两种不同的情况, 这要看用的是什么类型的内核, 是不可剥夺型的还是可剥夺型内核

    2.1 非抢占式内核


    非抢占式内核是由任务主动放弃CPU的使用权

    非抢占式调度法也称作合作型多任务, 各个任务彼此合作共享一个CPU. 异步事件还是由中断服务来处理. 中断服务可以使一个高优先级的任务由挂起状态变为就绪状态.

    但中断服务以后控制权还是回到原来被中断了的那个任务, 直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。非抢占式内核如下图所示.

    非抢占式内核

    非抢占式内核的优点有

    • 中断响应快(与抢占式内核比较);

    • 允许使用不可重入函数;

    • 几乎不需要使用信号量保护共享数据, 运行的任务占有CPU,不必担心被别的任务抢占。这不是绝对的,在打印机的使用上,仍需要满足互斥条件。

    非抢占式内核的缺点有

    • 任务响应时间慢。高优先级的任务已经进入就绪态,但还不能运行,要等到当前运行着的任务释放CPU

    • 非抢占式内核的任务级响应时间是不确定的,不知道什么时候最高优先级的任务才能拿到CPU的控制权,完全取决于应用程序什么时候释放CPU

    2.2 抢占式内核


    使用抢占式内核可以保证系统响应时间. 最高优先级的任务一旦就绪, 总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的CPU使用权就会被剥夺,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。

    抢占式内核如下图所示

    抢占式内核

    抢占式内核的优点有

    • 使用抢占式内核,最高优先级的任务什么时候可以执行,可以得到CPU的使用权是可知的。使用抢占式内核使得任务级响应时间得以最优化。 
      抢占式内核的缺点有:

    • 不能直接使用不可重入型函数。调用不可重入函数时,要满足互斥条件,这点可以使用互斥型信号量来实现。如果调用不可重入型函数时,低优先级的任务CPU的使用权被高优先级任务剥夺,不可重入型函数中的数据有可能被破坏。

    3 linux用户抢占


    3.1 linux用户抢占


    当内核即将返回用户空间时, 内核会检查need_resched是否设置, 如果设置, 则调用schedule(),此时,发生用户抢占.

    3.2 need_resched标识


    内核如何检查一个进程是否需要被调度呢?

    内核在即将返回用户空间时检查进程是否需要重新调度,如果设置了,就会发生调度, 这被称为用户抢占, 因此内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED

    并提供了一些设置可检测的函数

    函数 描述 定义
    set_tsk_need_resched 设置指定进程中的need_resched标志 include/linux/sched.h, L2920
    clear_tsk_need_resched 清除指定进程中的need_resched标志 include/linux/sched.h, L2926
    test_tsk_need_resched 检查指定进程need_resched标志 include/linux/sched.h, L2931

    而我们内核中调度时常用的need_resched()函数检查进程是否需要被重新调度其实就是通过test_tsk_need_resched实现的, 其定义如下所示

    // http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L3093
    static __always_inline bool need_resched(void)
    {
        return unlikely(tif_need_resched());
    }
    
    // http://lxr.free-electrons.com/source/include/linux/thread_info.h?v=4.6#L106
    #define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.3 用户抢占的发生时机(什么时候需要重新调度need_resched)

    一般来说,用户抢占发生几下情况:

    • 从系统调用返回用户空间;

    • 从中断(异常)处理程序返回用户空间

    从这里我们可以看到, 用户抢占是发生在用户空间的抢占现象.

    更详细的触发条件如下所示, 其实不外乎就是前面所说的两种情况: 从系统调用或者中断返回用户空间

    1. 时钟中断处理例程检查当前任务的时间片,当任务的时间片消耗完时,scheduler_tick()函数就会设置need_resched标志;

    2. 信号量、等到队列、completion等机制唤醒时都是基于waitqueue的,而waitqueue的唤醒函数为default_wake_function,其调用try_to_wake_up将被唤醒的任务更改为就绪状态并设置need_resched标志。

    3. 设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态;

    4. 改变任务的优先级时,可能会使高优先级的任务进入就绪状态;

    5. 新建一个任务时,可能会使高优先级的任务进入就绪状态;

    6. 对CPU(SMP)进行负载均衡时,当前任务可能需要放到另外一个CPU上运行

    4 linux内核抢占


    4.1 内核抢占的概念


    对比用户抢占, 顾名思义, 内核抢占就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取代.

    4.2 为什么linux需要内核抢占


    linux系统中, 进程在系统调用后返回用户态之前, 或者是内核中某些特定的点上, 都会调用调度器. 这确保除了一些明确指定的情况之外, 内核是无法中断的, 这不同于用户进程.

    如果内核处于相对耗时的操作中, 比如文件系统或者内存管理相关的任务, 这种行为可能会带来问题. 这种情况下, 内核代替特定的进程执行相当长的时间, 而其他进程无法执行, 无法调度, 这就造成了系统的延迟增加, 用户体验到”缓慢”的响应. 比如如果多媒体应用长时间无法得到CPU, 则可能发生视频和音频漏失现象.

    在编译内核时如果启用了对内核抢占的支持, 则可以解决这些问题. 如果高优先级进程有事情需要完成, 那么在启用了内核抢占的情况下, 不仅用户空间应用程序可以被中断, 内核也可以被中断,

    linux内核抢占是在Linux2.5.4版本发布时加入的, 尽管使内核可抢占需要的改动特别少, 但是该机制不像抢占用户空间进程那样容易实现. 如果内核无法一次性完成某些操作(例如, 对数据结构的操作), 那么可能出现静态条件而使得系统不一致.

    内核抢占和用户层进程被其他进程抢占是两个不同的概念, 内核抢占主要是从实时系统中引入的, 在非实时系统中的确也能提高系统的响应速度, 但也不是在所有情况下都是最优的,因为抢占也需要调度和同步开销,在某些情况下甚至要关闭内核抢占, 比如前面我们将主调度器的时候, linux内核在完成调度的过程中是关闭了内核抢占的.

    内核不能再任意点被中断, 幸运的是, 大多数不能中断的点已经被SMP实现标识出来了. 并且在实现内核抢占时可以重用这些信息. 如果内核可以被抢占, 那么单处理器系统也会像是一个SMP系统

    4.3 内核抢占的发生时机


    要满足什么条件,kernel才可以抢占一个任务的内核态呢?

    • 没持有锁。锁是用于保护临界区的,不能被抢占。

    • Kernel code可重入(reentrant)。因为kernel是SMP-safe的,所以满足可重入性。

    内核抢占发生的时机,一般发生在:

    1. 当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。

    2. 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数

    3. 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权

    4. 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权

    内核抢占,并不是在任何一个地方都可以发生,以下情况不能发生

    1. 内核正进行中断处理。在Linux内核中进程不能抢占中断(中断只能被其他中断中止、抢占,进程不能中止、抢占中断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。

    2. 内核正在进行中断上下文的Bottom Half(中断下半部,即软中断)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。如果此时正在执行其它软中断,则不再执行该软中断。

    3. 内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的保护状态中。内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占。

    4. 内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。

    5. 内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占。

    5 内核抢占的实现


    5.1 内核如何跟踪它能否被抢占?


    前面我们提到了, 系统中每个进程都有一个特定于体系结构的struct thread_info结构, 用户层程序被调度的时候会检查struct thread_info中的need_resched标识TLF_NEED_RESCHED标识来检查自己是否需要被重新调度.

    自然内核抢占·也可以应用同样的方法被实现, linux内核在thread_info结构中添加了一个自旋锁标识preempt_count, 称为抢占计数器(preemption counter).

    struct thread_info
    {
        /*  ......  */
        int preempt_count;   /* 0 => preemptable, <0 => BUG */
        /*  ......  */
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    preempt_count值 描述
    0
    禁止内核抢占, 其值标记了使用preempt_count的临界区的数目
    0 开启内核抢占
    <0 锁为负值, 内核出现错误

    内核自然也提供了一些函数或者宏, 用来开启, 关闭以及检测抢占计数器preempt_coun的值, 这些通用的函数定义在include/asm-generic/preempt.h, 而某些架构也定义了自己的接口, 比如x86架构/arch/x86/include/asm/preempt.h

    函数 描述 定义
    preempt_count 获取当前current进程抢占计数器的值 include/asm-generic/preempt.h, line 8
    preempt_count_ptr 返回指向当前current进程的抢占计数器的指针 include/asm-generic/preempt.h, line 13
    preempt_count_set 重设当前current进程的抢占计数器 include/asm-generic/preempt.h, line 18
    init_task_preempt_count 初始化task的抢占计数器为FORK_PREEMPT_COUNT include/asm-generic/preempt.h, line 26
    init_idle_preempt_count 初始化task的抢占计数器为PREEMPT_ENABLED include/asm-generic/preempt.h, line 30
    preempt_count_add 将增加current的抢占计数器增加val include/linux/preempt.h, line 132
    preempt_count_sub 将增加current的抢占计数器减少val include/linux/preempt.h, line 133
    preempt_count_dec_and_test 将current的抢占计数器减少1, 然后看是否可以进程内核抢占, 即检查抢占计数器是否为0(允许抢占), 同时检查tif_need_resched标识是否为真 include/linux/preempt.h, line 134, 61
    preempt_count_inc current的抢占计数器增加1 include/linux/preempt.h, line 140
    preempt_count_dec current的抢占计数器减少1 include/linux/preempt.h, line 141

    还有其他函数可用于开启和关闭内核抢占

    函数 描述 定义
    preempt_disable 通过preempt_count_inc来停用内核抢占, 并且通过路障barrier同步来避免编译器的优化 include/linux/preempt.h, line 145
    preempt_enable preempt_count_dec_and_test启用内核抢占, 然后通过__preempt_schedule检测是够有必要进行调度 include/linux/preempt.h, line 162
    preempt_enable_no_resched 开启抢占, 但是不进行重调度 include/linuxc/preempt.h, line 151
    preempt_check_resched 调用__preempt_schedule检测是够有必要进行调度 include/linux/preempt.h, line 176
    should_resched 检查current的抢占计数器是否为参数preempt_offset的值, 同时检查 tif_need_resched是否为真 include/linux/preempt.h, line 74
    preemptible 检查是否可以内核抢占, 检查抢占计数器是否为0, 以及是否停用了中断 /include/linux/preempt.h, line159

    5.2 内核如何知道是否需要抢占?


    首先必须设置了TLF_NEED_RESCHED标识来通知内核有进程在等待得到CPU时间, 然后会在判断抢占计数器preempt_count是否为0, 这个工作往往通过preempt_check_resched或者其相关来实现

    5.2.1 重新启用内核抢占时使用preempt_schedule检查抢占


    在内核停用抢占后重新启用时, 检测是否有进程打算抢占当前执行的内核代码, 是一个比较好的时机, 如果是这样, 应该尽快完成, 则无需等待下一次对调度器的例行调用.

    抢占机制中主要的函数是preempt_schedule, 设置了TIF_NEED_RESCHED标志并不能保证可以抢占内核, 内核可能处于临界区, 不能被干扰

    //  http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3307
    
    /*
     * this is the entry point to schedule() from in-kernel preemption
     * off of preempt_enable. Kernel preemptions off return from interrupt
     * occur there and call schedule directly.
     */
    asmlinkage __visible void __sched notrace preempt_schedule(void)
    {
        /*
         * If there is a non-zero preempt_count or interrupts are disabled,
         * we do not want to preempt the current task. Just return..
         */
         /* !preemptible() => preempt_count() != 0 || irqs_disabled()
          * 如果抢占计数器大于0, 那么抢占被停用, 该函数立即返回
          * 如果
         */
        if (likely(!preemptible())) 
            return;
    
        preempt_schedule_common();
    }
    NOKPROBE_SYMBOL(preempt_schedule);
    EXPORT_SYMBOL(preempt_schedule);
    
    // http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L159
     #define preemptible()   (preempt_count() == 0 && !irqs_disabled())
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    !preemptible => preempt_count() != 0 || irqs_disabled()表明

    • 如果抢占计数器大于0, 那么抢占仍然是被停用的, 因此内核不能被打断, 该函数立即结束.

    • 如果在某些重要的点上内核停用了硬件中断, 以保证一次性完成相关的处理, 那么抢占也是不可能的.irqs_disabled会检测是否停用了中断. 如果已经停用, 则内核不能被抢占

    接着如果可以被抢占, 则执行如下步骤

    
    static void __sched notrace preempt_schedule_common(void)
    {
        do {
            /*
                preempt_disable_notrace定义在
                http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L198             等待于__preempt_count_inc();
            */
            preempt_disable_notrace();
            /*  完成一次调度  */
            __schedule(true);
    
            /*
                preempt_enable_no_resched_notrace
                http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L204
                等价于__preempt_count_dec
            */
            preempt_enable_no_resched_notrace();
    
            /*
             * Check again in case we missed a preemption opportunity
             * between schedule and now.
             * 再次检查, 以免在__scheudle和当前点之间错过了抢占的时机
             */
        } while (need_resched());
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    我们可以看到, 内核在增加了抢占计数器的计数后, 用__schedule进行了一次调度, 参数传入preempt = true, 表明调度不是以普通的方式引发的, 而是由于内核抢占. 在内核重调度之后, 代码流程回到当前进程, 那么就井抢占计数器减少1.

    5.2.2 中断之后返回内核态时通过preempt_schedule_irq触发

    上面preempt_schedule只是触发内核抢占的一种方法, 另一种激活抢占的方式是在处理了一个硬件中断请求之后. 如果处理器在处理中断请求后返回内核态(返回用户态则没有影响), 特定体系结构的汇编例程会检查抢占计数器是否为0, 即是否允许抢占, 以及是否设置了重调度标识, 类似于preempt_schedule的处理. 如果两个条件都满足则通过preempt_schedule_irq调用调度器, 此时表明抢占请求发自中断上下文

    该函数与preempt_schedule的本质区别在于: preempt_schedule_irq调用时停用了中断, 防止终端造成的递归调用, 其定义在kernel/sched/core.c, line3360

    /*
     * this is the entry point to schedule() from kernel preemption
     * off of irq context.
     * Note, that this is called and return with irqs disabled. This will
     * protect us against recursive calling from irq.
     */
    asmlinkage __visible void __sched preempt_schedule_irq(void)
    {
        enum ctx_state prev_state;
    
        /* Catch callers which need to be fixed */
        BUG_ON(preempt_count() || !irqs_disabled());
    
        prev_state = exception_enter();
    
        do {
            preempt_disable();
            local_irq_enable();
            __schedule(true);
            local_irq_disable();
            sched_preempt_enable_no_resched();
        } while (need_resched());
    
        exception_exit(prev_state);
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    5.2.3 PREEMPT_ACTIVE标识位和PREEMPT_DISABLE_OFFSET


    之前的内核版本中, 抢占计数器中于一个标识位PREEMPT_ACTIVE, 这个位设置后即标识了可以进行内核抢占, 使得preempt_count有一个很大的值, 这样就不受普通的抢占计数器加1操作的影响了

    PREEMPT_ACTIVE的引入, 参见PREEMPT_ACTIVE: add default defines

    //  http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.3#L58
    #define PREEMPT_ACTIVE_BITS     1
    #define PREEMPT_ACTIVE_SHIFT    (NMI_SHIFT + NMI_BITS)
    #define PREEMPT_ACTIVE  (__IRQ_MASK(PREEMPT_ACTIVE_BITS) << PREEMPT_ACTIVE_SHIFT)
     
    • 1
    • 2
    • 3
    • 4

    然后也为其提供了一些置位的函数,其实就是将preempt_count加上/减去一个很大的数, 参见preempt: Disable preemption from preempt_schedule*() callers

    但是在linux-4.4版本之后移除了这个标志, 取而代之的是在linux-4.2时引入的PREEMPT_DISABLE_OFFSET

    参见

    Rename PREEMPT_CHECK_OFFSET to PREEMPT_DISABLE_OFFSET 
    preempt: Rename PREEMPT_CHECK_OFFSET to PREEMPT_DISABLE_OFFSET

    preempt: Remove PREEMPT_ACTIVE unmasking off in_atomic()

    sched: Kill PREEMPT_ACTIVE

    sched: Stop setting PREEMPT_ACTIVE

    参考

    内核随记(二)——内核抢占与中断返回

    PREEMPT_ACTIVE

    6 总结


    一般来说,CPU在任何时刻都处于以下三种情况之一:

    1. 运行于用户空间,执行用户进程

    2. 运行于内核空间,处于进程上下文

    3. 运行于内核空间,处于中断上下文

    6.1 用户抢占


    一般来说, 当进程从系统调用或者从中断(异常)处理程序返回用户空间时会触发主调度器进行用户抢占

    • 从系统调用返回用户空间

    • 从中断(异常)处理程序返回用户空间

    为了对一个进程需要被调度进行标记, 内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度,如果设置了,就会发生调度, 这被称为用户抢占

    6.2 内核抢占


    如果内核处于相对耗时的操作中, 比如文件系统或者内存管理相关的任务, 这种行为可能会带来问题. 这种情况下, 内核代替特定的进程执行相当长的时间, 而其他进程无法执行, 无法调度, 这就造成了系统的延迟增加, 用户体验到”缓慢”的响应. 因此linux内核引入了内核抢占.

    linux内核通过在thread_info结构中添加了一个自旋锁标识preempt_count, 称为抢占计数器(preemption counter)来作为内核抢占的标记,

    内核抢占的触发大致也是两类, 内核抢占关闭后重新开启时, 中断返回内核态时

    • 内核重新开启内核抢占时使用preempt_schedule检查内核抢占

    • 中断之后返回内核态时通过preempt_schedule_irq触发内核抢占

    而内核抢占时, 通过调用__schedule(true)传入的preempt=true来通知内核, 这是一个内核抢占


    转:@ http://blog.csdn.net/gatieme

    展开全文
  • 不一定要同时,不同代码块交替执行的性能,可以串行处理也可以并行处理并行:同时处理多个任务的能力,不同代码块同时执行的性能串行:指多个任务时,各个任务按顺序执行,完成一个之后才能执行下一个同步:所谓同步...

    并发拥有处理多个任务的能力,不一定要同时,不同代码块交替执行的性能,可以串行处理也可以并行处理

    并行:同时处理多个任务的能力,不同代码块同时执行的性能

    串行:指多个任务时,各个任务按顺序执行,完成一个之后才能执行下一个


    同步所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
    换句话说,就是由*调用者*主动等待这个*调用*的结果。


    异步异步则相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

    异步的实现①异步回调(多线程实现)

                     ②消息队列(多线程实现)

                     ③协程(时间片分隔实现)



    线程是CPU调度的基本单元


    对于单核:多线程和多进程的多任务是在单cpu交替执行(时间片轮转调度,优先级调度等),属于并发

    对于多核:同一个时间多个进程运行在不同的机器上或者同一个机器不同核心上,或者是同一个时间多个线程能分布在同一机器的不同核心上(线程数小于内核数),属于并行。


    a.多核CPU——计算密集型任务。此时要尽量使用多线程,可以提高任务执行效率,例如加密解密,数据压缩解压缩(视频、音频、普通数据),否则只能使一个核心满载,而其他核心闲置。

    b.单核CPU——计算密集型任务。此时的任务已经把CPU资源100%消耗了,就没必要也不可能使用多线程来提高计算效率了;相反,如果要做人机交互,最好还是要用多线程,避免用户没法对计算机进行操作。

    c.单核CPU——IO密集型任务,使用多线程还是为了人机交互方便,

    d.多核CPU——IO密集型任务,这就更不用说了,跟单核时候原因一样。

    展开全文
  • 最终实现DSP间2.520 Gb/s的数据传输速率,为理论值的50.40%,但如果除去线程调度和DSP间同步所用时间,其SRIO接口的数据传输速率可达到3.886 Gb/s,为理论值的77.72%。该设计具有较大的通用性,对其他同类型的芯片间...
  • 前 言嵌入式领域的处理器已向多核架构迅速发展,TI公司的KeyStone架构的TMS320C6678是目前市面上性能最高的多核DSP处理器。TMS320C6678集成8核C66x DSP...

    前 言

    嵌入式领域的处理器已向多核架构迅速发展,TI公司的KeyStone架构的TMS320C6678是目前市面上性能最高的多核DSP处理器。

    TMS320C6678集成8核C66x DSP处理器,每个内核频率高达1.25 GHz,提供每秒高达40 GMAC定点运算和20 GFLOP浮点运算能力;1片TMS320C6678提供等效达10 GHz的内核频率,单精度浮点并行运算能力理论上可达160 GFLOP,是TS201S的50倍、C67x+的115.2倍,适合于诸如油气勘探、雷达信号处理、图像处理以及定位导航等对定浮点运算能力及实时性有较高要求的超高性能计算应用。

    然而,核间通信是多核处理器系统所面临的主要难点,通信机制的优劣直接影响多核处理器的性能,高效的通信机制是发挥多核处理器高性能的重要保障。

    创龙科技(Tronlong)专注于DSP、ARM、FPGA多核异构技术开发,本文为您介绍TMS320C6678处理器开发中比较常用的两种多核通信方式:TI-IPC和OpenMP,以及多核编程注意事项。

    1 硬件平台

    本文基于创龙科技TL6678-EasyEVM评估板进行演示。

    TL6678-EasyEVM是一款基于TI KeyStone架构C6000系列TMS320C6678八核C66x定点/浮点高性能处理器设计的高端多核DSP评估板,由核心板与底板组成。核心板经过专业的PCB Layout和高低温测试验证,稳定可靠,可满足各种工业应用环境。

    评估板接口资源丰富,引出双路千兆网口、SRIO、PCIe等高速通信接口,方便用户快速进行产品方案评估与技术预研。

    TL6678-EasyEVM评估板视频简介

    开发案例主要包括: 

    (1) 裸机开发案例

    (2) RTOS(SYS/BIOS)开发案例

    (3) IPC、OpenMP 多核开发案例

    (4) SRIO、PCIe、双千兆网口开发案例

    (5) 图像处理开发案例

    (6) DSP算法开发案例

    (7) 串口、网络远程升级开发案例

    C66x DSP视频教程、中文手册、产品资料(用户手册、核心板硬件资料、产品规格书)可点击下方链接或扫码二维码获取。

    http://site.tronlong.com/pfdownload

    2 TI-IPC

    2.1

    简介

    TI-IPC(Inter-Processor Communication)组件提供与处理器硬件无关的API,可用于多核处理器核间通信、同一处理器进程间通信和设备间通信。API支持消息传递、流和链接列表,它们在单处理器和多处理器中配置均可兼容。

    TI-IPC被设计在运行SYS/BIOS应用程序的处理器上使用,一般为DSP处理器(如TMS320C6678、TMS320C6657),但在某些情况下亦可能是ARM处理器。

    IPC常见的通信模块如下:

    2.2

    常用模块

    2.2.1 MessageQ

    该模块具有如下特点:

    (1) 兼容性强,可在不更改运行代码的情况移植至另一个支持TI-IPC MessageQ的处理器或其他传输层(如Shared Memory、Multicore Navigator、SRIO)。

    (2) 支持可变长度消息的结构化发送与接收。

    (3) 单个MessageQ队列支持单个reader和多个writer。

    (4) 消息接收支持超时机制。

    (5) reader可根据消息头部信息对writer进行确认后再回复。

    (6) Ipc_start()会帮助用户完成下图中灰色框内的功能,用户仅需关注红色框中的内容即可,

    MessageQ通过消息队列发送和接收消息。reader是一个从消息队列中读取消息的线程,writer是一个将消息写入消息队列的线程。每个消息队列都有一个reader,但可有多个writer。

    ■ reader:调用MessageQ_create()、MessageQ_get()、MessageQ_free()和MessageQ_delete()。

    ■ writer:调用MessageQ_open()、MessageQ_alloc()、MessageQ_put()和MessageQ_close()。

    MessageQ常见的工作流程如下所示。

    下面以多核IPC通信的shmIpcBenchmark案例为例,分析代码中MessageQ的使用,见图中注释。

    2.2.2 Notify

    该模块具有如下特点:

    (1) 可独立于MessageQ模块进行使用。

    (2) 着重于多核通知功能,是更为简单的多核通信形式。

    (3)  仅可基于Shared Memroy方式进行使用。

    Notify通过硬件中断传输消息,Receiver注册Notify事件中断,Sender通过Notify发送事件中断,从而实现通知并携带小量消息的目的。

    ■ Receiver:调用Notify_registerEvent()注册事件中断服务函数。

    ■ Sender:调用Notify_sendEvent()发送事件中断。

    Notify常见的工作流程如下所示。

    2.3

    物理传输方式

    TI-IPC的数据传输需结合特定物理硬件与底层驱动,方可实现两个线程在同一个设备或跨设备间进行通信。常用三种的物理传输方式包括Shared Memory、Multicore Navigator和SRIO,具体说明如下。

    3 OpenMP

    3.1

    简介

    OpenMP是一种多核开发软件框架,其主要特性如下:

    (1) 可跨平台使用,代码兼容性强。

    (2) 以共享内存为通信基础。

    (3) 支持C/C++以及Fortran语言。

    (4) 一般基于SYS/BIOS运行。

    3.2

    基本语法

    #pragma omp 指令 [ 子句 [ [ [,] 子句 ] ... ]

    {

    ...

    }

    以裸机的omp_matavec案例为例,使用场景的概要流程图如下。

    C66xx_0核心创建主线程,通过OpenMP框架加载matvec算法至C66xx_0~C66xx_7核心进行并行运算,从而减少C66xx_0核心负载,并可加快运算速度。

    3.3

    代码分析

    以裸机的omp_matavec案例为例进行代码分析,见图中注释。

    4 多核编程注意事项

    4.1

    多核单/多镜像

    在开发过程中,需将程序可执行文件分别加载至对应的核心运行。此时需了解多核单/多镜像的优缺点,再根据实际情况进行选择。

    多核单镜像

    多核单镜像指所有核心运行完全相同的用户程序。

    优点:仅需维护一个工程,管理便捷。

    缺点:需兼容多个核心代码,程序可执行文件较大。

    多核多镜像

    多核多镜像指不同核心运行不同的用户程序。

    优点:无需考虑各核心功能的兼容性,单个程序可执行文件较小。

    缺点:需维护多个工程,管理不便。

    4.2

    外设访问

    所有核心共享外设,如SRIO、PCIe、Ethernet、SPI、I2C、EMIF等。在对外设进行初始化后,所有核心可在任意时间对外设进行读写,无需再次初始化。

    4.3

    数据存储

    (1) 注意区分全局与局部地址。

    L1/L2SRAM有全局与局部两个地址,全局地址可被所有核心访问,但局部地址仅可被指定核心访问。

    以C66xx_0核心的L2SRAM为例,对应的全局地址为0x10800000,局部地址为0x00800000。C66xx_0核心使用0x10800000或0x00800000,均可访问C66xx_0核心的L2SRAM。而C66xx_1核心使用0x00800000仅可访问C66xx_1核心的L2SRAM,使用0x10800000方可访问C66xx_0核心的L2SRAM。

    L2SRAM全局与局部地址对应关系如下表:

    (2) 注意避免内存冲突。

    如数据需存放至MSMCSRAM、DDR3共享内存设备,请将对应内存划分为MSMCSRAM_MASTER段(主核使用)与MSMCSRAM_SLAVE段(从核使用),从而避免运行时内存冲突。

    5 参考链接

    -IPC

    https://training.ti.com/system/files/docs/keystone-intro-ipc-slides.pdf

    http://software-dl.ti.com/processor-sdk-rtos/esd/docs/latest/rtos/index_Foundational_Components.html#ti-sdo-ipc-package

    http://software-dl.ti.com/processor-sdk-rtos/esd/docs/latest/rtos/index_Foundational_Components.html#gatemp-support-for-uio-and-misc-driver

    http://software-dl.ti.com/processor-sdk-rtos/esd/docs/latest/rtos/index_Foundational_Components.html#ti-sdo-utils-package

    https://processors.wiki.ti.com/index.php/IPC_Users_Guide/MessageQ_Module

    -OpenMP

    http://processors.wiki.ti.com/index.php/OpenMP_on_C6000

    https://processors.wiki.ti.com/index.php/BIOS_MCSDK_2.0_User_Guide#OpenMP

    http://community.topcoder.com/tc?module=Static&d1=features&d2=091106

    -Multicore Programming Guide

    https://www.ti.com/lit/an/sprab27b/sprab27b.pdf

    6 免费试用

    扫描下方二维码,即可申请TL6678-EasyEVM评估板进行快速评估,免费哦!

    7 技术交流群

    TMS320C6678 DSP交流群:79635273、332643352

    8 更多推荐

    基于IPC、OpenMP的C66x多核开发视频教程

     

    更多方案,欢迎与Tronlong联系:

    销售邮箱:sales@tronlong.com

    技术邮箱:support@tronlong.com

    创龙总机:020-8998-6280

    技术热线:020-3893-9734

    创龙官网:www.tronlong.com

    技术论坛:www.51ele.net

    官方商城:https://tronlong.taobao.com

    创龙官网

    创龙微信公众号

    【长按识别二维码关注我们】

    期待你的

    分享

    点赞

    在看

    展开全文
  • 本文是我写得一篇关于RTOS SMP扩展的硕士论文,希望对有志于了解RTOS-SMP工作机制的兄弟们提供些许帮助,O...多核处理器将多个较低主频的处理核心集成到一个芯片内部,通过提高IPC(Instruction Per Clock)来提升处...

    本文是我写得一篇关于RTOS SMP扩展的硕士论文,希望对有志于了解RTOS-SMP工作机制的兄弟们提供些许帮助,O(∩_∩)O~。

        一直以来人们通过提高主频来提升微处理器的性能,但是高功耗制约着主频的进一步提升,这种矛盾在仅依靠电池供电的嵌入式设备上表现的尤为突出。多核处理器将多个较低主频的处理核心集成到一个芯片内部,通过提高IPC(Instruction Per Clock)来提升处理器性能。多核处理器构架在提升性能的同时又降低了功耗,显然是嵌入式设备上处理器的理想选择。在这样的背景下,支持多核处理器的RTOS(Real Time Operating System)成为当前研究的热点,尤其是支持SMP(Symmetric Multi-Processing)的RTOS因具有较高的性能和功耗比、容易实现负载均衡等优点,更能发挥多核并行处理的优势。因此本文以单核RTOS的SMP扩展为研究重点,为单核RTOS扩展成为支持SMP模式提出一套可行性的解决方案。

        本文选择uC/OS-II为实验对象,在深入研究uC/OS-II任务调度、中断处理、任务间同步与通信机制的基础上,将其扩展成支持SMP的原型系统。在系统初始化方面,采用主CPU负责初始化系统、启动次CPU,次CPU享受主CPU的初始化成果;对于CPU间的互斥和通信,提出了基于FCFS(First Come First Serve)的Ticket内核锁机制,有效解决了CPU饥饿和Cache频繁刷新问题;同时设计并实现了核间中断接口用于CPU间的通信;在多核同步方面,本文设计了原子操作、内存屏障、自旋锁,提出了对uC/OS-II全局资源进行SMP扩展的原则,以便对互斥信号量、邮箱、消息队列等任务间同步与通信模块进行SMP扩展;在中断处理方面本文采用基于Ticket内核锁的中断动态分配算法;在任务调度方面,本文在uC/OS-II位图算法的基础上提出了基于全局队列的任务调度算法,有效实现了负载均衡,保证了系统的实时性和可预测性。

        文章最后在MPC8641D双核处理器平台对原型系统进行了测试,并对测试结果进行了定性和定量分析,验证了SMP扩展方案的可行性。

     

    第一章 序言

    1.1  术语解释

        CPU和处理器(Processor)在一般的计算机技术文档中是同义词,但是在SMP硬件平台上对两者进行区分有助于准确描述问题,同时我们还定义了其它几个术语。

    CPU:特指一个硅处理单元,该单元可以执行程序指令和处理数据,和多核(Multi-Core)中的核(Core)是同义词,本文对Core和CPU不加区分;

    处理器(Processor):指一个物理硅片,里面可能会包含一个或者多个CPU;

    多处理器(Multiprocessor):指的是一个硬件系统,其中可以包含有多个处理器(Processor);

    单核处理器(Uniprocessor):指的是一个物理硅片,该硅片中仅仅含有一个CPU;

    SMP硬件平台:指的是包含SMP处理器的硬件系统,SMP处理器可以是一个对称多核处理器,也可以是多个对等的单核处理器构成。CPU之间、或者单核处理器之间的地位平等,即它们平等的访问内存和外设资源,硬件系统的内存结构必须是UMA(一致内存访问)体系结构。在嵌入式业内多核处理器更多的是指对称多核处理器。

    举个例子,本文使用的SBC8641D开发板是一个SMP硬件平台,该平台使用的双核处理器MPC8641D是一个多核处理器(Multicore Processor),在该处理器中集成了两个CPU(即Core)。

     

    1.2  论文研究背景

        随着应用需求的扩展和软件技术的进步,人们对处理器性能的要求越来越高,仅仅依靠提高CPU主频来提升处理器性能的方式受到随之而来的高功耗的制约,“摩尔定律”难以继续维持。因此,多核处理器构架开始成为一条全新的解决方案。多核处理器构架[1]是指将两个以上(包括两个)的较低主频的CPU封装在一个芯片的内部,通过提高IPC(Instruction Per Clock)来达到在比较低的功耗下获得更高处理性能的目的,因此受到人们越来越多的关注。学术界对多核处理器构架进行深入研究始于20世纪90年代中后期,在此期间提出了大量的设想,其中最著名的是斯坦福大学提出的单片多处理器构架CMP(Chip Multi-Processor)[2]。随着微处理器技术的迅猛发展,多核处理器构架逐步成为通用处理器的主流,从IBM于2001年发布第一款通用双核处理器Power4以来,通用处理器开始进入多核时代,并陆续在桌面计算、服务器计算和高性能计算等领域得到全面应用和普及。

        虽然多核处理器目前已经成为桌面PC电脑处理器的主流,但在嵌入式领域却刚刚起步。随着移动互联网的发展,多核处理器芯片逐步渗入到嵌入式领域,从2011年开始在移动终端领域得到了迅猛发展,但是和通用桌面多核处理器一样,如果没有操作系统的配合,多核处理器芯片并不能发挥相对于单核处理器多核并行处理的优势。正因为如此,如何对传统单核RTOS进行多核扩展、甚至重新设计出支持多核处理器的RTOS,使其能够发挥多核并行处理的优势成为当前多核RTOS研究的热点。

        目前在嵌入式领域支持多核处理器芯片的操作系统体系结构有两种:

    一是AMP(Asymmetric Multi-processing)模式:AMP模式的RTOS在各个CPU上均运行一个操作系统实例(这些操作实例不一定完全相同),各个操作系统拥有自己专用的内存,相互之间通过访问受限的共享内存进行通信。AMP模式的操作系统结构需要用户参与系统资源的分配。这种类型的RTOS应用比较少,商用操作系统中仅有Wind River公司的VxWorks 提供AMP模式的配置。

    二是SMP(Symmetric Multi-processing)模式:SMP模式的操作系统构架是多核处理器技术的一种变体,由一个操作系统实例控制所有处理器,所有处理器共享内存。与AMP模式中每个CPU上运行一个操作系统实例不同,SMP模式系统中所有CPU的地位相同,共同运行一个操作系统实例,所有CPU共享系统内存和外设资源。相对于AMP模式,SMP模式的操作系统具有可共享内存、较高的性能和功耗比、以及易实现负载均衡等优点,更能发挥发挥多核处理器的硬件优势[3]

        本文着重研究将单核RTOS扩展为SMP模式的操作系统所面临的技术难题,提出相应的解决方案,在尽可能不改变原有系统结构、以及不降低原有系统性能的前提下完成SMP扩展。之所以选择对单核RTOS进行SMP扩展,一方面是因为重新设计支持SMP的RTOS工作量太大,而直接在单核版本上进行SMP扩展,可以利用单核RTOS现有的技术成果;另一方面,不管是对单核RTOS进行SMP扩展,还是重新设计一款支持SMP的RTOS,都必须解决系统引导、任务间同步与互斥、任务调度、中断处理等方面的SMP扩展问题,解决了这些问题也就解决了设计SMP模式的RTOS面临的核心难题。

     

    1.3  国内外研究现状

        多核处理器只有在软件能够充分利用多核并行处理的特性时,其相对于传统单核处理器的优势才会体现出来。在桌面系统业界,主流的操作系统厂商相继推出了支持SMP模式的操作系统,例如微软的Windows、Redhat公司的Redhat Enterprise Linux发行版、Sun微系统公司的Solaris等等。但是在嵌入式RTOS业内,支持SMP模式的操作系统发展相对缓慢,目前主要有WindRiver公司(该公司已经被Intel收购)的VxWorks-6.6及后续版本,QNX软件有限公司的Neutrino、以及由Linux-2.4内核演化而来的MontaVista Linux等少数的操作系统。

        随着SMP硬件平台的发展,如何让RTOS更好得支持SMP来充分发挥多核处理器的硬件特性成为当前的研究热点。在Cache利用率方面文献[4]提出支持SMP模式的操作系统如果能充分考虑Cache热度,通过计算任务的共享程度,将共享程度高的任务放在同一个CPU上运行,共享程度低的任务放在不同的CPU上运行,对提高系统的性能、降低系统功耗起着非常显著的作用;文献[5]提出二级Cache不命中比一级Cache不命中对操作系统性能的影响更大,同时指出操作系统可以通过改进调度算法来减少对二级Cache的访问;文献[6]指出为了减少线程对二级Cache的访问,可以将可能引起二级Cache访问的任务放在一个线程组中,使用合理的调度策略使得这个组中的任务不会并行执行;为了增加Cache访问的可预测性,文献[7]提出通过锁和分区机制来对共享Cache进行保护。

    为了更好的支持SMP硬件平台,WindRiver公司从VxWorks6.6版本开始引入对SMP的支持,但是它的支持是粗粒度的,即用一个大内核锁来保护VxWorks的内核态,每次只有一个CPU能够进入内核态。如果单从调度模块设计来看,VxWorks6.6及其后继版本对SMP的支持水平仅相当与Linux-2.2内核SMP的支持水平,特别需要指出的是Linux内核在通用计算机平台对SMP模式的支持技术目前是比较成熟的。

        2006年美国嵌入式操作系统开发联盟的James.Aderson提出了基于锁中断的中断分配算法,有效解决了多核实时操作系统在多个CPU间分配中断的问题,对多核RTOS自旋锁的研究与设计起到极大的推动作用[8]。

        2010年,多核应用协会提出了多核编程接口标准MCAPI V1.0(Multicore Resource API Specification),为多核操作系统设计、应用程序的开发及移植也起到极大的推动作用[9]

        和国外的研究相比,国内在支持SMP模式的操作系统研究方面起步较晚,一直处于跟踪研究状态,但随着近年来国内专家对多核技术的重视,仍取得了不少成果。文献[10]针对SMP硬件平台上的静态任务调度问题提出了新的调度模型和调度算法,在提高调度器性能的同时又降低了算法时间复杂度。文献[11]对支持多核处理器的启发式任务调度算法进行了深入研究,提出了一种包含两轮操作的任务调度算法,优化了任务分派到CPU的策略,在一定程度上降低了算法的复杂度。然而上述文献研究的更多是通用操作系统对多核处理器的支持,并不是针对嵌入式多核RTOS,因而支持多核硬件平台RTOS的实时性和可预测性并不能得到保证。文献[12]提出了对实时操作系统RTEMS进行SMP扩展的完整解决方案,文献[13]深入研究了SMP硬件平台的多核调度算法,可以说是目前对单核RTOS进行SMP扩展研究的最新成果。

     

    1.4  本文主要工作

        对任何一款单核RTOS进行SMP扩展,都必须要解决三个主要问题:系统完整性(System Integrity)、性能(Performance)和外部编程模型(External Programming Model)[14]。系统的完整性意味着如何正确协调CPU间的并行活动,来避免破坏内核数据结构;在保证系统完整性的前提下尽可能地提高系统性能也是SMP扩展必须要考虑的情况;外部编程模型决定了对应用程序的影响,如果系统调用接口与原系统不一致,那么许多为原系统编写的应用程序就必须修改,有时候修改的代价是巨大的。所以对RTOS的SMP扩展一般都会选择保持原有API(Application Interface)接口不变。另外RTOS的实时性和可预测性在进行SMP扩展时必须得到保证。

    本文深入研究单核RTOS进行SMP扩展面临的技术难题,并给出相应的解决方案,在对RTOS进行SMP扩展的过程中本文做了以下方面的工作。

        在系统初始化方面,我们将CPU分为主CPU和次CPU,由主CPU负责系统的初始化并启动次CPU,次CPU享受主CPU的初始化成果;

        在CPU同步方面,我们提出了原子操作、自旋锁、内存屏障来实现数据的可重入,同时设计了具有FCFS(First Come First Serve)功能的内核锁,有效解决了因自旋锁频繁涮洗Cache造成的系统性能损失、以及CPU饥饿问题;

        在系统调度模块设计方面,我们采用基于优先级的全局队列调度算法,实现了CPU间的负载均衡、以及算法时间复杂度与系统负载无关;

        在系统出错调试方面,我们设计了异常处理模块来输出系统产生异常时的栈帧和CPU寄存器值,方便内核开发者解决设计缺陷;

        在系统中断处理方面,我们提出了基于内核锁的中断动态分配算法;

        对于通用模块,比如任务间同步和通信模块,我们提出了完整的的SMP扩展原则。

     

    1.5  本文结构布局

        本文分为六章,各章组织如下:

        第1章为绪论部分,主要介绍了本文的研究背景、多核处理器构架的发展历程、以及国内外研究现状;

        第2章为对称多处理器技术综述,介绍了与本文研究相关的技术背景,包括SMP构架的基本特征和关键技术,讨论了支持SMP和AMP两种模式的操作系统异同,着重分析Linux内核支持SMP的机制,为单核RTOS的SMP扩展提供借鉴。

        第3章为uC/OS-II内核分析,深入研究了uC/OS-II核心机制,为对其进行SMP扩展做准备;

        第4章为RTOS的SMP扩展设计与实现,深入分析uC/OS-II内核在SMP扩展时所面临的技术难题,分别给出相应的解决方案;

        第5章为实验验证,在MPC8641D硬件平台上对SMP原型系统做功能性测试,并与uC/OS-II单核系统做性能对比测试、验证SMP扩展方案的可行性;

        第6章为结束语,对论文进行总结,对进一步的研究工作做论述和展望。

     

    2.1  对称多处理器构架

        摩尔定律指出芯片集成度每隔18至24个月翻一翻。从Intel第一块真正意义上的微处理器4004直到2000年左右,处理器生产厂商一直通过提高时钟频率来提升处理器性能,摩尔定律在过去的近30年中展现出惊人的准确性。然而主频的提高导致处理器功耗和发热量急剧增加,反过来制约了处理器性能的进一步提升。于是人们开始把研究重点转向将多个处理核心集成到单个芯片内部,通过将任务放在不同处理器核心上并行执行来达到提高处理器性能的目的,这就是多核硬件平台。

        SMP硬件平台指的是包含SMP处理器的硬件系统,SMP处理器可以是一个对称多核处理器,也可以是多个对等的单核处理器构成。CPU之间、或者单核处理器之间的地位相同,即它们平等的访问内存和外设资源,并且系统内存结构是一致内存访问(UMA)体系结构。双核SMP的典型硬件结构如图2-1。


        SMP模式的操作系统同时使用多个CPU,但从应用程序的角度来看,它们在逻辑上表现为一个单处理器。操作系统负责将任务分配到各个CPU上运行,从而极大地提高了整个系统的数据处理能力,并且所有CPU都可以平等地访问内存和外设资源,由操作系统负责将任务均匀地分配到所有可用CPU上运行。本文的实验平台所用的处理器MPC8641D[15]就是一种集成了两个E600[16]内核的典型SMP硬件结构,如图2-2所示。


    2.2  SMP与AMP体系

        目前支持多核处理器平台的实时操作系统体系结构有对称多处理SMP(Symmetric Multi-Processing)构架和非对称多处理AMP(Asymmetric Multi-Processing)构架两种。这两种操作系统的结构、代码和数据区的分配方面差别很大。SMP构架的系统中所有CPU共享系统内存和外设资源,由操作系统负责处理器间协作,并保持数据结构的一致性,而在AMP构架的系统中,用户需要对各个操作系统使用的硬件资源进行划分,CPU间的合作仅限于使用共享存储器的情况。由于CPU间的合作程度不同,AMP则称为松散耦合多CPU系统(图2-4所示),SMP系统称为紧耦合多CPU系统(图2-3所示)。


        图2-3所示的SMP模式操作系统负责协调两个处理器核之间的工作,两个处理器核共享主存中同一个操作系统实例。虽然在每个核中应用程序的地址相同,但是通过MMU把它们映射到主存中不同的位置,从而实现了两个应用程序间代码和数据空间的隔离。


        图2-4所示的是典型的AMP系统结构,每个CPU上运行一个操作系统实例,各个操作系统都有自己独占的资源(最基本的是独占各自专用的CPU),其它资源或者由两个系统共享、或者分配给各个系统专用。资源的分配由用户来决定,因而对用户是可见的。在商用实时操作系统当中只有WindRiver公司的VxWorks提供了AMP模式的支持,目前该模式的应用较少。

     

    2.3  Linux-SMP实现机制

     

        从Linux-2.0内核版本开始引入对SMP的支持开始,在全球Linux内核开发者的共同协作下,Linux对SMP的支持逐渐完善。特别是从Linux-2.4版本开始,对SMP的支持在内核态实现,真正实现了多CPU并行执行。目前Linux内核版本已经更新到linux-3.xx系列,但Linux-2.4和Linux-2.6系列版本对SMP的支持是革命性的,这两个版本的SMP系统发展历程,对实时操作系统的SMP研究具有极大的借鉴意义。因此我们着重从Linux-2.4内核以及linux-2.6内核对SMP支持的角度出发,对Linux支持SMP的关键技术进行分析。

     

    2.3.1  Linux-SMP初始化

     

        操作系统的初始化是操作系统获得CPU控制权的第一步,也是Linux支持SMP的关键部分。虽然在SMP系统中所有CPU的地位平等,但是由于在系统初始化的过程中只有一个“可执行上下文”,所以只能有一个CPU负责系统初始化。我们把该CPU称为引导CPU(Bootstrap CPU),也称之为主CPU,其它的CPU称为应用CPU,也称之为次CPU。支持SMP的Linux内核的初始化过程分三个步骤:

    • (1) Boot Loader首先对目标板系统做基本的初始化,搜集这个目标板的基本信息,比如内存大小、处理器主频、外设的使用情况等等。接着把这些信息传递给linux内核。最后Boot loader把linux内核复制到从0x0000 0000 开始的物理内存处(虚拟地址一般为0xc000 0000处)开始执行。
    • (2) Boot Loader将CPU的PC指针指向Linux-SMP内核映像的__start()入口处,由Linux-SMP内核映像来对目标板再次初始化(引导程序已经对系统做了一次初始化),这一部分分成两小步:

           第一小步:对CPU内部寄存器初始化,严格的说也分成两步:

    •  1)当MMU地址映射没有开启时,需要通过显式地进行链接地址到物理地址的转换来初始化处理器的Cache、TLB以及通用寄存器;
    •  2)开启临时的MMU地址映射,构造0号init_task任务的执行上下文,启动init_task任务继续完成初始化部分,init_task是Linux内核中唯一一个没有用kernel_thread() 函数创建的任务,在init_task任务执行后期,它会调用kernel_thread()函数创建第一个核心任务kernel_init(即我们常说的1号进程),而init_task任务则最终退化成主CPU上的idle任务。

           第二小步:1号进程kernel_init任务会接着完成应用程序的初始化,并启动系统中所有应用CPU。

    • (3) 1号kernel_init进程完成linux的各项配置(包括启动次CPU)后,会在/sbin,/etc,/bin目录中寻找init程序来运行。该init程序会替换kernel_init任务,此时内核态的1号kernel_init任务将会转换为用户空间内的1号init任务。最终init任务执行/bin/sh产生shell界面提供给用户与系统交互。

    整个初始化过程如图2-5所示。


    2.3.2  Linux-SMP任务调度

        Linux-2.4内核调度模块最主要的特征是内核不可抢占,即任务在内核态运行时,除非其主动放弃CPU,否则不能被任意挂起或者抢占。如果在内核态运行的任务出现死循环并且不主动让出CPU,那么系统就会失去响应。尽管这时候Linux内核仍在响应各种中断,其中包括时钟中断,却不会发生任务调度,其它任务没有机会得到运行。

        Linux内核中schedule()负责任务调度,它按照不同的调度策略对实时任务和非实时任务进行不同的调度,从中选择权值最大的任务投入运行。如果全局就绪队列中的所有任务时间片耗尽,schedule()会给所有任务重新分配时间片。

        Linux内核用task_struct结构体代表各个任务,将所有任务链接到以初始任务init_task为表头的全局双向链表中。Linux内核为每个CPU绑定一个idle任务,代表idle任务的task_struct类型指针放在init_tasks[NR_CPUS]数组中,schedule()通过idle_task(cpu)宏来访问这些idle任务。由于所有CPU共享一个全局就绪队列(如图2-6所示),因此需要一把全局锁机制来保证CPU互斥访问,这导致SMP系统中当某个CPU操作就绪队列时会阻止其它CPU同时操作这个队列,只有等到该CPU释放就绪队列锁后,其它CPU才能访问。这种全局调度队列机制显然会造成系统效率的下降,这也是采用全局队列不可避免的副作用,但是全局队列具有负载均衡的优势,所以我们在后面SMP扩展中所采用的就是全局就绪队列,后面会进一步的讨论。

     

    在单核处理器的操作系统中每个时刻只能有一个任务在运行,其它就绪任务都在等待CPU,操作系统通过中断机制(主要是时钟中断)进行频繁的任务切换,使得在一段时间间隔内所有任务都有机会运行,从而使得给用户宏观上的感觉好像每个任务都有一个CPU,所有任务在“并行运行”一样。但是这只是一种“并行运行”的幻象,微观上每个时刻只有一个任务在运行(毕竟只有一个CPU)。而在SMP系统中多个任务可以分配到不同的CPU上并行执行。只要各个CPU上运行的任务得到有效配合,SMP系统性能的提升是显而易见的。

    为了支持SMP,在Linux内核的task_struct结构体中增加了两个字段,has_cpu和processor,当has_cpu为1时表示任务正在编号为processor的CPU上运行。一个任务只有has_cpu为0才能被调度运行,宏can_schedule()来判断当前CPU是否可以接受调度,其代码如下:

    #define can_schedule(p,cpu) ((!(p)->has_cpu) && \

    ((p)->cpus_allowed & (1 << cpu)))

     

    相比与Linux-2.4内核,Linux-2.6内核做了以下改进,能够更好地发挥多核处理器的性能。

    1. Linux-2.6内核中各个CPU拥有私有就绪队列,也称为Per-CPU队列,使得同一时刻多个CPU可以同时访问各自私有的队列进行任务调度,由Linux内核负责监控CPU的负载情况进行负载平衡[17]。但由于负载平衡执行时间的不确定,在一定程度上影响了Linux内核的实时性和可预测性。这也是目前所有支持SMP模式的RTOS没有采用私有队列进行任务调度的根本原因。
    2. Linux-2.6内核采用了时间复杂度为0(1)的调度算法,提高了内核的实时性,同时新的调度算法在高负载的情况下性能也非常优秀[18],由于采用了调度类的设计思想,调度模块很容易扩展[19]
    3. Linux-2.6内核采用位图机制来辅助查找运行队列中优先级最高的任务(这也是所有RTOS调度模块采用的通用技术),Linux-2.6内核在5个32位存储字拼成的优先级位图队列中查找优先级最高的那一位,查找时间仅依赖于优先级的数量(目前仅使用前140位,来代表140个优先级),各个CPU上的调度队列如图2-7所示。

    2.3.3  Linux-SMP锁机制

    2.3.3.1  自旋锁

        Linux-SMP使用自旋锁来实现CPU间的同步与互斥,自旋锁(spinlock)采用忙等的锁机制,它不同于信号量等其它锁机制,CPU在获取自旋锁的过程中,会一直测试锁的状态直到其可用为止,自旋锁在锁不可用时是不会主动放弃CPU的,因此自旋锁只用于多核平台。Linux-2.4内核中其实现的代码如下:

    void arch_spin_lock(arch_spinlock_t *lock)

    {

    mb();//内存屏障

    while (1) {

    if (__arch_spin_trylock(lock) == 0)

    break;

    do {

     mb();//内存屏障

    } while(lock->slock != 0);

    }

    }

     

    void arch_spin_unlock(arch_spinlock_t *lock)

    {

      mb();//内存屏障

    lock->slock = 0;

    }

     #define spin_lock_irqsave(lock, flags) do{local_irq_save(flags);   \ arch_spin_lock(lock); }while(0)

    #define spin_unlock_irqrestore(lock, flags) do{arch_spin_unlock(lock);local_irq_restore(flags);}while(0)

        其中__arch_spin_trylock(lock)函数是一个原子操作,它测试lock是否被上锁,如果未上锁则返回原值0,然后把lock置为1;如果已上锁,则返回1。

        arch_spin_lock()/arch_spin_unlock自旋锁并没有关中断功能,只有中断处理程序从不访问该自旋锁保护的临界区时才会起作用。为了增加通用性,Linux-2.4内核在自旋锁的基础上使用中断锁(通过local_irq_save(flags)/ local_irq_restore(flags)实现),使得改进之后的自旋锁不但可以实现CPU间的互斥,还可以实现当前CPU上任务与中断ISR之间的互斥,需要注意的是关中断操作要放在申请自旋锁之前,开中断操作要放在释放自旋锁之后,否则会出现死锁。

     

    2.3.3.2  读/写锁

        Linux内核中引入读/写锁是为了增加内核的并行执行能力。如果没有内核控制路径(即Linux内核处理系统调用、异常或者中断所执行的指令序列)对数据结构进行修改,读/写锁会允许多个内核控制路径同时读取同一个数据结构。如果一个内核控制路径需要对该结构数据结构进行修改,那么它必须获得读/写锁中的写锁,写锁使得该控制路径独占这个资源。读写锁在Linux内核中应用广泛,但这种锁使用原子操作指令实现,因此需要原子地访问内存,获得锁的开销与访问内存的速度相关。

     

    2.3.3.3  互斥锁

        Linux-2.4内核采用读/写锁来实现对临界资源的保护,但由于读/写锁没有优先级继承功能,会发生优先级反转现象。比如有三个优先级依次降低的任务A、C、B,CPU 1上的B获取读/写锁来访问临界区,接着CPU 0上的A也发出了访问该临界区的请求,但是该临近区的读/写锁已经被B占用,A只能在CPU 0上自旋等待B释放读/写锁。这时优先级比B高的C就绪,C抢占B的CPU 1获得运行,此时CPU 1上执行比A优先级低的任务C,任务A只能在CPU 0上自旋等待更长的时间,直到B重新获得运行释放读/写锁为止。上述现象导致了C的优先级比A低却先完成,而优先级更高的A却得不到响应,降低了任务A的响应时间,如图2-8所示。


        为了解决使用读/写锁带来的优先级翻转的问题,MontaVista Linux提出了具有优先级继承功能的MUTEX锁,从根本上解决了优先级翻转问题。还是上面的例子,任务B获得MUTEX锁后正访问临界区,优先级较高的任务A想要访问该临界区时, MUTEX就会把任务B提升至与任务A相同的优先级,避免了因任务C抢占B而降低A的响应时间,并且在A被挂起的这段时间,CPU 0可以运行其它任务,提高了内核的运行效率,如图2-9所示。

     

    2.3.4 中断机制

        传统i386处理器通过使用8259A中断控制器来提供外部中断源与CPU之间连接。为了充分发挥SMP系统中多核并行处理特性,Intel从Pentium III开始引入APIC(Advanced Programmable Interrupt Controller)[20]高级可编程中断控制器,来替换8259A中断控制器。新近的主板为了支持老的操作系统都包含8259A和APIC两种芯片,Intel当前所有的处理器都含有一个本地LAPIC(Local APIC),LAPIC包含多个32位的寄存器、一个内部时钟、一个本地定时设备、以及为本地APIC中断保留的两条额外的IRQ线LINE0和LINE1。所有本地APIC都连接到一个外部I/O APIC上形成了一个中断分发网络[21],如图2-10所示。

       

     本地LAPIC位于CPU内部,而I/O APIC位于所有CPU外部,确切的说是位于南桥上,这样由一个I/O APIC和多个本地LAPIC组成了一个中断分发网络。I/O APIC根据其内部的PRT(Programmable Redirection Table)表格式化出一条中断消息,然后将其发送给某个CPU上的LAPIC,由LAPIC 通知CPU 进行处理。目前典型的I/O APIC具有24个中断管脚,每个管脚对应一个RTE(Redirection Table Entry)表项,连接在各个管脚上的设备是平等的。但这并不意味着APIC系统没有硬件优先级,APIC将优先级控制功能放到了LAPIC中。当I/O APIC某个管脚接收到中断信号后,会根据该管脚对应的RTE格式化出一条中断消息,将其发送给某个(或多个)CPU 上的LAPIC,收到来自I/O APIC的中断消息后,LAPIC 会将该中断交由CPU 处理[21]

     

    2.4  本章小结

        本章介绍了嵌入式SMP硬件平台的背景和关键技术,以及支持SMP模式的RTOS研究现状,为单核RTOS的SMP扩展提供借鉴。 

     

    待续。。。。。。

     

    展开全文
  • 调度程序所要做的 就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾 时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一 个进程是需要定时间的,包括保存和装入寄存器值及内存...
  • 1.4 操作系统对多核处理器的支持方法 1.4.1 调度与中断 随着多核处理器的发展,对软件开发有非常大的影响,而且核心的瓶颈在软件上。软件开发在多核环境下的核心是多线程开发。这个多线程不仅代表了软件实现上多...
  • 操作系统通过将cpu的执行时间分割成多个时间片,为每个任务分配时间片,因为cpu处理速度很快,这样就用户看来好像每个任务都在同时执行,感觉有多个cpu,但本质上一个时间点只有一个任务在运行。随着多核多线程的...
  • 一种这样的抽象模型,即基于actor的编程(请参阅参考资料 ),将整个应用程序划分为多个,以便可以将基础核心分配给这些并以有效的方式并行执行。 免责声明 本文中的所有观点仅是我的,不一定是我雇主的观点。 ...
  • 1. 单核和多核CPU上多线程执行效率的探讨a1: 多线程实际上是在单个CPU中按顺序执行的,但是系统可以帮助您切换该执行,但是它... 系统将时间片分配给每个线程以执行. 每个时间片约为10毫秒. 它似乎同时运行,但实...
  • TMS320C6678多核DSP的核间通信方法

    万次阅读 多人点赞 2017-08-22 15:46:20
    对Key-Stone架构TMS320C6678 处理器的多核间通信机制进行研究,利用处理器间中断和核间通信寄存器,设计并实现了多核之间的通信。从系统的角度出发,设计与仿真了两种多核通信拓扑结构,并分析对比了性能。对设计...
  • 操作系统通过将cpu的执行时间分割成多个时间片,为每个任务分配时间片,因为cpu处理速度很快,这样就用户看来好像每个任务都在同时执行,感觉有多个cpu,但本质上一个时间点只有一个任务在运行。随着多核多线程的...
  • 多线程技术和多核技术

    千次阅读 2020-08-10 11:07:38
    多核与多线程都是提升处理器处理性能的重要手段,如今多核处理器随处可见,多线程处理器似乎鲜有提及,其实多线程并不是一个新鲜的概念,在很多地方也有广泛的应用。到底多核处理器与多线程处理两者之间有何差异?各...
  • 其实EPYC也一样可以强于至强,只是至强可以四路甚至八路,而EPYC应该只能两路(被内用掉了,因为是多芯片封装的)。现在的3900X本身强于9900K,即使是3700X,在部分多核运算中也是强于9900K的。HEDT中的2990WX在多核...
  • AURIX TC397 Multicore 多核

    千次阅读 2020-11-06 18:08:30
    目录TC397 Multicore基础知识上系统互联和桥多核操作遐想新工程Multicore Example微信公众号 TC397 Multicore基础知识 参考 Multicore_1 for KIT_AURIX_TC397_TFT: AURIX™ TC3xx微控制器架构具有多达6个独立的...
  • 采用多核处理器芯片构成计算机系统一个内多处理器的并行计算机系统。种并行计算机系统过去只出现超级计算中心和集群计算机系统中。现在,随着处理器芯片进入多核时代,使得千家万户的计算机系统都成为多处理器的...
  • Linux内核争抢式并发在SMP多核扩展上的不足

    千次阅读 多人点赞 2019-08-23 00:04:42
    本文来自:《被神话的Linux, 一文带你看清Linux在多核可扩展性设计上的不足》 我们先来看一段来自猛士王垠的话: 跟有些人聊操作系统是件闹心的事,因为我往往会抛弃一些术语和概念,从零开始讨论。我试图从“计算...
  • 程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。 线程的概念: 线程是程序执行时的最小单位,它是进程的一个...
  • 嵌入式领域的处理器已向多核架构迅速发展,TI公司的KeyStone架构的TMS320C6678是目前市面上性能最高的多核DSP处理器。TMS320C6678集成8核C66x DSP处理器,每个内核频率高达1.25 GHz,提供每秒高达40GMAC定点运算和...
  • 单核CPU vs. 多核CPU

    千次阅读 2019-09-16 12:06:37
    多核CPU 单核CPU 起源: CPU的起源是1971年英特尔公司推出,由此正式宣告计算机的诞生,可以说是世界级的改变,并且严格准守冯诺依曼体系结构,在英特尔推出CPU之前的计算机所使用的处理器工作是非常麻烦的,...
  •  以上就是时间片轮询法的代码思路,理论上来讲,时间片轮询法忽略了任务的执行时间以及定时器中断函数的执行时间,只要不在任务代码中太长的延时、执行时间长的任务,那么任务的执行频率不会和理想化的相差太远。...
  • 以及记录自己在做这个实验中遇到的一些问题以及心得背景引入:CPU制造商为了追求CPU效率放弃了在CPU频率上的追求(CPU频率即CPU单位时间内可以完成任务的多少),反而开始把方向转向了多核CPU上。那么如何在多核CPU上...
  •  1 引言  如今,随着集成电路工艺发展到深亚微米的阶段,处理器体系结构的设计研究正朝着多 核的方向发展。...多个内核上同时执行的各个程序之间可能需要进行数据 共享与同步,因此多核处理器的硬
  • 1.多线程在单核和多核CPU上的执行效率问题的讨论 a1: 多线程在单cpu中其实也是顺序执行的,不过系统可以帮你切换那个执行而已,其实并没有快(反而慢)多个cpu的话就可以在两个cpu中同时执行了.............. a2:...
  • 多核 多线程 进程的概念

    千次阅读 2015-05-09 20:13:07
    我们在买电脑的时候经常遇到一些概念,我这电脑是多核多线程的,什么双核的,什么四核、八核的,这种运动速度电脑快!那么这样的电脑为什么运行速度快?当然,运行速度快有很多原因,比如主频、缓存什么的。这里我们...
  • 文章目录1 cpu架构和工作原理2 多核cpu和多cpu架构3 进程和线程4 多核、多CPU与多线程、多进程的对应关系5 总结 1 cpu架构和工作原理 计算机有5大基本组成部分,运算器,控制器,存储器,输入和输出。运算器和控制器...
  • NodeJS是基于chrome浏览器的V8引擎构建的,也就说明它的模型与浏览器是类似的。我们的javascript会运行在单个进程...但是V8引擎的单进程单线程并不是完美的结构,现如今CPU基本上都是多核的。真正的服务器往往有好几...
  • 浅谈多核CPU、多线程与并行计算 xiaofei08592017-05-09 17:07:113646收藏 展开 0.前言 笔者用过MPI和C#线程池,参加过比赛,有所感受,将近一年来,对多线程编程兴趣一直不减,一直有所关注,决定写篇文章,...
  • 浅谈多核CPU、多线程、多进程

    千次阅读 2020-05-12 22:08:51
    笔者斗胆预测,CPU各个核心之间的内总线将会采用4路组相连:),因为全相连太过复杂,单总线又不够给力。而且应该是非对称多核处理器,可能其中会混杂几个DSP处理器或流处理器。 2.多线程与并行计算的区别 (1)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,591
精华内容 7,036
关键字:

多核时间片同步