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步的一个宏包装