嵌入式LINUX驱动学习之8竞态和并发相关问题 (五)位原子操作

嵌入式LINUX驱动学习之8竞态和并发相关问题 (五)位原子操作


原子操作分为位原子操作的整形原子操作,即每次操作1位或1个整型;
位原子操作,建议最大修改的数据位为:BITS_PER_LONG -1,当大于或等于BITS_PER_LONG可能出现吐核心情况

一、位原子操作相关头文件函数及说明

/* 位操作函数: set_bit()、clear_bit()、change_bit()
   源码位置:include/asm-generic/bitops/atomic.h
   以set_bit()函数原码分析以例,见附A.1
   函数原型如下:
*/
void set_bit(int nr, volatile unsigned long *addr)//将变量addr的nr位设置为1;nr从0开始算
void clear_bit(int nr, volatile unsigned long *addr)//将变量addr的nr位设置为0;
void change_bit(int nr, volatile unsigned long *addr)//将变量addr的nr位取反

/*
    位查看函数:test_bit(),注:并没有进行原子操作
    源码位置: include/asm-generic/bitops/non-atomic.h
*/
    tatic inline int test_bit(int nr, const volatile unsigned long *addr)
{
    
    
        /*
           1、 addr[BIT_WORD(nr)]  在nr小于BITS_PER_LONG时,相当于addr[0],即addr起始地址
           2、 BITS_PER_LONG-1  相当于0~31位为1,或0~64位为1;
           3、nr & (BITS_PER_LONG-1) 相当于只用nr变量的低32位或64位
           4、综合上述:addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))相当于addr[0]右移nr位
              注:此时的nr经过了nr & (BITS_PER_LONG-1)计算的。
              此时addr[0]就是是原addr的第nr位
            5、return 1UL & addr[0],就可以得到addr的第nr位为0或1;
        */ 
        return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));
/*
其它几个组合位原子操作函数:
    test_and_set_bit(),test_and_clear_bit(),test_and_change_bit()
*/
    //通过位原子操作,设置变量addr的nr位为1,并返回操作前nr位的值(0/1)
    int test_and_set_bit(int nr, volatile unsigned long *addr)
    //通过位原子操作,设置变量addr的nr位为0,并返回操作前nr位的值(0/1)
    int test_and_clear_bit(int nr, volatile unsigned long *addr);
    //通过位原子操作,设置变量addr的nr位取反(0/1),并返回操作前nr位的值(0/1)
    int test_and_change_bit(int nr, volatile unsigned long *addr)
}

二、位原子操作用法

//注:以下注释中的第nr位,均为从0位开始算,如:第3位,实现为0 1 2 3 
static void xxx_fucnt(void){
    
    
unsigned long i =10,j = 20;
    int ret_i;
    set_bit(3,&i);//将变量i的第3位设置为1
    printk(" set_bit(3,&i) : %lu\n",i);
    clear_bit(4,&j);//将变量j的第4位设置为0
    printk(" clear_bit(4,j) : %lu\n",j);
    change_bit(5,&i); //将变量i的第5位按位取反
    printk(" change_bit(5,i) : %lu\n",i);
    ret_i = test_and_set_bit(6,&i);//将变量i的第6位设置为1,并返回设置之前第6位的
数值。
    printk("ret_i : %d , test_and_set_bit(6,i):%lu\n",ret_i , i);

}

附A.1

static inline void set_bit(int nr, volatile unsigned long *addr)
{
    
     
        unsigned long mask = BIT_MASK(nr);//将1左移nr位,详见附A.1.1
        /*
            将addr地址移至addr + (nr/(32/64))* sizeof(lonng)位置
            即当在64位操作系统中,nr < 64,addr不用动,
                                nr > 64 ,如nr = 65,addr的内存地址需要加(nr/64) *sizeof(long);
            因此,位原子操作时,当nr 大于 BITS_PER_LONG时,可能导致吐核。
            BIT_WORD功能实现详见附A.1.2
        */
        unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
        unsigned long flags;
        _atomic_spin_lock_irqsave(p, flags);
        *p  |= mask;
        _atomic_spin_unlock_irqrestore(p, flags);
}

附 A.1.1

/*
     BIT_MASK(nr) //将1UL左移nr位,当nr大于BITS_PER_LONG求余
     源码位置:include/linux/bitops.h
*/
#define BIT_MASK(nr)            (1UL << ((nr) % BITS_PER_LONG))

/*
    BITS_PER_LONG  //根据64 bit CPU64或32 bit CPU确定BITS_PER_LONG值
    源码位置:include/asm-generic/bitsperlong.h
*/
#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif

附A.1.2

/*
     BIT_WORD(nr) //将nr除以BITS_PER_LONG
     源码位置:include/linux/bitops.h
*/
#define BIT_WORD(nr)            ((nr) / BITS_PER_LONG)

猜你喜欢

转载自blog.csdn.net/weixin_47273317/article/details/108074977