`
dawuafang
  • 浏览: 1100687 次
文章分类
社区版块
存档分类
最新评论

Linux驱动学习7(阻塞IO的实现)

 
阅读更多
一、首先定义了一个等待任务头,如下两种定义方式:


1)静态定义并初始化,一个函数执行完两个操作,一步到位!
DECLARE_WAIT_QUEUE_HEAD(name) //使用:定义并初始化一个叫name的等待队列。
2)分开两步执行。
2.1)定义
wait_queue_head_t test_queue;
2.2)初始化
init_waitqueue_head(&test_queue);



这时候或许会好奇wait_queue_head_t是何方神圣,能够这样用,虽然我们已经猜到了
这是一个结构体,但是不妨进去看一看
struct __wait_queue_head {
	spinlock_t lock;							//自旋锁
	struct list_head task_list;		//等待任务链表头
};
typedef struct __wait_queue_head wait_queue_head_t;




果然是一个结构体。


二、第1步只是定义了一个等待任务头,那么既然有头,就肯定会有等待队列了
关于等待队列的使用暂且不提,等下用到再说


三、接着我们来了解一下进程休眠所需要的步骤
0. 定义并初始化(如果还没有的话)一个等待队列头(wait_queue_head_t),这个等待队列头应该是能被要休
眠的进程和负责唤醒的进程都能访问到。
1. 对进程的每次休眠,定义并初始化一个等待队列(wait_queue_t)
2. 把等待队列加入到相应的等待队列头中。
3. 把进程状态置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE
4. 再次检查休眠条件是否为真,否则跳过第5步
5. 执行 schedule()
6. 清理:将进程状态改为 TASK_RUNNING(通常已经是,除非是从第4步跳过来的),把等待队列从等待队列头
中删除(防止多次唤醒)
7. 如果是可中断休眠的话(TASK_INTERRUPTIBLE),检查是否是因信号而被唤醒。如果是的话,一般直接
return -ERESTARTSYS 让上层处理。
8. 检查需要等待的条件是否满足,如果不是的话,再回到第1步重新开始。


注意,第4步的检查条件非常重要。因为到第3步之前,条件可能已被满足,而如果我们再把进程状态设为
TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE ,直接执行 schedule() 的话就可能再也无法被调度到。
但如果条件是在第4步到第5步之间满足的话,那不用担心,因为既然条件能满足,就应该有唤醒,而我们
的进程状态应该又被设为了 TASK_RUNNING,所以 schedule() 会调度到我们。


另外,以上只是一个一般的步骤,具体休眠时可根据个人的理解和实际的需要灵活调整。


四、看内核函数如何实现进程休眠
1.首先定义了一个设备结构体
struct mem_pool_dev                                     
{                                                        
  struct cdev cdev; 		    				/*cdev结构体*/                       
  unsigned char mem[mem_pool_SIZE]; /*全局内存*/    
  wait_queue_head_t test_queue; 		//定义等待队列头
};


2.定义了一个设备指针
/*设备结构体指针*/
struct mem_pool_dev *mem_pool_devp;
然后我们就可以利用这个指针来进行初始化等待队列头了
我是这样初始化的
init_waitqueue_head(&mem_pool_devp->test_queue);
3.这时候我么完成了上面第三点的第0步,继续下去,我们来到了读函数里面的休眠部分
	if(!wait_event_interruptible(mem_pool_devp->test_queue, flag))
	{
			printk("Sleeping ... ...");
	}


4.或许你会和我一样好奇为什么这里wait_event_interruptible 调用这个函数就能够睡眠了,没关系
看完下面的代码就会很明朗了


#define __wait_event(wq, condition) 					\
do {									\
	DEFINE_WAIT(__wait);						\
									\
	for (;;) {							\
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);	\
		if (condition)						\
			break;						\
		schedule();						\
	}								\
	finish_wait(&wq, &__wait);					\
} while (0)



这上面的一个宏定义完成的工作就是上面提到的第三点的 1~8步
5.好了有了休眠函数,那必然有对应的唤醒函数,如下所示


flag = 1;
wake_up_interruptible(&mem_pool_devp->test_queue);
6.最后提一下休眠规则
不要在原子上下文中休眠。
禁止中断时,也不能休眠。
要确保有进程能唤醒自己。
休眠被唤醒之后仍要检查等待的条件是否为真,否则重新继续休眠。


五、贴代码
1.在mem_pool.c中,这里只列出有改变的函数
#include <linux/wait.h>
#include <linux/sched.h>


/*读函数*/
static ssize_t mem_pool_read(struct file *filp, char __user *buf, size_t size,
  loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data; 


  /*2. 分析和获取有效的写长度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
	if(!wait_event_interruptible(mem_pool_devp->test_queue, flag))
	{
			printk("Sleeping ... ...");
	}
	
  /*3. 内核空间->用户空间*/
  if (copy_to_user(buf, (void*)(dev->mem + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
    
    printk( "read %d bytes(s) from %d\n", count, p);
  }
  flag = 0;
  return ret;
}


/*写函数*/
static ssize_t mem_pool_write(struct file *filp, const char __user *buf,
  size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data; 


  /*2. 分析和获取有效的写长度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
    
  /*3. 用户空间->内核空间*/
  if (copy_from_user(dev->mem + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk( "written %d bytes(s) from %d\n", count, p);
  }
  flag = 1;
  wake_up_interruptible(&mem_pool_devp->test_queue);
  return ret;
}
/*设备驱动模块加载函数*/
int mem_pool_init(void)
{
  int result;
  dev_t devno = MKDEV(mem_pool_major, 0);


  printk( "mem_pool_init !\n");
  /*1. 申请设备号*/
  if (mem_pool_major)
    result = register_chrdev_region(devno, 1, "mem_pool");
  else  
  {
	/*2. 动态申请设备号 */  	
    result = alloc_chrdev_region(&devno, 0, 1, "mem_pool");
    mem_pool_major = MAJOR(devno);
  }  
  if (result < 0)
    return result;
    
  /*3. 动态申请设备结构体的内存*/
  mem_pool_devp = kmalloc(sizeof(struct mem_pool_dev), GFP_KERNEL);
  /*4. 申请失败*/
  if (!mem_pool_devp)    
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  /*5. 内存初始化*/
  memset(mem_pool_devp, 0, sizeof(struct mem_pool_dev));
  /* 等待队列头初始化定义 一步到位*/
  init_waitqueue_head(&mem_pool_devp->test_queue);
  /*6. 注册初始化设备*/
  mem_pool_setup_cdev(mem_pool_devp, devno);
  return 0;


  fail_malloc: unregister_chrdev_region(devno, 1);
  return result;
}


2.在mem_pool.h中
#ifndef __MEM_POOL_H
#define __MEM_POOL_H


#define mem_pool_SIZE		0x2000	/*全局内存最大8K字节*/


/*mem_pool设备结构体*/
struct mem_pool_dev                                     
{                                                        
  struct cdev cdev; 		    				/*cdev结构体*/                       
  unsigned char mem[mem_pool_SIZE]; /*全局内存*/    
  wait_queue_head_t test_queue; 		//定义等待队列头      
};
#define TEST_MAGIC 'x'
#define MEM_SET  _IO(TEST_MAGIC, 0)	/*清0全局内存
#define mem_pool_MAJOR 	250    	/*预设的mem_pool的主设
#endif


3.在test_read.c中
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
	int fd = 0;
	char buf[4096];
	
	
	/*1. 打开设备文件*/
	fd = open("/dev/mem_pool",O_RDWR);
	if (fd < 0)
	{
		printf("Open Dev Mem0 Error!\n");
		return -1;
	}
	
	/*2. 读设备文件 */
	if(read(fd,buf,sizeof(buf)) < 0 )
	{
		printf("read error  ... \n");		
	}
	
	printf("read buf is %s \n",buf);	
	/*3. 关闭文件*
	close(fd);
	return 0;	
}


4.在test_write.c中
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
	int fd = 0;
	char buf[4096] = "this is shopping";
	
	
	/*1. 打开设备文件*/
	fd = open("/dev/mem_pool",O_RDWR);
	if (fd < 0)
	{
		printf("Open Dev Mem0 Error!\n");
		return -1;
	}
	
	/*2. 写设备文件 */
	if(write(fd,buf,sizeof(buf)) < 0 )
	{
		printf("write error  ... \n");		
	}
	
	printf("write buf is %s \n",buf);	
	/*3. 关闭文件*
	close(fd);
	return 0;	
}
然后看两张运行图

最后说下调试中遇到的问题

1.函数传参的时候,不熟悉参数的类型导致老是报错

2.有时候会出现killed的情况,但是重新insmod一下就好了,原因暂时不知道


最后总结一下:


内核提供的操作方法
根据上面的步骤,内核提供了至少两种方法来帮助实现,我把他们称作是半自动DIY式的和全自动傻瓜式。


半自动DIY式
wait_queue_head_t wq; init_waitqueue_head(&wq); --对应第0步
DEFINE_WAIT(wait); --对应第1步
prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); --对应第2,3步
if (condition) schedule(); --对应第4,5步
finish_wait(&wq, &wait); --对应第6步
if (signal_pending(current)) return -ERESTARTSYS; --对应第7步


全自动傻瓜式
wait_queue_head_t wq; init_waitqueue_head(&wq); --对应第0步
int ret = wait_event_interruptible(wq, condition); --对应第1,2,3,4,5,6步和第7步前半步
if (ret != 0) return -ERESTARTSYS; --对应第7步后半步


如果看内核代码的话,就可以发现wait_event系列其实就是对第1,2,3,4,5,6,7步的一个宏包装





分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics