Linux内核最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,不能中断,且避免其他的CPU访问同一存储器单元。这些很小的原子操作可以建立在其他更灵活进制的基础之上以创建临界区。
在你编写C代码程序时,并不能保证编译器会为a = a +1 或甚至像 a ++这样的操作使用一个原子指令。因此Linux内核提供了一个专门的 atomic_t 类型和一些专门的函数和宏, 这些函数和宏作用于atomic_t 类型的变量,并当做单独的、原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个lock字节的前缀。
typedef struct {
int counter;
} atomic_t;
原子操作分为两种:整形原子操作和位原子操作
整形原子操作常用的操作如下
void atomic_set(atomic_t *v,int i); //设置原子变量v的值为i
atomic_t v = ATOMIC_INIT(0); //定义原子变量v,并初始化为0;
atomic_read(atomic_t* v); //返回原子变量v的值;
void atomic_add(int i, atomic_t* v); //原子变量v增加i;
void atomic_sub(int i, atomic_t* v);
void atomic_inc(atomic_t* v); //原子变量增加1;
void atomic_dec(atomic_t* v);
//先自增1,然后测试其值是否为0,若为0,则返回true,否则返回false;
int atomic_inc_and_test(atomic_t* v);
//先自减1,然后测试其值是否为0,若为0,则返回true,否则返回false;
int atomic_dec_and_test(atomic_t* v);
//先减i,然后测试其值是否为0,若为0,则返回true,否则返回false;
int atomic_sub_and_test(int i, atomic_t* v);
//注意:只有自加1,没有加i操作
//v的值加i后返回新的值;
int atomic_add_return(int i, atomic_t* v);
int atomic_sub_return(int i, atomic_t* v);
位原子操作常用操作如下
//设置地址addr的第nr位,所谓设置位,就是把位写为1;
void set_bit(int nr, volatile void* addr);
//清除地址addr的第nr位,所谓清除位,就是把位写为0;
void clear_bit(int nr, volatile void* addr);
//把地址addr的第nr位反转;
void change_bit(int nr, volatile void* addr);
//返回地址addr的第nr位;
int test_bit(int nr, volatile void* addr);
//将*addr 的第n位设置成1,并返回原来这一位的值
int test_and_set_bit(int nr, volatile void* addr);
//将*addr 的第n位设置成0,并返回原来这一位的值
int test_and_clear_bit(int nr, volatile void* addr);
//将*addr 的第n位翻转,并返回原来这一位的值
int test_and_change_bit(int nr, volatile void* addr);
实例
kernel/time/hrtimer.c
struct hrtimer_cpu_base {
......
atomic_t timer_waiters;
......
}
static void hrtimer_sync_wait_running(struct hrtimer_cpu_base *cpu_base,
unsigned long flags)
{
if (atomic_read(&cpu_base->timer_waiters)) {
raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
spin_unlock(&cpu_base->softirq_expiry_lock);
spin_lock(&cpu_base->softirq_expiry_lock);
raw_spin_lock_irq(&cpu_base->lock);
}
}
void hrtimer_cancel_wait_running(const struct hrtimer *timer)
{
/* Lockless read. Prevent the compiler from reloading it below */
struct hrtimer_clock_base *base = READ_ONCE(timer->base);
/*
* Just relax if the timer expires in hard interrupt context or if
* it is currently on the migration base.
*/
if (!timer->is_soft || is_migration_base(base)) {
cpu_relax();
return;
}
/*
* Mark the base as contended and grab the expiry lock, which is
* held by the softirq across the timer callback. Drop the lock
* immediately so the softirq can expire the next timer. In theory
* the timer could already be running again, but that's more than
* unlikely and just causes another wait loop.
*/
atomic_inc(&base->cpu_base->timer_waiters);
spin_lock_bh(&base->cpu_base->softirq_expiry_lock);
atomic_dec(&base->cpu_base->timer_waiters);
spin_unlock_bh(&base->cpu_base->softirq_expiry_lock);
}
drivers/staging/most/video/video.c
struct most_video_dev {
......
atomic_t access_ref;
......
};
static int comp_probe_channel(struct most_interface *iface, int channel_idx,
struct most_channel_config *ccfg, char *name,
char *args)
{
......
atomic_set(&mdev->access_ref, -1);
......
}
static int comp_vdev_open(struct file *filp)
{
if (!atomic_inc_and_test(&mdev->access_ref)) {
v4l2_err(&mdev->v4l2_dev, "too many clients\n");
ret = -EBUSY;
goto err_dec;
}
.....
err_dec:
atomic_dec(&mdev->access_ref);
}
static int comp_vdev_close(struct file *filp)
{
.....
atomic_dec(&mdev->access_ref);
.....
}
drivers/video/fbdev/s3c-fb.c
struct s3c_fb {
......
unsigned long irq_flags;
......
};
static void s3c_fb_enable_irq(struct s3c_fb *sfb)
{
void __iomem *regs = sfb->regs;
u32 irq_ctrl_reg;
if (!test_and_set_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) {
/* IRQ disabled, enable it */
irq_ctrl_reg = readl(regs + VIDINTCON0);
irq_ctrl_reg |= VIDINTCON0_INT_ENABLE;
irq_ctrl_reg |= VIDINTCON0_INT_FRAME;
irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL0_MASK;
irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_VSYNC;
irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL1_MASK;
irq_ctrl_reg |= VIDINTCON0_FRAMESEL1_NONE;
writel(irq_ctrl_reg, regs + VIDINTCON0);
}
}
static void s3c_fb_disable_irq(struct s3c_fb *sfb)
{
void __iomem *regs = sfb->regs;
u32 irq_ctrl_reg;
if (test_and_clear_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) {
/* IRQ enabled, disable it */
irq_ctrl_reg = readl(regs + VIDINTCON0);
irq_ctrl_reg &= ~VIDINTCON0_INT_FRAME;
irq_ctrl_reg &= ~VIDINTCON0_INT_ENABLE;
writel(irq_ctrl_reg, regs + VIDINTCON0);
}
}