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

来源:互联网 发布:扫码记录软件 编辑:程序博客网 时间:2024/05/03 12:30
一、首先定义了一个等待任务头,如下两种定义方式:


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_SIZE0x2000/*全局内存最大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步的一个宏包装





原创粉丝点击