LINUX设备驱动二:字符设备的阻塞与非阻塞

     阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不
停地查询,直至可以进行操作为止。

一、linux设备驱动中的阻塞之等待队列

     在 Linux 驱动程序中,可以使用等待队列( Wait Queue )来实现阻塞进程的唤醒。

     在本例中使用如下:

               当一个读驱动进程访问驱动buff为空时阻塞并等待写进程的唤醒。

                当进程写驱动buff为满阻塞并等待读进程(读进程会取走一部分驱动buff空间)唤醒。

wait_queue_head_t my_queue;/*定义等待队列头部*/
init_waitqueue_head(&my_queue);/*初始化等待队列头部*/
DECLARE_WAIT_QUEUE_HEAD (name);/*定义并初始化等待队列头部*/
DECLARE_WAITQUEUE(name, tsk);/*定义等待队列元素*/
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);/*添加等待队列*/
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);/*移除等待队列*/

/* 等待事件,等待第 1 个参数queue作为等待队列头部的队列被唤醒,而且第2个参数condition必须满足*/
wait_event(queue, condition);/**/
wait_event_interruptible(queue, condition);/*可被信号打断*/
wait_event_timeout(queue, condition, timeout);/*超时退出*/
wait_event_interruptible_timeout(queue, condition, timeout);/*可超时和信号打断*/

/*唤醒队列*/
void wake_up(wait_queue_head_t *queue);/*与wait_event和wait_event_timeout配套使用*/
void wake_up_interruptible(wait_queue_head_t *queue);/*与上同理*/
/*使队列睡眠*/
sleep_on(wait_queue_head_t *q );/*于wake_up配对使用*/
interruptible_sleep_on(wait_queue_head_t *q );/*于wake_up_interruptible配对使用*/

 

二、linux设备驱动中并发于竞态控制

并发和竞态广泛存在,中断屏蔽、原子操作、自旋锁和互斥体都是解决并发问题的机制。中断屏蔽很少单独被
使用,原子操作只能针对整数进行,因此自旋锁和互斥体应用最为广泛。
自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小。互斥体允许临界区阻塞,可以适用于
临界区大的情况

    1.中断屏蔽:只能禁止和使能本 CPU 内的中断,因此,并不能解决 SMP 多 CPU 引发的竞态。

local_irq_disable()/* 屏蔽中断*/
/* 临界区 */
local_irq_enable()/* 开中断 */

    2.原子操作:原子操作可以保证对一个整型数据的修改是排他性的(只能针对整形).

    3.自旋锁:

       1)CPU 在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。

       2)自旋锁可能导致系统死锁。引发这个问题最常见的情况是递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的 CPU 想            第二次获得这个自旋锁,则该 CPU 将死锁。              

       3)在自旋锁锁定期间不能调用可能引起进程调度的函数。如果进程获得自旋锁之后再阻塞,如调用 copy_from_user ()、
          copy_to_user ()、 kmalloc ()和 msleep ()等函数,则可能导致内核的崩溃。

spinlock_t lock;     /*定义*/
spin_lock_init(&lock);/*初始化*/
spin_lock (&lock) ;    /*上锁*/
spin_unlock (&lock) ; /*解锁*/

    4.信号量:信号量与操作系统中的经典概念 PV 操作对应。    

struct semaphore sem;/*定义*/
void sema_init(struct semaphore *sem, int val);/*初始化*/
/*获取信号量*/
void down(struct semaphore * sem);/*会导致睡眠,因此不能在中断上下文中使用*/
int down_interruptible(struct semaphore * sem);/*能被信号打断*/
int down_trylock(struct semaphore * sem);/*非柱塞获取,可以在中断上下文中使用*/

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

    5.互斥锁:(linux中互斥尽量用mutex,不用semaphore->https://www.cnblogs.com/svitter/p/4005996.html)

      定义:include/linux/mutex.h

      实现:kernel/mutex.c

struct mutex my_mutex; /* 定义
mutex_init(&my_mutex); /* 初始化
mutex_lock(&my_mutex); /* 获取
mutex_unlock(&my_mutex); /* 释放

三、代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
//#include  
#include 


#define GLOBALFIFO_SIZE			 (uint32_t)20
#define GLOBALFIFO_MAJOR         (uint8_t)'g'
static int globalfifo_major = GLOBALFIFO_MAJOR;

struct FIFO_DEV{
	struct cdev cdev;
	uint32_t current_len;
	uint8_t mem[GLOBALFIFO_SIZE];
	struct mutex mutex;
	wait_queue_head_t r_wait;
	wait_queue_head_t w_wait;
};
struct FIFO_DEV *fifo_dev;

static int globalfifo_open(struct inode *inode, struct file *filp)
{
	filp->private_data = fifo_dev;
	return 0;
}

static ssize_t globalfifo_read(struct file *filp, char __user * buf, size_t size,loff_t * ppos)
{
	unsigned int count = size;
	int ret = 0;
	struct FIFO_DEV *dev = filp->private_data;
	
	DECLARE_WAITQUEUE(wait, current);//申明初始化等待队列
	mutex_lock(&dev->mutex);//获取互斥锁(获取失败睡眠)
	add_wait_queue(&dev->r_wait, &wait);//添加等待队列
	
	while (dev->current_len == 0) {
		if (filp->f_flags & O_NONBLOCK) {//如果是非阻塞退出
			ret = -EAGAIN;
			goto out;
		}
		__set_current_state(TASK_INTERRUPTIBLE);//标记状态进入浅睡眠
		mutex_unlock(&dev->mutex); //释放锁
		schedule(); //进程调度,本进程睡眠,等待写进程调用wake_up_interruptible(&dev->r_wait);唤醒  #include 
		if (signal_pending(current)) {//被唤醒如果是被信号唤醒 #include 
			ret = -ERESTARTSYS;
			goto out2;
		}
		mutex_lock(&dev->mutex);
	}
	
	if (count > dev->current_len)
		count = dev->current_len;

	if (copy_to_user(buf, dev->mem, count)) {
		ret = -EFAULT;
		goto out;
	} else {
		memcpy(dev->mem, dev->mem + count, dev->current_len - count);
		dev->current_len -= count;
		printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count,dev->current_len);
		wake_up_interruptible(&dev->w_wait);//唤醒可能的写阻塞
		ret = count;
	}
out:
	mutex_unlock(&dev->mutex);;
out2:
	remove_wait_queue(&dev->r_wait, &wait);
	set_current_state(TASK_RUNNING);
	return ret;
}

static ssize_t globalfifo_write(struct file *filp, const char __user * buf,size_t count, loff_t *ppos)
{
	struct FIFO_DEV *dev = filp->private_data;
	int ret;
	DECLARE_WAITQUEUE(wait, current);

	mutex_lock(&dev->mutex);
	add_wait_queue(&dev->w_wait, &wait);

	while (dev->current_len == GLOBALFIFO_SIZE) {
		if (filp->f_flags & O_NONBLOCK) {
			ret = -EAGAIN;
			goto out;
		}
		__set_current_state(TASK_INTERRUPTIBLE);

		mutex_unlock(&dev->mutex);

		schedule();
		if (signal_pending(current)) {
			ret = -ERESTARTSYS;
			goto out2;
		}

		mutex_lock(&dev->mutex);
	}
	if (count > GLOBALFIFO_SIZE - dev->current_len)
		count = GLOBALFIFO_SIZE - dev->current_len;
	if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
		ret = -EFAULT;
		goto out;
	} else {
		dev->current_len += count;
		printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count,dev->current_len);
		wake_up_interruptible(&dev->r_wait);
		ret = count;
	}

out:
	mutex_unlock(&dev->mutex);;
out2:
	remove_wait_queue(&dev->w_wait, &wait);
	set_current_state(TASK_RUNNING);
	return ret;
}

static const struct file_operations globalfifo_fops = {
	.owner = THIS_MODULE,
	//.llseek = globalfifo_llseek,
	.read = globalfifo_read,
	.write = globalfifo_write,
	//.unlocked_ioctl = globalfifo_ioctl,
	.open = globalfifo_open,
	//.release = globalfifomem_release,
};
struct class *globalfifo_class;
static int __init drive_init(void)
{
	int ret;
	dev_t devno = MKDEV(globalfifo_major,0); 
	if(globalfifo_major)
		ret = register_chrdev_region(devno, 1, "drive_fifo");
	else{
		ret = alloc_chrdev_region(&devno, 0, 1, "drive_fifo");
		globalfifo_major = MAJOR(devno);
	}
	printk("\n alloc char device num .\n");
	if (ret < 0)
		return ret;
	
	fifo_dev = kzalloc(sizeof(struct FIFO_DEV), GFP_KERNEL);
 	if (!fifo_dev) {
 		ret = -ENOMEM;
 		goto fail_malloc;
 	}

	cdev_init(&fifo_dev->cdev, &globalfifo_fops);
	//ifo_dev->cdev.owner = THIS_MODULE;
	ret = cdev_add(&fifo_dev->cdev, devno, 1);
 	if (ret)
 		 printk(KERN_NOTICE "Error %d adding drive_fifo", ret);
 	/*创建一个设备节点*/
	//device_create(class_create(THIS_MODULE, "drive_fifo"), NULL, devno, NULL, "drive_fifo");
 	globalfifo_class = class_create(THIS_MODULE, "drive_fifo_class");
 	if(globalfifo_class == NULL)
 	{
 		printk("create class failed! \n");
 		return -1;
 	}
 	device_create(globalfifo_class,NULL, devno, NULL, "drive_fifo_node");
	
	mutex_init(&fifo_dev->mutex);
	init_waitqueue_head(&fifo_dev->r_wait);
	init_waitqueue_head(&fifo_dev->w_wait);
	
 	return 0;
 	
fail_malloc:
	unregister_chrdev_region(devno, 1);
	return ret;
}
module_init(drive_init);

static void __exit drive_exit(void)
{
	cdev_del(&fifo_dev->cdev);
	kfree(fifo_dev);
	unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
    device_destroy(globalfifo_class, MKDEV(globalfifo_major, 0));         //delete device node under /dev
    class_destroy(globalfifo_class);                               //delete class created under sys/class
}
module_exit(drive_exit);

MODULE_AUTHOR("Barry Song ");
MODULE_LICENSE("GPL v2");

四、测试

     1.启动两个进程,一个进程 cat/dev/drive_fifo_node & 在后台执行,一个进程 “echo 字符串 /dev/drive_fifo_node” 在前台执行。      

# cat /dev/globalfifo &
[1] 20910   //终端输出进程号 可用kill -9 num结束进程
# echo 'I want to be' > /dev/globalfifo
I want to be
# echo 'a great Chinese Linux Kernel Developer' > /dev/globalfifo
a great Chinese Linux kernel Developer

注;往 /dev/drive_fifo_node 里面 echo 需要 root 权限,直接运行 “sudo echo” 是不行的,可先sudo su 进入超级权限模式

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(linux学习笔记)