linux内核同步机制编程框架

linux系统中出现并发与竞态

相关概念:

  • 并发:多个执行单元(进程和中断)同时发生
  • 竞态:多个执行单元对共享资源的同时访问所形成的竞争的状态
须具备以下三个条件:
1. 必须有多个执行单元
2. 必须有共享资源
3. 必须同时访问
  • 共享资源:比如软件上的全局变量,或者是硬件资源
  • 可重入内核:所有的UNIX内核都是可重入的,这意味着若干个进程可以同时在内核态下执行。当然,在单处理器系统上只有一个进程在真正运行,但是有许多进程可能在等待CPU或某一I/O操作完成时再内核态下被阻塞。提供可重入的一种方式是编写函数,在这些函数中只能修改局部变量,而不能改变全局变量,这样的函数叫可重入函数。但是可重入内核,不仅仅局限于这样的可重入函数(尽管一些实时内核正是如此实现的)。相反,可重入内核可以包含非重入函数,并且利用锁机制保证一次只有一个进程执行一个非重入函数。摘自——《深入理解linux内核》第三版
  • 临界区:对共享资源访问的代码区域。
  • 互斥访问:当一个执行单元在访问临界区时,其他执行单元禁止访问临界区,直到访问临界区任务结束。
  • 执行路径具有原子性:当某个任务获取到CPU资源访问临界区时,不允许发生CPU资源的切换,要保证这个任务能够顺利访问临界区,而其他任务等待。

四种情形:

  • 多个CPU核之间的竞争:比如对共享资源(内存,GPIO等)的抢占
  • 同一个CPU上进程与进程之间对共享资源的抢占
  • 中断与进程之间(硬中断与软中断)
  • 中断与中断之间
    图形说明描述如下在这里插入图片描述

linux内核解决竞态引起的异常的方法:即同步方法

中断屏蔽

概念

这是在单处理器系统上的常用的一种同步机制:在进入临界区之前禁止所有的硬件中断,离开时再重新启用中断。这种机制尽管简单,但远不是最佳的。因为如果临界区比较大,那么在一个相对较长的时间内持续禁止中断就可能使所有的硬件活动处于冻结状态。

确保一组内核语句被当作一个临界区处理的主要机制之一就是 中断禁止。即使当硬件设备产生了一个IRQ信号时,中断禁止也让内核控制路径继续执行,因此,这就提供了一种有效的方式,确保中断处理程序访问的数据结构也受到保护。然而,禁止本地中断并不保护运行在另一个CPU上的中断处理程序对数据结构的并发访问,因此,在多处理器系统上,禁止本地中断经常与自旋锁结合使用。

摘自——《深入理解linux内核》第三版

特点
  1. 能够解决进程与进程之间的抢占引起的异常(进程之间的抢占本身基于软中断实现)
  2. 能够解决中断和进程的抢占引起的异常
  3. 能够解决中断和中断引起的异常
  4. 无法解决多核CPU引起的异常
  5. 中断屏蔽保护的临界区的代码执行速度要越快越好,更不能进行休眠操作。因为 linux 系统的很多机制跟中断密切相关(tasklet,软件定时器,硬件定时器等)长时间的屏蔽中断非常危险。
中断屏蔽的编程步骤

在这里插入图片描述
注意:屏蔽中断和恢复中断务必要逻辑上成对使用

中断可以以嵌套的方式执行,所以内核不必知道当前控制路径被执行之前IF禁止位的值是什么。在这种情况下,控制路径必须保存先前赋给该标志位上的值,并在执行结束时恢复它。摘自——《深入理解linux内核》第三版

中断屏蔽相关宏函数
  • local_irq_disable()
    定义在include\linux\irqflags.h
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_disable() \
	do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
#else
#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)
#endif

功能:宏 local_irq_disable()使用cli汇编语言指令关闭本地CPU上的中断。汇编语言指令cli设置eflags控制寄存器的IF标志。

IRQ配置位I:BIT[7] BIT[7] = 1 (禁止IRQ中断功能)
FIQ配置位F:BIT[6] BIT[6] = 1 (禁止FIQ中断功能)
  • local_irq_enable()
    定义在include\linux\irqflags.h
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_enable() \
	do { trace_hardirqs_on(); raw_local_irq_enable(); } while (0)
#else
#define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
#endif

功能:宏 local_irq_enable()使用sti汇编语言指令打开本地CPU上被关闭的中断。汇编语言指令sti设置eflags控制寄存器的IF标志。(参考上图)

  • local_irq_save(flags)
    定义在include\linux\irqflags.h
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_save(flags)				\
	do {						\
		raw_local_irq_save(flags);		\
		trace_hardirqs_off();			\
	} while (0)
#else
#define local_irq_save(flags)					\
	do {							\
		raw_local_irq_save(flags);			\
	} while (0)
#endif

功能:宏 local_irq_save(flags)先把eflags寄存器的内容拷贝到一个局部变量中,随后使用cli汇编指令将IF禁止位置1,关闭中断。

  • local_irq_restore(flags)
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_restore(flags)			\
	do {						\
		if (raw_irqs_disabled_flags(flags)) {	\
			raw_local_irq_restore(flags);	\
			trace_hardirqs_off();		\
		} else {				\
			trace_hardirqs_on();		\
			raw_local_irq_restore(flags);	\
		}					\
	} while (0)
#else
#define local_irq_restore(flags) \
 do { raw_local_irq_restore(flags); } while (0)
#endif

功能:宏 local_irq_restore(flags)恢复eflags原来的的内容。

应用实例

使用中断屏蔽解决LED灯被多个进程访问所引发的竞态问题,见下图
在这里插入图片描述

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//共享资源
static int open_cnt = 1; //记录LED打开的状态开关
static int led_open(struct inode *inode, struct file *file)
{
    unsigned long flags;
    //屏蔽中断
    local_irq_save(flags);
    //临界区
    if(--open_cnt != 0) {
        printk("设备已被打开!\n");
        open_cnt++;
        //恢复中断
        local_irq_restore(flags);
        return -EBUSY;//返回设备忙错误码
    }
    //恢复中断
    local_irq_restore(flags);
    printk("设备打开成功!\n");
    return 0; //open返回成功
}
static int led_close(struct inode *inode, struct file *file)
{
    unsigned long flags;
    //屏蔽中断
    local_irq_save(flags);
    //临界区
    open_cnt++;
    //恢复中断
    local_irq_restore(flags);
    return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};
static int led_init(void)
{
    misc_register(&led_misc);
    return 0;
}
static void led_exit(void)
{
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

自旋锁

概念

一种广泛应用的同步技术是加锁(locking)。当内核控制路径必须访问共享数据结构或进入临界区时,就需要为自己获取一把“锁”。锁机制保护的资源非常类似于限制在房间内的资源,当某人进入房间时,就把门锁上。如果内核控制路径希望访问资源,就试图获取钥匙“打开门”。当且仅当资源空闲时,它才能成功。然后,只要它还想使用这个资源,门就依然锁着。当内核控制路径释放了锁时,门就被打开,另一个内核控制路径就可以进入房间。如下图
上图显示了锁的使用。5个内核控制路径(P0,P1,P2,P3和P4)试图访问两个临界区(C1和C2)。内核控制路径P0正在C1中,而P2和P4正等待进入C1。同时,P1正在C2中,而P3正在等待进入C2。注意P0和P1可以并行运行。临界区C3的锁现在大开着,因为没有内核控制路径需要进入C3。
上图显示了锁的使用。5个内核控制路径(P0,P1,P2,P3和P4)试图访问两个临界区(C1和C2)。内核控制路径P0正在C1中,而P2和P4正等待进入C1。同时,P1正在C2中,而P3正在等待进入C2。注意P0和P1可以并行运行。临界区C3的锁现在大开着,因为没有内核控制路径需要进入C3。

自旋锁(spin lock)是用来在多处理器环境中工作的一种特殊的锁。如果内核控制路径发现自旋锁“开着”,就获取锁并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在周围“旋转”,反复执行一条紧凑的循环指令,直到锁被释放。

自旋锁的循环指令表示“忙等"。即使等待的内核控制路径无事可做(除了浪费时间),它也在CPU上保持运行。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段;所以说,释放CPU和随后又获得CPU都不会消耗多少时间。

一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的。在单处理器系统上,这种锁本身并不起锁的作用,自旋锁原语仅仅是禁止或启用内核抢占。注意:在自旋锁忙等期间,内核抢占还是有效的,因此,等待自旋锁释放的进程有可能被更高优先级的进程替代。
摘自——《深入理解linux内核》第三版

特点
  1. 自旋锁能够解决多核引起的竞态问题
  2. 自旋锁能够解决进程与进程之前的抢占引起的竞态问题
  3. 自旋锁无法解决中断引起的竞态问题
  4. 自旋锁保护的临界区的代码执行速度要快,更不能进行休眠操作
  5. 没有获取自旋锁的任务将会原地忙等待(原地空转)
利用自旋锁同步的编程步骤

在这里插入图片描述
注意:获取自旋锁和释放自旋锁务必要逻辑上成对使用

自旋锁相关定义和配套宏函数
  • spinlock_t
    在linux内核中,每个自旋锁都用spinlock_t结构体来表示,其中包含两个字段:
  1. slock
    该字段表示自旋锁的状态:值为1表示“未加锁”状态,而任何负数和0都表示“加锁”状态。
  2. break_lock
    表示进程正在忙等自旋锁(只在内核支持SMP和内核抢占的情况下使用)

spinlock_t结构体在linux内核3.1.18中的定义如下
定义在include\linux\spinlock_types.h

typedef struct spinlock {
	union {
		struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct {
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		};
#endif
	};
} spinlock_t;

struct raw_spinlock定义如下
定义在include\linux\spinlock_types.h

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
/*表示进程正在忙等自旋锁(只在内核支持SMP和内核抢占的情况下使用)*/
	unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;

arch_spinlock_t定义如下
定义在arch\arc\include\asm\spinlock_types.h

typedef struct {
/*该字段表示自旋锁的状态:值为1表示“未加锁”状态,而任何负数和0都表示“加锁”状态*/
	volatile unsigned int slock;
} arch_spinlock_t;

  • spin_lock_init()
    定义在include\linux\spinock.h
#define spin_lock_init(_lock)				\
do {							\
	spinlock_check(_lock);				\
	raw_spin_lock_init(&(_lock)->rlock);		\
} while (0)

功能:动态初始化自旋锁,并把自旋锁置为1(未锁)

  • void spin_lock(spinlock_t *)
    定义在include\linux\spinock.h
static inline void spin_lock(spinlock_t *lock)
{
	raw_spin_lock(&lock->rlock);
}

功能:循环等待,直到自旋锁变成1(未锁),然后把自旋锁置为0(锁上),即获取指定的自旋锁

  • spin_unlock()
    定义在include\linux\spinock.h
static inline void spin_unlock(spinlock_t *lock)
{
	raw_spin_unlock(&lock->rlock);
}

功能:把自旋锁置为1(未锁),即释放指定的锁

应用实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>

//共享资源
static int open_cnt = 1; //记录LED打开的状态开关

//定义自旋锁对象
static spinlock_t lock;

static int led_open(struct inode *inode, 
                        struct file *file)
{
    //获取自旋锁
    spin_lock(&lock);
    //临界区
    if(--open_cnt != 0) {
        printk("设备已被打开!\n");
        open_cnt++;
        //释放自旋锁
        spin_unlock(&lock);
        return -EBUSY;//返回设备忙错误码
    }
    //释放自旋锁
    spin_unlock(&lock);
    printk("设备打开成功!\n");
    return 0; //open返回成功
}
static int led_close(struct inode *inode, 
                        struct file *file)
{
    //获取自旋锁
    spin_lock(&lock);
    //临界区
    open_cnt++;
    //释放自旋锁
    spin_unlock(&lock);
    return 0;
}

//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close
};

//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};

static int led_init(void)
{
    misc_register(&led_misc);
    //初始化自旋锁对象
    spin_lock_init(&lock);
    return 0;
}

static void led_exit(void)
{
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

衍生自旋锁

将以上中断屏蔽和自旋锁组合起来,可以解决所有竞态引起的同步问题。

介绍两个相关的函数
  • spin_lock_irqsave(lock, flags)
#define spin_lock_irqsave(lock, flags)				\
do {								\
	raw_spin_lock_irqsave(spinlock_check(lock), flags);	\
} while (0)

功能:保存本地中断的当前状态,禁止本地中断,并获取指定的锁

  • void spin_unlock_irqrestore(spinlock_t *, unsigned long )
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
	raw_spin_unlock_irqrestore(&lock->rlock, flags);
}

功能:释放指定的锁,并让本地中断恢复到以前的状态。

应用实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>

//共享资源
static int open_cnt = 1; //记录LED打开的状态开关

//定义自旋锁对象
static spinlock_t lock;

static int led_open(struct inode *inode, 
                        struct file *file)
{
    unsigned long flags;
    //屏蔽中断,获取衍生自旋锁
    spin_lock_irqsave(&lock, flags);
    //临界区
    if(--open_cnt != 0) {
        printk("设备已被打开!\n");
        open_cnt++;
        //释放自旋锁,恢复中断
        spin_unlock_irqrestore(&lock, flags);
        return -EBUSY;//返回设备忙错误码
    }
    //释放自旋锁,恢复中断
    spin_unlock_irqrestore(&lock, flags);
    printk("设备打开成功!\n");
    return 0; //open返回成功
}
static int led_close(struct inode *inode, 
                        struct file *file)
{
    unsigned long flags;
    //屏蔽中断,获取衍生自旋锁
    spin_lock_irqsave(&lock, flags);
    //临界区
    open_cnt++;
    //释放自旋锁,恢复中断
    spin_unlock_irqrestore(&lock, flags);
    return 0;
}

//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close
};

//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};

static int led_init(void)
{
    misc_register(&led_misc);
    //初始化自旋锁对象
    spin_lock_init(&lock);
    return 0;
}

static void led_exit(void)
{
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

信号量

概念

广泛使用的一种机制是信号量(semaphore),它在单处理器系统和多处理器系统上都有效。信号量仅仅是与一个数据结构相关的计数器。所有内核线程在试图访问这个数据结构之前,都要检查这个信号量。可以把每个信号量看成一个对象,其组成如下:

  • 一个整数变量
  • 一个等待进程的链表
  • 两个原子方法:down()和up()

down()方法对信号量的值减1,如果这个新值小于0,该方法就把正在运行的进程加入到这个信号量链表,然后阻塞该进程(即调用调度程序)。
up()方法对信号量的值加1,如果这个新值大于或等于0,则激活这个信号量链表中的一个或多个进程。

每个要保护的数据结构都有它自己的信号量,其初始值为1。当内核控制路径希望访问这个数据结构时,它在相应的信号量上执行down()方法。如果信号量的当前值不是负数,则允许访问这个数据结构。否则,把执行内核控制路径的进程加入到这个信号量的链表并阻塞该进程。当另一个进程在那个信号量上执行up()方法时,允许信号量链表上的一个进程继续执行。

从本质上说,它们实现了一个加锁原语,即让等待者睡眠,知道等待的资源变为空闲。实际上,Linux提供两种信号量:

  • 内核信号量,由内核控制路径使用
  • System V IPC信号量,由用户态进程使用。
    这里我们讨论内核信号量。
    摘自——《深入理解linux内核》第三版
特点
  • 信号量又称睡眠锁,基于自旋锁实现的。
  • 信号量保护的临界区可以进行休眠操作。
  • 信号量仅仅用于进程
  • 如果进程获取信号量失败,那么进程将进行休眠
内核信号量定义和相关宏函数
  • linux 内核描述信号量的数据结构: struct semaphore
/* Please don't access any members of this structure directly */
struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;  //在本案赋值为1
	struct list_head	wait_list;
};
  • void sema_init(struct semaphore *sem, int val)
static inline void sema_init(struct semaphore *sem, int val)
{
	static struct lock_class_key __key;
	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
	lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
#define __SEMAPHORE_INITIALIZER(name, n)				\
{									\
	.lock		= __RAW_SPIN_LOCK_UNLOCKED((name).lock),	\
	.count		= n,						\
	.wait_list	= LIST_HEAD_INIT((name).wait_list),		\
}

功能:初始化信号量对象

  • void down(struct semaphore *sem);
/**
 * down - acquire the semaphore
 * @sem: the semaphore to be acquired
 *
 * Acquires the semaphore.  If no more tasks are allowed to acquire the
 * semaphore, calling this function will put the task to sleep until the semaphore is released.
 *
 * Use of this function is deprecated, please use down_interruptible() or
 * down_killable() instead.
 */
void down(struct semaphore *sem)
{
	unsigned long flags;

	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		__down(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(down);

功能:获取信号量,如果获取信号量成功,进程从此函数中立马返回
然后可以踏踏实实的访问临界区,如果获取信号量失败,进程将进入此函数中进入不可中断的休眠状态(释放 CPU 资源,在休眠期间接收到信号不会立即处理信号)
“不可中断的休眠状态”:进程在休眠期间,如果接收到了一个 kill 信号
进程不会立即处理接收到的信号,而是获取信号量的任务释放信号量以后唤醒这个休眠的进程,进程一旦被唤醒以后会处理之前接收到的信号

  • int down_interruptible(struct semaphore *sem)
/**
 * down_interruptible - acquire the semaphore unless interrupted
 * @sem: the semaphore to be acquired
 *
 * Attempts to acquire the semaphore.  If no more tasks are allowed to
 * acquire the semaphore, calling this function will put the task to sleep.
 * If the sleep is interrupted by a signal, this function will return -EINTR.(#define	EINTR		 4	/* Interrupted system call */* If the semaphore is successfully acquired, this function returns 0.
 */
int down_interruptible(struct semaphore *sem)
{
	unsigned long flags;
	int result = 0;

	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		result = __down_interruptible(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);

	return result;
}
EXPORT_SYMBOL(down_interruptible);

功能:获取信号量,如果获取信号量成功,进程从此函数中立马
返回,然后去访问临界区,如果获取信号量失败,进程将进入可中断的休眠状态
“可中断的休眠状态”:进程在休眠期间,如果接收到了一个信号进程会被立即唤醒并且处理接收到的信号

  • int down_killable(struct semaphore *sem);
/**
 * down_killable - acquire the semaphore unless killed
 * @sem: the semaphore to be acquired
 *
 * Attempts to acquire the semaphore.  If no more tasks are allowed to
 * acquire the semaphore, calling this function will put the task to sleep.
 * If the sleep is interrupted by a fatal signal, this function will return
 * -EINTR.  If the semaphore is successfully acquired, this function returns
 * 0.
 */
int down_killable(struct semaphore *sem)
{
	unsigned long flags;
	int result = 0;

	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		result = __down_killable(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);

	return result;
}
EXPORT_SYMBOL(down_killable);
  • void up(struct semaphore *sem)
/**
 * up - release the semaphore
 * @sem: the semaphore to release
 *
 * Release the semaphore.  Unlike mutexes, up() may be called from any context and even by tasks which have never called down().
 */
void up(struct semaphore *sem)
{
	unsigned long flags;

	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(list_empty(&sem->wait_list)))
		sem->count++;
	else
		__up(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);

功能:释放信号量

应用案例
//头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/semaphore.h>
//声明描述LED灯硬件信息的结构体
static struct led_resource{
    int gpio;
    char* name;
};
//定义信号量对象
static struct semaphore sem;
//定义初始化led2灯的硬件信息
struct led_resource led2_info ={PAD_GPIO_C+7, "LED2"};
//led_open
static int led_open(struct inode* inode, struct file* file){
    //获取信号量
    down(&sem);
    //开灯
    gpio_set_value(led2_info.gpio, 0);
    return 0;
}
//led_close
static int led_close(struct inode* inode, struct file* file){
    //释放信号量
    up(&sem);
    return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .open = led_open,
    .release = led_close,
    .owner = THIS_MODULE
};
//定义初始化一个混杂设备对象
static struct miscdevice led2_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .fops = &led_fops,
    .name = "v_led2"
};
//入口函数
static int led_drv_init(void){
    gpio_request(led2_info.gpio, led2_info.name);
    gpio_direction_output(led2_info.gpio, 1);

    misc_register(&led2_misc);
    //初始化信号量
    sema_init(&sem, 1);
    return 0;
}
//出口函数
static void led_drv_exit(void){
    gpio_set_value(led2_info.gpio, 1);
    gpio_free(led2_info.gpio);

    misc_deregister(&led2_misc);
}
//各种修饰和GPL规则
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main(void){
    int fd = open("/dev/v_led2", O_RDWR);
    
    sleep(60);

    close(fd);
    return 0;
}
遇到问题
/tmp/vaccine # insmod led_drv.ko
/tmp/vaccine # ./led_test & //启动 A 进程,A 进程打开成功,A 进程调用 sleep 进行休眠 不关闭
/tmp/vaccine # ./led_test & // 启动 B 进程,B 进程获取信号量失败,进入不可中断的休眠状态,等待 A 进程来唤醒
ps    //查看 A,B 的 PID
top  //查看 A,B 的进程状态
	S:进程进入可中断的休眠状态
	D:进程进入不可中断的休眠状态

在上位机进行以上操作之后,结果出现吐核现象如下

/tmp/vaccine # [  310.229000] INFO: task led_test:124 blocked for more than 10 seconds.
[  310.230000] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[  310.232000] led_test        D c06cdb3c     0   124     85 0x00000000
[  310.235000] [<c06cdb3c>] (__schedule+0x384/0x7d4) from [<c06cc190>] (schedule_timeout+0x1bc/0x2f8)
[  310.236000] [<c06cc190>] (schedule_timeout+0x1bc/0x2f8) from [<c06cd1f8>] (__down+0x6c/0x9c)
[  310.237000] [<c06cd1f8>] (__down+0x6c/0x9c) from [<c0066320>] (down+0x44/0x4c)
[  310.238000] [<c0066320>] (down+0x44/0x4c) from [<bf000028>] (led_open+0x10/0x2c [led_drv])
[  310.240000] [<bf000028>] (led_open+0x10/0x2c [led_drv]) from [<c02c49e8>] (misc_open+0x134/0x1a8)
[  310.242000] [<c02c49e8>] (misc_open+0x134/0x1a8) from [<c0120c0c>] (chrdev_open+0x88/0x170)
[  310.243000] [<c0120c0c>] (chrdev_open+0x88/0x170) from [<c011b0d4>] (__dentry_open+0x200/0x2b8)
[  310.245000] [<c011b0d4>] (__dentry_open+0x200/0x2b8) from [<c011c1bc>] (nameidata_to_filp+0x60/0x68)
[  310.246000] [<c011c1bc>] (nameidata_to_filp+0x60/0x68) from [<c012a04c>] (do_last+0x35c/0x7ec)
[  310.247000] [<c012a04c>] (do_last+0x35c/0x7ec) from [<c012a6e4>] (path_openat+0xbc/0x3d8)
[  310.248000] [<c012a6e4>] (path_openat+0xbc/0x3d8) from [<c012aad8>] (do_filp_open+0x2c/0x80)
[  310.249000] [<c012aad8>] (do_filp_open+0x2c/0x80) from [<c011c2ac>] (do_sys_open+0xe8/0x174)
[  310.250000] [<c011c2ac>] (do_sys_open+0xe8/0x174) from [<c000ec80>] (ret_fast_syscall+0x0/0x30)

解决办法:下位机执行:
原因是因为进程led_test休眠超过10秒

echo 0 > /proc/sys/kernel/hung_task_timeout_secs

原子操作

概念

若干汇编语言指令具有“读——修改——写”类型——也就是说,它们访问存储单元两次,第一次读原值,第二次写新值。

假定运行在两个CPU上的两个内核控制路径试图通过执行非原子操作来同时 “读——修改——写” 同一存储器单元。首先,两个CPU都试图读同一单元,但是存储器仲裁器【对访问RAM芯片的操作进行串行化的硬件电路】插手,只允许其中的一个访问而让另一个延迟。然而,当第一个读操作已经完成后,延迟的CPU从那个存储器单元正好读到同一个(旧)值。然后,两个CPU都试图向那个存储器单元写一个新值,总线存储器访问再一次被存储器仲裁器串行化,最终,两个写操作都成功。但是,全局的结果是不对的,因为两个CPU写入同一(新)值。因此,两个交错的 “读——修改——写” 操作成了一个单独的操作。

避免由于 “读——修改——写” 指令引起的竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,中间不能中断,且避免其他的CPU访问同一存储器单元。
在这里插入图片描述
在编写C代码程序时,并不能保证编译器会为a=a+1 或甚至像a++ 这样的操作使用一个原子指令。因此,linux内核提供了一个专门的atomic_t类型(一个原子访问计数器)和一些专门的函数和宏。这些函数和宏作用于atomic_t类型的变量,并当做单独的,原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个lock字节的前缀。

特点
  • 原子操作能够解决所有的竞态问题
  • linux内核原子操作分两类:位原子操作和整型原子操作
原子操作的相关宏或者函数
位原子操作的相关宏或者函数
  • 位原子操作:即是位操作的过程具有原子性,对共享资源进行位操作的过程中,不允许发生CPU资源的切换。

本文介绍的是arm芯片架构下的宏定义,其操作上具有原子性
都定义在arch\arm\include\asm\bitops.h

  • set_bit(nr, p)
#define set_bit(nr,p)			ATOMIC_BITOP(set_bit,nr,p)
#ifndef CONFIG_SMP
/*
 * The __* form of bitops are non-atomic and may be reordered.
 */
#define ATOMIC_BITOP(name,nr,p)			\
	(__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
#else
#define ATOMIC_BITOP(name,nr,p)		_##name(nr,p)
#endif

功能:将p地址内数据的第 nr 位置 1(nr 从 0 开始),具有原子性,不允许发生CPU资源的切换。

  • clear_bit(nr, p)
#define clear_bit(nr,p)			ATOMIC_BITOP(clear_bit,nr,p)

功能:将p地址内数据的第 nr 位清 0(nr 从 0 开始),具有原子性,不允许发生CPU资源的切换。

  • change_bit(nr, p)
#define change_bit(nr,p)		ATOMIC_BITOP(change_bit,nr,p)

功能:将p地址内数据的第 nr 位反转(nr 从 0 开始),具有原子性,不允许发生CPU资源的切换。

  • test_and_set_bit(nr,p)
#define test_and_set_bit(nr,p)  ATOMIC_BITOP(test_and_set_bit,nr,p)

功能:将p地址内数据的第 nr 位置 1(nr 从 0 开始),并返回原来的值

  • test_and_clear_bit(nr,p)
#define test_and_clear_bit(nr,p) \
ATOMIC_BITOP(test_and_clear_bit,nr,p)

功能:将p地址内数据的第 nr 位清 0(nr 从 0 开始),并返回原来的值

  • test_and_change_bit(nr,p)
#define test_and_change_bit(nr,p)  \
ATOMIC_BITOP(test_and_change_bit,nr,p)

功能:将p地址内数据的第 nr 位反转(nr 从 0 开始),并返回原来的值

注意:以上函数不要被表面给欺骗,它们的内在核心在于在 addr 地址内数据进行位操作过程不允许发生 CPU 资源切换以上函数的操作对象一定是共享资源,虽说也可以利用以上函数对非共享资源进行位操作,但是致命的后果就是代码执行效率降低

  • 举例
参考代码:
int open_cnt = 1; //共享资源
open_cnt &= ~(1 << 1); //临界区,处于裸奔状态,如果考虑竞态问题,此代码相当危险
优化:
采用中断屏蔽
local_irq_save
open_cnt &= ~(1 << 1);
local_irq_restore
采用衍生自旋锁
spin_lock_irqsave
open_cnt &= ~(1 << 1);
spin_unlock_irqrestore
采用信号量
down
open_cnt &= ~(1 << 1);
up
采用位原子操作
clear_bit(1, &open_cnt)
整型原子操作的相关宏或者函数
  • 整型原子操作:即是对整型数的操作具有原子性,在对驱动代码的共享资源进行整型操作过程中,不允许发生CPU资源的切换。

  • 数据类型
    linux 内核对于整形原子操作专门提供了一个数据类型: atomic_t
    定义在include\linux\types.h

typedef struct {
	int counter;
} atomic_t;
  • 相关宏函数
#define ATOMIC_INIT(i)		{ (i) }

第一个宏用来定义,比如

static atomic_t my_counter = ATOMIC_INIT(1);
void atomic_set(atomic_t *v, int i);   //设置原子变量v的值为i
atomic_t v = ATOMIC_INIT(0);            //定义原子变量v, 并初始化为0  **************************
atomic_read(atomic_t *v);        //获得原子变量的值,返回原子变量的值
void atomic_add(int i, atomic_t *v);    //原子整型变量+i
void atomic_sub(int i, atomic_t *v);    //原子整型变量-i
void atomic_inc(atomic_t *v);           //原子整型变量自增           *******************************
void atomic_dec(atomic_t *v);           //原子整型变量自减
int atomic_inc_and_test(atomic_t *v); //原子整型变量自增后,判断其是否为0,为0返回真,不为0,返回假
int atomic_dec_and_test(atomic_t *v);//原子整型变量自减后,判断其是否为0,为0返回真,不为0,返回假              
int atomic_sub_and_test(int i, atomic_t *v);//原子整型变量减去i,判断其是否为0,为0返回真,不为0,返回假       
int atomic_add_return(int i, atomic_t *v); //原子整型变量加上i,然后返回新的值
int atomic_sub_return(int i, atomic_t *v);//原子整型变量减去i,然后返回新的值
int atomic_inc_return(atomic_t *v);//原子整型变量自增后,然后返回新的值
int atomic_sub_return(atomic_t *v);//原子整型变量自减后,然后返回新的值
应用案例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//定义原子变量,初始化为 1
static atomic_t open_cnt = ATOMIC_INIT(1); //记录LED打开的状态开关
static int led_open(struct inode *inode, struct file *file)
{
    if(!atomic_dec_and_test(&open_cnt)) {
        printk("设备已被打开!\n");
        atomic_inc(&open_cnt);
        return -EBUSY;//返回设备忙错误码
    }
    printk("设备打开成功!\n");
    return 0; //open返回成功
}
static int led_close(struct inode *inode, 
                        struct file *file)
{
    atomic_inc(&open_cnt);
    return 0;
}

//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close
};

//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};

static int led_init(void)
{
    misc_register(&led_misc);
    return 0;
}

static void led_exit(void)
{
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

猜你喜欢

转载自blog.csdn.net/weixin_43326587/article/details/107430928