精华内容
下载资源
问答
  • 内核同步原语

    2017-08-25 22:08:30
    最好的内核同步手段是不需要同步。 对于SMP系统,如果数据结构可以设计为每cpu变量,将最有效的避免cpu间的同步。 原子锁要求一些寄存器读写动作采用automic_t型变量,并在单条指令内完成,并对该指令加automic锁,...

    最好的内核同步手段是不需要同步。

    对于SMP系统,如果数据结构可以设计为每cpu变量,将最有效的避免cpu间的同步。

    原子锁要求一些寄存器读写动作采用automic_t型变量,并在单条指令内完成,并对该指令加automic锁,提供了对单一寄存器的访问保护。

    自旋锁用在保护SMP下各cpu间的同步,等待锁的进程会空转直到获取到锁,对系统效率会造成一定的影响。

    读写锁可以保护一段临界区的数据,其原理是允许并发的读,但是只能有一个进程在写,并且写期间不允许其它进程读。

    顺序锁是对读写锁的改进,允许并发的读和写,不过为了确保读的有效性,读的操作完成后,读进程要再次确认数据是否发生更新,如果有更新,则不得不丢弃改数据重读。

    RCU机制允许并发的读写操作而不使用锁,其原理对内存的访问采用指针,改写数据就是改写指针指向的地址,在读进程访问旧数据时,写进程建立一份新数据的内存并将指针指向改内存,在确信读进程已经读完的情况下,释放掉旧数据,后面的读访问都将引用新内存。

    信号量类似于自旋锁,区别在于等待的进程会挂起而不是空转。信号量的等待队列严格遵从FIFO调度。

    通过禁止本地中断和禁止可延迟函数,可以有效保护包含这类内核控制路径的数据结构。这类禁止加上自旋锁便可以同时保护好多cpu内核控制路径访问的数据结构。


    内核如果使用了锁原语来保护数据,就应该同时禁止内核抢占。

    在访问每cpu变量时,也需要禁止内核抢占。


    展开全文
  • 避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。 ——题记 内核源码:Linux-2.6.38.8.tar.bz2 目标平台:ARM体系结构 原子操作确保对同一数据的“读取-修改-写入”操作在...

    避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。 

    ——题记

        内核源码:Linux-2.6.38.8.tar.bz2

        目标平台:ARM体系结构

     

        原子操作确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,要么全部执行完成,要么根本不会执行。例如在ARM上对全局变量的++运算至少要经历以下三步: 

    1. ldr r3, [r3, #0]  
    2. add r2, r3, #1  
    3. str r2, [r3, #0]  
    	ldr	r3, [r3, #0]
    	add	r2, r3, #1
    	str	r2, [r3, #0]

        这就给并发访问制造了可能,所以在编写内核代码时需要给有可能被并发访问的变量加上原子操作。

        Linux内核提供了两组原子操作接口,一组用于整数,一组用于位操作。

        1、原子整数操作

        针对整数的原子操作只能对atomic_t类型的数据进行处理。atomic_t定义如下: 

    1. /* linux-2.6.38.8/include/linux/types.h */  
    2. typedef struct {  
    3.     int counter;  
    4. } atomic_t;  
    /* linux-2.6.38.8/include/linux/types.h */
    typedef struct {
    	int counter;
    } atomic_t;
    

        下面将详细分析所有原子整数操作函数在ARMv6之前各种CPU上的实现(定义在linux-2.6.38.8/arch/arm/include/asm/atomic.h文件中)。 

    1. #define ATOMIC_INIT(i)  { (i) }  
    #define ATOMIC_INIT(i)	{ (i) }

        ATOMIC_INIT用于把atomic_t变量初始化为i,如下例把a初始化为2: 

    1. atomic_t a = ATOMIC_INIT(2);  
    atomic_t a = ATOMIC_INIT(2);

     

    1. #define atomic_read(v)  (*(volatile int *)&(v)->counter)  
    2. #define atomic_set(v,i) (((v)->counter) = (i))  
    #define atomic_read(v)	(*(volatile int *)&(v)->counter)
    #define atomic_set(v,i)	(((v)->counter) = (i))

        atomic_read(v)用于读取atomic_t变量*v的值,而atomic_set(v,i)用于把atomic_t变量*v设置为i。 

    1. static inline int atomic_add_return(int i, atomic_t *v)  
    2. {  
    3.     unsigned long flags;  
    4.     int val;  
    5.   
    6.     raw_local_irq_save(flags);  
    7.     val = v->counter;  
    8.     v->counter = val += i;  
    9.     raw_local_irq_restore(flags);  
    10.   
    11.     return val;  
    12. }  
    13. #define atomic_add(i, v)    (void) atomic_add_return(i, v)  
    static inline int atomic_add_return(int i, atomic_t *v)
    {
    	unsigned long flags;
    	int val;
    
    	raw_local_irq_save(flags);
    	val = v->counter;
    	v->counter = val += i;
    	raw_local_irq_restore(flags);
    
    	return val;
    }
    #define atomic_add(i, v)	(void) atomic_add_return(i, v)

        atomic_add(i, v)用于把atomic_t变量*v加i。这里的原子性实现只是简单地通过raw_local_irq_save函数来禁止中断。 

    1. /* linux-2.6.38.8/include/linux/irqflags.h */  
    2. #define raw_local_irq_save(flags)           \  
    3.     do {                        \  
    4.         typecheck(unsigned long, flags);    \  
    5.         flags = arch_local_irq_save();      \  
    6.     } while (0)  
    7. /* linux-2.6.38.8/arch/arm/include/asm/irqflags.h */  
    8. static inline unsigned long arch_local_irq_save(void)  
    9. {  
    10.     unsigned long flags, temp;  
    11.   
    12.     asm volatile(  
    13.         "   mrs %0, cpsr    @ arch_local_irq_save\n"  
    14.         "   orr %1, %0, #128\n"  
    15.         "   msr cpsr_c, %1"  
    16.         : "=r" (flags), "=r" (temp)  
    17.         :  
    18.         : "memory", "cc");  
    19.     return flags;  
    20. }  
    /* linux-2.6.38.8/include/linux/irqflags.h */
    #define raw_local_irq_save(flags)			\
    	do {						\
    		typecheck(unsigned long, flags);	\
    		flags = arch_local_irq_save();		\
    	} while (0)
    /* linux-2.6.38.8/arch/arm/include/asm/irqflags.h */
    static inline unsigned long arch_local_irq_save(void)
    {
    	unsigned long flags, temp;
    
    	asm volatile(
    		"	mrs	%0, cpsr	@ arch_local_irq_save\n"
    		"	orr	%1, %0, #128\n"
    		"	msr	cpsr_c, %1"
    		: "=r" (flags), "=r" (temp)
    		:
    		: "memory", "cc");
    	return flags;
    }

        typecheck函数用来确保参数flags的数据类型为unsigned long,arch_local_irq_save函数通过内嵌汇编设置当前程序状态寄存器的I位为1,从而禁止IRQ。待加操作完成后,再通过raw_local_irq_restore函数使能中断。 

    1. static inline int atomic_sub_return(int i, atomic_t *v)  
    2. {  
    3.     unsigned long flags;  
    4.     int val;  
    5.   
    6.     raw_local_irq_save(flags);  
    7.     val = v->counter;  
    8.     v->counter = val -= i;  
    9.     raw_local_irq_restore(flags);  
    10.   
    11.     return val;  
    12. }  
    13. #define atomic_sub(i, v)    (void) atomic_sub_return(i, v)  
    static inline int atomic_sub_return(int i, atomic_t *v)
    {
    	unsigned long flags;
    	int val;
    
    	raw_local_irq_save(flags);
    	val = v->counter;
    	v->counter = val -= i;
    	raw_local_irq_restore(flags);
    
    	return val;
    }
    #define atomic_sub(i, v)	(void) atomic_sub_return(i, v)

        atomic_sub(i, v)用于把atomic_t变量*v减i。 

    1. #define atomic_inc(v)       atomic_add(1, v)  
    2. #define atomic_dec(v)       atomic_sub(1, v)  
    3.   
    4. #define atomic_inc_and_test(v)  (atomic_add_return(1, v) == 0)  
    5. #define atomic_dec_and_test(v)  (atomic_sub_return(1, v) == 0)  
    6. #define atomic_inc_return(v)    (atomic_add_return(1, v))  
    7. #define atomic_dec_return(v)    (atomic_sub_return(1, v))  
    8. #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)  
    9.   
    10. #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)  
    #define atomic_inc(v)		atomic_add(1, v)
    #define atomic_dec(v)		atomic_sub(1, v)
    
    #define atomic_inc_and_test(v)	(atomic_add_return(1, v) == 0)
    #define atomic_dec_and_test(v)	(atomic_sub_return(1, v) == 0)
    #define atomic_inc_return(v)    (atomic_add_return(1, v))
    #define atomic_dec_return(v)    (atomic_sub_return(1, v))
    #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)
    
    #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)

        这些函数只是上面所讲函数的封装。 

    1. static inline int atomic_cmpxchg(atomic_t *v, int old, int new)  
    2. {  
    3.     int ret;  
    4.     unsigned long flags;  
    5.   
    6.     raw_local_irq_save(flags);  
    7.     ret = v->counter;  
    8.     if (likely(ret == old))  
    9.         v->counter = new;  
    10.     raw_local_irq_restore(flags);  
    11.   
    12.     return ret;  
    13. }  
    14. static inline int atomic_add_unless(atomic_t *v, int a, int u)  
    15. {  
    16.     int c, old;  
    17.   
    18.     c = atomic_read(v);  
    19.     while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)  
    20.         c = old;  
    21.     return c != u;  
    22. }  
    23. #define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)  
    static inline int atomic_cmpxchg(atomic_t *v, int old, int new)
    {
    	int ret;
    	unsigned long flags;
    
    	raw_local_irq_save(flags);
    	ret = v->counter;
    	if (likely(ret == old))
    		v->counter = new;
    	raw_local_irq_restore(flags);
    
    	return ret;
    }
    static inline int atomic_add_unless(atomic_t *v, int a, int u)
    {
    	int c, old;
    
    	c = atomic_read(v);
    	while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)
    		c = old;
    	return c != u;
    }
    #define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)

        atomic_inc_not_zero(v)用于将atomic_t变量*v加1,并测试加1后的*v是否不为零,如果不为零则返回真。

        注意,这里所讲函数的参数v都是atomic_t类型数据的地址。

        2、原子位操作

        在CONFIG_SMP和__ARMEB__都未定义的情况下,原子位操作相关函数的定义如下: 

    1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */  
    2. #define ATOMIC_BITOP_LE(name,nr,p)      \  
    3.     (__builtin_constant_p(nr) ?     \  
    4.      ____atomic_##name(nr, p) :     \  
    5.      _##name##_le(nr,p))  
    6.   
    7. #define set_bit(nr,p)           ATOMIC_BITOP_LE(set_bit,nr,p)  
    8. #define clear_bit(nr,p)         ATOMIC_BITOP_LE(clear_bit,nr,p)  
    9. #define change_bit(nr,p)        ATOMIC_BITOP_LE(change_bit,nr,p)  
    10. #define test_and_set_bit(nr,p)      ATOMIC_BITOP_LE(test_and_set_bit,nr,p)  
    11. #define test_and_clear_bit(nr,p)    ATOMIC_BITOP_LE(test_and_clear_bit,nr,p)  
    12. #define test_and_change_bit(nr,p)   ATOMIC_BITOP_LE(test_and_change_bit,nr,p)  
    /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */
    #define	ATOMIC_BITOP_LE(name,nr,p)		\
    	(__builtin_constant_p(nr) ?		\
    	 ____atomic_##name(nr, p) :		\
    	 _##name##_le(nr,p))
    
    #define set_bit(nr,p)			ATOMIC_BITOP_LE(set_bit,nr,p)
    #define clear_bit(nr,p)			ATOMIC_BITOP_LE(clear_bit,nr,p)
    #define change_bit(nr,p)		ATOMIC_BITOP_LE(change_bit,nr,p)
    #define test_and_set_bit(nr,p)		ATOMIC_BITOP_LE(test_and_set_bit,nr,p)
    #define test_and_clear_bit(nr,p)	ATOMIC_BITOP_LE(test_and_clear_bit,nr,p)
    #define test_and_change_bit(nr,p)	ATOMIC_BITOP_LE(test_and_change_bit,nr,p)

        相对应的非原子位操作函数定义在linux-2.6.38.8/include/asm-generic/bitops/non-atomic.h文件中,它们的名字前都多带有两个下划线,如set_bit的非原子操作函数为__set_bit。

         原子位操作函数根据参数nr是否为常量分为两种实现方式:

        (1)、nr为常量时 

    1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */  
    2. static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)  
    3. {  
    4.     unsigned long flags;  
    5.     unsigned long mask = 1UL << (bit & 31);  
    6.   
    7.     p += bit >> 5;  
    8.   
    9.     raw_local_irq_save(flags);  
    10.     *p |= mask;  
    11.     raw_local_irq_restore(flags);  
    12. }  
    13. static inline void ____atomic_clear_bit(unsigned int bit, volatile unsigned long *p)  
    14. {  
    15.     unsigned long flags;  
    16.     unsigned long mask = 1UL << (bit & 31);  
    17.   
    18.     p += bit >> 5;  
    19.   
    20.     raw_local_irq_save(flags);  
    21.     *p &= ~mask;  
    22.     raw_local_irq_restore(flags);  
    23. }  
    24. static inline void ____atomic_change_bit(unsigned int bit, volatile unsigned long *p)  
    25. {  
    26.     unsigned long flags;  
    27.     unsigned long mask = 1UL << (bit & 31);  
    28.   
    29.     p += bit >> 5;  
    30.   
    31.     raw_local_irq_save(flags);  
    32.     *p ^= mask;  
    33.     raw_local_irq_restore(flags);  
    34. }  
    /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */
    static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
    {
    	unsigned long flags;
    	unsigned long mask = 1UL << (bit & 31);
    
    	p += bit >> 5;
    
    	raw_local_irq_save(flags);
    	*p |= mask;
    	raw_local_irq_restore(flags);
    }
    static inline void ____atomic_clear_bit(unsigned int bit, volatile unsigned long *p)
    {
    	unsigned long flags;
    	unsigned long mask = 1UL << (bit & 31);
    
    	p += bit >> 5;
    
    	raw_local_irq_save(flags);
    	*p &= ~mask;
    	raw_local_irq_restore(flags);
    }
    static inline void ____atomic_change_bit(unsigned int bit, volatile unsigned long *p)
    {
    	unsigned long flags;
    	unsigned long mask = 1UL << (bit & 31);
    
    	p += bit >> 5;
    
    	raw_local_irq_save(flags);
    	*p ^= mask;
    	raw_local_irq_restore(flags);
    }

        在32位机上,参数bit的理想取值为0到31,例如取值为1,就表示对*p的bit[1]进行操作。

        当bit取值大于31时,函数操作的就不是你想要操作的那个变量*p了(通过p += bit>> 5;语句实现),所以在实际的应用中应该要确保bit的取值在合理的范围内。

        *p |= mask;语句实现bit位的置位。

        *p &= ~mask;语句实现bit位的清零。

        *p ^= mask;语句实现bit位的翻转,即*p的bit位为0时被置位,为1时则被清零。 

    1. static inline int  
    2. ____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)  
    3. {  
    4.     unsigned long flags;  
    5.     unsigned int res;  
    6.     unsigned long mask = 1UL << (bit & 31);  
    7.   
    8.     p += bit >> 5;  
    9.   
    10.     raw_local_irq_save(flags);  
    11.     res = *p;  
    12.     *p = res | mask;  
    13.     raw_local_irq_restore(flags);  
    14.   
    15.     return (res & mask) != 0;  
    16. }  
    17. static inline int  
    18. ____atomic_test_and_clear_bit(unsigned int bit, volatile unsigned long *p)  
    19. {  
    20.     unsigned long flags;  
    21.     unsigned int res;  
    22.     unsigned long mask = 1UL << (bit & 31);  
    23.   
    24.     p += bit >> 5;  
    25.   
    26.     raw_local_irq_save(flags);  
    27.     res = *p;  
    28.     *p = res & ~mask;  
    29.     raw_local_irq_restore(flags);  
    30.   
    31.     return (res & mask) != 0;  
    32. }  
    33. static inline int  
    34. ____atomic_test_and_change_bit(unsigned int bit, volatile unsigned long *p)  
    35. {  
    36.     unsigned long flags;  
    37.     unsigned int res;  
    38.     unsigned long mask = 1UL << (bit & 31);  
    39.   
    40.     p += bit >> 5;  
    41.   
    42.     raw_local_irq_save(flags);  
    43.     res = *p;  
    44.     *p = res ^ mask;  
    45.     raw_local_irq_restore(flags);  
    46.   
    47.     return (res & mask) != 0;  
    48. }  
    static inline int
    ____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)
    {
    	unsigned long flags;
    	unsigned int res;
    	unsigned long mask = 1UL << (bit & 31);
    
    	p += bit >> 5;
    
    	raw_local_irq_save(flags);
    	res = *p;
    	*p = res | mask;
    	raw_local_irq_restore(flags);
    
    	return (res & mask) != 0;
    }
    static inline int
    ____atomic_test_and_clear_bit(unsigned int bit, volatile unsigned long *p)
    {
    	unsigned long flags;
    	unsigned int res;
    	unsigned long mask = 1UL << (bit & 31);
    
    	p += bit >> 5;
    
    	raw_local_irq_save(flags);
    	res = *p;
    	*p = res & ~mask;
    	raw_local_irq_restore(flags);
    
    	return (res & mask) != 0;
    }
    static inline int
    ____atomic_test_and_change_bit(unsigned int bit, volatile unsigned long *p)
    {
    	unsigned long flags;
    	unsigned int res;
    	unsigned long mask = 1UL << (bit & 31);
    
    	p += bit >> 5;
    
    	raw_local_irq_save(flags);
    	res = *p;
    	*p = res ^ mask;
    	raw_local_irq_restore(flags);
    
    	return (res & mask) != 0;
    }

        这三个函数增加了return (res & mask) != 0;语句,用来判断*p的bit位原值是否为1,如果为1则函数返回1,否则返回0。

        (2)、nr不为常量时

        当nr不为常量时,原子位操作函数的定义如下: 

    1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */  
    2. extern void _set_bit_le(int nr, volatile unsigned long * p);  
    3. extern void _clear_bit_le(int nr, volatile unsigned long * p);  
    4. extern void _change_bit_le(int nr, volatile unsigned long * p);  
    5. extern int _test_and_set_bit_le(int nr, volatile unsigned long * p);  
    6. extern int _test_and_clear_bit_le(int nr, volatile unsigned long * p);  
    7. extern int _test_and_change_bit_le(int nr, volatile unsigned long * p);  
    /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */
    extern void _set_bit_le(int nr, volatile unsigned long * p);
    extern void _clear_bit_le(int nr, volatile unsigned long * p);
    extern void _change_bit_le(int nr, volatile unsigned long * p);
    extern int _test_and_set_bit_le(int nr, volatile unsigned long * p);
    extern int _test_and_clear_bit_le(int nr, volatile unsigned long * p);
    extern int _test_and_change_bit_le(int nr, volatile unsigned long * p);

        它们都是通过汇编语言来实现的,定义如下: 

    1. /* linux-2.6.38.8/arch/arm/lib/setbit.S */  
    2. ENTRY(_set_bit_le)  
    3.     bitop   orr  
    4. ENDPROC(_set_bit_le)  
    5.   
    6. /* linux-2.6.38.8/arch/arm/lib/clearbit.S */  
    7. ENTRY(_clear_bit_le)  
    8.     bitop   bic  
    9. ENDPROC(_clear_bit_le)  
    10.   
    11. /* linux-2.6.38.8/arch/arm/lib/changebit.S */  
    12. ENTRY(_change_bit_le)  
    13.     bitop   eor  
    14. ENDPROC(_change_bit_le)  
    15.   
    16. /* linux-2.6.38.8/arch/arm/lib/testsetbit.S */  
    17. ENTRY(_test_and_set_bit_le)  
    18.     testop  orreq, streqb  
    19. ENDPROC(_test_and_set_bit_le)  
    20.   
    21. /* linux-2.6.38.8/arch/arm/lib/testclearbit.S */  
    22. ENTRY(_test_and_clear_bit_le)  
    23.     testop  bicne, strneb  
    24. ENDPROC(_test_and_clear_bit_le)  
    25.   
    26. /* linux-2.6.38.8/arch/arm/lib/testchangebit.S */  
    27. ENTRY(_test_and_change_bit_le)  
    28.     testop  eor, strb  
    29. ENDPROC(_test_and_change_bit_le)  
    /* linux-2.6.38.8/arch/arm/lib/setbit.S */
    ENTRY(_set_bit_le)
    	bitop	orr
    ENDPROC(_set_bit_le)
    
    /* linux-2.6.38.8/arch/arm/lib/clearbit.S */
    ENTRY(_clear_bit_le)
    	bitop	bic
    ENDPROC(_clear_bit_le)
    
    /* linux-2.6.38.8/arch/arm/lib/changebit.S */
    ENTRY(_change_bit_le)
    	bitop	eor
    ENDPROC(_change_bit_le)
    
    /* linux-2.6.38.8/arch/arm/lib/testsetbit.S */
    ENTRY(_test_and_set_bit_le)
    	testop	orreq, streqb
    ENDPROC(_test_and_set_bit_le)
    
    /* linux-2.6.38.8/arch/arm/lib/testclearbit.S */
    ENTRY(_test_and_clear_bit_le)
    	testop	bicne, strneb
    ENDPROC(_test_and_clear_bit_le)
    
    /* linux-2.6.38.8/arch/arm/lib/testchangebit.S */
    ENTRY(_test_and_change_bit_le)
    	testop	eor, strb
    ENDPROC(_test_and_change_bit_le)

        使用ENTRY和ENDPROC两个宏来定义一个名为name的函数,如下所示: 

    1. /* linux-2.6.38.8/include/linux/linkage.h */  
    2. #define ENTRY(name) \  
    3.   .globl name; \  
    4.   ALIGN; \  
    5.   name:  
    6.   
    7. #define ALIGN __ALIGN  
    8.   
    9. #define END(name) \  
    10.   .size name, .-name  
    11.   
    12. /* linux-2.6.38.8/arch/arm/include/asm/linkage.h */  
    13. #define __ALIGN .align 0  
    14.   
    15. #define ENDPROC(name) \  
    16.   .type name, %function; \  
    17.   END(name)  
    /* linux-2.6.38.8/include/linux/linkage.h */
    #define ENTRY(name) \
      .globl name; \
      ALIGN; \
      name:
    
    #define ALIGN __ALIGN
    
    #define END(name) \
      .size name, .-name
    
    /* linux-2.6.38.8/arch/arm/include/asm/linkage.h */
    #define __ALIGN .align 0
    
    #define ENDPROC(name) \
      .type name, %function; \
      END(name)

        而汇编代码实现的宏bitop和testop被相应函数所调用,并传递给它们相应的参数,代码如下所示: 

    1. /* linux-2.6.38.8/arch/arm/lib/bitops.h */  
    2.     .macro  bitop, instr  
    3.     and r2, r0, #7  
    4.     mov r3, #1  
    5.     mov r3, r3, lsl r2  
    6.     save_and_disable_irqs ip  
    7.     ldrb    r2, [r1, r0, lsr #3]  
    8.     \instr  r2, r2, r3  
    9.     strb    r2, [r1, r0, lsr #3]  
    10.     restore_irqs ip  
    11.     mov pc, lr  
    12.     .endm  
    13.   
    14.     .macro  testop, instr, store  
    15.     add r1, r1, r0, lsr #3  
    16.     and r3, r0, #7  
    17.     mov r0, #1  
    18.     save_and_disable_irqs ip  
    19.     ldrb    r2, [r1]  
    20.     tst r2, r0, lsl r3  
    21.     \instr  r2, r2, r0, lsl r3  
    22.     \store  r2, [r1]  
    23.     moveq   r0, #0  
    24.     restore_irqs ip  
    25.     mov pc, lr  
    26.     .endm  

    转载于:https://www.cnblogs.com/zxc2man/p/6649747.html

    展开全文
  • 一、原子操作的命名规则 Linux内核中提供了各种各样的原子操作函数。除了最原始的读取和设置之外,还包括各种运算,以及位操作等等。而且有的原子操作还要返回操作过后变量的值,有的要返回操作之前变量的值,如果再...

    一、原子操作的命名规则

    Linux内核中提供了各种各样的原子操作函数。除了最原始的读取和设置之外,还包括各种运算,以及位操作等等。而且有的原子操作还要返回操作过后变量的值,有的要返回操作之前变量的值,如果再牵涉到内存屏障的问题,将这些因素组合起来,有非常多的原子操作函数。这些原子操作函数看似非常杂乱,其实函数命名是有规律的。

    1)基本型

    基本型包括非RMW(Read Modifiy Write)的读写操作,以及RMW的算术和位操作等。

    非RMW的操作很简单,只有两个,即用来读取的atomic_read()和用来写入的atomic_set()。一般对单独变量的读取或写入操作本身都是原子的,如果代码中只对单个变量进行读写操作,而从来没对它使用RMW操作,那一般说明你用错了,没必要使用内核提供的原子操作。

    RMW操作就很多了,包括下面几种:

    1. 算术操作:包括加、减、递增和递减,命名形式是atomic_{add,sub,inc,dec}();
    2. Bit位操作:包括与、或、异或和与非,命名形式是atomic_{and,or,xor,andnot}();
    3. 交换操作:包括交换(atomic_xchg())、比较交换(atomic_cmpxchg())和添加了返回成功与否的比较交换(atomic_try_cmpxchg())。

    除了atomic_t类型外,Linux内核还支持对64位和长整形的atomic64_t和atomic_long_t类型,它们也都有对应的基本原子操作函数,只不过函数前缀名分别是atomic64_和atomic_long_,具体就不在赘述了。

    2)要返回修改过后的值

    如果一个原子操作函数要返回修改过后的原子变量的值,那么该函数名会含有“_return”字符串,并且是在表示具体操作字符串的后面。例如,atomic_add_return、atomic_dec_return_relaxed等。

    3)要返回修改之前的值

    如果一个原子操作函数要返回修改之前的原子变量的值,那么该函数名会含有“_fetch”字符串,并且是在表示具体操作字符串的前面。例如,atomic_fetch_and、atomic_fetch_or_acquire等。

    4)有Acquire和Release单向屏障语义

     如果一个原子操作函数还需要包括Acquire或者Release单向屏障语义,那么该函数名会有“_acquire”或者“_release”后缀。例如,atomic_xchg_acquire、atomic_cmpxchg_release等。

    相反的,如果一个原子操作函数名有“_relaxed”后缀,表示这个函数没有被任何内存屏障保护,可以被任意重排序。例如,atomic_add_return_relaxed、atomic_xchg_relaxed等。

    只有当一个原子操作函数要返回值的时候才有可能添加_acquire、_release和_relaxed后缀。

    二、原子操作的重排序规则

    Linux内核的原子操作只保证对单一变量的某个操作是原子的,多个CPU同时操作时,不会出现中间的错误状态。但是,对这个原子操作本身,并不一定保证其执行的顺序,在SMP系统下,有可能会出现重排序的问题。因此,前面也提到过,有些原子操作函数自己就带了一定的内存屏障的语义。具体有没有带,带了多少,可以通过函数名看出来,具体规则如下:

    • 非RMW的原子操作可以被任意重排序;
    • RMW的原子操作,如果没有返回值可以被任意重排序;
    • RMW的原子操作,如果有返回值,并且没有_relaxed、_acquire和_release后缀,是有内存屏障保护的,可以保证不被重排序;
    • RMW的原子操作,如果有返回值,且有_relaxed后缀,没有任何内存屏障保护,可以被任意重排序;
    • RMW的原子操作,如果有返回值,且有_acquire后缀,表示读(RMW中的R)操作是有Acquire单向屏障保护的;
    • RMW的原子操作,如果有返回值,且有_release后缀,表示写(RMW中的W)操作是有Release单向屏障保护的;
    • RMW的原子操作,如果有条件判断,那么条件是否的那部分会被任意重排序。

    对于那些不提供内存屏障语义的原子操作来说,为了保证其本身不被重排序,还需要显式的在其前面或后面使用内存屏障。Linux内核提供了两个函数,分别是smp_mb__before_atomic()用于原子操作函数的前面,和smp_mb__after_atomic()用于原子操作函数的后面。注意,函数的中间是“__”而不是“_”,而且说起来是两个不同的函数,但是内核实际上都把它们映射成了普通的通用内存屏障:

    #ifndef __smp_mb__before_atomic
    #define __smp_mb__before_atomic()	__smp_mb()
    #endif
    
    #ifndef __smp_mb__after_atomic
    #define __smp_mb__after_atomic()	__smp_mb()
    #endif
    
    ......
    
    #ifndef smp_mb__before_atomic
    #define smp_mb__before_atomic()	__smp_mb__before_atomic()
    #endif
    
    #ifndef smp_mb__after_atomic
    #define smp_mb__after_atomic()	__smp_mb__after_atomic()
    #endif

    三、ARMv8架构下原子操作的实现

    下面我们来看看上面提到的所有原子操作函数在Linux内核中是如何实现的,由于原子操作函数实在太多了,我们挑出几个有代表性的来分析,分别是atomic_add_return_acquire()、atomic_fetch_or_release()和atomic_cmpxchg()。它们的定义如下(代码位于include/asm-generic/atomic-instrumented.h中):

    #if defined(arch_atomic_add_return_acquire)
    static inline int
    atomic_add_return_acquire(int i, atomic_t *v)
    {
    	kasan_check_write(v, sizeof(*v));
    	return arch_atomic_add_return_acquire(i, v);
    }
    #define atomic_add_return_acquire atomic_add_return_acquire
    #endif
    
    ......
    
    #if defined(arch_atomic_fetch_or_release)
    static inline int
    atomic_fetch_or_release(int i, atomic_t *v)
    {
    	kasan_check_write(v, sizeof(*v));
    	return arch_atomic_fetch_or_release(i, v);
    }
    #define atomic_fetch_or_release atomic_fetch_or_release
    #endif
    
    ......
    
    #if !defined(arch_atomic_cmpxchg_relaxed) || defined(arch_atomic_cmpxchg)
    static inline int
    atomic_cmpxchg(atomic_t *v, int old, int new)
    {
    	kasan_check_write(v, sizeof(*v));
    	return arch_atomic_cmpxchg(v, old, new);
    }
    #define atomic_cmpxchg atomic_cmpxchg
    #endif
    

    可以看出来,具体的原子操作实现都是和架构相关的。那万一哪个原子操作在当前平台下没有被定义怎么办呢?例如,在ARMv8下,arch_atomic_try_cmpxchg就没有被定义,那么atomic_try_cmpxchg也不会被定义:

    #if defined(arch_atomic_try_cmpxchg)
    static inline bool
    atomic_try_cmpxchg(atomic_t *v, int *old, int new)
    {
    	kasan_check_write(v, sizeof(*v));
    	kasan_check_write(old, sizeof(*old));
    	return arch_atomic_try_cmpxchg(v, old, new);
    }
    #define atomic_try_cmpxchg atomic_try_cmpxchg
    #endif

    对于那些没有平台架构没有定义的原子操作,还有一次补救的机会,例如在ARMv8下,atomic_try_cmpxchg函数真正的定义如下(代码位于include/linux/atomic-fallback.h中):

    #ifndef atomic_try_cmpxchg
    static inline bool
    atomic_try_cmpxchg(atomic_t *v, int *old, int new)
    {
    	int r, o = *old;
    	r = atomic_cmpxchg(v, o, new);
    	if (unlikely(r != o))
    		*old = r;
    	return likely(r == o);
    }
    #define atomic_try_cmpxchg atomic_try_cmpxchg
    #endif

    所以,atomic_try_cmpxchg函数其实是基于已有的atomic_cmpxchg函数来实现的。

    接着上面说,所以atomic_add_return_acquire函数实际调用的是arch_atomic_add_return_acquire函数,atomic_fetch_or_release函数实际调用的是arch_atomic_fetch_or_release函数,atomic_cmpxchg函数实际调用的是arch_cmpxchg_relaxed函数,它们分别定义如下(代码位于arch/arm64/include/asm/atomic.h中):

    #define arch_atomic_add_return_acquire		arch_atomic_add_return_acquire
    ......
    #define arch_atomic_fetch_or_release		arch_atomic_fetch_or_release
    ......
    #define arch_atomic_cmpxchg(v, old, new) \
    	arch_cmpxchg(&((v)->counter), (old), (new))

    arch_cmpxchg函数先放一下,后面说。arch_atomic_add_return_acquire函数和atomic_fetch_or_release函数都是由下面的宏定义的:

    #define ATOMIC_FETCH_OP(name, op)					\
    static inline int arch_##op##name(int i, atomic_t *v)			\
    {									\
    	return __lse_ll_sc_body(op##name, i, v);			\
    }
    
    #define ATOMIC_FETCH_OPS(op)						\
    	ATOMIC_FETCH_OP(_relaxed, op)					\
    	ATOMIC_FETCH_OP(_acquire, op)					\
    	ATOMIC_FETCH_OP(_release, op)					\
    	ATOMIC_FETCH_OP(        , op)
    
    ATOMIC_FETCH_OPS(atomic_fetch_andnot)
    ATOMIC_FETCH_OPS(atomic_fetch_or)
    ATOMIC_FETCH_OPS(atomic_fetch_xor)
    ATOMIC_FETCH_OPS(atomic_fetch_add)
    ATOMIC_FETCH_OPS(atomic_fetch_and)
    ATOMIC_FETCH_OPS(atomic_fetch_sub)
    ATOMIC_FETCH_OPS(atomic_add_return)
    ATOMIC_FETCH_OPS(atomic_sub_return)
    
    #undef ATOMIC_FETCH_OP
    #undef ATOMIC_FETCH_OPS

    所以,这里定义了一组以arch_打头的原子操作函数,且它们都是内联(inline)的。但是最终都是被映射成了__lse_ll_sc_body宏(代码位于arch/arm64/include/asm/lse.h中):

    static inline bool system_uses_lse_atomics(void)
    {
    	return (static_branch_likely(&arm64_const_caps_ready)) &&
    		static_branch_likely(&cpu_hwcap_keys[ARM64_HAS_LSE_ATOMICS]);
    }
    
    #define __lse_ll_sc_body(op, ...)					\
    ({									\
    	system_uses_lse_atomics() ?					\
    		__lse_##op(__VA_ARGS__) :				\
    		__ll_sc_##op(__VA_ARGS__);				\
    })

    system_uses_lse_atomics函数用来判断当前系统是否支持ARMv8.1及之后才新增的所谓LSE(Large System Extention)指令,其中有很多原子操作指令,方便许多,不用再用LL/SC操作了。但是,如果当前Arm平台不支持的话,只能继续通过老的LL/SC操作来实现原子操作了。

    所以,最终arch_atomic_add_return_acquire被映射成内联函数__lse_atomic_add_return_acquire或__ll_sc_atomic_add_return_acquire,arch_atomic_fetch_or_release被映射成内联函数__lse_atomic_fetch_or_release或__ll_sc_atomic_fetch_or_release。

    我们先来开老的使用LL/SC操作的实现(代码位于arch/arm64/include/asm/atomic_ll_sc.h):

    #define ATOMIC_OPS(...)							\
    	ATOMIC_OP(__VA_ARGS__)						\
    	ATOMIC_OP_RETURN(        , dmb ish,  , l, "memory", __VA_ARGS__)\
    	ATOMIC_OP_RETURN(_relaxed,        ,  ,  ,         , __VA_ARGS__)\
    	ATOMIC_OP_RETURN(_acquire,        , a,  , "memory", __VA_ARGS__)\
    	ATOMIC_OP_RETURN(_release,        ,  , l, "memory", __VA_ARGS__)\
    	ATOMIC_FETCH_OP (        , dmb ish,  , l, "memory", __VA_ARGS__)\
    	ATOMIC_FETCH_OP (_relaxed,        ,  ,  ,         , __VA_ARGS__)\
    	ATOMIC_FETCH_OP (_acquire,        , a,  , "memory", __VA_ARGS__)\
    	ATOMIC_FETCH_OP (_release,        ,  , l, "memory", __VA_ARGS__)
    
    ATOMIC_OPS(add, add, I)
    ATOMIC_OPS(sub, sub, J)
    
    #undef ATOMIC_OPS
    #define ATOMIC_OPS(...)							\
    	ATOMIC_OP(__VA_ARGS__)						\
    	ATOMIC_FETCH_OP (        , dmb ish,  , l, "memory", __VA_ARGS__)\
    	ATOMIC_FETCH_OP (_relaxed,        ,  ,  ,         , __VA_ARGS__)\
    	ATOMIC_FETCH_OP (_acquire,        , a,  , "memory", __VA_ARGS__)\
    	ATOMIC_FETCH_OP (_release,        ,  , l, "memory", __VA_ARGS__)
    
    ATOMIC_OPS(and, and, K)
    ATOMIC_OPS(or, orr, K)
    ATOMIC_OPS(xor, eor, K)
    ATOMIC_OPS(andnot, bic, )
    
    #undef ATOMIC_OPS
    #undef ATOMIC_FETCH_OP
    #undef ATOMIC_OP_RETURN
    #undef ATOMIC_OP

    又是一堆宏定义,__ll_sc_atomic_add_return_acquire函数是通过宏ATOMIC_OPS(add, add, I)定义出的一堆关于add原子操作函数其中的一个,也就是接着通过宏ATOMIC_OP_RETURN(_acquire,        , a,  , "memory", __VA_ARGS__)定义出来的函数:

    #define ATOMIC_OP_RETURN(name, mb, acq, rel, cl, op, asm_op, constraint)\
    static inline int							\
    __ll_sc_atomic_##op##_return##name(int i, atomic_t *v)			\
    {									\
    	unsigned long tmp;						\
    	int result;							\
    									\
    	asm volatile("// atomic_" #op "_return" #name "\n"		\
    	__LL_SC_FALLBACK(						\
    "	prfm	pstl1strm, %2\n"					\
    "1:	ld" #acq "xr	%w0, %2\n"					\
    "	" #asm_op "	%w0, %w0, %w3\n"				\
    "	st" #rel "xr	%w1, %w0, %2\n"					\
    "	cbnz	%w1, 1b\n"						\
    "	" #mb )								\
    	: "=&r" (result), "=&r" (tmp), "+Q" (v->counter)		\
    	: __stringify(constraint) "r" (i)				\
    	: cl);								\
    									\
    	return result;							\
    }

    这是一段嵌入C语言中的汇编代码,ATOMIC_OP_RETURN宏的参数name对应于_acquire,参数mb对应的是空,参数acq对应的是a,参数rel对应的是空,参数cl对应的是"memory",参数op对应的是add,参数asm_op对应的也是add,参数constrait对应的是I。

    这段汇编代码被包含在了__LL_SC_FALLBACK宏中:

    #if IS_ENABLED(CONFIG_ARM64_LSE_ATOMICS) && IS_ENABLED(CONFIG_AS_LSE)
    #define __LL_SC_FALLBACK(asm_ops)					\
    "	b	3f\n"							\
    "	.subsection	1\n"						\
    "3:\n"									\
    asm_ops "\n"								\
    "	b	4f\n"							\
    "	.previous\n"							\
    "4:\n"
    #else
    #define __LL_SC_FALLBACK(asm_ops) asm_ops
    #endif

    如果内核配置了CONFIG_ARM64_LSE_ATOMICS和CONFIG_AS_LSE,则将包含的代码放到当前段中的名字为“1”的一个子段中,在函数的入口处跳入进来,当执行完之后在跳回去;如果内核没有配置的话,则是空的,什么都没做。

    这里假设__LL_SC_FALLBACK的定义是空的,那么__ll_sc_atomic_add_return_acquire函数扩展后为:

    static inline int
    __ll_sc_atomic_add_return_acquire(int i, atomic_t *v)
    {
    	unsigned long tmp;
    	int result;
    
    	asm volatile(
    "       // 将v->counter预取到CPU缓存\n"                                   \
    "	prfm    pstl1strm, %2\n"					\
    "       // 将v->counter独占的读入result中\n"                              \
    "1:	ldaxr    %w0, %2\n"					        \
    "       // result = result + i\n"                                       \
    "	add    %w0, %w0, %w3\n"				                \
    "       // 将result的值独占的存入v->counter中\n"                           \
    "       // 如果存入时独占标记被清除则将tmp置1\n"                             \
    "	stxr	%w1, %w0, %2\n"					        \
    "       // 如果tmp被置1则从头再次执行一遍\n"                                \
    "	cbnz	%w1, 1b\n"						\
            )								\
    	: "=&r" (result), "=&r" (tmp), "+Q" (v->counter)		\
    	: __stringify(I) "r" (i)				        \
    	: "memory");					                \
    									\
    	return result;							\
    }

    ldxr和stxr是ARMv8指令集下的实现LL/SC操作的独占访问指令,而ldaxr多了一个a,表示比ldxr指令多了一个Load-Acquire语义,这也就是__ll_sc_atomic_add_return_acquire函数后面_acquire后缀的由来。操作数中加上w(比如%w0,而不是%0)表示是要操作32位的数,使用wn寄存器,否则在64位下,默认使用xn寄存器。所以,这段函数的原理就是独占读取count的值,然后加上i,最后试着独占存入count。如果发现独占存入失败就重新执行一次上述操作,直到成功为止。

    我们接着来看__ll_sc_atomic_fetch_or_release函数,其是通过宏ATOMIC_OPS(or, orr, K)定义出的一堆关于or原子操作函数其中的一个,也就是接着通过宏ATOMIC_FETCH_OP (_release, , , l, "memory", __VA_ARGS__)定义出来的函数:

    #define ATOMIC_FETCH_OP(name, mb, acq, rel, cl, op, asm_op, constraint) \
    static inline int							\
    __ll_sc_atomic_fetch_##op##name(int i, atomic_t *v)			\
    {									\
    	unsigned long tmp;						\
    	int val, result;						\
    									\
    	asm volatile("// atomic_fetch_" #op #name "\n"			\
    	__LL_SC_FALLBACK(						\
    "	prfm	pstl1strm, %3\n"					\
    "1:	ld" #acq "xr	%w0, %3\n"					\
    "	" #asm_op "	%w1, %w0, %w4\n"				\
    "	st" #rel "xr	%w2, %w1, %3\n"					\
    "	cbnz	%w2, 1b\n"						\
    "	" #mb )								\
    	: "=&r" (result), "=&r" (val), "=&r" (tmp), "+Q" (v->counter)	\
    	: __stringify(constraint) "r" (i)				\
    	: cl);								\
    									\
    	return result;							\
    }

    ATOMIC_FETCH_OP宏的参数name对应于_release,参数mb对应的是空,参数acq对应的是空,参数rel对应的是l,参数cl对应的是"memory",参数op对应的是or,参数asm_open对应的是orr,参数constraint对应的是K。同样,将__ll_sc_atomic_fetch_or_release函数扩展后为:

    static inline int							\
    __ll_sc_atomic_fetch_or_release(int i, atomic_t *v)			\
    {									\
    	unsigned long tmp;						\
    	int val, result;						\
    									\
    	asm volatile(			                                \
    "       // 将v->counter预取到CPU缓存\n"                                   \
    "	prfm	pstl1strm, %3\n"					\
    "       // 将v->counter独占的读入result中\n"                              \
    "1:	ldxr	%w0, %3\n"					        \
    "       // val = result | i\n"                                          \
    "	orr	%w1, %w0, %w4\n"				        \
    "       // 将val的值独占的存入v->counter中\n"                              \
    "       // 如果存入时独占标记被清除则将tmp置1\n"                             \
    "	stlxr	%w2, %w1, %3\n"					        \
    "       // 如果tmp被置1则从头再次执行一遍\n"                                \
    "	cbnz	%w2, 1b\n"						\
    	: "=&r" (result), "=&r" (val), "=&r" (tmp), "+Q" (v->counter)	\
    	: __stringify(K) "r" (i)				        \
    	: "memory");							\
    									\
    	return result;							\
    }

    这里用到了stlxr指令,比stxr指令多了一个l,表示比stxr指令多了一个Store-Release语义,这也就是__ll_sc_atomic_fetch_or_release函数后面_release后缀的由来。由于要返回计算之前的值,因此还需要一个临时变量val用来存放计算之后的值,而result用来存放原来的值。

    如果当前平台支持LSE指令,那就会调用函数__lse_atomic_add_return_acquire和__lse_atomic_fetch_or_release。我们先来看__lse_atomic_add_return_acquire函数(代码位于arch/arm64/include/asm/atomic_lse.h中):

    #define ATOMIC_OP_ADD_RETURN(name, mb, cl...)				\
    static inline int __lse_atomic_add_return##name(int i, atomic_t *v)	\
    {									\
    	u32 tmp;							\
    									\
    	asm volatile(							\
    	__LSE_PREAMBLE							\
    	"	ldadd" #mb "	%w[i], %w[tmp], %[v]\n"			\
    	"	add	%w[i], %w[i], %w[tmp]"				\
    	: [i] "+r" (i), [v] "+Q" (v->counter), [tmp] "=&r" (tmp)	\
    	: "r" (v)							\
    	: cl);								\
    									\
    	return i;							\
    }
    
    ATOMIC_OP_ADD_RETURN(_relaxed,   )
    ATOMIC_OP_ADD_RETURN(_acquire,  a, "memory")
    ATOMIC_OP_ADD_RETURN(_release,  l, "memory")
    ATOMIC_OP_ADD_RETURN(        , al, "memory")
    
    #undef ATOMIC_OP_ADD_RETURN

    所以,__lse_atomic_add_return_acquire函数由宏ATOMIC_OP_ADD_RETURN(_acquire,  a, "memory")进行定义。参数name对应于_acquire,参数mb对应域a,参数cl对应于"memory"。宏__LSE_PREAMBLE定义成了:

    #define __LSE_PREAMBLE	".arch_extension lse\n"

    因此,__lse_atomic_add_return_acquire函数最终被扩展成:

    static inline int __lse_atomic_add_return_acquire(int i, atomic_t *v)	\
    {									\
    	u32 tmp;							\
    									\
    	asm volatile(							\
    	".arch_extension lse\n"                                         \
            "// tmp = v->count; v->count = v->count + i;\n"                 \
    	"ldadda	%w[i], %w[tmp], %[v]\n"			                \
            "// i = i + tmp;\n"                                             \
    	"add	%w[i], %w[i], %w[tmp]"				        \
    	: [i] "+r" (i), [v] "+Q" (v->counter), [tmp] "=&r" (tmp)	\
    	: "r" (v)							\
    	: "memory");							\
    									\
    	return i;							\
    }

    ldadd本身就是一个原子操作,但是它只会返回加操作之前内存的值,但是函数要返回的是加之后的值,所以后面还要再执行一次加的操作。不过这并不会影响原子性,因为内存中的值真的已经被原子的改变过了。ldadda指令相对于ldadd指令多了一个a,表示比ldadd指令多了一个Load-Acquire语义,这也就是__lse_atomic_add_return_acquire函数后面_acquire后缀的由来。

    我们接着看__lse_atomic_fetch_or_release函数

    #define ATOMIC_FETCH_OP(name, mb, op, asm_op, cl...)			\
    static inline int __lse_atomic_fetch_##op##name(int i, atomic_t *v)	\
    {									\
    	asm volatile(							\
    	__LSE_PREAMBLE							\
    "	" #asm_op #mb "	%w[i], %w[i], %[v]"				\
    	: [i] "+r" (i), [v] "+Q" (v->counter)				\
    	: "r" (v)							\
    	: cl);								\
    									\
    	return i;							\
    }
    
    #define ATOMIC_FETCH_OPS(op, asm_op)					\
    	ATOMIC_FETCH_OP(_relaxed,   , op, asm_op)			\
    	ATOMIC_FETCH_OP(_acquire,  a, op, asm_op, "memory")		\
    	ATOMIC_FETCH_OP(_release,  l, op, asm_op, "memory")		\
    	ATOMIC_FETCH_OP(        , al, op, asm_op, "memory")
    
    ATOMIC_FETCH_OPS(andnot, ldclr)
    ATOMIC_FETCH_OPS(or, ldset)
    ATOMIC_FETCH_OPS(xor, ldeor)
    ATOMIC_FETCH_OPS(add, ldadd)
    
    #undef ATOMIC_FETCH_OP
    #undef ATOMIC_FETCH_OPS

    所以,__lse_atomic_fetch_or_release函数由ATOMIC_FETCH_OPS(or, ldset)宏和ATOMIC_FETCH_OP(_release,  l, op, asm_op, "memory")宏共同定义。ATOMIC_FETCH_OP宏的name参数对应于_release,mb参数对应于l,op参数对应于or,asm_op参数对应于ldset,cl参数对应于"memory"。扩展过后,__lse_atomic_fetch_or_release被定义为:

    static inline int __lse_atomic_fetch_or_release(int i, atomic_t *v)	\
    {									\
    	asm volatile(							\
    	".arch_extension lse\n"                                         \
            "ldsetl	%w[i], %w[i], %[v]"				        \
    	: [i] "+r" (i), [v] "+Q" (v->counter)				\
    	: "r" (v)							\
    	: cl);								\
    									\
    	return i;							\
    }

    ldset指令本身就可以保证原子的或操作,而且会返回没修改之前的值,因此一条指令就搞定了。ldsetl指令相对于ldset指令多了一个l,表示比ldset指令多了一个Store-Release语义,这也就是__lse_atomic_fetch_or_release函数后面_release后缀的由来。

    最后,我们再回过头来看看arch_cmpxchg函数的实现:

    #define __cmpxchg_wrapper(sfx, ptr, o, n)				\
    ({									\
    	__typeof__(*(ptr)) __ret;					\
    	__ret = (__typeof__(*(ptr)))					\
    		__cmpxchg##sfx((ptr), (unsigned long)(o),		\
    				(unsigned long)(n), sizeof(*(ptr)));	\
    	__ret;								\
    })
    ......
    #define arch_cmpxchg(...)		__cmpxchg_wrapper( _mb, __VA_ARGS__)

    所以,arch_cmpxchg最终被宏定义成了__cmpxchg_wrapper,而它又调用了__cmpxchg_mb函数:

    #define __CMPXCHG_GEN(sfx)						\
    static __always_inline unsigned long __cmpxchg##sfx(volatile void *ptr,	\
    					   unsigned long old,		\
    					   unsigned long new,		\
    					   int size)			\
    {									\
    	switch (size) {							\
    	case 1:								\
    		return __cmpxchg_case##sfx##_8(ptr, old, new);		\
    	case 2:								\
    		return __cmpxchg_case##sfx##_16(ptr, old, new);		\
    	case 4:								\
    		return __cmpxchg_case##sfx##_32(ptr, old, new);		\
    	case 8:								\
    		return __cmpxchg_case##sfx##_64(ptr, old, new);		\
    	default:							\
    		BUILD_BUG();						\
    	}								\
    									\
    	unreachable();							\
    }
    
    __CMPXCHG_GEN()
    __CMPXCHG_GEN(_acq)
    __CMPXCHG_GEN(_rel)
    __CMPXCHG_GEN(_mb)
    
    #undef __CMPXCHG_GEN

    __cmpxchg_mb函数是靠宏__CMPXCHG_GEN(_mb)定义的,按照要交换数据的大小,分别调用了__cmpxchg_case_mb_8、__cmpxchg_case_mb_16、__cmpxchg_case_mb_32或__cmpxchg_case_mb_64:

    #define __CMPXCHG_CASE(name, sz)			\
    static inline u##sz __cmpxchg_case_##name##sz(volatile void *ptr,	\
    					      u##sz old,		\
    					      u##sz new)		\
    {									\
    	return __lse_ll_sc_body(_cmpxchg_case_##name##sz,		\
    				ptr, old, new);				\
    }
    
    __CMPXCHG_CASE(    ,  8)
    __CMPXCHG_CASE(    , 16)
    __CMPXCHG_CASE(    , 32)
    __CMPXCHG_CASE(    , 64)
    __CMPXCHG_CASE(acq_,  8)
    __CMPXCHG_CASE(acq_, 16)
    __CMPXCHG_CASE(acq_, 32)
    __CMPXCHG_CASE(acq_, 64)
    __CMPXCHG_CASE(rel_,  8)
    __CMPXCHG_CASE(rel_, 16)
    __CMPXCHG_CASE(rel_, 32)
    __CMPXCHG_CASE(rel_, 64)
    __CMPXCHG_CASE(mb_,  8)
    __CMPXCHG_CASE(mb_, 16)
    __CMPXCHG_CASE(mb_, 32)
    __CMPXCHG_CASE(mb_, 64)

    最后又回到了宏__lse_ll_sc_body,我们这里只分析一下大小为16的情况。所以,如果当前平台支持LSE,那么将调用__lse_cmpxchg_case_mb_16函数,否则将调用__ll_sc_cmpxchg_case_mb_16函数。

    我们先来看LL/SC方式的实现__ll_sc_cmpxchg_case_mb_16函数:

    #define __CMPXCHG_CASE(w, sfx, name, sz, mb, acq, rel, cl, constraint)	\
    static inline u##sz							\
    __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr,			\
    					 unsigned long old,		\
    					 u##sz new)			\
    {									\
    	unsigned long tmp;						\
    	u##sz oldval;							\
    									\
    	if (sz < 32)							\
    		old = (u##sz)old;					\
    									\
    	asm volatile(							\
    	__LL_SC_FALLBACK(						\
    	"	prfm	pstl1strm, %[v]\n"				\
    	"1:	ld" #acq "xr" #sfx "\t%" #w "[oldval], %[v]\n"		\
    	"	eor	%" #w "[tmp], %" #w "[oldval], %" #w "[old]\n"	\
    	"	cbnz	%" #w "[tmp], 2f\n"				\
    	"	st" #rel "xr" #sfx "\t%w[tmp], %" #w "[new], %[v]\n"	\
    	"	cbnz	%w[tmp], 1b\n"					\
    	"	" #mb "\n"						\
    	"2:")								\
    	: [tmp] "=&r" (tmp), [oldval] "=&r" (oldval),			\
    	  [v] "+Q" (*(u##sz *)ptr)					\
    	: [old] __stringify(constraint) "r" (old), [new] "r" (new)	\
    	: cl);								\
    									\
    	return oldval;							\
    }
    
    __CMPXCHG_CASE(w, b,     ,  8,        ,  ,  ,         , K)
    __CMPXCHG_CASE(w, h,     , 16,        ,  ,  ,         , K)
    __CMPXCHG_CASE(w,  ,     , 32,        ,  ,  ,         , K)
    __CMPXCHG_CASE( ,  ,     , 64,        ,  ,  ,         , L)
    __CMPXCHG_CASE(w, b, acq_,  8,        , a,  , "memory", K)
    __CMPXCHG_CASE(w, h, acq_, 16,        , a,  , "memory", K)
    __CMPXCHG_CASE(w,  , acq_, 32,        , a,  , "memory", K)
    __CMPXCHG_CASE( ,  , acq_, 64,        , a,  , "memory", L)
    __CMPXCHG_CASE(w, b, rel_,  8,        ,  , l, "memory", K)
    __CMPXCHG_CASE(w, h, rel_, 16,        ,  , l, "memory", K)
    __CMPXCHG_CASE(w,  , rel_, 32,        ,  , l, "memory", K)
    __CMPXCHG_CASE( ,  , rel_, 64,        ,  , l, "memory", L)
    __CMPXCHG_CASE(w, b,  mb_,  8, dmb ish,  , l, "memory", K)
    __CMPXCHG_CASE(w, h,  mb_, 16, dmb ish,  , l, "memory", K)
    __CMPXCHG_CASE(w,  ,  mb_, 32, dmb ish,  , l, "memory", K)
    __CMPXCHG_CASE( ,  ,  mb_, 64, dmb ish,  , l, "memory", L)
    
    #undef __CMPXCHG_CASE

    定义__ll_sc_cmpxchg_case_mb_16函数,__CMPXCHG_CASE宏的w参数对应于w,sfx参数对应于空,name参数对应于mb_,sz参数对应于16,mb参数对应于dmb ish,acq参数对应于空,rel参数对应于l,cl参数对应于"memory",constraint参数对应于K。所以,__ll_sc_cmpxchg_case_mb_16函数扩展开来的实现是:

    static inline u16							\
    __ll_sc__cmpxchg_case_mb_16(volatile void *ptr,			        \
    					 unsigned long old,		\
    					 u16 new)			\
    {									\
    	unsigned long tmp;						\
    	u16 oldval;							\
    									\
    	old = (u16)old;					                \
    									\
    	asm volatile(							\
            "       // 将*ptr的值预取到CPU缓存\n"                              \
    	"	prfm	pstl1strm, %[v]\n"				\
            "       // oldval = *ptr;\n"                                    \
    	"1:	ldxr    %w[oldval], %[v]\n"		                \
            "       // tmp = oldval ^ old;\n"                               \
    	"	eor     %w[tmp], %w[oldval], %w[old]\n"	                \
    	"	cbnz	%w[tmp], 2f\n"				        \
            "       // *ptr = new;\n"                                       \
    	"	stlxr   %w[tmp], %w[new], %[v]\n"	                \
    	"	cbnz	%w[tmp], 1b\n"					\
    	"	dmb ish\n"						\
    	"2:"								\
    	: [tmp] "=&r" (tmp), [oldval] "=&r" (oldval),			\
    	  [v] "+Q" (*(u16 *)ptr)					\
    	: [old] __stringify(K) "r" (old), [new] "r" (new)	        \
    	: "memory");							\
    									\
    	return oldval;							\
    }

    使用了eor异或指令判断新老值是否相等,如果不等则直接退出,如果相等则尝试独占写入新的数据。这里使用了stlxr指令而不是普通的stxr指令,添加了Store-Release语义,这是为了保证写入内存的数据能立即被系统中其它模块感知到。最后添加了一个作用于内部共享域的数据内存屏障,这也印证了前面提到的命名规则,即RMW的原子操作,如果有返回值,并且没有_relaxed、_acquire和_release后缀,是有内存屏障保护的,可以保证不被重排序。

    再来看一下__lse_cmpxchg_case_mb_16函数的实现:

    #define __CMPXCHG_CASE(w, sfx, name, sz, mb, cl...)			\
    static __always_inline u##sz						\
    __lse__cmpxchg_case_##name##sz(volatile void *ptr,			\
    					      u##sz old,		\
    					      u##sz new)		\
    {									\
    	register unsigned long x0 asm ("x0") = (unsigned long)ptr;	\
    	register u##sz x1 asm ("x1") = old;				\
    	register u##sz x2 asm ("x2") = new;				\
    	unsigned long tmp;						\
    									\
    	asm volatile(							\
    	__LSE_PREAMBLE							\
    	"	mov	%" #w "[tmp], %" #w "[old]\n"			\
    	"	cas" #mb #sfx "\t%" #w "[tmp], %" #w "[new], %[v]\n"	\
    	"	mov	%" #w "[ret], %" #w "[tmp]"			\
    	: [ret] "+r" (x0), [v] "+Q" (*(unsigned long *)ptr),		\
    	  [tmp] "=&r" (tmp)						\
    	: [old] "r" (x1), [new] "r" (x2)				\
    	: cl);								\
    									\
    	return x0;							\
    }
    
    __CMPXCHG_CASE(w, b,     ,  8,   )
    __CMPXCHG_CASE(w, h,     , 16,   )
    __CMPXCHG_CASE(w,  ,     , 32,   )
    __CMPXCHG_CASE(x,  ,     , 64,   )
    __CMPXCHG_CASE(w, b, acq_,  8,  a, "memory")
    __CMPXCHG_CASE(w, h, acq_, 16,  a, "memory")
    __CMPXCHG_CASE(w,  , acq_, 32,  a, "memory")
    __CMPXCHG_CASE(x,  , acq_, 64,  a, "memory")
    __CMPXCHG_CASE(w, b, rel_,  8,  l, "memory")
    __CMPXCHG_CASE(w, h, rel_, 16,  l, "memory")
    __CMPXCHG_CASE(w,  , rel_, 32,  l, "memory")
    __CMPXCHG_CASE(x,  , rel_, 64,  l, "memory")
    __CMPXCHG_CASE(w, b,  mb_,  8, al, "memory")
    __CMPXCHG_CASE(w, h,  mb_, 16, al, "memory")
    __CMPXCHG_CASE(w,  ,  mb_, 32, al, "memory")
    __CMPXCHG_CASE(x,  ,  mb_, 64, al, "memory")
    
    #undef __CMPXCHG_CASE

    定义__lse_cmpxchg_case_mb_16函数,__CMPXCHG_CASE宏的w参数对应于w,sfx参数对应于空,name参数对应于mb_,sz参数对应于16,mb参数对应于al,cl参数对应于"memory"。所以,__ll_sc_cmpxchg_case_mb_16函数扩展开来的实现是:

    static __always_inline u16						\
    __lse__cmpxchg_case_mb_16(volatile void *ptr,			        \
    			    u16 old,		                        \
    			    u16 new)		                        \
    {									\
    	register unsigned long x0 asm ("x0") = (unsigned long)ptr;	\
    	register u16 x1 asm ("x1") = old;				\
    	register u16 x2 asm ("x2") = new;				\
    	unsigned long tmp;						\
    									\
    	asm volatile(							\
    	"       .arch_extension lse\n"                                  \
            "       // tmp = old;\n"                                        \
    	"	mov	%w[tmp], %w[old]\n"			        \
    	"	casal   %w[tmp], %w[new], %[v]\n"	                \
            "       // ret = tmp;\n"                                        \
    	"	mov	%[ret], %w[tmp]"			        \
    	: [ret] "+r" (x0), [v] "+Q" (*(unsigned long *)ptr),		\
    	  [tmp] "=&r" (tmp)						\
    	: [old] "r" (x1), [new] "r" (x2)				\
    	: "memory");							\
    									\
    	return x0;							\
    }

    cas指令是LSE中原生提供的一个比较交换指令,会比较第一个寄存器中的值是否和内存中的值相同,如果相同的话就将第二个寄存器的值写入内存,并且会在第一个寄存器中放入修改内存之前的值。casal指令相对于cas指令多了al,表示比cas指令多了一个Load-Acquire和Store-Release语义,其实就是一个完整的内存屏障,保证不被重排序。

    展开全文
  • 在Linux内核代码中,信号量被定义成semaphore结构体(代码位于include/linux/semaphore.h中): struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list; }; 这个结构体由三...

    在Linux内核代码中,信号量被定义成semaphore结构体(代码位于include/linux/semaphore.h中):

    struct semaphore {
    	raw_spinlock_t		lock;
    	unsigned int		count;
    	struct list_head	wait_list;
    };

    这个结构体由三部分组成:

    • lock:用来保护这个信号量结构体的自旋锁。
    • count:信号量用来保护的共享资源的数量。如果该值大于0,表示资源是空闲的,调用者可以立即获得这个信号量,进而访问被保护的共享资源;如果该值等于0,表示资源是忙的,但是没有别的进程在等待这个信号量被释放;如果这个值小于0,表示资源是忙的,且有至少一个别的进程正在等待该信号量被释放。
    • wait_list:在这个信号量上等待的所有进程链表。

    所有插入wait_list等待链表的进程节点由semaphore_waiter结构体表示:

    struct semaphore_waiter {
    	struct list_head list;
    	struct task_struct *task;
    	bool up;
    };

    这个结构体也由三部分组成:

    • list:插入链表的节点。
    • task:指向等待进程的task_struct结构体。
    • up:真表示该等待进程是因为信号量释放被唤醒的,否则都是假。

    初始化信号量

    信号量在使用之前要对其进行初始化,一般有两种方法:

    DEFINE_SEMAPHORE(sem1);
     
    struct semaphore sem2;
    sema_init(&lock2);

    第一种方法是用宏直接定义并且初始化一个顺序锁变量:

    #define __SEMAPHORE_INITIALIZER(name, n)				\
    {									\
    	.lock		= __RAW_SPIN_LOCK_UNLOCKED((name).lock),	\
    	.count		= n,						\
    	.wait_list	= LIST_HEAD_INIT((name).wait_list),		\
    }
    
    #define DEFINE_SEMAPHORE(name)	\
    	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

    所以,直接通过宏定义初始化就是定义了一个semaphore结构体变量,将其内部的自旋锁lock初始化为未加锁,将表示共享资源数量的count变量初始化为1,将等待进程链表初始化为空链表。count的值为1,表示这个信号量只能同时由一个进程持有,也就是说被保护的共享资源只能被互斥的访问,这种特殊的信号量也称作二值(Binary)信号量。但是,通常大家用的都是这种二值信号量,用法如下:

    down(&sem);
    /* 临界区 */
    up(&sem);

    第二种方法是自己定义一个semaphore结构体变量,然后调用sema_init函数将其初始化:

    static inline void sema_init(struct semaphore *sem, int val)
    {
    	......
    	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    	......
    }

    最终也是通过__SEMAPHORE_INITIALIZER对信号量进行的初始化。但是,通过这种方式初始化可以指定count的值,而不像前者默认设置成1。

    获取信号量

    要想获取一个信号量,通常是通过调用down函数:

    void down(struct semaphore *sem)
    {
    	unsigned long flags;
    
            /* 获得自旋锁并关中断 */
    	raw_spin_lock_irqsave(&sem->lock, flags);
            /* 如果信号量的count大于0 */
    	if (likely(sem->count > 0))
                    /* 直接获得该信号量并将count值递减 */
    		sem->count--;
    	else
    		__down(sem);
            /* 释放自旋锁并开中断 */
    	raw_spin_unlock_irqrestore(&sem->lock, flags);
    }
    EXPORT_SYMBOL(down);

    如果信号量的count值大于0,则直接将其递减然后返回,调用的进程直接获得该信号量。如果小于或等于0,则接着调用__down函数:

    static noinline void __sched __down(struct semaphore *sem)
    {
    	__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
    }

    __down函数直接调用了__down_common函数。传入的第二个参数是TASK_UNINTERRUPTIBLE,表示;传入的第三个参数是MAX_SCHEDULE_TIMEOUT,表示会一直等待该信号量,直到获得为止,没有到期时间。

    除了最基本的down函数外,还可以通过下面几个函数来获得信号量:

    • down_interruptible:基本功能和down相同,区别是如果无法获得信号量,会将该进程置于TASK_INTERRUPTIBLE状态。因此,在进程睡眠时可以通过信号(Signal)将其唤醒。
    • down_killable:如果无法获得信号量,会将该进程置于TASK_KILLABLE状态。
    • down_timeout:不会一直等待该信号量,而是有一个到期时间,时间到了后即使没有获得信号量也会返回。等待的时候也和down函数一样,会将进程置于TASK_UNINTERRUPTIBLE状态。

    所有获得信号量的方法最终都是调用了__down_common函数,只不过传入的第二个和第三个参数不一样。

    static noinline int __sched __down_interruptible(struct semaphore *sem)
    {
    	return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
    }
    
    static noinline int __sched __down_killable(struct semaphore *sem)
    {
    	return __down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT);
    }
    
    static noinline int __sched __down_timeout(struct semaphore *sem, long timeout)
    {
    	return __down_common(sem, TASK_UNINTERRUPTIBLE, timeout);
    }

    我们接着回来看__down_common函数的实现:

    static inline int __sched __down_common(struct semaphore *sem, long state,
    								long timeout)
    {
    	struct semaphore_waiter waiter;
    
            /* 将表示等待进程的节点插入信号量的等待列表中 */
    	list_add_tail(&waiter.list, &sem->wait_list);
    	waiter.task = current;
    	waiter.up = false;
    
    	for (;;) {
                    /* 如果有待处理的信号则跳到interrupted标签处 */
    		if (signal_pending_state(state, current))
    			goto interrupted;
                    /* 如果超时了则跳到timed_out标签处 */
    		if (unlikely(timeout <= 0))
    			goto timed_out;
                    /* 设置当前进程的状态 */
    		__set_current_state(state);
                    /* 释放自旋锁 */
    		raw_spin_unlock_irq(&sem->lock);
                    /* 当前进程睡眠 */
    		timeout = schedule_timeout(timeout);
                    /* 再次获得自旋锁 */
    		raw_spin_lock_irq(&sem->lock);
                    /* 如果是因为自旋锁释放而被唤醒的则返回0 */
    		if (waiter.up)
    			return 0;
    	}
    
     timed_out:
    	list_del(&waiter.list);
    	return -ETIME;
    
     interrupted:
    	list_del(&waiter.list);
    	return -EINTR;
    }

    该函数先将表示等待进程的节点插入信号量的等待列表中,该节点的task变量指向表示当前进程的task_struct结构体,up变量被初始化为否。然后会进入一个大的循环中,循环的退出条件有三个:

    1. 当前进程有待处理的信号。
    2. 当前进程等待信号量超时了。
    3. 当前进程被另一个释放信号量的进程唤醒。

    signal_pending_state函数用来判断当前进程是否有待处理的信号(代码位于include/linux/sched/signal.h中):

    static inline int signal_pending_state(long state, struct task_struct *p)
    {
    	if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))
    		return 0;
    	if (!signal_pending(p))
    		return 0;
    
    	return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
    }

    所以,当前进程的状态必须包含TASK_INTERRUPTIBLE或TASK_WAKEKILL才行。也就是说,只有通过调用down_interruptible或down_killable函数获得信号量时,signal_pending_state函数才有可能返回真。

    schedule_timeout用来将当前进程休眠,同时设置到期时间(代码位于kernel/time/timer.c中):

    signed long __sched schedule_timeout(signed long timeout)
    {
    	struct process_timer timer;
    	unsigned long expire;
    
    	switch (timeout)
    	{
    	case MAX_SCHEDULE_TIMEOUT:
    		/* 没有到期时间间隔直接睡眠 */
    		schedule();
    		goto out;
    	default:
    		if (timeout < 0) {
    			printk(KERN_ERR "schedule_timeout: wrong timeout "
    				"value %lx\n", timeout);
    			dump_stack();
    			current->state = TASK_RUNNING;
    			goto out;
    		}
    	}
    
            /* 计算到期时间 */
    	expire = timeout + jiffies;
    
    	timer.task = current;
            /* 设置定时器到期处理函数为process_timeout */
    	timer_setup_on_stack(&timer.timer, process_timeout, 0);
            /* 设置定时器 */
    	__mod_timer(&timer.timer, expire, 0);
            /* 休眠 */
    	schedule();
            /* 删除定时器 */
    	del_singleshot_timer_sync(&timer.timer);
    
    	......
    
            /* 计算距离到期时间还剩多少时间 */
    	timeout = expire - jiffies;
    
     out:
    	return timeout < 0 ? 0 : timeout;
    }
    EXPORT_SYMBOL(schedule_timeout);

    如果传入的到期时间间隔被设置成了MAX_SCHEDULE_TIMEOUT,表示当前进程在获得信号量时没有设置超时,因此直接调用schedule调度另一个进程执行,本进程进入休眠。如果设置了一个有效的到期时间间隔,那么在调用schedule之前必须要先添加一个定时器,让其在到期时间点被触发。但是,表示定时器本身的结构并不包含由哪个进程设置它的信息,所以还需要定义一个新的结构体process_timer:

    struct process_timer {
    	struct timer_list timer;
    	struct task_struct *task;
    };

    注意,表示定时器的结构体timer_list是在process_timer结构体中的第一个变量。这样,如果我们有了一个指向表示定时器的timer_list结构体的指针,那么它其实也是指向process_timer结构体的。通过前面的分析可以看到,定时器的到期函数被设置成了process_timeout,它的参数就是一个指向timer_list结构体的指针:

    static void process_timeout(struct timer_list *t)
    {
            /* 从timer_list指针获得包含它的process_timer */
    	struct process_timer *timeout = from_timer(timeout, t, timer);
    
            /* 唤醒到期进程 */
    	wake_up_process(timeout->task);
    }

    就是通过指向定时器结构体timer_list的指针获得包含它的process_timer结构体指针,从而获得设置该定时器的进程,然后唤醒它。

    释放信号量

    要想释放一个信号量,只能通过调用up函数:

    void up(struct semaphore *sem)
    {
    	unsigned long flags;
    
            /* 获得自旋锁并关中断 */
    	raw_spin_lock_irqsave(&sem->lock, flags);
            /* 如果等待进程链表为空 */
    	if (likely(list_empty(&sem->wait_list)))
                    /* 直接将count递增 */
    		sem->count++;
    	else
    		__up(sem);
            /* 释放自旋锁并开中断 */
    	raw_spin_unlock_irqrestore(&sem->lock, flags);
    }
    EXPORT_SYMBOL(up);

    如果释放的时候发现等待进程链表是空的,表示当前没有被的进程在等这个信号量,直接递增count值就行了;如果不为空,表示有别的进程在等这个信号量被释放,那么当前进程需要接着调用__up函数试着将某个等待进程唤醒。

    static noinline void __sched __up(struct semaphore *sem)
    {
            /* 获得等待链表中的第一个节点 */
    	struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
    						struct semaphore_waiter, list);
            /* 将该节点从等待链表中删除 */
    	list_del(&waiter->list);
    	waiter->up = true;
            /* 将该等待进程唤醒 */
    	wake_up_process(waiter->task);
    }

    功能很简单,获得等待链表中的第一个节点,然后唤醒该节点表示的进程。需要把该节点的up字段置为真,让被唤醒的进程知道其是因为信号量被释放才被唤醒的。

    使用场景

    要使用信号量,一般要满足以下的一些使用场景或条件:

    • 信号量适合于保护较长的临界区。它不应该用来保护较短的临界区,因为竞争信号量时有可能使进程睡眠和切换,然后被再次唤醒,代价很高,这种场景下应该使用自旋锁。
    • 如果被保护的共享资源有多份,并不只是互斥访问的,那非常适合使用信号量。
    • 只有允许睡眠的场景下才能使用内核信号量,也就是说在中断处理程序和可延迟函数中都不能使用信号量。
    展开全文
  • 在Linux内核代码中,大量使用了自旋锁。 自旋锁不会让
  • 避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。 ——题记 内核源码:linux-2.6.38.8.tar.bz2 目标平台:ARM体系结构 原子操作确保对同一数据的“读取-修改-写入”操作在...
  • 避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。  ——题记  内核源码:linux-2.6.38.8.tar.bz2  目标平台:ARM体系结构    当创建一个per-cpu变量时,系
  • MAC内核编程指南-同步原语

    千次阅读 2011-05-10 12:01:00
    MAC内核编程指南-同步原语
  • Linux内核中的同步原语 自旋锁,信号量,互斥锁,读写信号量,顺序锁 rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容。 1. Linux 内核中的同步原语介绍 这一部分为 linux-insides...
  • 在阅读linux内核代码时,毫无疑问会遇到spin-lock,下面谈谈我对于spin-lock的arm源码分析。 首先看一下spinlock_t的结构 //arm/include/asm/spinlock_types.h typedef struct { union { u32 slock; struct ...
  • 内核中常用的同步原语使用说明

    千次阅读 2008-02-27 16:16:00
    内核中常用的同步原语使用说明在可以休眠的地方,一般使用mutex或semaphore, 不可以休眠的地方一般用spinlock(自旋锁)mutex/semaphore: 可以在无法进入临界区的时候进入休眠状态, 一般不能用在中断或其他异步上下文中...
  • 同步原语

    2014-10-08 19:55:00
    ecos是多线程系统,并发执行造成了一些新问题的产生:多线程协同工作、对临界资源的竞争、线程间通信、线程间同步等等。其实,所有的多任务系统都会遇 到类似问题,计算机专家...ecos内核同步机制提供了许多同...
  • 内核同步 1 临界区 2 内核同步原语 >> 每cpu变量、volatile关键字、屏障、atomic变量、禁中断、禁抢占、自旋锁、读写锁、顺序锁、信号量、互斥锁、cru 1 临界区 >>
  • linux kernle 同步原语

    千次阅读 2019-04-18 10:41:45
    转载:同步原语 如何避免由于对共享数据的不安全访问导致的数据崩溃? 内核使用的各种同步技术: 技术 说明 适用范围 每CPU变量 在CPU之间复制数据结构 所有CPU 原子操作 对一个计数器原子地...
  • .NET线程同步原语

    2019-07-30 16:43:37
    第五章 [互斥]  用户模式 Interlock原子锁(原子加减交换,非原子:将共享... 内核模式 Mutex互斥锁(那个线程获取,那个线程释放;可以跨进程)  混合模式 Monter,SpinLock自旋锁(struct,构造函数false表...
  • 首先,说明信号量的作用,信号量的作用类似于自旋锁(其实,同步原语均有相似之处),相似之处在于,都有临界区,各进程需互斥访问,linux中提供了两种信号量的实现,有内核态的和用户态的,这里只介绍内核态的 ...
  • Linux的新式线程同步原语——Futex

    千次阅读 2010-05-29 17:03:00
    Linux的新式线程同步原语——Futex 在我的上一篇文章《本地POSIX线程库》中,提到了Futex一词,发现好多读者误以为这是我的笔误,将Mutex错写为Futex了。其实Futex是Linux的一种全新的线程同步原语。本文将为您解读...
  • 操作系统课程设计《设计内核同步原语》 2014-07-10 13:17落寞的丶年华 分类:网站使用 | 浏览 201 次  操作系统计算机 问题:怎样将自己写的原语,放入内核?具体怎么操作? 我有更好的答案 ...
  • 在Linux内核代码中,顺序锁被定义成seqlock_t结构体(代码位于include/linux/seqlock.h中): typedef struct { struct seqcount seqcount; spinlock_t lock; } seqlock_t; 所以,包含一个自旋锁lock和一个表示当前...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 165
精华内容 66
关键字:

内核同步原语