天天看点

一、字符设备驱动5-同步互斥阻塞源码

原子操作

原子操作指在执行过程中不会被别的代码路径所中断的操作。

常用函数列举:

atomic_t v = ATOMIC_INIT(0);	// 定义原子变量v并初始化为0
atomic_read(atomic_t *v);		// 返回原子变量的值
void atomic_inc(atomic_t *v);	// 原子变量+1
void atomic_dec(atomic_t *v);	// 原子变量-1
int atomic_dec_and_test(atomic_t *v);	// 自减操作后测试其是否为0,=0则返回true,!=0则返回false
           

信号量

信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。

当获取不到信号量时,进程进入休眠等待状态。

相关操作函数:

struct semaphore sem;	// 定义信号量
void sema_init(struct semaphore *sem, int val);	// 初始化信号量
void init_MUTEX(struct semaphore *sem);		// 初始化为0

static DECLARE_MUTEX(button_lock);	// 定义并初始化互斥锁

void down(struct semaphore *sem);	// 获得信号量
int down_interruptible(struct semaphore *sem);	// 获取信号量,获取失败进入可中断睡眠态
int down_trylock(struct semaphore *sem);	// 获取信号量,获取失败也立即返回错误,不会休眠

void up(struct semaphore *sem);	// 释放信号量
           

阻塞操作

阻塞操作:在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

非阻塞操作:进程在不能进行设备操作时并不挂起,它或者放弃,或者不停的查询,直至可以进行操作为止。

如何使用?

在open时传入一个参数:

fd = open(“...”, O_RDWR | O_NONBLOCK);   // 若不加这个标记,默认是阻塞方式

源码

驱动程序:

/*
 * 引脚:PI0,1,2
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/signal.h>

#define IF_WANT_USE_ATOMIC	0	// 使用原子变量实现同一时刻只有一个应用程序能打开本驱动
#define IF_WANT_USE_SEMA	0	// 使用信号量实现同一时刻只有一个应用程序能打开本驱动
#define IF_WANT_USE_NOBLOCK	1	// 使用非阻塞打开本驱动,同时使用信号量
								// 第二次打开本驱动时,若定义为1则立即返回,若为0,则休眠直到资源能获取

#if IF_WANT_USE_ATOMIC
#include <asm/atomic.h>
#include <linux/types.h>
#endif
#if IF_WANT_USE_SEMA
#include <linux/semaphore.h>
#endif

struct pin_desc{
	int pin;
	int val;
};

/*
 * 按下时,返回:0x81, 0x82, 0x83
 * 
 */

static struct pin_desc pins_desc[3] = {
	{NUC970_PI0, 0x1},
	{NUC970_PI1, 0x2},
	{NUC970_PI2, 0x3},
};

static unsigned char val;
static DECLARE_WAIT_QUEUE_HEAD(buttons_waitq);
int evpress = 0;
struct fasync_struct *buttons_async;

#if IF_WANT_USE_ATOMIC
static atomic_t canopen = ATOMIC_INIT(1); // 定义并初始化原子变量
#endif

#if IF_WANT_USE_SEMA
static struct semaphore button_lock;
#endif

#if IF_WANT_USE_NOBLOCK
static struct semaphore button_lock;
#endif

static irqreturn_t buttons_handler(int irq, void *dev_id)
{
	struct pin_desc *pin = (struct pin_desc *)dev_id;
	//int res;

	//res = gpio_get_value(pin->pin);
	
	val = 0x80 | pin->val;

	evpress = 1;
	wake_up_interruptible(&buttons_waitq);
	kill_fasync(&buttons_async, SIGIO, POLL_IN); // 发送信号
	
	return IRQ_HANDLED;
}

static int buttons_open(struct inode *inode, struct file *filp)
{
	// request_irq会自动设置引脚,此处不再配置

#if IF_WANT_USE_ATOMIC
	if (!atomic_dec_and_test(&canopen))
	{
		atomic_inc(&canopen);
		return -EBUSY;
	}
#endif
#if IF_WANT_USE_SEMA
	down(&button_lock);
#endif
#if IF_WANT_USE_NOBLOCK
	if (filp->f_flags & O_NONBLOCK)
	{
		if (down_trylock(&button_lock))
		{
			return -EBUSY;
		}
	}
	else 
	{
		down(&button_lock);
	}
#endif

	request_irq(gpio_to_irq(pins_desc[0].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S1", &pins_desc[0]);
	request_irq(gpio_to_irq(pins_desc[1].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S2", &pins_desc[1]);
	request_irq(gpio_to_irq(pins_desc[2].pin), buttons_handler, IRQF_TRIGGER_FALLING, "S3", &pins_desc[2]);
	
	return 0;
}

static ssize_t buttons_read (struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
	if (count != 1)
	{
		return -EINVAL;
	}

#if IF_WANT_USE_NOBLOCK
	if (filp->f_flags & O_NONBLOCK)
	{
		if (evpress == 0)
		{
			return -EAGAIN;
		}
	}
#endif

	wait_event_interruptible(buttons_waitq, evpress);
	evpress = 0;

	copy_to_user(buf, &val, 1);
	
	return 1;
}

int buttons_release (struct inode *inode, struct file *filp)
{
	free_irq(gpio_to_irq(pins_desc[0].pin), &pins_desc[0]);
	free_irq(gpio_to_irq(pins_desc[1].pin), &pins_desc[1]);
	free_irq(gpio_to_irq(pins_desc[2].pin), &pins_desc[2]);

#if IF_WANT_USE_ATOMIC
	atomic_inc(&canopen);
#endif
#if (IF_WANT_USE_SEMA | IF_WANT_USE_NOBLOCK)
	up(&button_lock);
#endif	
	return 0;
}

static unsigned int buttons_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;

	poll_wait(file, &buttons_waitq, wait); // 不会立即休眠,只是把进程挂到buttons_waitq队列

	if (evpress)
	{
		mask = POLLIN | POLLRDNORM;
	}

	return mask;
}

// 每当应用程序修改FAYNC标记时,此程序就会被调用
static int buttons_fasync(int fd, struct file *filp, int on)
{
	return fasync_helper(fd, filp, on, &buttons_async); // 此函数会初始化buttons_async
}

static struct file_operations buttons_fops = {
	.owner   = THIS_MODULE,
	.open    = buttons_open,
	.read    = buttons_read,
	.release = buttons_release,
	.poll    = buttons_poll,
	.fasync  = buttons_fasync,
};

static int major;
static struct class *buttons_class;
static struct device *button_device;

static int buttons_init(void)
{
	major = register_chrdev(0, "buttons", &buttons_fops);
	
	buttons_class = class_create(THIS_MODULE, "buttons");
	button_device = device_create(buttons_class, NULL, MKDEV(major, 0), NULL, "buttons");

#if (IF_WANT_USE_SEMA | IF_WANT_USE_NOBLOCK)
	sema_init(&button_lock, 1);
#endif

	return 0;
}

static void buttons_exit(void)
{
	device_destroy(buttons_class, MKDEV(major, 0));
	class_destroy(buttons_class);

	unregister_chrdev(major, "buttons");
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");

           

测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <fcntl.h>

/* ./buttontest <O_BLOCK | O_NONBLOCK> */
int main(int argc, char *argv[])
{
	int fd;
	int ret;
	unsigned char key_val = 0;

	if (argc != 2)
	{
		printf("Usage:\n");
		printf("%s <O_BLOCK | O_NONBLOCK>\n", argv[0]);
		return -1;
	}

	if (strcmp(argv[1], "O_BLOCK") == 0)
	{
		fd = open("/dev/buttons", O_RDONLY);
	}
	else if (strcmp(argv[1], "O_NONBLOCK") == 0)
	{
		fd = open("/dev/buttons", O_RDONLY | O_NONBLOCK);
	}
	else 
	{
		printf("Usage:\n");
		printf("%s <O_BLOCK | O_NONBLOCK>\n", argv[0]);
		return -1;
	}
	if (fd < 0)
	{
		printf("Can't open /dev/buttons\n");
		return -1;
	}
	
	while (1)
	{
		ret = read(fd, &key_val, 1);
		printf("key_val = 0x%x, ret = %d\n", key_val, ret);
		sleep(5);
	}
	close(fd);
	return 0;
}