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步的一个宏包装
- Linux驱动学习7(阻塞IO的实现)
- Linux驱动学习8(非阻塞IO的实现--select/poll方法)
- Linux驱动阻塞的实现
- linux设备驱动--阻塞IO
- 【Linux开发】linux设备驱动归纳总结(三):5.阻塞型IO实现
- Linux学习:驱动层实现阻塞和非阻塞
- linux下五种IO模型小结(阻塞IO、非阻塞IO、IO复用、信号驱动式IO、异步IO)
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- [arm驱动]linux等待队列阻塞中断IO的应用
- 求后序遍历
- Smallest Difference-模拟
- 收集的面试
- 混合背包
- Mongodb 基础
- Linux驱动学习7(阻塞IO的实现)
- 创造你的物理世界(1)
- Sorting by Swapping
- Android Init Language介绍
- 分析淘宝网络框架tbnet
- test
- 已知后序遍历和中序遍历求前序遍历
- String Problem-kmp+最小表示法
- 最小生成树 Kruskal算法